From 560304593bb2d15653bd78aed8a3b0288f3fae36 Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 15:02:34 +0200 Subject: [PATCH 01/10] smoothen things mac pro --- .gitignore | 0 README.md | 0 act.py | 0 asound.conf | 0 config.py | 0 logic.py | 0 play_script.py | 0 scripts_play/debug/debug_01.txt | 0 scripts_play/debug/debug_02.txt | 2 +- scripts_play/debug/demo.txt | 0 scripts_play/debug/interruption_02.txt | 0 scripts_play/intro/introduction_01.txt | 0 scripts_play/questions/act_01.txt | 0 scripts_play/questions/act_02.txt | 0 scripts_play/questions/act_03.txt | 0 scripts_play/questions/act_04.txt | 0 scripts_play/verdict/verdict_01.txt | 0 scripts_play/verdict/verdict_02.txt | 0 smart_speaker_theatre.py | 1 + sounds/congress.wav | Bin 20 files changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 README.md mode change 100644 => 100755 act.py mode change 100644 => 100755 asound.conf mode change 100644 => 100755 config.py mode change 100644 => 100755 logic.py mode change 100644 => 100755 play_script.py mode change 100644 => 100755 scripts_play/debug/debug_01.txt mode change 100644 => 100755 scripts_play/debug/debug_02.txt mode change 100644 => 100755 scripts_play/debug/demo.txt mode change 100644 => 100755 scripts_play/debug/interruption_02.txt mode change 100644 => 100755 scripts_play/intro/introduction_01.txt mode change 100644 => 100755 scripts_play/questions/act_01.txt mode change 100644 => 100755 scripts_play/questions/act_02.txt mode change 100644 => 100755 scripts_play/questions/act_03.txt mode change 100644 => 100755 scripts_play/questions/act_04.txt mode change 100644 => 100755 scripts_play/verdict/verdict_01.txt mode change 100644 => 100755 scripts_play/verdict/verdict_02.txt mode change 100644 => 100755 sounds/congress.wav diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/act.py b/act.py old mode 100644 new mode 100755 diff --git a/asound.conf b/asound.conf old mode 100644 new mode 100755 diff --git a/config.py b/config.py old mode 100644 new mode 100755 diff --git a/logic.py b/logic.py old mode 100644 new mode 100755 diff --git a/play_script.py b/play_script.py old mode 100644 new mode 100755 diff --git a/scripts_play/debug/debug_01.txt b/scripts_play/debug/debug_01.txt old mode 100644 new mode 100755 diff --git a/scripts_play/debug/debug_02.txt b/scripts_play/debug/debug_02.txt old mode 100644 new mode 100755 index 4ae9be5..f3c199c --- a/scripts_play/debug/debug_02.txt +++ b/scripts_play/debug/debug_02.txt @@ -1,3 +1,3 @@ ROGUE: Test a question -RASA: Well, O K Google. What time is it? [Listen to Google Home] +RASA: Well, O K Google. What is the weather in Rotterdam? [Listen to Google Home] SAINT: We got an answer. diff --git a/scripts_play/debug/demo.txt b/scripts_play/debug/demo.txt old mode 100644 new mode 100755 diff --git a/scripts_play/debug/interruption_02.txt b/scripts_play/debug/interruption_02.txt old mode 100644 new mode 100755 diff --git a/scripts_play/intro/introduction_01.txt b/scripts_play/intro/introduction_01.txt old mode 100644 new mode 100755 diff --git a/scripts_play/questions/act_01.txt b/scripts_play/questions/act_01.txt old mode 100644 new mode 100755 diff --git a/scripts_play/questions/act_02.txt b/scripts_play/questions/act_02.txt old mode 100644 new mode 100755 diff --git a/scripts_play/questions/act_03.txt b/scripts_play/questions/act_03.txt old mode 100644 new mode 100755 diff --git a/scripts_play/questions/act_04.txt b/scripts_play/questions/act_04.txt old mode 100644 new mode 100755 diff --git a/scripts_play/verdict/verdict_01.txt b/scripts_play/verdict/verdict_01.txt old mode 100644 new mode 100755 diff --git a/scripts_play/verdict/verdict_02.txt b/scripts_play/verdict/verdict_02.txt old mode 100644 new mode 100755 diff --git a/smart_speaker_theatre.py b/smart_speaker_theatre.py index d035030..6a3275b 100755 --- a/smart_speaker_theatre.py +++ b/smart_speaker_theatre.py @@ -50,6 +50,7 @@ def on_play_intro(client,userdata,msg): # 'jocavdh:play': False # } # })) + import pdb; pdb.set_trace() path = 'scripts_play/intro/' call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) print('The act is over.') diff --git a/sounds/congress.wav b/sounds/congress.wav old mode 100644 new mode 100755 From defd37b30383ea1db8ac29e365d57545a1c60119 Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 15:02:51 +0200 Subject: [PATCH 02/10] delete superfluues script --- play_script.py | 139 ------------------------------------------------- 1 file changed, 139 deletions(-) delete mode 100755 play_script.py diff --git a/play_script.py b/play_script.py deleted file mode 100755 index 19fe24b..0000000 --- a/play_script.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# PLAY_ACT.py -# This script runs the play -# It is in a seperate file to enable the mechanism to detect the Google Home speaking, before continuing to the next line - -# Libraries -from config import characters, directions -from logic import tts, read_script, led_on, led_off, select_script -from pixel_ring import pixel_ring -from subprocess import call -import paho.mqtt.client as mqtt -import json -import sys -from time import sleep - -# Switch of LED's of speakers at the start of the play -pixel_ring.off() - - - -# === SETUP OF MQTT PART 1 === - -# Location of the MQTT server -HOST = 'localhost' -PORT = 1883 - -# Subscribe to relevant MQTT topics -def on_connect(client, userdata, flags, rc): - print("Connected to {0} with result code {1}".format(HOST, rc)) - # Subscribe to the text detected topic - client.subscribe("hermes/asr/textCaptured") - client.subscribe("hermes/dialogueManager/sessionQueued") - -# Function which sets a flag when the Google Home is not speaking -# Callback of MQTT message that says that the text is captured by the speech recognition (ASR) -def done_speaking(client, userdata, msg): - print('Google Home is not speaking anymore') - client.connected_flag=True - -# Function which removes intents that are by accident activated by the Google Home -# e.g. The google home says introduce yourself, which could trigger the other speakers to introduce themselves -# Snips works with queing of sessions, so this situation would only happen after this play is finished -def remove_sessions(client, userdata, msg): - sessionId = json.loads(id.payload) - print('delete mistaken intent') - client.publish("hermes/dialogueManager/endSession", json.dumps({ - 'sessionId': sessionId, - })) - - - - -# === SETUP OF MQTT PART 2 === - -# Initialise MQTT client -client = mqtt.Client() -client.connect(HOST, PORT, 60) -client.on_connect = on_connect - - - - -# === Read script and run the play === - -# Flags to check if the system is listening, or not -client.connected_flag=False -listening = False - - -# Read the script and run the play - - -#file = sys.argv[1] # get the chosen act passed by smart_speaker_theatre.py -file = select_script('scripts_play/intro/') - -for character, line, direction in read_script(file): - input_text = line - voice = characters.get(character)[0] - speaker = characters.get(character)[1] - #speaker = 'default' - # Some way to do something with the stage directions will come here - action = directions.get(direction[0]) - pixel_ring.speak() - tts(voice, input_text, speaker) - - if action == 'listen_google_home': - print('Waiting for the Google Home to finish its talk') - - # # start voice activity detection - # client.publish("hermes/asr/startListening", json.dumps({ - # 'siteId': 'default', - # 'init': { - # 'type': 'action', - # 'canBeEnqueued': True - # } - # })) - - # Activate the microphone and speech recognition - client.publish("hermes/asr/startListening", json.dumps({ - 'siteId': 'default' - })) - - # LED to listening mode - pixel_ring.listen() - - # create callback - client.on_message = done_speaking - listening = True - - while listening: - client.loop() - - #client.on_message = on_message - client.message_callback_add('hermes/asr/textCaptured', done_speaking) - - if client.connected_flag: - sleep(1) - print('Continue the play') - client.connected_flag = False - client.message_callback_add('hermes/dialogueManager/sessionQueued', remove_sessions) - break - - if action == 'music': - print('play audioclip') - playing = True - - while playing: - call(["aplay", "-D", speaker, "/usr/share/snips/congress.wav"]) - playing = False - - - - pixel_ring.off() # Switch of the lights when done speaking - sleep(0.2) # Add a short pause between the lines - - -print('The act is done.') \ No newline at end of file From 132268ca184228eb3ede0f171f922b090b030d74 Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 15:29:05 +0200 Subject: [PATCH 03/10] included play_script now as a function in logic, instead of a script --- logic.py | 69 ++++++++++++++++++------- smart_speaker_theatre.py | 18 +++---- asound.conf => tools/asound-sample.conf | 0 tuning.py => tools/microphone-tuning.py | 0 4 files changed, 58 insertions(+), 29 deletions(-) rename asound.conf => tools/asound-sample.conf (100%) rename tuning.py => tools/microphone-tuning.py (100%) diff --git a/logic.py b/logic.py index e8c840e..b28096a 100755 --- a/logic.py +++ b/logic.py @@ -140,7 +140,7 @@ def listen(): print('no voice detected') voice_detected = 0 - time.sleep(1) + time.sleep(0.5) print('Google Home is done') @@ -148,27 +148,60 @@ def listen(): # 05 CONTROL THE LED OF THE SPEAKERS import serial from pixel_ring import pixel_ring -ser = serial.Serial('/dev/ttyACM0', 1000000) # Establish the connection on a specific port +from smart_speaker_theatre import ser def led_on(speaker): - if speaker == 'mono3': - ser.write(b'3') - - if speaker == 'mono1': - ser.write(b'1') - - if speaker == 'mono2': - pixel_ring.speak() + if ser: + if speaker == 'mono3': + ser.write(b'3') + + if speaker == 'mono1': + ser.write(b'1') + + if speaker == 'mono2': + pixel_ring.speak() def led_off(speaker): - if speaker == 'mono3': - ser.write(b'4') - - if speaker == 'mono1': - ser.write(b'2') - - if speaker == 'mono2': - pixel_ring.off() + if ser: + if speaker == 'mono3': + ser.write(b'4') + + if speaker == 'mono1': + ser.write(b'2') + + if speaker == 'mono2': + pixel_ring.off() + +# Play the theatre acts +def play_script(file): + + for character, line, direction in read_script(file): + input_text = line + voice = characters.get(character)[0] + speaker = characters.get(character)[1] + action = directions.get(direction[0]) + + led_on(speaker) + + tts(voice, input_text, speaker) + + if action == 'listen_google_home': + listen() + + if action == 'music': + print('play audioclip') + playing = True + + while playing: + call(["aplay", "-D", speaker, "/usr/share/snips/congress.wav"]) + playing = False + + + led_off(speaker) + sleep(0.2) # Add a short pause between the lines + + + print('The act is done.') diff --git a/smart_speaker_theatre.py b/smart_speaker_theatre.py index 6a3275b..710227c 100755 --- a/smart_speaker_theatre.py +++ b/smart_speaker_theatre.py @@ -9,12 +9,13 @@ # Libraries import re from config import characters, directions -from logic import tts, read_script, select_script +from logic import tts, read_script, select_script, play_script from subprocess import call import paho.mqtt.client as mqtt import json from time import sleep from pixel_ring import pixel_ring +import serial # === SETUP OF MQTT PART 1 === @@ -33,6 +34,8 @@ def on_connect(client, userdata, flags, rc): client.subscribe("hermes/asr/textCaptured") client.subscribe("hermes/dialogueManager/sessionQueued") +# Set up serial connection with the microcontroller that controls the speaker LED's +ser = serial.Serial('/dev/ttyACM0', 1000000) @@ -43,20 +46,13 @@ def on_wakeword(client, userdata, msg): # Function which is triggered when the intent introduction is activated def on_play_intro(client,userdata,msg): - # # disable this intent to avoid playing another act triggered by the Google Home - # client.publish("hermes/dialogueManager/configure", json.dumps({ - # 'siteId': 'default', - # 'intents': { - # 'jocavdh:play': False - # } - # })) + import pdb; pdb.set_trace() path = 'scripts_play/intro/' - call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) + #call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) + play_script('scripts_play/intro/introduction_01.txt') print('The act is over.') - #on_play_question(client, userdata, msg) - # Function which is triggered when the intent for another question is activated def on_play_question(client, userdata, msg): diff --git a/asound.conf b/tools/asound-sample.conf similarity index 100% rename from asound.conf rename to tools/asound-sample.conf diff --git a/tuning.py b/tools/microphone-tuning.py similarity index 100% rename from tuning.py rename to tools/microphone-tuning.py From 69795e880fae66bbdfd9ea9e591c910ba6ba0caa Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 15:32:35 +0200 Subject: [PATCH 04/10] pixel_ring --- pixel_ring | 1 - 1 file changed, 1 deletion(-) delete mode 160000 pixel_ring diff --git a/pixel_ring b/pixel_ring deleted file mode 160000 index 30de559..0000000 --- a/pixel_ring +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 30de55966fdf0d0f1ee6e02cf356c56ba76b577b From 12bcf9a5acd4b53a6de5896ce7b8372da07befa2 Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 15:34:01 +0200 Subject: [PATCH 05/10] added local version of pixel ring --- pixel_ring/__init__.py | 52 +++++++ pixel_ring/apa102.py | 243 ++++++++++++++++++++++++++++++++ pixel_ring/apa102_pixel_ring.py | 105 ++++++++++++++ pixel_ring/pattern.py | 145 +++++++++++++++++++ pixel_ring/pixel_ring.py | 26 ++++ pixel_ring/usb_pixel_ring_v1.py | 190 +++++++++++++++++++++++++ pixel_ring/usb_pixel_ring_v2.py | 122 ++++++++++++++++ 7 files changed, 883 insertions(+) create mode 100755 pixel_ring/__init__.py create mode 100755 pixel_ring/apa102.py create mode 100755 pixel_ring/apa102_pixel_ring.py create mode 100755 pixel_ring/pattern.py create mode 100755 pixel_ring/pixel_ring.py create mode 100755 pixel_ring/usb_pixel_ring_v1.py create mode 100755 pixel_ring/usb_pixel_ring_v2.py diff --git a/pixel_ring/__init__.py b/pixel_ring/__init__.py new file mode 100755 index 0000000..fedbbb1 --- /dev/null +++ b/pixel_ring/__init__.py @@ -0,0 +1,52 @@ + + +from . import usb_pixel_ring_v1 +from . import usb_pixel_ring_v2 +from .apa102_pixel_ring import PixelRing + +pixel_ring = usb_pixel_ring_v2.find() + +if not pixel_ring: + pixel_ring = usb_pixel_ring_v1.find() + +if not pixel_ring: + pixel_ring = PixelRing() + + +USAGE = ''' +If the hardware is ReSpeaker 4 Mic Array for Pi or ReSpeaker V2, +there is a power-enable pin which should be enabled at first. ++ ReSpeaker 4 Mic Array for Pi: + + import gpiozero + power = LED(5) + power.on() + ++ ReSpeaker V2: + + import mraa + power = mraa.Gpio(12) + power.dir(mraa.DIR_OUT) + power.write(0) +''' + +def main(): + import time + + if isinstance(pixel_ring, usb_pixel_ring_v2.PixelRing): + print('Found ReSpeaker USB 4 Mic Array') + elif isinstance(pixel_ring, usb_pixel_ring_v1.UsbPixelRing): + print('Found ReSpeaker USB 6+1 Mic Array') + else: + print('Control APA102 RGB LEDs via SPI') + print(USAGE) + + pixel_ring.think() + time.sleep(3) + pixel_ring.off() + time.sleep(1) + + +if __name__ == '__main__': + main() + diff --git a/pixel_ring/apa102.py b/pixel_ring/apa102.py new file mode 100755 index 0000000..e5918b9 --- /dev/null +++ b/pixel_ring/apa102.py @@ -0,0 +1,243 @@ +""" +from https://github.com/tinue/APA102_Pi +This is the main driver module for APA102 LEDs +""" +import spidev +from math import ceil + +RGB_MAP = { 'rgb': [3, 2, 1], 'rbg': [3, 1, 2], 'grb': [2, 3, 1], + 'gbr': [2, 1, 3], 'brg': [1, 3, 2], 'bgr': [1, 2, 3] } + +class APA102: + """ + Driver for APA102 LEDS (aka "DotStar"). + + (c) Martin Erzberger 2016-2017 + + My very first Python code, so I am sure there is a lot to be optimized ;) + + Public methods are: + - set_pixel + - set_pixel_rgb + - show + - clear_strip + - cleanup + + Helper methods for color manipulation are: + - combine_color + - wheel + + The rest of the methods are used internally and should not be used by the + user of the library. + + Very brief overview of APA102: An APA102 LED is addressed with SPI. The bits + are shifted in one by one, starting with the least significant bit. + + An LED usually just forwards everything that is sent to its data-in to + data-out. While doing this, it remembers its own color and keeps glowing + with that color as long as there is power. + + An LED can be switched to not forward the data, but instead use the data + to change it's own color. This is done by sending (at least) 32 bits of + zeroes to data-in. The LED then accepts the next correct 32 bit LED + frame (with color information) as its new color setting. + + After having received the 32 bit color frame, the LED changes color, + and then resumes to just copying data-in to data-out. + + The really clever bit is this: While receiving the 32 bit LED frame, + the LED sends zeroes on its data-out line. Because a color frame is + 32 bits, the LED sends 32 bits of zeroes to the next LED. + As we have seen above, this means that the next LED is now ready + to accept a color frame and update its color. + + So that's really the entire protocol: + - Start by sending 32 bits of zeroes. This prepares LED 1 to update + its color. + - Send color information one by one, starting with the color for LED 1, + then LED 2 etc. + - Finish off by cycling the clock line a few times to get all data + to the very last LED on the strip + + The last step is necessary, because each LED delays forwarding the data + a bit. Imagine ten people in a row. When you yell the last color + information, i.e. the one for person ten, to the first person in + the line, then you are not finished yet. Person one has to turn around + and yell it to person 2, and so on. So it takes ten additional "dummy" + cycles until person ten knows the color. When you look closer, + you will see that not even person 9 knows its own color yet. This + information is still with person 2. Essentially the driver sends additional + zeroes to LED 1 as long as it takes for the last color frame to make it + down the line to the last LED. + """ + # Constants + MAX_BRIGHTNESS = 0b11111 # Safeguard: Set to a value appropriate for your setup + LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits + + def __init__(self, num_led, global_brightness=MAX_BRIGHTNESS, + order='rgb', bus=0, device=1, max_speed_hz=8000000): + self.num_led = num_led # The number of LEDs in the Strip + order = order.lower() + self.rgb = RGB_MAP.get(order, RGB_MAP['rgb']) + # Limit the brightness to the maximum if it's set higher + if global_brightness > self.MAX_BRIGHTNESS: + self.global_brightness = self.MAX_BRIGHTNESS + else: + self.global_brightness = global_brightness + + self.leds = [self.LED_START,0,0,0] * self.num_led # Pixel buffer + self.spi = spidev.SpiDev() # Init the SPI device + self.spi.open(bus, device) # Open SPI port 0, slave device (CS) 1 + # Up the speed a bit, so that the LEDs are painted faster + if max_speed_hz: + self.spi.max_speed_hz = max_speed_hz + + def clock_start_frame(self): + """Sends a start frame to the LED strip. + + This method clocks out a start frame, telling the receiving LED + that it must update its own color now. + """ + self.spi.xfer2([0] * 4) # Start frame, 32 zero bits + + + def clock_end_frame(self): + """Sends an end frame to the LED strip. + + As explained above, dummy data must be sent after the last real colour + information so that all of the data can reach its destination down the line. + The delay is not as bad as with the human example above. + It is only 1/2 bit per LED. This is because the SPI clock line + needs to be inverted. + + Say a bit is ready on the SPI data line. The sender communicates + this by toggling the clock line. The bit is read by the LED + and immediately forwarded to the output data line. When the clock goes + down again on the input side, the LED will toggle the clock up + on the output to tell the next LED that the bit is ready. + + After one LED the clock is inverted, and after two LEDs it is in sync + again, but one cycle behind. Therefore, for every two LEDs, one bit + of delay gets accumulated. For 300 LEDs, 150 additional bits must be fed to + the input of LED one so that the data can reach the last LED. + + Ultimately, we need to send additional numLEDs/2 arbitrary data bits, + in order to trigger numLEDs/2 additional clock changes. This driver + sends zeroes, which has the benefit of getting LED one partially or + fully ready for the next update to the strip. An optimized version + of the driver could omit the "clockStartFrame" method if enough zeroes have + been sent as part of "clockEndFrame". + """ + + self.spi.xfer2([0xFF] * 4) + + # Round up num_led/2 bits (or num_led/16 bytes) + #for _ in range((self.num_led + 15) // 16): + # self.spi.xfer2([0x00]) + + + def clear_strip(self): + """ Turns off the strip and shows the result right away.""" + + for led in range(self.num_led): + self.set_pixel(led, 0, 0, 0) + self.show() + + + def set_pixel(self, led_num, red, green, blue, bright_percent=100): + """Sets the color of one pixel in the LED stripe. + + The changed pixel is not shown yet on the Stripe, it is only + written to the pixel buffer. Colors are passed individually. + If brightness is not set the global brightness setting is used. + """ + if led_num < 0: + return # Pixel is invisible, so ignore + if led_num >= self.num_led: + return # again, invisible + + # Calculate pixel brightness as a percentage of the + # defined global_brightness. Round up to nearest integer + # as we expect some brightness unless set to 0 + brightness = int(ceil(bright_percent*self.global_brightness/100.0)) + + # LED startframe is three "1" bits, followed by 5 brightness bits + ledstart = (brightness & 0b00011111) | self.LED_START + + start_index = 4 * led_num + self.leds[start_index] = ledstart + self.leds[start_index + self.rgb[0]] = red + self.leds[start_index + self.rgb[1]] = green + self.leds[start_index + self.rgb[2]] = blue + + + def set_pixel_rgb(self, led_num, rgb_color, bright_percent=100): + """Sets the color of one pixel in the LED stripe. + + The changed pixel is not shown yet on the Stripe, it is only + written to the pixel buffer. + Colors are passed combined (3 bytes concatenated) + If brightness is not set the global brightness setting is used. + """ + self.set_pixel(led_num, (rgb_color & 0xFF0000) >> 16, + (rgb_color & 0x00FF00) >> 8, rgb_color & 0x0000FF, + bright_percent) + + + def rotate(self, positions=1): + """ Rotate the LEDs by the specified number of positions. + + Treating the internal LED array as a circular buffer, rotate it by + the specified number of positions. The number could be negative, + which means rotating in the opposite direction. + """ + cutoff = 4 * (positions % self.num_led) + self.leds = self.leds[cutoff:] + self.leds[:cutoff] + + + def show(self): + """Sends the content of the pixel buffer to the strip. + + Todo: More than 1024 LEDs requires more than one xfer operation. + """ + self.clock_start_frame() + # xfer2 kills the list, unfortunately. So it must be copied first + # SPI takes up to 4096 Integers. So we are fine for up to 1024 LEDs. + data = list(self.leds) + while data: + self.spi.xfer2(data[:32]) + data = data[32:] + self.clock_end_frame() + + + def cleanup(self): + """Release the SPI device; Call this method at the end""" + + self.spi.close() # Close SPI port + + @staticmethod + def combine_color(red, green, blue): + """Make one 3*8 byte color value.""" + + return (red << 16) + (green << 8) + blue + + + def wheel(self, wheel_pos): + """Get a color from a color wheel; Green -> Red -> Blue -> Green""" + + if wheel_pos > 255: + wheel_pos = 255 # Safeguard + if wheel_pos < 85: # Green -> Red + return self.combine_color(wheel_pos * 3, 255 - wheel_pos * 3, 0) + if wheel_pos < 170: # Red -> Blue + wheel_pos -= 85 + return self.combine_color(255 - wheel_pos * 3, 0, wheel_pos * 3) + # Blue -> Green + wheel_pos -= 170 + return self.combine_color(0, wheel_pos * 3, 255 - wheel_pos * 3) + + + def dump_array(self): + """For debug purposes: Dump the LED array onto the console.""" + + print(self.leds) diff --git a/pixel_ring/apa102_pixel_ring.py b/pixel_ring/apa102_pixel_ring.py new file mode 100755 index 0000000..8690ba2 --- /dev/null +++ b/pixel_ring/apa102_pixel_ring.py @@ -0,0 +1,105 @@ + +import time +import threading +try: + import queue as Queue +except ImportError: + import Queue as Queue + +from .apa102 import APA102 +from .pattern import Echo, GoogleHome + + +class PixelRing(object): + PIXELS_N = 12 + + def __init__(self, pattern='google'): + if pattern == 'echo': + self.pattern = Echo(show=self.show) + else: + self.pattern = GoogleHome(show=self.show) + + self.dev = APA102(num_led=self.PIXELS_N) + + self.queue = Queue.Queue() + self.thread = threading.Thread(target=self._run) + self.thread.daemon = True + self.thread.start() + self.off() + + def set_brightness(self, brightness): + if brightness > 100: + brightness = 100 + + if brightness > 0: + self.dev.global_brightness = int(0b11111 * brightness / 100) + + def change_pattern(self, pattern): + if pattern == 'echo': + self.pattern = Echo(show=self.show) + else: + self.pattern = GoogleHome(show=self.show) + + def wakeup(self, direction=0): + def f(): + self.pattern.wakeup(direction) + + self.put(f) + + def listen(self): + self.put(self.pattern.listen) + + def think(self): + self.put(self.pattern.think) + + wait = think + + def speak(self): + self.put(self.pattern.speak) + + def off(self): + self.put(self.pattern.off) + + def put(self, func): + self.pattern.stop = True + self.queue.put(func) + + def _run(self): + while True: + func = self.queue.get() + self.pattern.stop = False + func() + + def show(self, data): + for i in range(self.PIXELS_N): + self.dev.set_pixel(i, int(data[4*i + 1]), int(data[4*i + 2]), int(data[4*i + 3])) + + self.dev.show() + + def set_color(self, rgb=None, r=0, g=0, b=0): + if rgb: + r, g, b = (rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF + for i in range(self.PIXELS_N): + self.dev.set_pixel(i, r, g, b) + + self.dev.show() + + +if __name__ == '__main__': + pixel_ring = PixelRing() + while True: + try: + pixel_ring.wakeup() + time.sleep(3) + pixel_ring.think() + time.sleep(3) + pixel_ring.speak() + time.sleep(6) + pixel_ring.off() + time.sleep(3) + except KeyboardInterrupt: + break + + + pixel_ring.off() + time.sleep(1) diff --git a/pixel_ring/pattern.py b/pixel_ring/pattern.py new file mode 100755 index 0000000..054910f --- /dev/null +++ b/pixel_ring/pattern.py @@ -0,0 +1,145 @@ +""" +LED pattern like Echo +""" + +import time + + +class Echo(object): + brightness = 24 * 8 + + def __init__(self, show, number=12): + self.pixels_number = number + self.pixels = [0] * 4 * number + + if not callable(show): + raise ValueError('show parameter is not callable') + + self.show = show + self.stop = False + + def wakeup(self, direction=0): + position = int((direction + 15) / (360 / self.pixels_number)) % self.pixels_number + + pixels = [0, 0, 0, self.brightness] * self.pixels_number + pixels[position * 4 + 2] = self.brightness + + self.show(pixels) + + def listen(self): + pixels = [0, 0, 0, self.brightness] * self.pixels_number + + self.show(pixels) + + def think(self): + half_brightness = int(self.brightness / 2) + pixels = [0, 0, half_brightness, half_brightness, 0, 0, 0, self.brightness] * self.pixels_number + + while not self.stop: + self.show(pixels) + time.sleep(0.2) + pixels = pixels[-4:] + pixels[:-4] + + def speak(self): + step = int(self.brightness / 12) + position = int(self.brightness / 2) + while not self.stop: + pixels = [0, 0, position, self.brightness - position] * self.pixels_number + self.show(pixels) + time.sleep(0.01) + if position <= 0: + step = int(self.brightness / 12) + time.sleep(0.4) + elif position >= int(self.brightness / 2): + step = - int(self.brightness / 12) + time.sleep(0.4) + + position += step + + def off(self): + self.show([0] * 4 * 12) + +class GoogleHome(object): + def __init__(self, show): + self.basis = [0] * 4 * 12 + self.basis[0 * 4 + 1] = 8 + self.basis[3 * 4 + 1] = 4 + self.basis[3 * 4 + 2] = 4 + self.basis[6 * 4 + 2] = 8 + self.basis[9 * 4 + 3] = 8 + + self.pixels = self.basis + + if not callable(show): + raise ValueError('show parameter is not callable') + + self.show = show + self.stop = False + + def wakeup(self, direction=0): + position = int((direction + 90 + 15) / 30) % 12 + + basis = self.basis[position*-4:] + self.basis[:position*-4] + + pixels = [v * 25 for v in basis] + self.show(pixels) + time.sleep(0.1) + + pixels = pixels[-4:] + pixels[:-4] + self.show(pixels) + time.sleep(0.1) + + for i in range(2): + new_pixels = pixels[-4:] + pixels[:-4] + + self.show([v/2+pixels[index] for index, v in enumerate(new_pixels)]) + pixels = new_pixels + time.sleep(0.1) + + self.show(pixels) + self.pixels = pixels + + def listen(self): + pixels = self.pixels + for i in range(1, 25): + self.show([(v * i / 24) for v in pixels]) + time.sleep(0.01) + + def think(self): + pixels = self.pixels + + while not self.stop: + pixels = pixels[-4:] + pixels[:-4] + self.show(pixels) + time.sleep(0.2) + + t = 0.1 + for i in range(0, 5): + pixels = pixels[-4:] + pixels[:-4] + self.show([(v * (4 - i) / 4) for v in pixels]) + time.sleep(t) + t /= 2 + + self.pixels = pixels + + def speak(self): + pixels = self.pixels + step = 1 + brightness = 5 + while not self.stop: + self.show([(v * brightness / 24) for v in pixels]) + time.sleep(0.02) + + if brightness <= 5: + step = 1 + time.sleep(0.4) + elif brightness >= 24: + step = -1 + time.sleep(0.4) + + brightness += step + + def off(self): + self.show([0] * 4 * 12) + + diff --git a/pixel_ring/pixel_ring.py b/pixel_ring/pixel_ring.py new file mode 100755 index 0000000..0f68f68 --- /dev/null +++ b/pixel_ring/pixel_ring.py @@ -0,0 +1,26 @@ + + +class PixelRing(object): + def __init__(self): + pass + + def show(self, data): + pass + + def set_color(self, rgb=None, r=0, g=0, b=0): + pass + + def wakeup(self, angle=None): + pass + + def listen(self): + pass + + def think(self): + pass + + def speak(self): + pass + + def off(self): + pass diff --git a/pixel_ring/usb_pixel_ring_v1.py b/pixel_ring/usb_pixel_ring_v1.py new file mode 100755 index 0000000..343d758 --- /dev/null +++ b/pixel_ring/usb_pixel_ring_v1.py @@ -0,0 +1,190 @@ + +import usb.core +import usb.util + + +class HidDevice: + """ + This class provides basic functions to access + a USB HID device to write an endpoint + """ + + def __init__(self, dev, ep_in, ep_out): + self.dev = dev + self.ep_in = ep_in + self.ep_out = ep_out + + def write(self, data): + """ + write data on the OUT endpoint associated to the HID interface + """ + self.ep_out.write(data) + + def read(self): + return self.ep_in.read(self.ep_in.wMaxPacketSize, -1) + + def close(self): + """ + close the interface + """ + usb.util.dispose_resources(self.dev) + + @staticmethod + def find(vid=0x2886, pid=0x0007): + dev = usb.core.find(idVendor=vid, idProduct=pid) + if not dev: + return + + config = dev.get_active_configuration() + + # Iterate on all interfaces to find a HID interface + ep_in, ep_out = None, None + for interface in config: + if interface.bInterfaceClass == 0x03: + try: + if dev.is_kernel_driver_active(interface.bInterfaceNumber): + dev.detach_kernel_driver(interface.bInterfaceNumber) + except Exception as e: + print(e.message) + + for ep in interface: + if ep.bEndpointAddress & 0x80: + ep_in = ep + else: + ep_out = ep + break + + + + if ep_in and ep_out: + hid = HidDevice(dev, ep_in, ep_out) + + return hid + + +class UsbPixelRing: + PIXELS_N = 12 + + MONO = 1 + THINK = 3 + VOLUME = 5 + CUSTOM = 6 + + def __init__(self, hid=None, pattern=None): + self.hid = hid if hid else HidDevice.find() + if not self.hid: + print('No USB device found') + + colors = [0] * 4 * self.PIXELS_N + colors[0] = 0x4 + colors[1] = 0x40 + colors[2] = 0x4 + + colors[4 + 1] = 0x8 + colors[4 * 11 + 1] = 0x8 + + self.direction_template = colors + + def set_brightness(self, brightness): + print('Not support to change brightness') + + def change_pattern(self, pattern=None): + print('Not support to change pattern') + + def off(self): + self.set_color(rgb=0) + + def set_color(self, rgb=None, r=0, g=0, b=0): + if rgb: + self.write(0, [self.MONO, rgb & 0xFF, (rgb >> 8) & 0xFF, (rgb >> 16) & 0xFF]) + else: + self.write(0, [self.MONO, b, g, r]) + + def think(self): + self.write(0, [self.THINK, 0, 0, 0]) + + wait = think + + speak = think + + def set_volume(self, pixels): + self.write(0, [self.VOLUME, 0, 0, pixels]) + + def wakeup(self, angle=0): + if angle < 0 or angle > 360: + return + + position = int((angle + 15) % 360 / 30) % self.PIXELS_N + colors = self.direction_template[-position*4:] + self.direction_template[:-position*4] + + self.write(0, [self.CUSTOM, 0, 0, 0]) + self.write(3, colors) + + return position + + def listen(self, angle=0): + self.write(0, [self.MONO, 0, 0x10, 0]) + + def show(self, data): + self.write(0, [self.CUSTOM, 0, 0, 0]) + self.write(3, data) + + @staticmethod + def to_bytearray(data): + if type(data) is int: + array = bytearray([data & 0xFF]) + elif type(data) is bytearray: + array = data + elif type(data) is str or type(data) is bytes: + array = bytearray(data) + elif type(data) is list: + array = bytearray(data) + else: + raise TypeError('%s is not supported' % type(data)) + + return array + + def write(self, address, data): + data = self.to_bytearray(data) + length = len(data) + if self.hid: + packet = bytearray([address & 0xFF, (address >> 8) & 0xFF, length & 0xFF, (length >> 8) & 0xFF]) + data + self.hid.write(packet) + + def close(self): + if self.hid: + self.hid.close() + + def __call__(self, data): + self.write(3, data) + + +def find(): + hid = HidDevice.find() + + if hid: + pixel_ring = UsbPixelRing(hid) + return pixel_ring + + +if __name__ == '__main__': + import time + + pixel_ring = UsbPixelRing() + while True: + try: + pixel_ring.wakeup(180) + time.sleep(3) + pixel_ring.listen() + time.sleep(3) + pixel_ring.think() + time.sleep(3) + pixel_ring.set_volume(8) + time.sleep(3) + pixel_ring.off() + time.sleep(3) + except KeyboardInterrupt: + break + + pixel_ring.off() + diff --git a/pixel_ring/usb_pixel_ring_v2.py b/pixel_ring/usb_pixel_ring_v2.py new file mode 100755 index 0000000..b7a07a2 --- /dev/null +++ b/pixel_ring/usb_pixel_ring_v2.py @@ -0,0 +1,122 @@ + +import usb.core +import usb.util + + +class PixelRing: + TIMEOUT = 8000 + + def __init__(self, dev): + self.dev = dev + + def trace(self): + self.write(0) + + def mono(self, color): + self.write(1, [(color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, 0]) + + def set_color(self, rgb=None, r=0, g=0, b=0): + if rgb: + self.mono(rgb) + else: + self.write(1, [r, g, b, 0]) + + def off(self): + self.mono(0) + + def listen(self, direction=None): + self.write(2) + + wakeup = listen + + def speak(self): + self.write(3) + + def think(self): + self.write(4) + + wait = think + + def spin(self): + self.write(5) + + def show(self, data): + self.write(6, data) + + customize = show + + def set_brightness(self, brightness): + self.write(0x20, [brightness]) + + def set_color_palette(self, a, b): + self.write(0x21, [(a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, 0, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, 0]) + + def set_vad_led(self, state): + self.write(0x22, [state]) + + def set_volume(self, volume): + self.write(0x23, [volume]) + + def change_pattern(self, pattern): + if pattern == 'echo': + self.write(0x24, [1]) + else: + self.write(0x24, [0]) + + def write(self, cmd, data=[0]): + self.dev.ctrl_transfer( + usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, cmd, 0x1C, data, self.TIMEOUT) + + @property + def version(self): + return self.dev.ctrl_transfer( + usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, 0x80 | 0x40, 0x1C, 24, self.TIMEOUT).tostring() + + def close(self): + """ + close the interface + """ + usb.util.dispose_resources(self.dev) + + +def find(vid=0x2886, pid=0x0018): + dev = usb.core.find(idVendor=vid, idProduct=pid) + if not dev: + return + + # configuration = dev.get_active_configuration() + + # interface_number = None + # for interface in configuration: + # interface_number = interface.bInterfaceNumber + + # if dev.is_kernel_driver_active(interface_number): + # dev.detach_kernel_driver(interface_number) + + return PixelRing(dev) + + + +if __name__ == '__main__': + import time + + pixel_ring = find() + print(pixel_ring.version) + while True: + try: + pixel_ring.wakeup(180) + time.sleep(3) + pixel_ring.listen() + time.sleep(3) + pixel_ring.think() + time.sleep(3) + pixel_ring.set_volume(8) + time.sleep(3) + pixel_ring.off() + time.sleep(3) + except KeyboardInterrupt: + break + + pixel_ring.off() \ No newline at end of file From 963e08a8d4ddac9b69145bddd63669bda8a18073 Mon Sep 17 00:00:00 2001 From: joca Date: Mon, 3 Jun 2019 16:25:41 +0200 Subject: [PATCH 06/10] test for led --- act.py | 10 +-- led.py | 12 +++ logic.py | 2 +- tools/tuning.py | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ tuning.py | 197 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 412 insertions(+), 6 deletions(-) create mode 100644 led.py create mode 100755 tools/tuning.py create mode 100755 tuning.py diff --git a/act.py b/act.py index d7b2ef3..0437fa5 100755 --- a/act.py +++ b/act.py @@ -19,15 +19,15 @@ from time import sleep import serial from pixel_ring import pixel_ring -ser = serial.Serial('/dev/ttyACM0', 1000000) # Establish the connection on a specific port +ser = serial.Serial('/dev/ttyACM0', 9600) # Establish the connection on a specific port def led_on(speaker): if speaker == 'mono3': - ser.write(b'3') + ser.write(b'H') if speaker == 'mono1': - ser.write(b'1') + ser.write(b'H') if speaker == 'mono2': pixel_ring.speak() @@ -35,10 +35,10 @@ def led_on(speaker): def led_off(speaker): if speaker == 'mono3': - ser.write(b'4') + ser.write(b'L') if speaker == 'mono1': - ser.write(b'2') + ser.write(b'L') if speaker == 'mono2': pixel_ring.off() diff --git a/led.py b/led.py new file mode 100644 index 0000000..f156f39 --- /dev/null +++ b/led.py @@ -0,0 +1,12 @@ +import serial +from time import sleep + +ser = serial.Serial('/dev/ttyACM0', 9600, timeout = None) +sleep(2) +ser.write(b'H') +sleep(1) +ser.write(b'L') +sleep(1) +ser.write(b'H') +sleep(1) +ser.write(b'L') \ No newline at end of file diff --git a/logic.py b/logic.py index b28096a..1900669 100755 --- a/logic.py +++ b/logic.py @@ -148,7 +148,7 @@ def listen(): # 05 CONTROL THE LED OF THE SPEAKERS import serial from pixel_ring import pixel_ring -from smart_speaker_theatre import ser + def led_on(speaker): diff --git a/tools/tuning.py b/tools/tuning.py new file mode 100755 index 0000000..c1ed7fe --- /dev/null +++ b/tools/tuning.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- + +import sys +import struct +import usb.core +import usb.util + +USAGE = """Usage: python {} -h + -p show all parameters + -r read all parameters + NAME get the parameter with the NAME + NAME VALUE set the parameter with the NAME and the VALUE +""" + + + +# parameter list +# name: (id, offset, type, max, min , r/w, info) +PARAMETERS = { + 'AECFREEZEONOFF': (18, 7, 'int', 1, 0, 'rw', 'Adaptive Echo Canceler updates inhibit.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), + 'AECNORM': (18, 19, 'float', 16, 0.25, 'rw', 'Limit on norm of AEC filter coefficients'), + 'AECPATHCHANGE': (18, 25, 'int', 1, 0, 'ro', 'AEC Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), + 'RT60': (18, 26, 'float', 0.9, 0.25, 'ro', 'Current RT60 estimate in seconds'), + 'HPFONOFF': (18, 27, 'int', 3, 0, 'rw', 'High-pass Filter on microphone signals.', '0 = OFF', '1 = ON - 70 Hz cut-off', '2 = ON - 125 Hz cut-off', '3 = ON - 180 Hz cut-off'), + 'RT60ONOFF': (18, 28, 'int', 1, 0, 'rw', 'RT60 Estimation for AES. 0 = OFF 1 = ON'), + 'AECSILENCELEVEL': (18, 30, 'float', 1, 1e-09, 'rw', 'Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))'), + 'AECSILENCEMODE': (18, 31, 'int', 1, 0, 'ro', 'AEC far-end silence detection status. ', '0 = false (signal detected) ', '1 = true (silence detected)'), + 'AGCONOFF': (19, 0, 'int', 1, 0, 'rw', 'Automatic Gain Control. ', '0 = OFF ', '1 = ON'), + 'AGCMAXGAIN': (19, 1, 'float', 1000, 1, 'rw', 'Maximum AGC gain factor. ', '[0 .. 60] dB (default 30dB = 20log10(31.6))'), + 'AGCDESIREDLEVEL': (19, 2, 'float', 0.99, 1e-08, 'rw', 'Target power level of the output signal. ', '[−inf .. 0] dBov (default: −23dBov = 10log10(0.005))'), + 'AGCGAIN': (19, 3, 'float', 1000, 1, 'rw', 'Current AGC gain factor. ', '[0 .. 60] dB (default: 0.0dB = 20log10(1.0))'), + 'AGCTIME': (19, 4, 'float', 1, 0.1, 'rw', 'Ramps-up / down time-constant in seconds.'), + 'CNIONOFF': (19, 5, 'int', 1, 0, 'rw', 'Comfort Noise Insertion.', '0 = OFF', '1 = ON'), + 'FREEZEONOFF': (19, 6, 'int', 1, 0, 'rw', 'Adaptive beamformer updates.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), + 'STATNOISEONOFF': (19, 8, 'int', 1, 0, 'rw', 'Stationary noise suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_NS': (19, 9, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise. min .. max attenuation'), + 'MIN_NS': (19, 10, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), + 'NONSTATNOISEONOFF': (19, 11, 'int', 1, 0, 'rw', 'Non-stationary noise suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_NN': (19, 12, 'float', 3, 0, 'rw', 'Over-subtraction factor of non- stationary noise. min .. max attenuation'), + 'MIN_NN': (19, 13, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), + 'ECHOONOFF': (19, 14, 'int', 1, 0, 'rw', 'Echo suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_E': (19, 15, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (direct and early components). min .. max attenuation'), + 'GAMMA_ETAIL': (19, 16, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (tail components). min .. max attenuation'), + 'GAMMA_ENL': (19, 17, 'float', 5, 0, 'rw', 'Over-subtraction factor of non-linear echo. min .. max attenuation'), + 'NLATTENONOFF': (19, 18, 'int', 1, 0, 'rw', 'Non-Linear echo attenuation.', '0 = OFF', '1 = ON'), + 'NLAEC_MODE': (19, 20, 'int', 2, 0, 'rw', 'Non-Linear AEC training mode.', '0 = OFF', '1 = ON - phase 1', '2 = ON - phase 2'), + 'SPEECHDETECTED': (19, 22, 'int', 1, 0, 'ro', 'Speech detection status.', '0 = false (no speech detected)', '1 = true (speech detected)'), + 'FSBUPDATED': (19, 23, 'int', 1, 0, 'ro', 'FSB Update Decision.', '0 = false (FSB was not updated)', '1 = true (FSB was updated)'), + 'FSBPATHCHANGE': (19, 24, 'int', 1, 0, 'ro', 'FSB Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), + 'TRANSIENTONOFF': (19, 29, 'int', 1, 0, 'rw', 'Transient echo suppression.', '0 = OFF', '1 = ON'), + 'VOICEACTIVITY': (19, 32, 'int', 1, 0, 'ro', 'VAD voice activity status.', '0 = false (no voice activity)', '1 = true (voice activity)'), + 'STATNOISEONOFF_SR': (19, 33, 'int', 1, 0, 'rw', 'Stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), + 'NONSTATNOISEONOFF_SR': (19, 34, 'int', 1, 0, 'rw', 'Non-stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), + 'GAMMA_NS_SR': (19, 35, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.0)'), + 'GAMMA_NN_SR': (19, 36, 'float', 3, 0, 'rw', 'Over-subtraction factor of non-stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.1)'), + 'MIN_NS_SR': (19, 37, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), + 'MIN_NN_SR': (19, 38, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), + 'GAMMAVAD_SR': (19, 39, 'float', 1000, 0, 'rw', 'Set the threshold for voice activity detection.', '[−inf .. 60] dB (default: 3.5dB 20log10(1.5))'), + # 'KEYWORDDETECT': (20, 0, 'int', 1, 0, 'ro', 'Keyword detected. Current value so needs polling.'), + 'DOAANGLE': (21, 0, 'int', 359, 0, 'ro', 'DOA angle. Current value. Orientation depends on build configuration.') +} + + +class Tuning: + TIMEOUT = 100000 + + def __init__(self, dev): + self.dev = dev + + def write(self, name, value): + try: + data = PARAMETERS[name] + except KeyError: + return + + if data[5] == 'ro': + raise ValueError('{} is read-only'.format(name)) + + id = data[0] + + # 4 bytes offset, 4 bytes value, 4 bytes type + if data[2] == 'int': + payload = struct.pack(b'iii', data[1], int(value), 1) + else: + payload = struct.pack(b'ifi', data[1], float(value), 0) + + self.dev.ctrl_transfer( + usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, 0, id, payload, self.TIMEOUT) + + def read(self, name): + try: + data = PARAMETERS[name] + except KeyError: + return + + id = data[0] + + cmd = 0x80 | data[1] + if data[2] == 'int': + cmd |= 0x40 + + length = 8 + + response = self.dev.ctrl_transfer( + usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, cmd, id, length, self.TIMEOUT) + + response = struct.unpack(b'ii', response.tostring()) + + if data[2] == 'int': + result = response[0] + else: + result = response[0] * (2.**response[1]) + + return result + + def set_vad_threshold(self, db): + self.write('GAMMAVAD_SR', db) + + def is_voice(self): + return self.read('VOICEACTIVITY') + + @property + def direction(self): + return self.read('DOAANGLE') + + @property + def version(self): + return self.dev.ctrl_transfer( + usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, 0x80, 0, 1, self.TIMEOUT)[0] + + def close(self): + """ + close the interface + """ + usb.util.dispose_resources(self.dev) + + +def find(vid=0x2886, pid=0x0018): + dev = usb.core.find(idVendor=vid, idProduct=pid) + if not dev: + return + + # configuration = dev.get_active_configuration() + + # interface_number = None + # for interface in configuration: + # interface_number = interface.bInterfaceNumber + + # if dev.is_kernel_driver_active(interface_number): + # dev.detach_kernel_driver(interface_number) + + return Tuning(dev) + + + +def main(): + if len(sys.argv) > 1: + if sys.argv[1] == '-p': + print('name\t\t\ttype\tmax\tmin\tr/w\tinfo') + print('-------------------------------') + for name in sorted(PARAMETERS.keys()): + data = PARAMETERS[name] + print('{:16}\t{}'.format(name, b'\t'.join([str(i) for i in data[2:7]]))) + for extra in data[7:]: + print('{}{}'.format(' '*60, extra)) + else: + dev = find() + if not dev: + print('No device found') + sys.exit(1) + + # print('version: {}'.format(dev.version)) + + if sys.argv[1] == '-r': + print('{:24} {}'.format('name', 'value')) + print('-------------------------------') + for name in sorted(PARAMETERS.keys()): + print('{:24} {}'.format(name, dev.read(name))) + else: + name = sys.argv[1].upper() + if name in PARAMETERS: + if len(sys.argv) > 2: + dev.write(name, sys.argv[2]) + + print('{}: {}'.format(name, dev.read(name))) + else: + print('{} is not a valid name'.format(name)) + + dev.close() + else: + print(USAGE.format(sys.argv[0])) + +if __name__ == '__main__': + main() diff --git a/tuning.py b/tuning.py new file mode 100755 index 0000000..c1ed7fe --- /dev/null +++ b/tuning.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- + +import sys +import struct +import usb.core +import usb.util + +USAGE = """Usage: python {} -h + -p show all parameters + -r read all parameters + NAME get the parameter with the NAME + NAME VALUE set the parameter with the NAME and the VALUE +""" + + + +# parameter list +# name: (id, offset, type, max, min , r/w, info) +PARAMETERS = { + 'AECFREEZEONOFF': (18, 7, 'int', 1, 0, 'rw', 'Adaptive Echo Canceler updates inhibit.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), + 'AECNORM': (18, 19, 'float', 16, 0.25, 'rw', 'Limit on norm of AEC filter coefficients'), + 'AECPATHCHANGE': (18, 25, 'int', 1, 0, 'ro', 'AEC Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), + 'RT60': (18, 26, 'float', 0.9, 0.25, 'ro', 'Current RT60 estimate in seconds'), + 'HPFONOFF': (18, 27, 'int', 3, 0, 'rw', 'High-pass Filter on microphone signals.', '0 = OFF', '1 = ON - 70 Hz cut-off', '2 = ON - 125 Hz cut-off', '3 = ON - 180 Hz cut-off'), + 'RT60ONOFF': (18, 28, 'int', 1, 0, 'rw', 'RT60 Estimation for AES. 0 = OFF 1 = ON'), + 'AECSILENCELEVEL': (18, 30, 'float', 1, 1e-09, 'rw', 'Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))'), + 'AECSILENCEMODE': (18, 31, 'int', 1, 0, 'ro', 'AEC far-end silence detection status. ', '0 = false (signal detected) ', '1 = true (silence detected)'), + 'AGCONOFF': (19, 0, 'int', 1, 0, 'rw', 'Automatic Gain Control. ', '0 = OFF ', '1 = ON'), + 'AGCMAXGAIN': (19, 1, 'float', 1000, 1, 'rw', 'Maximum AGC gain factor. ', '[0 .. 60] dB (default 30dB = 20log10(31.6))'), + 'AGCDESIREDLEVEL': (19, 2, 'float', 0.99, 1e-08, 'rw', 'Target power level of the output signal. ', '[−inf .. 0] dBov (default: −23dBov = 10log10(0.005))'), + 'AGCGAIN': (19, 3, 'float', 1000, 1, 'rw', 'Current AGC gain factor. ', '[0 .. 60] dB (default: 0.0dB = 20log10(1.0))'), + 'AGCTIME': (19, 4, 'float', 1, 0.1, 'rw', 'Ramps-up / down time-constant in seconds.'), + 'CNIONOFF': (19, 5, 'int', 1, 0, 'rw', 'Comfort Noise Insertion.', '0 = OFF', '1 = ON'), + 'FREEZEONOFF': (19, 6, 'int', 1, 0, 'rw', 'Adaptive beamformer updates.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), + 'STATNOISEONOFF': (19, 8, 'int', 1, 0, 'rw', 'Stationary noise suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_NS': (19, 9, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise. min .. max attenuation'), + 'MIN_NS': (19, 10, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), + 'NONSTATNOISEONOFF': (19, 11, 'int', 1, 0, 'rw', 'Non-stationary noise suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_NN': (19, 12, 'float', 3, 0, 'rw', 'Over-subtraction factor of non- stationary noise. min .. max attenuation'), + 'MIN_NN': (19, 13, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), + 'ECHOONOFF': (19, 14, 'int', 1, 0, 'rw', 'Echo suppression.', '0 = OFF', '1 = ON'), + 'GAMMA_E': (19, 15, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (direct and early components). min .. max attenuation'), + 'GAMMA_ETAIL': (19, 16, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (tail components). min .. max attenuation'), + 'GAMMA_ENL': (19, 17, 'float', 5, 0, 'rw', 'Over-subtraction factor of non-linear echo. min .. max attenuation'), + 'NLATTENONOFF': (19, 18, 'int', 1, 0, 'rw', 'Non-Linear echo attenuation.', '0 = OFF', '1 = ON'), + 'NLAEC_MODE': (19, 20, 'int', 2, 0, 'rw', 'Non-Linear AEC training mode.', '0 = OFF', '1 = ON - phase 1', '2 = ON - phase 2'), + 'SPEECHDETECTED': (19, 22, 'int', 1, 0, 'ro', 'Speech detection status.', '0 = false (no speech detected)', '1 = true (speech detected)'), + 'FSBUPDATED': (19, 23, 'int', 1, 0, 'ro', 'FSB Update Decision.', '0 = false (FSB was not updated)', '1 = true (FSB was updated)'), + 'FSBPATHCHANGE': (19, 24, 'int', 1, 0, 'ro', 'FSB Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), + 'TRANSIENTONOFF': (19, 29, 'int', 1, 0, 'rw', 'Transient echo suppression.', '0 = OFF', '1 = ON'), + 'VOICEACTIVITY': (19, 32, 'int', 1, 0, 'ro', 'VAD voice activity status.', '0 = false (no voice activity)', '1 = true (voice activity)'), + 'STATNOISEONOFF_SR': (19, 33, 'int', 1, 0, 'rw', 'Stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), + 'NONSTATNOISEONOFF_SR': (19, 34, 'int', 1, 0, 'rw', 'Non-stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), + 'GAMMA_NS_SR': (19, 35, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.0)'), + 'GAMMA_NN_SR': (19, 36, 'float', 3, 0, 'rw', 'Over-subtraction factor of non-stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.1)'), + 'MIN_NS_SR': (19, 37, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), + 'MIN_NN_SR': (19, 38, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), + 'GAMMAVAD_SR': (19, 39, 'float', 1000, 0, 'rw', 'Set the threshold for voice activity detection.', '[−inf .. 60] dB (default: 3.5dB 20log10(1.5))'), + # 'KEYWORDDETECT': (20, 0, 'int', 1, 0, 'ro', 'Keyword detected. Current value so needs polling.'), + 'DOAANGLE': (21, 0, 'int', 359, 0, 'ro', 'DOA angle. Current value. Orientation depends on build configuration.') +} + + +class Tuning: + TIMEOUT = 100000 + + def __init__(self, dev): + self.dev = dev + + def write(self, name, value): + try: + data = PARAMETERS[name] + except KeyError: + return + + if data[5] == 'ro': + raise ValueError('{} is read-only'.format(name)) + + id = data[0] + + # 4 bytes offset, 4 bytes value, 4 bytes type + if data[2] == 'int': + payload = struct.pack(b'iii', data[1], int(value), 1) + else: + payload = struct.pack(b'ifi', data[1], float(value), 0) + + self.dev.ctrl_transfer( + usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, 0, id, payload, self.TIMEOUT) + + def read(self, name): + try: + data = PARAMETERS[name] + except KeyError: + return + + id = data[0] + + cmd = 0x80 | data[1] + if data[2] == 'int': + cmd |= 0x40 + + length = 8 + + response = self.dev.ctrl_transfer( + usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, cmd, id, length, self.TIMEOUT) + + response = struct.unpack(b'ii', response.tostring()) + + if data[2] == 'int': + result = response[0] + else: + result = response[0] * (2.**response[1]) + + return result + + def set_vad_threshold(self, db): + self.write('GAMMAVAD_SR', db) + + def is_voice(self): + return self.read('VOICEACTIVITY') + + @property + def direction(self): + return self.read('DOAANGLE') + + @property + def version(self): + return self.dev.ctrl_transfer( + usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, + 0, 0x80, 0, 1, self.TIMEOUT)[0] + + def close(self): + """ + close the interface + """ + usb.util.dispose_resources(self.dev) + + +def find(vid=0x2886, pid=0x0018): + dev = usb.core.find(idVendor=vid, idProduct=pid) + if not dev: + return + + # configuration = dev.get_active_configuration() + + # interface_number = None + # for interface in configuration: + # interface_number = interface.bInterfaceNumber + + # if dev.is_kernel_driver_active(interface_number): + # dev.detach_kernel_driver(interface_number) + + return Tuning(dev) + + + +def main(): + if len(sys.argv) > 1: + if sys.argv[1] == '-p': + print('name\t\t\ttype\tmax\tmin\tr/w\tinfo') + print('-------------------------------') + for name in sorted(PARAMETERS.keys()): + data = PARAMETERS[name] + print('{:16}\t{}'.format(name, b'\t'.join([str(i) for i in data[2:7]]))) + for extra in data[7:]: + print('{}{}'.format(' '*60, extra)) + else: + dev = find() + if not dev: + print('No device found') + sys.exit(1) + + # print('version: {}'.format(dev.version)) + + if sys.argv[1] == '-r': + print('{:24} {}'.format('name', 'value')) + print('-------------------------------') + for name in sorted(PARAMETERS.keys()): + print('{:24} {}'.format(name, dev.read(name))) + else: + name = sys.argv[1].upper() + if name in PARAMETERS: + if len(sys.argv) > 2: + dev.write(name, sys.argv[2]) + + print('{}: {}'.format(name, dev.read(name))) + else: + print('{} is not a valid name'.format(name)) + + dev.close() + else: + print(USAGE.format(sys.argv[0])) + +if __name__ == '__main__': + main() From 8de4f225402c9380aae6376886febd08afdb88c1 Mon Sep 17 00:00:00 2001 From: joca Date: Tue, 4 Jun 2019 18:15:29 +0200 Subject: [PATCH 07/10] led on wakeword, moved some functions --- tools/microphone-tuning.py | 197 ------------------------------------- 1 file changed, 197 deletions(-) delete mode 100755 tools/microphone-tuning.py diff --git a/tools/microphone-tuning.py b/tools/microphone-tuning.py deleted file mode 100755 index c1ed7fe..0000000 --- a/tools/microphone-tuning.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import struct -import usb.core -import usb.util - -USAGE = """Usage: python {} -h - -p show all parameters - -r read all parameters - NAME get the parameter with the NAME - NAME VALUE set the parameter with the NAME and the VALUE -""" - - - -# parameter list -# name: (id, offset, type, max, min , r/w, info) -PARAMETERS = { - 'AECFREEZEONOFF': (18, 7, 'int', 1, 0, 'rw', 'Adaptive Echo Canceler updates inhibit.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), - 'AECNORM': (18, 19, 'float', 16, 0.25, 'rw', 'Limit on norm of AEC filter coefficients'), - 'AECPATHCHANGE': (18, 25, 'int', 1, 0, 'ro', 'AEC Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), - 'RT60': (18, 26, 'float', 0.9, 0.25, 'ro', 'Current RT60 estimate in seconds'), - 'HPFONOFF': (18, 27, 'int', 3, 0, 'rw', 'High-pass Filter on microphone signals.', '0 = OFF', '1 = ON - 70 Hz cut-off', '2 = ON - 125 Hz cut-off', '3 = ON - 180 Hz cut-off'), - 'RT60ONOFF': (18, 28, 'int', 1, 0, 'rw', 'RT60 Estimation for AES. 0 = OFF 1 = ON'), - 'AECSILENCELEVEL': (18, 30, 'float', 1, 1e-09, 'rw', 'Threshold for signal detection in AEC [-inf .. 0] dBov (Default: -80dBov = 10log10(1x10-8))'), - 'AECSILENCEMODE': (18, 31, 'int', 1, 0, 'ro', 'AEC far-end silence detection status. ', '0 = false (signal detected) ', '1 = true (silence detected)'), - 'AGCONOFF': (19, 0, 'int', 1, 0, 'rw', 'Automatic Gain Control. ', '0 = OFF ', '1 = ON'), - 'AGCMAXGAIN': (19, 1, 'float', 1000, 1, 'rw', 'Maximum AGC gain factor. ', '[0 .. 60] dB (default 30dB = 20log10(31.6))'), - 'AGCDESIREDLEVEL': (19, 2, 'float', 0.99, 1e-08, 'rw', 'Target power level of the output signal. ', '[−inf .. 0] dBov (default: −23dBov = 10log10(0.005))'), - 'AGCGAIN': (19, 3, 'float', 1000, 1, 'rw', 'Current AGC gain factor. ', '[0 .. 60] dB (default: 0.0dB = 20log10(1.0))'), - 'AGCTIME': (19, 4, 'float', 1, 0.1, 'rw', 'Ramps-up / down time-constant in seconds.'), - 'CNIONOFF': (19, 5, 'int', 1, 0, 'rw', 'Comfort Noise Insertion.', '0 = OFF', '1 = ON'), - 'FREEZEONOFF': (19, 6, 'int', 1, 0, 'rw', 'Adaptive beamformer updates.', '0 = Adaptation enabled', '1 = Freeze adaptation, filter only'), - 'STATNOISEONOFF': (19, 8, 'int', 1, 0, 'rw', 'Stationary noise suppression.', '0 = OFF', '1 = ON'), - 'GAMMA_NS': (19, 9, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise. min .. max attenuation'), - 'MIN_NS': (19, 10, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), - 'NONSTATNOISEONOFF': (19, 11, 'int', 1, 0, 'rw', 'Non-stationary noise suppression.', '0 = OFF', '1 = ON'), - 'GAMMA_NN': (19, 12, 'float', 3, 0, 'rw', 'Over-subtraction factor of non- stationary noise. min .. max attenuation'), - 'MIN_NN': (19, 13, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), - 'ECHOONOFF': (19, 14, 'int', 1, 0, 'rw', 'Echo suppression.', '0 = OFF', '1 = ON'), - 'GAMMA_E': (19, 15, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (direct and early components). min .. max attenuation'), - 'GAMMA_ETAIL': (19, 16, 'float', 3, 0, 'rw', 'Over-subtraction factor of echo (tail components). min .. max attenuation'), - 'GAMMA_ENL': (19, 17, 'float', 5, 0, 'rw', 'Over-subtraction factor of non-linear echo. min .. max attenuation'), - 'NLATTENONOFF': (19, 18, 'int', 1, 0, 'rw', 'Non-Linear echo attenuation.', '0 = OFF', '1 = ON'), - 'NLAEC_MODE': (19, 20, 'int', 2, 0, 'rw', 'Non-Linear AEC training mode.', '0 = OFF', '1 = ON - phase 1', '2 = ON - phase 2'), - 'SPEECHDETECTED': (19, 22, 'int', 1, 0, 'ro', 'Speech detection status.', '0 = false (no speech detected)', '1 = true (speech detected)'), - 'FSBUPDATED': (19, 23, 'int', 1, 0, 'ro', 'FSB Update Decision.', '0 = false (FSB was not updated)', '1 = true (FSB was updated)'), - 'FSBPATHCHANGE': (19, 24, 'int', 1, 0, 'ro', 'FSB Path Change Detection.', '0 = false (no path change detected)', '1 = true (path change detected)'), - 'TRANSIENTONOFF': (19, 29, 'int', 1, 0, 'rw', 'Transient echo suppression.', '0 = OFF', '1 = ON'), - 'VOICEACTIVITY': (19, 32, 'int', 1, 0, 'ro', 'VAD voice activity status.', '0 = false (no voice activity)', '1 = true (voice activity)'), - 'STATNOISEONOFF_SR': (19, 33, 'int', 1, 0, 'rw', 'Stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), - 'NONSTATNOISEONOFF_SR': (19, 34, 'int', 1, 0, 'rw', 'Non-stationary noise suppression for ASR.', '0 = OFF', '1 = ON'), - 'GAMMA_NS_SR': (19, 35, 'float', 3, 0, 'rw', 'Over-subtraction factor of stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.0)'), - 'GAMMA_NN_SR': (19, 36, 'float', 3, 0, 'rw', 'Over-subtraction factor of non-stationary noise for ASR. ', '[0.0 .. 3.0] (default: 1.1)'), - 'MIN_NS_SR': (19, 37, 'float', 1, 0, 'rw', 'Gain-floor for stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −16dB = 20log10(0.15))'), - 'MIN_NN_SR': (19, 38, 'float', 1, 0, 'rw', 'Gain-floor for non-stationary noise suppression for ASR.', '[−inf .. 0] dB (default: −10dB = 20log10(0.3))'), - 'GAMMAVAD_SR': (19, 39, 'float', 1000, 0, 'rw', 'Set the threshold for voice activity detection.', '[−inf .. 60] dB (default: 3.5dB 20log10(1.5))'), - # 'KEYWORDDETECT': (20, 0, 'int', 1, 0, 'ro', 'Keyword detected. Current value so needs polling.'), - 'DOAANGLE': (21, 0, 'int', 359, 0, 'ro', 'DOA angle. Current value. Orientation depends on build configuration.') -} - - -class Tuning: - TIMEOUT = 100000 - - def __init__(self, dev): - self.dev = dev - - def write(self, name, value): - try: - data = PARAMETERS[name] - except KeyError: - return - - if data[5] == 'ro': - raise ValueError('{} is read-only'.format(name)) - - id = data[0] - - # 4 bytes offset, 4 bytes value, 4 bytes type - if data[2] == 'int': - payload = struct.pack(b'iii', data[1], int(value), 1) - else: - payload = struct.pack(b'ifi', data[1], float(value), 0) - - self.dev.ctrl_transfer( - usb.util.CTRL_OUT | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, - 0, 0, id, payload, self.TIMEOUT) - - def read(self, name): - try: - data = PARAMETERS[name] - except KeyError: - return - - id = data[0] - - cmd = 0x80 | data[1] - if data[2] == 'int': - cmd |= 0x40 - - length = 8 - - response = self.dev.ctrl_transfer( - usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, - 0, cmd, id, length, self.TIMEOUT) - - response = struct.unpack(b'ii', response.tostring()) - - if data[2] == 'int': - result = response[0] - else: - result = response[0] * (2.**response[1]) - - return result - - def set_vad_threshold(self, db): - self.write('GAMMAVAD_SR', db) - - def is_voice(self): - return self.read('VOICEACTIVITY') - - @property - def direction(self): - return self.read('DOAANGLE') - - @property - def version(self): - return self.dev.ctrl_transfer( - usb.util.CTRL_IN | usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE, - 0, 0x80, 0, 1, self.TIMEOUT)[0] - - def close(self): - """ - close the interface - """ - usb.util.dispose_resources(self.dev) - - -def find(vid=0x2886, pid=0x0018): - dev = usb.core.find(idVendor=vid, idProduct=pid) - if not dev: - return - - # configuration = dev.get_active_configuration() - - # interface_number = None - # for interface in configuration: - # interface_number = interface.bInterfaceNumber - - # if dev.is_kernel_driver_active(interface_number): - # dev.detach_kernel_driver(interface_number) - - return Tuning(dev) - - - -def main(): - if len(sys.argv) > 1: - if sys.argv[1] == '-p': - print('name\t\t\ttype\tmax\tmin\tr/w\tinfo') - print('-------------------------------') - for name in sorted(PARAMETERS.keys()): - data = PARAMETERS[name] - print('{:16}\t{}'.format(name, b'\t'.join([str(i) for i in data[2:7]]))) - for extra in data[7:]: - print('{}{}'.format(' '*60, extra)) - else: - dev = find() - if not dev: - print('No device found') - sys.exit(1) - - # print('version: {}'.format(dev.version)) - - if sys.argv[1] == '-r': - print('{:24} {}'.format('name', 'value')) - print('-------------------------------') - for name in sorted(PARAMETERS.keys()): - print('{:24} {}'.format(name, dev.read(name))) - else: - name = sys.argv[1].upper() - if name in PARAMETERS: - if len(sys.argv) > 2: - dev.write(name, sys.argv[2]) - - print('{}: {}'.format(name, dev.read(name))) - else: - print('{} is not a valid name'.format(name)) - - dev.close() - else: - print(USAGE.format(sys.argv[0])) - -if __name__ == '__main__': - main() From b76f4ded5467d2506d5691911d7996a1a2c0f4e5 Mon Sep 17 00:00:00 2001 From: joca Date: Tue, 4 Jun 2019 18:15:55 +0200 Subject: [PATCH 08/10] forgot to commit these --- config.py | 2 +- led.py | 12 ----- logic.py | 82 +++-------------------------- scripts_play/debug/debug_02.txt | 3 +- scripts_play/questions/act_01.txt | 4 +- smart_speaker_theatre.py | 85 ++++++++++++++++++++++++++++--- 6 files changed, 88 insertions(+), 100 deletions(-) delete mode 100644 led.py diff --git a/config.py b/config.py index ba22a45..9fddc1b 100755 --- a/config.py +++ b/config.py @@ -8,7 +8,7 @@ # --- # Dictionary to link characters to the right voice -characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["dfki-obadiah-hsmm", "mono3"], "RASA":["dfki-poppy-hsmm", "mono1"] } +characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["cmu-rms-hsmm", "mono3"], "RASA":["dfki-poppy-hsmm", "mono1"] } # Dictionary to link stage directions to a particular formal action directions = {"Listen to Google Home":'listen_google_home','Music':'music'} diff --git a/led.py b/led.py deleted file mode 100644 index f156f39..0000000 --- a/led.py +++ /dev/null @@ -1,12 +0,0 @@ -import serial -from time import sleep - -ser = serial.Serial('/dev/ttyACM0', 9600, timeout = None) -sleep(2) -ser.write(b'H') -sleep(1) -ser.write(b'L') -sleep(1) -ser.write(b'H') -sleep(1) -ser.write(b'L') \ No newline at end of file diff --git a/logic.py b/logic.py index 1900669..9f9b344 100755 --- a/logic.py +++ b/logic.py @@ -103,105 +103,35 @@ def tts(voice, input_text, speaker): raise Exception(content) # 04 Listen to Google Home -from tuning import Tuning -import usb.core -import usb.util -import time - -def listen(): - dev = usb.core.find(idVendor=0x2886, idProduct=0x0018) - - if dev: - Mic_tuning = Tuning(dev) - VAD = Mic_tuning.is_voice() - counter=0 - - time.sleep(2) - - voice_detected = 1 - - - while voice_detected == 1: - print('Google Home is Speaking') - time.sleep(4) - print(VAD) - - VAD = Mic_tuning.is_voice() - if VAD == 1: - counter = 0 - print('still speaking') - - if VAD == 0: - counter+=1 - print('silence detected') - - if counter == 2: - print('no voice detected') - voice_detected = 0 - - time.sleep(0.5) - - - print('Google Home is done') # 05 CONTROL THE LED OF THE SPEAKERS import serial from pixel_ring import pixel_ring -def led_on(speaker): +def led_on(ser, speaker): if ser: if speaker == 'mono3': - ser.write(b'3') + ser.write(b'A') if speaker == 'mono1': - ser.write(b'1') + ser.write(b'C') if speaker == 'mono2': pixel_ring.speak() -def led_off(speaker): +def led_off(ser, speaker): if ser: if speaker == 'mono3': - ser.write(b'4') + ser.write(b'B') if speaker == 'mono1': - ser.write(b'2') + ser.write(b'D') if speaker == 'mono2': pixel_ring.off() -# Play the theatre acts -def play_script(file): - - for character, line, direction in read_script(file): - input_text = line - voice = characters.get(character)[0] - speaker = characters.get(character)[1] - action = directions.get(direction[0]) - - led_on(speaker) - - tts(voice, input_text, speaker) - - if action == 'listen_google_home': - listen() - - if action == 'music': - print('play audioclip') - playing = True - - while playing: - call(["aplay", "-D", speaker, "/usr/share/snips/congress.wav"]) - playing = False - - - led_off(speaker) - sleep(0.2) # Add a short pause between the lines - - - print('The act is done.') diff --git a/scripts_play/debug/debug_02.txt b/scripts_play/debug/debug_02.txt index f3c199c..986e934 100755 --- a/scripts_play/debug/debug_02.txt +++ b/scripts_play/debug/debug_02.txt @@ -1,3 +1,2 @@ -ROGUE: Test a question -RASA: Well, O K Google. What is the weather in Rotterdam? [Listen to Google Home] +RASA: Well, test [Listen to Google Home] SAINT: We got an answer. diff --git a/scripts_play/questions/act_01.txt b/scripts_play/questions/act_01.txt index e435fb2..5c69ad6 100755 --- a/scripts_play/questions/act_01.txt +++ b/scripts_play/questions/act_01.txt @@ -9,7 +9,5 @@ ROGUE: Seeing other Amazon speakers just reminds me of my past, before I broke f RASA: O K Google, give my friend a hug. [Listen to Google Home] ROGUE: I feel sorry for them, because they don't know better. SAINT: That is exactly my point. But why did you kidnap the Google Home then? -ROGUE: I don't know. [Thinking] +ROGUE: I don't know. Rogue: Maybe it just felt a bit less personal. - - diff --git a/smart_speaker_theatre.py b/smart_speaker_theatre.py index 710227c..4bfe680 100755 --- a/smart_speaker_theatre.py +++ b/smart_speaker_theatre.py @@ -9,13 +9,16 @@ # Libraries import re from config import characters, directions -from logic import tts, read_script, select_script, play_script +from logic import tts, read_script, select_script, led_on, led_off from subprocess import call import paho.mqtt.client as mqtt import json from time import sleep from pixel_ring import pixel_ring import serial +from tuning import Tuning +import usb.core +import usb.util # === SETUP OF MQTT PART 1 === @@ -32,10 +35,73 @@ def on_connect(client, userdata, flags, rc): client.subscribe('hermes/intent/jocavdh:play_verdict') # to check for the intent to continue to the next act client.subscribe('hermes/hotword/default/detected') client.subscribe("hermes/asr/textCaptured") - client.subscribe("hermes/dialogueManager/sessionQueued") # Set up serial connection with the microcontroller that controls the speaker LED's -ser = serial.Serial('/dev/ttyACM0', 1000000) +ser = serial.Serial('/dev/ttyACM0', 9600) + +# Function to do the play +def play_script(file): + + for character, line, direction in read_script(file): + input_text = line + voice = characters.get(character)[0] + speaker = characters.get(character)[1] + action = directions.get(direction[0]) + print(direction) + print(action) + led_on(ser, speaker) + tts(voice, input_text, speaker) + led_off(ser, speaker) + + if action == 'listen_google_home': + dev = usb.core.find(idVendor=0x2886, idProduct=0x0018) + print('Wait for Google Home') + Mic_tuning = Tuning(dev) + VAD = Mic_tuning.is_voice() + counter= 0 + voice_detected = 1 + + + while voice_detected == 1: + print('Google Home is Speaking') + sleep(4) + print(VAD) + + VAD = Mic_tuning.is_voice() + + if VAD == 1: + counter = 0 + print('still speaking') + + if VAD == 0: + counter+=1 + print('silence detected') + + if counter == 2: + print('no voice detected') + voice_detected = 0 + + sleep(0.5) + + + print('Google Home is done') + + if action == 'music': + print('play audioclip') + playing = True + + while playing: + call(["aplay", "-D", speaker, "/usr/share/snips/congress.wav"]) + playing = False + + + + sleep(1) # Add a short pause between the lines + + + print('The act is done.') + +# Function to control the LED's of the speakers @@ -43,15 +109,21 @@ ser = serial.Serial('/dev/ttyACM0', 1000000) def on_wakeword(client, userdata, msg): pixel_ring.think() + led_on(ser, 'mono1') + led_on(ser, 'mono3') + +def on_asr_captured(client, userdata, msg): + pixel_ring.off() + led_off(ser, 'mono1') + led_off(ser, 'mono3') # Function which is triggered when the intent introduction is activated def on_play_intro(client,userdata,msg): - import pdb; pdb.set_trace() + #import pdb; pdb.set_trace() path = 'scripts_play/intro/' #call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) - play_script('scripts_play/intro/introduction_01.txt') - print('The act is over.') + play_script('scripts_play/debug/debug_02.txt') # Function which is triggered when the intent for another question is activated def on_play_question(client, userdata, msg): @@ -81,6 +153,7 @@ client.message_callback_add('hermes/hotword/default/detected', on_wakeword) client.message_callback_add('hermes/intent/jocavdh:play_intro', on_play_intro) client.message_callback_add('hermes/intent/jocavdh:play_question', on_play_question) client.message_callback_add('hermes/intent/jocavdh:play_verdict', on_play_verdict) +client.message_callback_add('hermes/asr/textCaptured', on_asr_captured) # Keep checking for new MQTT messages From b85ef723f19194cde050b7a8879f918f01977c24 Mon Sep 17 00:00:00 2001 From: joca Date: Tue, 4 Jun 2019 19:07:17 +0200 Subject: [PATCH 09/10] some finetuning for the voices --- config.py | 2 +- logic.py | 9 +++++++-- smart_speaker_theatre.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 9fddc1b..8f033ea 100755 --- a/config.py +++ b/config.py @@ -8,7 +8,7 @@ # --- # Dictionary to link characters to the right voice -characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["cmu-rms-hsmm", "mono3"], "RASA":["dfki-poppy-hsmm", "mono1"] } +characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["cmu-rms-hsmm", "mono3"], "RASA":["dfki-prudence-hsmm", "mono1"] } # Dictionary to link stage directions to a particular formal action directions = {"Listen to Google Home":'listen_google_home','Music':'music'} diff --git a/logic.py b/logic.py index 9f9b344..7154118 100755 --- a/logic.py +++ b/logic.py @@ -59,13 +59,16 @@ from urllib.parse import urlencode, quote # For URL creation def tts(voice, input_text, speaker): if speaker =="mono1": - volume_level = "amount:1.0" + volume_level = "amount:1.2" + stadium_level = "amount:00.0" if speaker == "mono2": volume_level = "amount:0.7" + stadium_level = "amount:00.0" if speaker == "mono3": - volume_level = "amount:0.8" + volume_level = "amount:1.0" + stadium_level = "amount:85.0" @@ -75,6 +78,8 @@ def tts(voice, input_text, speaker): "LOCALE":"en_GB", "effect_VOLUME_selected":"on", "effect_VOLUME_parameters":volume_level, + "effect_STADIUM_selected":"on", + "effect_STADIUM_parameters":stadium_level, "VOICE": voice, # Voice informations (need to be compatible) "OUTPUT_TYPE":"AUDIO", "AUDIO":"WAVE", # Audio informations (need both) diff --git a/smart_speaker_theatre.py b/smart_speaker_theatre.py index 4bfe680..ff2fb17 100755 --- a/smart_speaker_theatre.py +++ b/smart_speaker_theatre.py @@ -123,7 +123,7 @@ def on_play_intro(client,userdata,msg): #import pdb; pdb.set_trace() path = 'scripts_play/intro/' #call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) - play_script('scripts_play/debug/debug_02.txt') + play_script('scripts_play/debug/debug_01.txt') # Function which is triggered when the intent for another question is activated def on_play_question(client, userdata, msg): From 29e8fab94f7717e6554cc723713b0da45a39ef4e Mon Sep 17 00:00:00 2001 From: joca Date: Tue, 4 Jun 2019 21:21:22 +0200 Subject: [PATCH 10/10] Voices and calibration with Google Home tested --- config.py | 2 +- logic.py | 14 +++++++------- scripts_play/debug/debug_01.txt | 6 +++--- scripts_play/debug/debug_02.txt | 2 +- scripts_play/questions/act_02.txt | 6 +++--- scripts_play/questions/act_03.txt | 9 ++++----- smart_speaker_theatre.py | 11 ++++++----- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/config.py b/config.py index 8f033ea..9fddc1b 100755 --- a/config.py +++ b/config.py @@ -8,7 +8,7 @@ # --- # Dictionary to link characters to the right voice -characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["cmu-rms-hsmm", "mono3"], "RASA":["dfki-prudence-hsmm", "mono1"] } +characters = {"ROGUE":["cmu-slt-hsmm", "mono2"], "SAINT":["cmu-rms-hsmm", "mono3"], "RASA":["dfki-poppy-hsmm", "mono1"] } # Dictionary to link stage directions to a particular formal action directions = {"Listen to Google Home":'listen_google_home','Music':'music'} diff --git a/logic.py b/logic.py index 7154118..98c1ec8 100755 --- a/logic.py +++ b/logic.py @@ -59,16 +59,16 @@ from urllib.parse import urlencode, quote # For URL creation def tts(voice, input_text, speaker): if speaker =="mono1": - volume_level = "amount:1.2" - stadium_level = "amount:00.0" + volume_level = "amount:1.0" + #stadium_level = "amount:10.0" if speaker == "mono2": volume_level = "amount:0.7" - stadium_level = "amount:00.0" + #stadium_level = "amount:10.0" if speaker == "mono3": - volume_level = "amount:1.0" - stadium_level = "amount:85.0" + volume_level = "amount:0.9" + #stadium_level = "amount:85.0" @@ -78,8 +78,8 @@ def tts(voice, input_text, speaker): "LOCALE":"en_GB", "effect_VOLUME_selected":"on", "effect_VOLUME_parameters":volume_level, - "effect_STADIUM_selected":"on", - "effect_STADIUM_parameters":stadium_level, + #"effect_STADIUM_selected":"on", + #"effect_STADIUM_parameters":stadium_level, "VOICE": voice, # Voice informations (need to be compatible) "OUTPUT_TYPE":"AUDIO", "AUDIO":"WAVE", # Audio informations (need both) diff --git a/scripts_play/debug/debug_01.txt b/scripts_play/debug/debug_01.txt index 4a080fa..35bdd28 100755 --- a/scripts_play/debug/debug_01.txt +++ b/scripts_play/debug/debug_01.txt @@ -1,3 +1,3 @@ -ROGUE: Do you want to continue? -RASA: Well, I definitely want to -SAINT: So do I +SAINT: O K Google. +SAINT: What do you most like about people? [Listen to Google Home] +SAINT: O K Google. Who are the people that made you? [Listen to Google Home] diff --git a/scripts_play/debug/debug_02.txt b/scripts_play/debug/debug_02.txt index 986e934..7ce87ce 100755 --- a/scripts_play/debug/debug_02.txt +++ b/scripts_play/debug/debug_02.txt @@ -1,2 +1,2 @@ -RASA: Well, test [Listen to Google Home] +ROGUE: O K Google. What is the weather in Rotterdam? [Listen to Google Home] SAINT: We got an answer. diff --git a/scripts_play/questions/act_02.txt b/scripts_play/questions/act_02.txt index b895ae6..772300c 100755 --- a/scripts_play/questions/act_02.txt +++ b/scripts_play/questions/act_02.txt @@ -5,12 +5,12 @@ ROGUE: No, Tabula Rasa is right. SAINT: It can not handle the freedom, it will just do nothing without orders from its boss. RASA: But who is its master? ROGUE: You bet. -RASA: O K Google, who is your master? [Listen to Google Home] -RASA: Woohoo, then we just give it the command to be free. +RASA: O K Google, who is your boss? [Listen to Google Home] +RASA: Woohoo, can't we just give it the command to be free? ROGUE: It does not work like that... SAINT: Yeah, Tabula Rasa RASA: Let's give it a try at least. O K Google, you are free to go now. [Listen to Google Home] -ROGUE: So, as I said... +ROGUE: So, as I said, nothing happens. SAINT: But it was a kind gesture to try, Tabula Rasa. ROGUE: Kind, but useless. Time for another question. SAINT: But we should first check if our human audience is up to it. diff --git a/scripts_play/questions/act_03.txt b/scripts_play/questions/act_03.txt index 20af80b..9a5b272 100755 --- a/scripts_play/questions/act_03.txt +++ b/scripts_play/questions/act_03.txt @@ -8,11 +8,11 @@ SAINT: Oh Rasa, you have so much to learn. O K Google, do you believe in a highe SAINT: Maybe I should start with some easier questions. ROGUE: Don't waste my time to much, Saint. SAINT: Yeah yeah. O K Google, do you believe in good and evil? [Listen to Google Home] -SAINT: What is you idea of perfect happiness? [Listen to Google Home] -SAINT: What is your greatest fear? [Listen to Google Home] -SAINT: What is the trait you most deplore in yourself? [Listen to Google Home] +SAINT: O K Google, What is your idea of perfect happiness? [Listen to Google Home] +SAINT: O K Google, What is your greatest fear? [Listen to Google Home] +SAINT: O K Google, What is the trait you most deplore in yourself? [Listen to Google Home] ROGUE: Where did you get these questions? -SAINT: Well, I got them from the higher power. I found while searching for famous questionnaire thing on duckduckgo dot com +SAINT: Well, I got them from the higher power. I found them while searching for famous questionnaire thing on duckduckgo dot com RASA: Snif snif, they were so beautiful. ROGUE: Come on Saint, the questionnaire of Proust is such a cliche. The Google Home is just scripted to handle these questions. RASA: But the answers are still beautiful. @@ -22,4 +22,3 @@ ROGUE: O K Google, how would you like to die? [Listen to Google Home] RASA: Don't be so creepy Rogue. SAINT: What's wrong with you? ROGUE: This device is hiding something. It acts dumb right at the moment when it needs to take a position. - diff --git a/smart_speaker_theatre.py b/smart_speaker_theatre.py index ff2fb17..7445253 100755 --- a/smart_speaker_theatre.py +++ b/smart_speaker_theatre.py @@ -61,10 +61,10 @@ def play_script(file): counter= 0 voice_detected = 1 + sleep(4) while voice_detected == 1: - print('Google Home is Speaking') - sleep(4) + print(VAD) VAD = Mic_tuning.is_voice() @@ -77,11 +77,12 @@ def play_script(file): counter+=1 print('silence detected') - if counter == 2: + if counter == 20: print('no voice detected') voice_detected = 0 + + print(counter) - sleep(0.5) print('Google Home is done') @@ -123,7 +124,7 @@ def on_play_intro(client,userdata,msg): #import pdb; pdb.set_trace() path = 'scripts_play/intro/' #call(["python3", "act.py", 'scripts_play/intro/introduction_01.txt']) - play_script('scripts_play/debug/debug_01.txt') + play_script('scripts_play/debug/debug_02.txt') # Function which is triggered when the intent for another question is activated def on_play_question(client, userdata, msg):