diff --git a/Dockerfiles/Dockerfile b/Dockerfiles/Dockerfile new file mode 100644 index 0000000..fb6c612 --- /dev/null +++ b/Dockerfiles/Dockerfile @@ -0,0 +1,41 @@ +FROM python:3.9.4-alpine + +ENV PACKAGES="\ + alsa-plugins \ + alsa-plugins-a52 \ + alsa-plugins-jack \ + alsa-plugins-lavrate \ + alsa-plugins-pulse \ + bash \ + git \ + libc6-compat \ + libgfortran \ + libstdc++ \ + openblas \ + portaudio \ + " + +ENV PYTHON_PACKAGES="\ + gpiozero \ + numpy \ + pigpio \ + pyaudio \ + spidev \ + wave \ + " + +RUN apk add --virtual build-deps build-base openblas-dev freetype-dev pkgconfig gfortran linux-headers portaudio-dev \ + && ln -s /usr/include/locale.h /usr/include/xlocale.h \ + && pip install --no-cache-dir $PYTHON_PACKAGES \ + && apk del build-deps \ + && apk add --no-cache --virtual build-runtime $PACKAGES \ + && rm -rf /var/cache/apk/* + +WORKDIR /usr/src/app + +RUN git clone https://github.com/lavirott/4mics_hat + +WORKDIR /usr/src/app/4mics_hat + +ENTRYPOINT ["python3"] +CMD ["pixels_demo.py"] diff --git a/Dockerfiles/README.md b/Dockerfiles/README.md new file mode 100644 index 0000000..f2896ec --- /dev/null +++ b/Dockerfiles/README.md @@ -0,0 +1,11 @@ +4MICs HAT for Raspberry Pi +======================== + +## Docker +To build the docker image, you need to upgrade the libseccomp2 library (bug identified in 12/2020) + +1. wget http://ftp.fr.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.5.1-1_armhf.deb +2. wget http://ftp.fr.debian.org/debian/pool/main/libs/libseccomp/libseccomp-dev_2.5.1-1_armhf.deb +3. sudo dpkg -i libseccomp2_2.5.1-1_armhf.deb libseccomp-dev_2.5.1-1_armhf.deb +4. rm *.deb +5. docker-docker-compose build \ No newline at end of file diff --git a/Dockerfiles/docker-compose.yml b/Dockerfiles/docker-compose.yml new file mode 100644 index 0000000..6e80410 --- /dev/null +++ b/Dockerfiles/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3" +services: + respeaker: + image: lavirott/respeaker:latest + container_name: respeaker + build: . + devices: + - /dev/spidev0.1:/dev/spidev0.1 + - /dev/gpiomem:/dev/gpiomem + - /dev/snd:/dev/snd + volumes: + - /usr/share/alsa:/usr/share/alsa + - /etc/alsa:/etc/alsa \ No newline at end of file diff --git a/alexa_led_pattern.py b/alexa_led_pattern.py index 5fe0fd4..783d8b8 100644 --- a/alexa_led_pattern.py +++ b/alexa_led_pattern.py @@ -20,9 +20,9 @@ class AlexaLedPattern(object): - def __init__(self, show=None, number=12): - self.pixels_number = number - self.pixels = [0] * 4 * number + def __init__(self, show=None, num_led=12): + self.num_led = num_led + self.pixels = [0] * 4 * num_led if not show or not callable(show): def dummy(data): @@ -33,20 +33,21 @@ def dummy(data): self.stop = False def wakeup(self, direction=0): - position = int((direction + 15) / (360 / self.pixels_number)) % self.pixels_number + position = int((direction + 15) / (360 / self.num_led)) % self.num_led - pixels = [0, 0, 0, 24] * self.pixels_number + pixels = [0, 0, 0, 24] * self.num_led pixels[position * 4 + 2] = 48 - + print(pixels) + print(position) self.show(pixels) def listen(self): - pixels = [0, 0, 0, 24] * self.pixels_number + pixels = [0, 0, 0, 24] * self.num_led self.show(pixels) def think(self): - pixels = [0, 0, 12, 12, 0, 0, 0, 24] * self.pixels_number + pixels = [0, 0, 12, 12, 0, 0, 0, 24] * self.num_led while not self.stop: self.show(pixels) @@ -57,7 +58,7 @@ def speak(self): step = 1 position = 12 while not self.stop: - pixels = [0, 0, position, 24 - position] * self.pixels_number + pixels = [0, 0, position, 24 - position] * self.num_led self.show(pixels) time.sleep(0.01) if position <= 0: diff --git a/google_home_led_pattern.py b/google_home_led_pattern.py index d476a4d..e5af952 100644 --- a/google_home_led_pattern.py +++ b/google_home_led_pattern.py @@ -24,7 +24,8 @@ class GoogleHomeLedPattern(object): - def __init__(self, show=None): + def __init__(self, show=None, num_led=12): + self.num_led = num_led self.basis = numpy.array([0] * 4 * 12) self.basis[0 * 4 + 1] = 2 self.basis[3 * 4 + 1] = 1 @@ -43,7 +44,7 @@ def dummy(data): self.stop = False def wakeup(self, direction=0): - position = int((direction + 15) / 30) % 12 + position = int((direction + 15) / 30) % self.num_led basis = numpy.roll(self.basis, position * 4) for i in range(1, 25): @@ -105,6 +106,6 @@ def speak(self): brightness += step def off(self): - self.show([0] * 4 * 12) + self.show([0] * 4 * self.num_led) diff --git a/pixels_demo.py b/pixels_demo.py index 79f8c92..96cca94 100755 --- a/pixels_demo.py +++ b/pixels_demo.py @@ -1,14 +1,29 @@ +import signal +import sys import time from pixels import Pixels, pixels from alexa_led_pattern import AlexaLedPattern from google_home_led_pattern import GoogleHomeLedPattern +from rainbow_led_pattern import RainbowLedPattern -if __name__ == '__main__': +global pixels - pixels.pattern = GoogleHomeLedPattern(show=pixels.show) +class GracefulKiller: + def __init__(self): + signal.signal(signal.SIGINT, self.exit_gracefully) + signal.signal(signal.SIGTERM, self.exit_gracefully) - while True: + def exit_gracefully(self,signum, frame): + global pixels + pixels.off() + sys.exit(0) + +if __name__ == '__main__': + global pixels + pixels.pattern = RainbowLedPattern(show=pixels.show) + killer = GracefulKiller() + while True: try: pixels.wakeup() time.sleep(3) @@ -21,6 +36,5 @@ except KeyboardInterrupt: break - pixels.off() time.sleep(1) diff --git a/rainbow_led_pattern.py b/rainbow_led_pattern.py new file mode 100644 index 0000000..d9deeed --- /dev/null +++ b/rainbow_led_pattern.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 Seeed Technology Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import numpy +import time +import colorsys + +def hsv2rgb(h,s,v): + return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h,s,v)) + +class RainbowLedPattern(object): + def __init__(self, show=None, num_led=12): + self.num_led = num_led + + jump = 1.0 / (self.num_led * 1.0) + pixels = numpy.array([0.0] * 4 * self.num_led) + for i in range(self.num_led): + color = [jump * i, 0.5, 0.5] + for j in range(3): + pixels[(i * 4) + j + 1] = color[j] + self.pixels = pixels + + if not show or not callable(show): + def dummy(data): + pass + show = dummy + + self.show = show + self.stop = False + + def convert_to_rgb(self, pixels_hsv): + pixels_rgb = numpy.copy(pixels_hsv) + for i in range(self.num_led): + color_rgb = hsv2rgb(pixels_hsv[(i*4)+1], pixels_hsv[(i*4)+2], pixels_hsv[(i*4)+3]) + for j in range(3): + pixels_rgb[(i*4)+j+1] = color_rgb[j] + return pixels_rgb + + def wakeup(self, direction=0): + position = int((direction + 15) / (360 / self.num_led)) % self.num_led + pixels = self.convert_to_rgb(self.pixels) + self.show(pixels) + + def listen(self): + self.show(self.pixels) + + def think(self): + pixels = self.convert_to_rgb(self.pixels) + while not self.stop: + self.show(pixels) + time.sleep(0.2) + pixels = numpy.roll(pixels, 4) + + def change_sv(self, pixels_hsv, incr): + pixels = numpy.copy(pixels_hsv) + for i in range(self.num_led): + pixels[(i*4)+2] += incr + pixels[(i*4)+3] += incr + return pixels + + def speak(self): + pixels_hsv = numpy.copy(self.pixels) + step = 0.025 + while not self.stop: + pixels_hsv = self.change_sv(pixels_hsv, step) + pixels_rgb = self.convert_to_rgb(pixels_hsv) + self.show(pixels_rgb) + time.sleep(0.1) + if pixels_hsv[2] <= 0.5: + step = 0.1 + time.sleep(0.4) + elif pixels_hsv[2] >= 0.8: + step = -0.1 + time.sleep(0.4) + + def off(self): + self.show([0] * 4 * self.num_led)