rhasspy / wyoming-satellite

Remote voice satellite using Wyoming protocol
MIT License
587 stars 85 forks source link

2mic_service.py doesn't activate the LEDs on my ReSpeaker 4-Mic Array #13

Closed jkms closed 9 months ago

jkms commented 9 months ago

Issue

I cannot make the lights in examples/2mic_service.py work correctly. Voice commands are working perfectly (thank you for this. I used rhasspy in the past, and this was so much easier to setup, it's honestly amazing!)

Hardware

Troubleshooting

changed NUM_LEDS and LEDS_GPIO

I have changed the following values. These are the correct values for the ReSpeaker 4-Mic Array

from 2mic_service.p:

#!/usr/bin/env python3
"""Controls the LEDs on the ReSpeaker 2mic HAT."""
import argparse
import asyncio
import logging
import time
from functools import partial
from math import ceil
from typing import Tuple

import gpiozero
import spidev
from wyoming.asr import Transcript
from wyoming.event import Event
from wyoming.satellite import RunSatellite, StreamingStarted, StreamingStopped
from wyoming.server import AsyncEventHandler, AsyncServer
from wyoming.vad import VoiceStarted
from wyoming.wake import Detection

_LOGGER = logging.getLogger()

NUM_LEDS = 12 # <------------------------- Correct setting for 4-MIC Array (changed from 3 to 12)
LEDS_GPIO = 5 # <------------------------- Correct setting for 4-MIC Array (changed from 12 to 5)
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],
}

debug logging

I know that I'm running the service correctly, see an example output from $ journalctl -u 2mic_leds.service -f

Dec 15 22:46:32 respeaker systemd[1]: Started 2Mic LEDs.
Dec 15 22:46:34 respeaker python3[6019]: DEBUG:root:Namespace(uri='tcp://127.0.0.1:10500', debug=True)
Dec 15 22:46:34 respeaker python3[6019]: INFO:root:Ready
Dec 15 22:46:35 respeaker python3[6019]: DEBUG:root:Client connected: 15988357915295
Dec 15 22:46:35 respeaker python3[6019]: DEBUG:root:Event(type='run-satellite', data=None, payload=None)
Dec 15 22:46:35 respeaker python3[6019]: DEBUG:root:Event(type='streaming-stopped', data=None, payload=None)
Dec 15 22:46:47 respeaker python3[6019]: DEBUG:root:Event(type='run-pipeline', data={'start_stage': 'asr', 'end_stage': 'tts'}, payload=None)
Dec 15 22:46:47 respeaker python3[6019]: DEBUG:root:Event(type='detection', data={'name': 'hey_jarvis_v0.1', 'timestamp': 16000219248171}, payload=None)
Dec 15 22:46:48 respeaker python3[6019]: DEBUG:root:Event(type='streaming-started', data=None, payload=None)
Dec 15 22:46:48 respeaker python3[6019]: DEBUG:root:Event(type='transcribe', data={'language': 'en-CA'}, payload=None)
Dec 15 22:46:49 respeaker python3[6019]: DEBUG:root:Event(type='voice-started', data={'timestamp': 685}, payload=None)
Dec 15 22:46:50 respeaker python3[6019]: DEBUG:root:Event(type='voice-stopped', data={'timestamp': 1220}, payload=None)
Dec 15 22:46:50 respeaker python3[6019]: DEBUG:root:Event(type='transcript', data={'text': 'Turn off desk lamp.'}, payload=None)
Dec 15 22:46:51 respeaker python3[6019]: DEBUG:root:Event(type='streaming-stopped', data=None, payload=None)
Dec 15 22:46:51 respeaker python3[6019]: DEBUG:root:Event(type='synthesize', data={'text': 'Turned off light', 'voice': {'name': 'ClaraNeural'}}, payload=None)
Dec 15 22:46:51 respeaker python3[6019]: DEBUG:root:Event(type='audio-start', data={'rate': 16000, 'width': 2, 'channels': 1, 'timestamp': 0}, payload=None)
Dec 15 22:46:52 respeaker python3[6019]: DEBUG:root:Event(type='audio-stop', data={'timestamp': 1.9680000000000013}, payload=None)

explicitly setting an LED in main()

So, disclaimer. I'm not a programmer, and I don't know anything about anything. But I wanted to try and figure this out. I tried the following, and i was able to make one LED blink for split second:

async def main() -> None:
    """Main entry point."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--uri", required=True, help="unix:// or tcp://")
    #
    parser.add_argument("--debug", action="store_true", help="Log DEBUG messages")
    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
    _LOGGER.debug(args)

    _LOGGER.info("Ready")

    # Turn on power to LEDs
    led_power = gpiozero.LED(LEDS_GPIO, active_high=False)
    led_power.on()

    leds = APA102(num_led=NUM_LEDS)
    # This just makes one LED flash for a second
    leds.set_pixel(0,255,000,255) # <-------------------------- set the LED
    leds.show() # <-------------------------------------------- show the LED

    # Start server
    server = AsyncServer.from_uri(args.uri)

    try:
        await server.run(partial(LEDsEventHandler, args, leds))
    except KeyboardInterrupt:
        pass
    finally:
        leds.cleanup()
        led_power.off()

https://github.com/rhasspy/wyoming-satellite/assets/660916/0eb5607f-45d3-42f8-873e-1fd5c34e8646

explicitly setting an LED in LEDsEventHandler()

I tried the same set_pixel() and show() functions elsewhere to try and hunt down the problem, but when I call those functions inside LEDsEventHandler() I get no LED activity whatsoever. (I used different LEDs, to make sure I didn't miss anything, and i was sure to run though several successful voice commands).

class LEDsEventHandler(AsyncEventHandler):
    """Event handler for clients."""

    def __init__(
        self,
        cli_args: argparse.Namespace,
        leds: "APA102",
        *args,
        **kwargs,
    ) -> None:
        super().__init__(*args, **kwargs)

        self.cli_args = cli_args
        self.client_id = str(time.monotonic_ns())
        self.leds = leds
        self.leds.set_pixel(1,255,000,255) # <----------------------- This does nothing
        self.leds.show() # <----------------------------------------- This does nothing

        _LOGGER.debug("Client connected: %s", self.client_id)

    async def handle_event(self, event: Event) -> bool:
        _LOGGER.debug(event)
        self.leds.set_pixel(2,255,000,255) # <----------------------- This does nothing
        self.leds.show() # <----------------------------------------- This does nothing

        if StreamingStarted.is_type(event.type):
            self.color(_YELLOW)
        elif Detection.is_type(event.type):
            self.color(_BLUE)
            await asyncio.sleep(1.0)  # show for 1 sec
        elif VoiceStarted.is_type(event.type):
            self.color(_YELLOW)
        elif Transcript.is_type(event.type):
            self.color(_GREEN)
            await asyncio.sleep(1.0)  # show for 1 sec
        elif StreamingStopped.is_type(event.type):
            self.color(_BLACK)
        elif RunSatellite.is_type(event.type):
            self.color(_BLACK)

        return True

    def color(self, rgb: Tuple[int, int, int]) -> None:
        for i in range(NUM_LEDS):
            self.leds.set_pixel(i, rgb[0], rgb[1], rgb[2])

        self.leds.show()

pixels.py

And I also know that my hardware is fine, because I can run pixels.py and my pi/respeaker lights up like a Christmas tree

synesthesiam commented 9 months ago

@jkms Thanks for the detailed write-up!

Can you try changing this line:

led_power = gpiozero.LED(LEDS_GPIO, active_high=False)

to this:

led_power = gpiozero.LED(LEDS_GPIO, active_high=True)

I wonder if the GPIO pin is just active high for the 4mic HAT?

jkms commented 9 months ago

Success!!

https://github.com/rhasspy/wyoming-satellite/assets/660916/950a4137-d6fe-41f0-b05b-e41af426f424

synesthesiam commented 9 months ago

Yay!

adamaze commented 9 months ago

@jkms can you tell me what version of the 4-mic array you have? I got mine working with your updated PR, but my LEDs are on seemingly full brightness all the time. they change color appropriate times, and my voice commands work, but when the command is done, the LEDs go back to full on until it hears the wakeword again. my board says "v1.1 01/10/2020" thanks!

jkms commented 9 months ago

v1.2 01/11/2021 image