diff --git a/CHANGELOG.md b/CHANGELOG.md index a680de6630..0db7e7673b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,18 @@ ### Version 1.25.0 - August 23 2021 Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) -**XMOS-only change** - version 0: add support for KWD using GPIO INT_N pin. This will be an alternative to using the Sensory KWD. +**XMOS-only change** - version 0: + + * add support for KWD using GPIO INT_N pin. This will be an alternative to using the Sensory KWD. + +**XMOS-only change** - version 1: + + * update links in README file. + +**XMOS-only change** - version 2: + + * add support for KWD using HID event. This will be an alternative to using the Sensory KWD with UA device. + * add mechanism to retrieve WW indexes for HID and GPIO KWD's ### Version 1.24.0 - June 1 2021 Feature enhancements, updates, and resolved issues from all releases are available on the [Amazon developer portal](https://developer.amazon.com/docs/alexa/avs-device-sdk/release-notes.html) diff --git a/KWD/CMakeLists.txt b/KWD/CMakeLists.txt index e5e238dc38..e2c39afb85 100644 --- a/KWD/CMakeLists.txt +++ b/KWD/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(SENSORY_KEY_WORD_DETECTOR) add_subdirectory("Sensory") endif() +if(HID_KEY_WORD_DETECTOR) + add_subdirectory("HID") +endif() if(GPIO_KEY_WORD_DETECTOR) add_subdirectory("GPIO") endif() diff --git a/KWD/GPIO/include/GPIO/GPIOKeywordDetector.h b/KWD/GPIO/include/GPIO/GPIOKeywordDetector.h index c892135d4b..533d5a7444 100644 --- a/KWD/GPIO/include/GPIO/GPIOKeywordDetector.h +++ b/KWD/GPIO/include/GPIO/GPIOKeywordDetector.h @@ -49,10 +49,7 @@ class GPIOKeywordDetector : public AbstractKeywordDetector { * @param audioFormat The format of the audio data located within the stream. * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. - * @param msToPushPerIteration The amount of data in milliseconds to push to GPIO WW at a time. Smaller sizes will - * lead to less delay but more CPU usage. Additionally, larger amounts of data fed into the engine per iteration - * might lead longer delays before receiving keyword detection events. This has been defaulted to 10 milliseconds - * as it is a good trade off between CPU usage and recognition delay. Additionally, this was the amount used by + * @param msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by * Sensory in example code. * @return A new @c GPIOKeywordDetector, or @c nullptr if the operation failed. */ @@ -77,10 +74,8 @@ class GPIOKeywordDetector : public AbstractKeywordDetector { * @param audioFormat The format of the audio data located within the stream. * @param keyWordObservers The observers to notify of keyword detections. * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. - * @param msToPushPerIteration The amount of data in milliseconds to push to GPIO WW at a time. Smaller sizes will - * lead to less delay but more CPU usage. Additionally, larger amounts of data fed into the engine per iteration - * might lead longer delays before receiving keyword detection events. This has been defaulted to 10 milliseconds - * as it is a good trade off between CPU usage and recognition delay. + * @param msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. */ GPIOKeywordDetector( std::shared_ptr stream, @@ -99,6 +94,8 @@ class GPIOKeywordDetector : public AbstractKeywordDetector { /// The main function that reads data and feeds it into the engine. void detectionLoop(); + /// The main function that reads data and feeds it into the engine. + void readAudioLoop(); /// Indicates whether the internal main loop should keep running. std::atomic m_isShuttingDown; @@ -115,7 +112,13 @@ class GPIOKeywordDetector : public AbstractKeywordDetector { */ avsCommon::avs::AudioInputStream::Index m_beginIndexOfStreamReader; - /// Internal thread that monitors GPIO. + /// The file descriptor to access I2C port + int m_fileDescriptor; + + /// Internal thread that read audio samples + std::thread m_readAudioThread; + + /// Internal thread that monitors GPIO pin. std::thread m_detectionThread; /** diff --git a/KWD/GPIO/src/GPIOKeywordDetector.cpp b/KWD/GPIO/src/GPIOKeywordDetector.cpp index e471257a5b..7bc5b0a6fa 100644 --- a/KWD/GPIO/src/GPIOKeywordDetector.cpp +++ b/KWD/GPIO/src/GPIOKeywordDetector.cpp @@ -16,10 +16,13 @@ */ #include - -#include #include +#include +#include +#include +#include +#include #include "GPIO/GPIOKeywordDetector.h" namespace alexaClientSDK { @@ -41,12 +44,8 @@ static const std::string TAG("GPIOKeywordDetector"); // Wiring Pi pin 2 which corresponds to Physical/Board pin 13 and GPIO/BCM pin 27 static const int GPIO_PIN = 2; -/// Number of m_maxSamplesPerPush * KW_REWIND_SAMPLES to rewind when WW is detected on GPIO -// m_maxSamplesPerPush is 10ms -static const size_t KW_REWIND_SAMPLES = 10; - -/// Wakeword string -static const std::string WAKEWORD_STRING = "alexa"; +/// Keyword string +static const std::string KEYWORD_STRING = "alexa"; /// The number of hertz per kilohertz. static const size_t HERTZ_PER_KILOHERTZ = 1000; @@ -54,62 +53,125 @@ static const size_t HERTZ_PER_KILOHERTZ = 1000; /// The timeout to use for read calls to the SharedDataStream. const std::chrono::milliseconds TIMEOUT_FOR_READ_CALLS = std::chrono::milliseconds(1000); -/// The GPIO WW compatible AVS sample rate of 16 kHz. +/// The GPIO KW compatible AVS sample rate of 16 kHz. static const unsigned int GPIO_COMPATIBLE_SAMPLE_RATE = 16000; -/// The GPIO WW compatible bits per sample of 16. +/// The GPIO KW compatible bits per sample of 16. static const unsigned int GPIO_COMPATIBLE_SAMPLE_SIZE_IN_BITS = 16; -/// The GPIO WW compatible number of channels, which is 1. +/// The GPIO KW compatible number of channels, which is 1. static const unsigned int GPIO_COMPATIBLE_NUM_CHANNELS = 1; -/// The GPIO WW compatible audio encoding of LPCM. +/// The GPIO KW compatible audio encoding of LPCM. static const avsCommon::utils::AudioFormat::Encoding GPIO_COMPATIBLE_ENCODING = avsCommon::utils::AudioFormat::Encoding::LPCM; -/// The GPIO WW compatible endianness which is little endian. +/// The GPIO KW compatible endianness which is little endian. static const avsCommon::utils::AudioFormat::Endianness GPIO_COMPATIBLE_ENDIANNESS = avsCommon::utils::AudioFormat::Endianness::LITTLE; +/// The device name of the I2C port connected to the device. +static const char *DEVNAME = "/dev/i2c-1"; + +/// The address of the I2C port connected to the device. +static const unsigned char I2C_ADDRESS = 0x2C; + +/// The maximum size in bytes of the I2C transaction +static const int I2C_TRANSACTION_MAX_BYTES = 256; + +/// The resource ID of the XMOS control command. +static const int CONTROL_RESOURCE_ID = 0xE0; + +/// The command ID of the XMOS control command. +static const int CONTROL_CMD_ID = 0xAF; + +/// The lenght of the payload of the XMOS control command +/// one control byte plus 3 uint64_t values +static const int CONTROL_CMD_PAYLOAD_LEN = 25; + +/** + * Read a specific index from the payload of the USB control message + * + * @param payload The data returned via control message + * @param start_index The index in the payload to start reading from + * @return value stored in payload + */ +static uint64_t readIndex(uint8_t* payload, int start_index) { + uint64_t u64value = 0; + // convert array of bytes into uint64_t value + memcpy(&u64value, &payload[start_index], sizeof(uint64_t)); + // swap bytes of uint64_t value + u64value = __bswap_64(u64value); + return u64value; +} + +/** + * Open the I2C port connected to the device + * + * @return file descriptor with the connected device + */ +static uint8_t openI2CDevice() { + int rc = 0; + int fd = -1; + // Open port for reading and writing + if ((fd = open(DEVNAME, O_RDWR)) < 0) { + ACSDK_ERROR(LX("openI2CDeviceFailed") + .d("reason", "openFailed")); + perror( "" ); + return -1; + } + // Set the port options and set the address of the device we wish to speak to + if ((rc = ioctl(fd, I2C_SLAVE, I2C_ADDRESS)) < 0) { + ACSDK_ERROR(LX("openI2CDeviceFailed") + .d("reason", "setI2CConfigurationFailed")); + perror( "" ); + return -1; + } + + ACSDK_INFO(LX("openI2CDeviceSuccess").d("port", I2C_ADDRESS)); + + return fd; +} + /** - * Checks to see if an @c avsCommon::utils::AudioFormat is compatible with GPIO WW. + * Checks to see if an @c avsCommon::utils::AudioFormat is compatible with GPIO KW. * * @param audioFormat The audio format to check. - * @return @c true if the audio format is compatible with GPIO WW and @c false otherwise. + * @return @c true if the audio format is compatible with GPIO KW and @c false otherwise. */ -static bool isAudioFormatCompatibleWithGPIOWW(avsCommon::utils::AudioFormat audioFormat) { +static bool isAudioFormatCompatibleWithGPIOKW(avsCommon::utils::AudioFormat audioFormat) { if (GPIO_COMPATIBLE_ENCODING != audioFormat.encoding) { - ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOWWFailed") + ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOKWFailed") .d("reason", "incompatibleEncoding") - .d("gpiowwEncoding", GPIO_COMPATIBLE_ENCODING) + .d("gpioKWEncoding", GPIO_COMPATIBLE_ENCODING) .d("encoding", audioFormat.encoding)); return false; } if (GPIO_COMPATIBLE_ENDIANNESS != audioFormat.endianness) { - ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOWWFailed") + ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOKWFailed") .d("reason", "incompatibleEndianess") - .d("gpiowwEndianness", GPIO_COMPATIBLE_ENDIANNESS) + .d("gpioKWEndianness", GPIO_COMPATIBLE_ENDIANNESS) .d("endianness", audioFormat.endianness)); return false; } if (GPIO_COMPATIBLE_SAMPLE_RATE != audioFormat.sampleRateHz) { - ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOWWFailed") + ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOKWFailed") .d("reason", "incompatibleSampleRate") - .d("gpiowwSampleRate", GPIO_COMPATIBLE_SAMPLE_RATE) + .d("gpioKWSampleRate", GPIO_COMPATIBLE_SAMPLE_RATE) .d("sampleRate", audioFormat.sampleRateHz)); return false; } if (GPIO_COMPATIBLE_SAMPLE_SIZE_IN_BITS != audioFormat.sampleSizeInBits) { - ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOWWFailed") + ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOKWFailed") .d("reason", "incompatibleSampleSizeInBits") - .d("gpiowwSampleSizeInBits", GPIO_COMPATIBLE_SAMPLE_SIZE_IN_BITS) + .d("gpioKWSampleSizeInBits", GPIO_COMPATIBLE_SAMPLE_SIZE_IN_BITS) .d("sampleSizeInBits", audioFormat.sampleSizeInBits)); return false; } if (GPIO_COMPATIBLE_NUM_CHANNELS != audioFormat.numChannels) { - ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOWWFailed") + ACSDK_ERROR(LX("isAudioFormatCompatibleWithGPIOKWFailed") .d("reason", "incompatibleNumChannels") - .d("gpiowwNumChannels", GPIO_COMPATIBLE_NUM_CHANNELS) + .d("gpioKWNumChannels", GPIO_COMPATIBLE_NUM_CHANNELS) .d("numChannels", audioFormat.numChannels)); return false; } @@ -134,7 +196,7 @@ std::unique_ptr GPIOKeywordDetector::create( return nullptr; } - if (!isAudioFormatCompatibleWithGPIOWW(audioFormat)) { + if (!isAudioFormatCompatibleWithGPIOKW(audioFormat)) { return nullptr; } @@ -164,6 +226,8 @@ GPIOKeywordDetector::~GPIOKeywordDetector() { m_isShuttingDown = true; if (m_detectionThread.joinable()) m_detectionThread.join(); + if (m_readAudioThread.joinable()) + m_readAudioThread.join(); } bool GPIOKeywordDetector::init() { @@ -172,9 +236,13 @@ bool GPIOKeywordDetector::init() { ACSDK_ERROR(LX("initFailed").d("reason", "wiringPiSetup failed")); return false; } - pinMode(GPIO_PIN, INPUT); + if ((m_fileDescriptor = openI2CDevice())<0) { + ACSDK_ERROR(LX("detectionLoopFailed").d("reason", "openI2CDeviceFailed")); + return false; + } + m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); if (!m_streamReader) { ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); @@ -182,18 +250,17 @@ bool GPIOKeywordDetector::init() { } m_isShuttingDown = false; + m_readAudioThread = std::thread(&GPIOKeywordDetector::readAudioLoop, this); m_detectionThread = std::thread(&GPIOKeywordDetector::detectionLoop, this); return true; } -void GPIOKeywordDetector::detectionLoop() { - m_beginIndexOfStreamReader = m_streamReader->tell(); - notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); +void GPIOKeywordDetector::readAudioLoop() { std::vector audioDataToPush(m_maxSamplesPerPush); - int oldGpioValue = HIGH; + bool didErrorOccur = false; + while (!m_isShuttingDown) { - bool didErrorOccur = false; - auto wordsRead = readFromStream( + readFromStream( m_streamReader, m_stream, audioDataToPush.data(), @@ -201,35 +268,103 @@ void GPIOKeywordDetector::detectionLoop() { TIMEOUT_FOR_READ_CALLS, &didErrorOccur); if (didErrorOccur) { - /* - * Note that this does not include the overrun condition, which the base class handles by instructing the - * reader to seek to BEFORE_WRITER. - */ - break; - } else if (wordsRead == AudioInputStream::Reader::Error::OVERRUN) { - /* - * Updating reference point of Reader so that new indices that get emitted to keyWordObservers can be - * relative to it. - */ - m_beginIndexOfStreamReader = m_streamReader->tell(); - } else if (wordsRead > 0) { - // Words were successfully read. - // Read gpio value - int gpioValue = digitalRead(GPIO_PIN); - - // Check if GPIO pin is changing from high to low - if (gpioValue == LOW && oldGpioValue == HIGH) - { - ACSDK_INFO(LX("WW detected")); - notifyKeyWordObservers( - m_stream, - WAKEWORD_STRING, - // avsCommon::sdkInterfaces::KeyWordObserverInterface::UNSPECIFIED_INDEX, - (m_streamReader->tell() < (m_maxSamplesPerPush*KW_REWIND_SAMPLES) ? 0 : m_streamReader->tell() - (m_maxSamplesPerPush*KW_REWIND_SAMPLES)), - m_streamReader->tell()); + m_isShuttingDown = true; + } + } +} + +void GPIOKeywordDetector::detectionLoop() { + m_beginIndexOfStreamReader = m_streamReader->tell(); + notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); + int oldGpioValue = HIGH; + + std::chrono::steady_clock::time_point prev_time; + std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); + + while (!m_isShuttingDown) { + auto currentIndex = m_streamReader->tell(); + + // Read gpio value + int gpioValue = digitalRead(GPIO_PIN); + + // Check if GPIO pin is changing from high to low + if (gpioValue == LOW && oldGpioValue == HIGH) + { + std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); + + // Check if this is not the first HID event + if (prev_time != std::chrono::steady_clock::time_point()) { + ACSDK_DEBUG0(LX("detectionLoopGPIOevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); + } + prev_time = current_time; + + // Retrieve device indexes using control message via USB + unsigned char payload[64]; + + uint8_t cmd_ret = 1; + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + int rc = 0; + while (cmd_ret!=0) { + // Do a repeated start (write followed by read with no stop bit) + unsigned char read_hdr[I2C_TRANSACTION_MAX_BYTES]; + read_hdr[0] = CONTROL_RESOURCE_ID; + read_hdr[1] = CONTROL_CMD_ID; + read_hdr[2] = (uint8_t)CONTROL_CMD_PAYLOAD_LEN; + + struct i2c_msg rdwr_msgs[2] = { + { // Start address + .addr = I2C_ADDRESS, + .flags = 0, // write + .len = 3, // this is always 3 + .buf = read_hdr + }, + { // Read buffer + .addr = I2C_ADDRESS, + .flags = I2C_M_RD, // read + .len = CONTROL_CMD_PAYLOAD_LEN, + .buf = payload + } + }; + + struct i2c_rdwr_ioctl_data rdwr_data = { + .msgs = rdwr_msgs, + .nmsgs = 2 + }; + + rc = ioctl( m_fileDescriptor, I2C_RDWR, &rdwr_data ); + + if ( rc < 0 ) { + ACSDK_ERROR(LX("detectionLoopControlCommandFailed").d("reason", rc)); + perror( "" ); + } + + cmd_ret = payload[0]; } - oldGpioValue = gpioValue; + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); + + // Read indexes + uint64_t currentDeviceIndex = readIndex(payload, 1); + uint64_t beginKWDeviceIndex = readIndex(payload, 9); + uint64_t endKWDeviceIndex = readIndex(payload, 17); + auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); + + // Send information to the server + notifyKeyWordObservers( + m_stream, + KEYWORD_STRING, + beginKWServerIndex, + currentIndex); + ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) + .d("deviceCurrentIndex", currentDeviceIndex) + .d("deviceKWEndIndex", endKWDeviceIndex) + .d("deviceKWBeginIndex", beginKWDeviceIndex) + .d("serverKWEndIndex", currentIndex) + .d("serverKWBeginIndex", beginKWServerIndex)); } + oldGpioValue = gpioValue; } m_streamReader->close(); } diff --git a/KWD/HID/CMakeLists.txt b/KWD/HID/CMakeLists.txt new file mode 100644 index 0000000000..5df9f258f7 --- /dev/null +++ b/KWD/HID/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(HID LANGUAGES CXX) + +include(${AVS_CMAKE_BUILD}/BuildDefaults.cmake) + +add_subdirectory("src") diff --git a/KWD/HID/include/HID/HIDKeywordDetector.h b/KWD/HID/include/HID/HIDKeywordDetector.h new file mode 100644 index 0000000000..8cf6ad9f21 --- /dev/null +++ b/KWD/HID/include/HID/HIDKeywordDetector.h @@ -0,0 +1,140 @@ +// Copyright (c) 2021 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright 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 ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ +#define ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "KWD/AbstractKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon; +using namespace avsCommon::avs; +using namespace avsCommon::sdkInterfaces; + +// A specialization of a KeyWordEngine, where a trigger comes from HID +class HIDKeywordDetector : public AbstractKeywordDetector { +public: + /** + * Creates a @c HIDKeywordDetector. + * + * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and + * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. + * @param audioFormat The format of the audio data located within the stream. + * @param keyWordObservers The observers to notify of keyword detections. + * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. + * @param msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + * @return A new @c HIDKeywordDetector, or @c nullptr if the operation failed. + */ + static std::unique_ptr create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Destructor. + */ + ~HIDKeywordDetector(); + +private: + /** + * Constructor. + * + * @param stream The stream of audio data. This should be formatted in LPCM encoded with 16 bits per sample and + * have a sample rate of 16 kHz. Additionally, the data should be in little endian format. + * @param audioFormat The format of the audio data located within the stream. + * @param keyWordObservers The observers to notify of keyword detections. + * @param keyWordDetectorStateObservers The observers to notify of state changes in the engine. + * @param msToPushPerIteration The amount of data in milliseconds to push to the cloud at a time. This was the amount used by + * Sensory in example code. + */ + HIDKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration = std::chrono::milliseconds(10)); + + /** + * Initializes the stream reader, sets up the HID, and kicks off a thread to begin processing data from + * the stream. This function should only be called once with each new @c HIDKeywordDetector. + * + * @return @c true if the engine was initialized properly and @c false otherwise. + */ + bool init(); + + /// The function that updates the audio stream. + void readAudioLoop(); + + /// The function that waits for HID events and notifies the server + void detectionLoop(); + + /// Indicates whether the internal main loop should keep running. + std::atomic m_isShuttingDown; + + /// The stream of audio data. + const std::shared_ptr m_stream; + + /// The reader that will be used to read audio data from the stream. + std::shared_ptr m_streamReader; + + /** + * This serves as a reference point used when notifying observers of keyword detection indices since Sensory has no + * way of specifying a start index. + */ + avsCommon::avs::AudioInputStream::Index m_beginIndexOfStreamReader; + + + /// The device handler necessary for reading HID events + struct libevdev *m_evdev; + + //The device handler necessary for sending control commands + libusb_device_handle *m_devh; + + /// Internal thread that read audio samples + std::thread m_readAudioThread; + + /// Internal thread that monitors HID. + std::thread m_detectionThread; + + /** + * The max number of samples to push into the underlying engine per iteration. This will be determined based on the + * sampling rate of the audio data passed in. + */ + const size_t m_maxSamplesPerPush; +}; + +} // namespace kwd +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_KWD_HID_INCLUDE_HID_HIDKEYWORDDETECTOR_H_ diff --git a/KWD/HID/src/CMakeLists.txt b/KWD/HID/src/CMakeLists.txt new file mode 100644 index 0000000000..904e243fca --- /dev/null +++ b/KWD/HID/src/CMakeLists.txt @@ -0,0 +1,18 @@ +add_definitions("-DACSDK_LOG_MODULE=HIDKeywordDetector") + +find_package(PkgConfig) +pkg_check_modules(libevdev REQUIRED libevdev) +find_package(PkgConfig) +pkg_check_modules(libusb-1.0 REQUIRED libusb-1.0) + +add_library(HID SHARED + HIDKeywordDetector.cpp) + +target_include_directories(HID PUBLIC + "${KWD_SOURCE_DIR}/include" + "${HID_SOURCE_DIR}/include") + +target_link_libraries(HID KWD AVSCommon evdev usb-1.0) + +# install target +asdk_install() diff --git a/KWD/HID/src/HIDKeywordDetector.cpp b/KWD/HID/src/HIDKeywordDetector.cpp new file mode 100644 index 0000000000..ff3b2a5c9b --- /dev/null +++ b/KWD/HID/src/HIDKeywordDetector.cpp @@ -0,0 +1,374 @@ +// Copyright (c) 2021 XMOS LIMITED. This Software is subject to the terms of the +// XMOS Public License: Version 1 +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include +#include + +#include + +#include "HID/HIDKeywordDetector.h" + +namespace alexaClientSDK { +namespace kwd { + +using namespace avsCommon::utils::logger; + +/// String to identify log entries originating from this file. +static const std::string TAG("HIDKeywordDetector"); + +/** + * Create a LogEntry using this file's TAG and the specified event string. + * + * @param The event string for this @c LogEntry. + */ +#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) + +/// Keyword string +static const std::string KEYWORD_STRING = "alexa"; + +/// The number of hertz per kilohertz. +static const size_t HERTZ_PER_KILOHERTZ = 1000; + +/// The timeout to use for read calls to the SharedDataStream. +const std::chrono::milliseconds TIMEOUT_FOR_READ_CALLS = std::chrono::milliseconds(1000); + +/// The HID KW compatible AVS sample rate of 16 kHz. +static const unsigned int HID_COMPATIBLE_SAMPLE_RATE = 16000; + +/// The HID KW compatible bits per sample of 16. +static const unsigned int HID_COMPATIBLE_SAMPLE_SIZE_IN_BITS = 16; + +/// The HID KW compatible number of channels, which is 1. +static const unsigned int HID_COMPATIBLE_NUM_CHANNELS = 1; + +/// The HID KW compatible audio encoding of LPCM. +static const avsCommon::utils::AudioFormat::Encoding HID_COMPATIBLE_ENCODING = + avsCommon::utils::AudioFormat::Encoding::LPCM; + +/// The HID KW compatible endianness which is little endian. +static const avsCommon::utils::AudioFormat::Endianness HID_COMPATIBLE_ENDIANNESS = + avsCommon::utils::AudioFormat::Endianness::LITTLE; + +/// HID keycode to monitor: +static const char * HID_KEY_CODE = "KEY_T"; + +/// HID device path + +static const char * HID_DEVICE_PATH = "/dev/input/event0"; + +/// USB Product ID of XMOS device +static const int USB_VENDOR_ID = 0x20B1; + +/// USB Product ID of XMOS device +static const int USB_PRODUCT_ID = 0x18; + +/// USB timeout for control transfer +static const int USB_TIMEOUT_MS = 500; + +/// The resource ID of the XMOS control command. +static const int CONTROL_RESOURCE_ID = 0xE0; + +/// The command ID of the XMOS control command. +static const int CONTROL_CMD_ID = 0xAF; + +/// The lenght of the payload of the XMOS control command +/// one control byte plus 3 uint64_t values +static const int CONTROL_CMD_PAYLOAD_LEN = 25; + +/** + * Read a specific index from the payload of the USB control message + * + * @param payload The data returned via control message + * @param start_index The index in the payload to start reading from + * @return value stored in payload + */ +static uint64_t readIndex(uint8_t* payload, int start_index) { + uint64_t u64value = 0; + // convert array of bytes into uint64_t value + memcpy(&u64value, &payload[start_index], sizeof(uint64_t)); + // swap bytes of uint64_t value + u64value = __bswap_64(u64value); + return u64value; +} + +/** + * Search for an USB device, open the connection and return the correct handlers + * + * @param evdev The device handler necessary for reading HID events + * @param devh The device handler necessary for sending control commands + * @return 0 if device is found and handlers correctly set + */ +static uint8_t openUSBDevice(libevdev** evdev, libusb_device_handle** devh) { + int rc = 1; + libusb_device **devs = NULL; + libusb_device *dev = NULL; + + ACSDK_INFO(LX("openUSBDeviceOngoing") + .d("HIDDevicePath", HID_DEVICE_PATH) + .d("USBVendorID", USB_VENDOR_ID) + .d("USBProductID", USB_PRODUCT_ID)); + + // Find USB device for reading HID events + int fd; + fd = open(HID_DEVICE_PATH, O_RDONLY|O_NONBLOCK); + rc = libevdev_new_from_fd(fd, evdev); + if (rc < 0) { + ACSDK_ERROR(LX("openUSBDeviceFailed") + .d("reason", "initialiseLibevdevFailed") + .d("error", strerror(-rc))); + return -1; + } + + // Find USB device for sending control commands + int ret = libusb_init(NULL); + if (ret < 0) { + ACSDK_ERROR(LX("openUSBDeviceFailed").d("reason", "initialiseLibUsbFailed")); + return -1; + } + + int num_dev = libusb_get_device_list(NULL, &devs); + + for (int i = 0; i < num_dev; i++) { + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(devs[i], &desc); + if (desc.idVendor == USB_VENDOR_ID && desc.idProduct == USB_PRODUCT_ID) { + dev = devs[i]; + break; + } + } + + if (dev == NULL) { + ACSDK_ERROR(LX("openUSBDeviceFailed").d("reason", "UsbDeviceNotFound")); + return -1; + } + + if (libusb_open(dev, devh) < 0) { + ACSDK_ERROR(LX("openUSBDeviceFailed").d("reason", "UsbDeviceNotOpened")); + return -1; + } + + libusb_free_device_list(devs, 1); + ACSDK_INFO(LX("openUSBDeviceSuccess").d("reason", "UsbDeviceOpened")); + return 0; +} + + +/** + * Checks to see if an @c avsCommon::utils::AudioFormat is compatible with HID KW. + * + * @param audioFormat The audio format to check. + * @return @c true if the audio format is compatible with HID KW and @c false otherwise. + */ +static bool isAudioFormatCompatibleWithHIDKW(avsCommon::utils::AudioFormat audioFormat) { + if (HID_COMPATIBLE_ENCODING != audioFormat.encoding) { + ACSDK_ERROR(LX("isAudioFormatCompatibleWithHIDKWFailed") + .d("reason", "incompatibleEncoding") + .d("gpiowwEncoding", HID_COMPATIBLE_ENCODING) + .d("encoding", audioFormat.encoding)); + return false; + } + if (HID_COMPATIBLE_ENDIANNESS != audioFormat.endianness) { + ACSDK_ERROR(LX("isAudioFormatCompatibleWithHIDKWFailed") + .d("reason", "incompatibleEndianess") + .d("gpiowwEndianness", HID_COMPATIBLE_ENDIANNESS) + .d("endianness", audioFormat.endianness)); + return false; + } + if (HID_COMPATIBLE_SAMPLE_RATE != audioFormat.sampleRateHz) { + ACSDK_ERROR(LX("isAudioFormatCompatibleWithHIDKWFailed") + .d("reason", "incompatibleSampleRate") + .d("gpiowwSampleRate", HID_COMPATIBLE_SAMPLE_RATE) + .d("sampleRate", audioFormat.sampleRateHz)); + return false; + } + if (HID_COMPATIBLE_SAMPLE_SIZE_IN_BITS != audioFormat.sampleSizeInBits) { + ACSDK_ERROR(LX("isAudioFormatCompatibleWithHIDKWFailed") + .d("reason", "incompatibleSampleSizeInBits") + .d("gpiowwSampleSizeInBits", HID_COMPATIBLE_SAMPLE_SIZE_IN_BITS) + .d("sampleSizeInBits", audioFormat.sampleSizeInBits)); + return false; + } + if (HID_COMPATIBLE_NUM_CHANNELS != audioFormat.numChannels) { + ACSDK_ERROR(LX("isAudioFormatCompatibleWithHIDKWFailed") + .d("reason", "incompatibleNumChannels") + .d("gpiowwNumChannels", HID_COMPATIBLE_NUM_CHANNELS) + .d("numChannels", audioFormat.numChannels)); + return false; + } + return true; +} + +std::unique_ptr HIDKeywordDetector::create( + std::shared_ptr stream, + avsCommon::utils::AudioFormat audioFormat, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + std::chrono::milliseconds msToPushPerIteration) { + + if (!stream) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullStream")); + return nullptr; + } + + // TODO: ACSDK-249 - Investigate cpu usage of converting bytes between endianness and if it's not too much, do it. + if (isByteswappingRequired(audioFormat)) { + ACSDK_ERROR(LX("createFailed").d("reason", "endianMismatch")); + return nullptr; + } + + if (!isAudioFormatCompatibleWithHIDKW(audioFormat)) { + return nullptr; + } + + std::unique_ptr detector(new HIDKeywordDetector( + stream, keyWordObservers, keyWordDetectorStateObservers, audioFormat)); + + if (!detector->init()) { + ACSDK_ERROR(LX("createFailed").d("reason", "initDetectorFailed")); + return nullptr; + } + + return detector; +} + +HIDKeywordDetector::HIDKeywordDetector( + std::shared_ptr stream, + std::unordered_set> keyWordObservers, + std::unordered_set> keyWordDetectorStateObservers, + avsCommon::utils::AudioFormat audioFormat, + std::chrono::milliseconds msToPushPerIteration) : + AbstractKeywordDetector(keyWordObservers, keyWordDetectorStateObservers), + m_stream{stream}, + m_maxSamplesPerPush((audioFormat.sampleRateHz / HERTZ_PER_KILOHERTZ) * msToPushPerIteration.count()) { +} + +HIDKeywordDetector::~HIDKeywordDetector() { + m_isShuttingDown = true; + if (m_detectionThread.joinable()) + m_detectionThread.join(); + if (m_readAudioThread.joinable()) + m_readAudioThread.join(); + +} + +bool HIDKeywordDetector::init() { + m_streamReader = m_stream->createReader(AudioInputStream::Reader::Policy::BLOCKING); + if (!m_streamReader) { + ACSDK_ERROR(LX("initFailed").d("reason", "createStreamReaderFailed")); + return false; + } + + if (openUSBDevice(&m_evdev, &m_devh) != 0) { + return false; + } + + m_isShuttingDown = false; + m_readAudioThread = std::thread(&HIDKeywordDetector::readAudioLoop, this); + m_detectionThread = std::thread(&HIDKeywordDetector::detectionLoop, this); + return true; +} + +void HIDKeywordDetector::readAudioLoop() { + std::vector audioDataToPush(m_maxSamplesPerPush); + bool didErrorOccur = false; + while (!m_isShuttingDown) { + readFromStream( + m_streamReader, + m_stream, + audioDataToPush.data(), + audioDataToPush.size(), + TIMEOUT_FOR_READ_CALLS, + &didErrorOccur); + if (didErrorOccur) { + m_isShuttingDown = true; + } + + } +} + +void HIDKeywordDetector::detectionLoop() { + notifyKeyWordDetectorStateObservers(KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE); + int rc = 1; + + std::chrono::steady_clock::time_point prev_time; + std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); + + while (!m_isShuttingDown) { + auto currentIndex = m_streamReader->tell(); + struct input_event ev; + rc = libevdev_next_event(m_evdev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + // wait for HID_KEY_CODE true event + if (rc == 0 && strcmp(libevdev_event_type_get_name(ev.type), "EV_KEY")==0 && \ + strcmp(libevdev_event_code_get_name(ev.type, ev.code), HID_KEY_CODE)==0 && \ + ev.value == 1) + { + std::chrono::steady_clock::time_point current_time = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("absoluteElapsedTime (ms)", std::chrono::duration_cast (current_time - start_time).count())); + + // Check if this is not the first HID event + if (prev_time != std::chrono::steady_clock::time_point()) { + ACSDK_DEBUG0(LX("detectionLoopHIDevent").d("elapsedTimeFromPreviousEvent (ms)", std::chrono::duration_cast (current_time - prev_time).count())); + } + prev_time = current_time; + + // Retrieve device indexes using control message via USB + unsigned char payload[64]; + + uint8_t cmd_ret = 1; + + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + while (cmd_ret!=0) { + rc = libusb_control_transfer(m_devh, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + 0, CONTROL_CMD_ID, CONTROL_RESOURCE_ID, payload, CONTROL_CMD_PAYLOAD_LEN, USB_TIMEOUT_MS); + + cmd_ret = payload[0]; + } + if (rc != CONTROL_CMD_PAYLOAD_LEN) { + ACSDK_ERROR(LX("detectionLoopControlCommand").d("reason", "USBControlTransferFailed")); + } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + ACSDK_DEBUG0(LX("detectionLoopControlCommand").d("time (us)", std::chrono::duration_cast (end - begin).count() )); + + // Read indexes + uint64_t currentDeviceIndex = readIndex(payload, 1); + uint64_t beginKWDeviceIndex = readIndex(payload, 9); + uint64_t endKWDeviceIndex = readIndex(payload, 17); + + auto beginKWServerIndex = currentIndex - (currentDeviceIndex - beginKWDeviceIndex); + + // Send information to the server + notifyKeyWordObservers( + m_stream, + KEYWORD_STRING, + beginKWServerIndex, + currentIndex); + + ACSDK_DEBUG0(LX("detectionLoopIndexes").d("hostCurrentIndex", currentIndex) + .d("deviceCurrentIndex", currentDeviceIndex) + .d("deviceKWEndIndex", endKWDeviceIndex) + .d("deviceKWBeginIndex", beginKWDeviceIndex) + .d("serverKWEndIndex", currentIndex) + .d("serverKWBeginIndex", beginKWServerIndex)); + } + } +} + +} // namespace kwd +} // namespace alexaClientSDK diff --git a/KWD/KWDProvider/src/CMakeLists.txt b/KWD/KWDProvider/src/CMakeLists.txt index f2433d6e20..25612c8f71 100644 --- a/KWD/KWDProvider/src/CMakeLists.txt +++ b/KWD/KWDProvider/src/CMakeLists.txt @@ -10,6 +10,10 @@ if(SENSORY_KEY_WORD_DETECTOR) target_link_libraries(KeywordDetectorProvider SENSORY) endif() +if(HID_KEY_WORD_DETECTOR) + target_link_libraries(KeywordDetectorProvider HID) +endif() + if(GPIO_KEY_WORD_DETECTOR) target_link_libraries(KeywordDetectorProvider GPIO) endif() diff --git a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp index 5ed0e77081..243d0d0818 100644 --- a/KWD/KWDProvider/src/KeywordDetectorProvider.cpp +++ b/KWD/KWDProvider/src/KeywordDetectorProvider.cpp @@ -19,6 +19,8 @@ #include #elif KWD_GPIO #include +#elif KWD_HID +#include #endif using namespace alexaClientSDK; @@ -46,6 +48,12 @@ std::unique_ptr KeywordDetectorProvider::create( audioFormat, keyWordObservers, keyWordDetectorStateObservers); +#elif defined(KWD_HID) + return kwd::HIDKeywordDetector::create( + stream, + audioFormat, + keyWordObservers, + keyWordDetectorStateObservers); #else return nullptr; #endif diff --git a/SampleApp/src/main.cpp b/SampleApp/src/main.cpp index ed4511e5e9..8269ba48c6 100644 --- a/SampleApp/src/main.cpp +++ b/SampleApp/src/main.cpp @@ -91,7 +91,7 @@ int main(int argc, char* argv[]) { } } } else { -#if defined(KWD_SENSORY) || defined(KWD_GPIO) +#if defined(KWD_SENSORY) || defined(KWD_GPIO) || defined(KWD_HID) if (argc < 3) { ConsolePrinter::simplePrint( "USAGE: " + std::string(argv[0]) + diff --git a/cmakeBuild/cmake/KeywordDetector.cmake b/cmakeBuild/cmake/KeywordDetector.cmake index b2e04c58cb..f549fe0f10 100644 --- a/cmakeBuild/cmake/KeywordDetector.cmake +++ b/cmakeBuild/cmake/KeywordDetector.cmake @@ -16,8 +16,7 @@ # -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH= # -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR= # -DGPIO_KEY_WORD_DETECTOR=ON -# -DGPIO_KEY_WORD_DETECTOR_LIB_PATH= -# -DGPIO_KEY_WORD_DETECTOR_INCLUDE_DIR= +# -DHID_KEY_WORD_DETECTOR=ON # option(AMAZON_KEY_WORD_DETECTOR "Enable Amazon keyword detector." OFF) @@ -42,7 +41,7 @@ set(SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR "" CACHE PATH "Sensory keyword detecto mark_as_dependent(SENSORY_KEY_WORD_DETECTOR_LIB_PATH SENSORY_KEY_WORD_DETECTOR) mark_as_dependent(SENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR SENSORY_KEY_WORD_DETECTOR) -if(NOT AMAZON_KEY_WORD_DETECTOR AND NOT AMAZONLITE_KEY_WORD_DETECTOR AND NOT SENSORY_KEY_WORD_DETECTOR AND NOT GPIO_KEY_WORD_DETECTOR) +if(NOT AMAZON_KEY_WORD_DETECTOR AND NOT AMAZONLITE_KEY_WORD_DETECTOR AND NOT SENSORY_KEY_WORD_DETECTOR AND NOT GPIO_KEY_WORD_DETECTOR AND NOT HID_KEY_WORD_DETECTOR) message("No keyword detector type specified, skipping build of keyword detector.") return() endif() @@ -94,12 +93,19 @@ if(SENSORY_KEY_WORD_DETECTOR) endif() if(GPIO_KEY_WORD_DETECTOR) - message("Creating ${PROJECT_NAME} with keyword detector type: GPIO") + message("Creating ${PROJECT_NAME} with keyword detector type: GPIO") add_definitions(-DKWD) add_definitions(-DKWD_GPIO) set(KWD ON) endif() +if(HID_KEY_WORD_DETECTOR) + message("Creating ${PROJECT_NAME} with keyword detector type: HID") + add_definitions(-DKWD) + add_definitions(-DKWD_HID) + set(KWD ON) +endif() + if(PI_HAT_CTRL) add_definitions(-DPI_HAT_CTRL) endif() diff --git a/tools/Install/pi.sh b/tools/Install/pi.sh index 5173913e9f..f312146fa2 100644 --- a/tools/Install/pi.sh +++ b/tools/Install/pi.sh @@ -28,16 +28,19 @@ show_help() { echo 'Usage: pi.sh [OPTIONS]' echo '' echo 'Optional parameters' - echo ' -g Flag to enable keyword detector on GPIO interrupt' + echo ' -G Flag to enable keyword detector on GPIO interrupt' + echo ' -H Flag to enable keyword detector on HID event' echo ' -h Display this help and exit' } - -OPTIONS=gh +OPTIONS=GHh while getopts "$OPTIONS" opt ; do case $opt in - g ) GPIO_KEY_WORD_DETECTOR_FLAG="ON" + G ) + GPIO_KEY_WORD_DETECTOR_FLAG="ON" + ;; + H ) + HID_KEY_WORD_DETECTOR_FLAG="ON" ;; - h ) show_help exit 1 @@ -48,24 +51,24 @@ done SOUND_CONFIG="$HOME/.asoundrc" START_SCRIPT="$INSTALL_BASE/startsample.sh" START_PREVIEW_SCRIPT="$INSTALL_BASE/startpreview.sh" - -if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] +CMAKE_PLATFORM_SPECIFIC=(-DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON \ + -DPORTAUDIO_LIB_PATH="$THIRD_PARTY_PATH/portaudio/lib/.libs/libportaudio.$LIB_SUFFIX" \ + -DPORTAUDIO_INCLUDE_DIR="$THIRD_PARTY_PATH/portaudio/include" \ + -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ + -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) + +# Add the flags for the different keyword detectors +if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] && [ -z $HID_KEY_WORD_DETECTOR_FLAG ] then - CMAKE_PLATFORM_SPECIFIC=(-DSENSORY_KEY_WORD_DETECTOR=ON \ - -DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON \ - -DPORTAUDIO_LIB_PATH="$THIRD_PARTY_PATH/portaudio/lib/.libs/libportaudio.$LIB_SUFFIX" \ - -DPORTAUDIO_INCLUDE_DIR="$THIRD_PARTY_PATH/portaudio/include" \ + CMAKE_PLATFORM_SPECIFIC+=(-DSENSORY_KEY_WORD_DETECTOR=ON \ -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=$THIRD_PARTY_PATH/alexa-rpi/lib/libsnsr.a \ - -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=$THIRD_PARTY_PATH/alexa-rpi/include \ - -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ - -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) -else - CMAKE_PLATFORM_SPECIFIC=(-DGPIO_KEY_WORD_DETECTOR=ON \ - -DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON \ - -DPORTAUDIO_LIB_PATH="$THIRD_PARTY_PATH/portaudio/lib/.libs/libportaudio.$LIB_SUFFIX" \ - -DPORTAUDIO_INCLUDE_DIR="$THIRD_PARTY_PATH/portaudio/include" \ - -DCURL_INCLUDE_DIR=${THIRD_PARTY_PATH}/curl-${CURL_VER}/include \ - -DCURL_LIBRARY=${THIRD_PARTY_PATH}/curl-${CURL_VER}/lib/.libs/libcurl.so) + -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=$THIRD_PARTY_PATH/alexa-rpi/include) +elif [ -n "$GPIO_KEY_WORD_DETECTOR_FLAG" ] +then + CMAKE_PLATFORM_SPECIFIC+=(-DGPIO_KEY_WORD_DETECTOR=ON) +elif [ -n "$HID_KEY_WORD_DETECTOR_FLAG" ] +then + CMAKE_PLATFORM_SPECIFIC+=(-DHID_KEY_WORD_DETECTOR=ON) fi GSTREAMER_AUDIO_SINK="alsasink" @@ -78,7 +81,7 @@ install_dependencies() { run_os_specifics() { build_port_audio build_curl - if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] + if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG || -z $HID_KEY_WORD_DETECTOR_FLAG ] then build_kwd_engine fi diff --git a/tools/Install/setup.sh b/tools/Install/setup.sh index 27360caea5..d3e862a124 100755 --- a/tools/Install/setup.sh +++ b/tools/Install/setup.sh @@ -67,6 +67,7 @@ VOCALFUSION_3510_SALES_DEMO_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_sales_de VOCALFUSION_3510_AVS_SETUP_PATH_FILE="$PATH_FILES_DIR/vocalfusion_3510_avs_setup_path" PI_HAT_CTRL_PATH="$THIRD_PARTY_PATH/pi_hat_ctrl" GPIO_KEY_WORD_DETECTOR_FLAG="" +HID_KEY_WORD_DETECTOR_FLAG="" ALIASES="$HOME/.bash_aliases" # Default value for XMOS device @@ -149,7 +150,7 @@ XMOS_TAG=$2 shift 2 -OPTIONS=s:a:d:m:x:gh +OPTIONS=s:a:d:m:x:GHh while getopts "$OPTIONS" opt ; do case $opt in s ) @@ -172,8 +173,11 @@ while getopts "$OPTIONS" opt ; do x ) XMOS_DEVICE="$OPTARG" ;; - g ) - GPIO_KEY_WORD_DETECTOR_FLAG="-g" + G ) + GPIO_KEY_WORD_DETECTOR_FLAG="-G" + ;; + H ) + HID_KEY_WORD_DETECTOR_FLAG="-H" ;; h ) show_help @@ -192,7 +196,7 @@ PLATFORM=${PLATFORM:-$(get_platform)} if [ "$PLATFORM" == "Raspberry pi" ] then - PI_CMD="pi.sh ${GPIO_KEY_WORD_DETECTOR_FLAG}" + PI_CMD="pi.sh ${GPIO_KEY_WORD_DETECTOR_FLAG} ${HID_KEY_WORD_DETECTOR_FLAG}" echo "Running command ${PI_CMD}" source $PI_CMD elif [ "$PLATFORM" == "Windows mingw64" ] @@ -312,7 +316,7 @@ while true; do esac done -if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] +if [ -z $GPIO_KEY_WORD_DETECTOR_FLAG ] && [ -z $HID_KEY_WORD_DETECTOR_FLAG ] then SENSORY_OP_POINT_FLAG="-DSENSORY_OP_POINT=ON" XMOS_AVS_TESTS_FLAG="-DXMOS_AVS_TESTS=ON"