@@ -0,0 +1,77 @@
#!/usr/bin/env python
##########################################################################
# gpio_led.py
#
# Copyright 2019, Stephen Penrod
#
# 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 RPi.GPIO as GPIO
from threading import Timer

class GPIO_LED:

def __init__(self, GPIO_BCM=21):
""" Interface with an LED connected to a GPIO pin
Control an LED connected to a GPIO pin (and ground)
Args:
GPIO_BCM (int, optional): The BCM number of the GPIO pin to use.
Defaults to 21.
"""
super().__init__()
self.led_bcm = GPIO_BCM
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.led_bcm, GPIO.OUT)
self.__timer = None

def turn_on(self, flash_on_duration=None, flash_off_duration=0.1):
""" Turn on the LED and optionally flash it
Args:
flash_on_duration (float, optional): Seconds to remain lit,
or None for constant.
flash_off_duration (float, optional): Seconds to go dark.
Defaults to 0.1.
"""
if self.__timer:
self.__timer.cancel()
self.timer = None
GPIO.output(self.led_bcm, GPIO.HIGH)
if flash_on_duration:
self.__timer = Timer(flash_on_duration, self.__flasher,
[True, flash_on_duration, flash_off_duration])
self.__timer.start()

def turn_off(self):
""" Illuminate the LED """
if self.__timer:
self.__timer.cancel()
self.timer = None
GPIO.output(self.led_bcm, GPIO.LOW)

def __flasher(self, is_on, on_duration, off_duration):
if self.__timer:
self.__timer.cancel()
self.timer = None
if is_on:
GPIO.output(self.led_bcm, GPIO.HIGH)
next_change = off_duration
else:
GPIO.output(self.led_bcm, GPIO.LOW)
next_change = on_duration
self.__timer = Timer(next_change, self.__flasher,
[not is_on, on_duration, off_duration])
self.__timer.start()
@@ -0,0 +1,219 @@
#!/usr/bin/env python
##########################################################################
# picroft_enclosure.py
#
# Copyright 2019, Stephen Penrod
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

# This file defines the base enclosure for your Picroft. By default
# it supports:
# * A button connected to the GPIO-23 (and ground) as a "Stop"
# * A LED connected to the GPIO-21 as an activity indicator
# * Reboot and shutdown administrative actions
#
# Customizations should be built on top of this. See the ../templates
# folder for examles, and my_enclosure.py which the setup wizard
# creates for your own special behavior.

from mycroft.client.enclosure.generic import EnclosureGeneric
from mycroft.messagebus.message import Message
from mycroft.util import create_signal
from time import sleep
import os
import sys

from .gpio_button import GPIO_Button
from .gpio_led import GPIO_LED

##########################################################################

class Picroft_Enclosure(EnclosureGeneric):

def __init__(self, button_gpio_bcm=23, led_gpio_bcm=21):
super().__init__()

# Administrative messages
self.bus.on("system.shutdown", self.on_shutdown)
self.bus.on("system.reboot", self.on_reboot)
self.bus.on("system.update", self.on_software_update)

# Interaction feedback
self.bus.on("recognizer_loop:wakeword", self.indicate_listening)
self.bus.on("recognizer_loop:record_begin", self.indicate_listening)
self.bus.on("recognizer_loop:record_end", self.indicate_listening_done)

self.bus.on("recognizer_loop:sleep", self.indicate_sleeping)
self.bus.on("mycroft.awoken", self.indicate_waking)

self.bus.on("recognizer_loop:audio_output_start", self.indicate_talking)
self.bus.on("recognizer_loop:audio_output_end", self.indicate_talking_done)
self.bus.on("mycroft.skill.handler.start", self.indicate_thinking)
self.bus.on("mycroft.skill.handler.complete", self.indicate_thinking_done)

# Visual indication that system is booting
self.bus.on("mycroft.skills.initialized", self.on_ready)

# Setup to support a button on a GPIO -- default is GPIO23
if button_gpio_bcm:
self.button = GPIO_Button(GPIO_BCM=button_gpio_bcm,
on_press=self.on_button_pressed,
on_double_press=self.on_button_double_press,
on_release=self.on_button_released,
on_hold=self.on_button_held)

# Indicate activity using a LED on selected GPIO
if led_gpio_bcm:
self.indicator = GPIO_LED(led_gpio_bcm) # Feedback LED light
else:
self.indicator = None

self.asleep = False

self.bus.on("mycroft.skills.initialized", self.indicate_booting_done)
self.indicate_booting()

def indicate_booting(self):
# Placeholder, override to start something during the bootup sequence
if self.indicator:
self.indicator.turn_on(flash_on_duration=0.5)

def indicate_booting_done(self, message):
# Boot sequence has completed, turn off booting visualization
# Override if visualization is done differently
if self.indicator:
self.indicator.turn_off()

def indicate_sleeping(self, message):
# Turn lights when asleep
if self.indicator:
self.indicator.turn_off()
self.asleep = True

def indicate_waking(self, message):
if self.indicator:
self.indicator.turn_on(flash_on_duration=0.25)
sleep(2)
if self.indicator:
self.indicator.turn_off()
self.asleep = False

######################################################################
# Basic button support. By default behave like the Mark 1 button where
# pressing it either stops a current action or starts listening
#
# Override these actions to react to button events
# NOTE: Basic "button click" actions should happen on the Release instead
# of the Press. This allows the Held to occur without triggering
# a "button click".

def on_button_pressed(self):
""" The button has been depressed once """
pass

def on_button_double_press(self):
""" The button has been depressed within 1 sec of a previous pressing
NOTE: No on_button_pressed() is generated for the second press.
"""
pass

def on_button_held(self):
""" The button has been pressed and held for a second """
pass

def on_button_released(self):
""" The button has been released after a press
NOTE: No on_button_released() occurs when held or double-pressed.
"""
# Generate a Listen/Stop signal like the top button press on a Mark 1.
create_signal('buttonPress')
self.bus.emit(Message("mycroft.stop"))

######################################################################
# Interaction sequence indicators. The trypical sequence is:
# - indicate_listening()
# - indicate_listening_done()
# - indicate_thinking()
# - indicate_talking()
# - indicate_talking_done()
# - indicate_thinking_done()
# There are variations on this, for example an inadvertant recording might
# begin a handler, thus no "thinking". Or there might be multiple talking
# sequences within once thinking session.

# Illuminate lights when listening
def indicate_listening(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_on()

def indicate_listening_done(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_off()

def indicate_thinking(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_on()

def indicate_thinking_done(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_off()

def indicate_talking(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_on(flash_on_duration=0.25)

def indicate_talking_done(self, message):
if self.asleep or not self.indicator:
return
self.indicator.turn_off()

######################################################################
# Administrative actions
#
# Many of the following require root access, but can operate because
# this process is launched using sudo.

def on_software_update(self, message):
# Mycroft updates itself on reboots
self.speak("Updating system, please wait")
sleep(5)
os.system("cd /home/pi/mycroft-core && git pull")
sleep(2)

self.speak("Rebooting to apply update")
sleep(5)
if self.indicator:
self.indicator.turn_off()
os.system("shutdown --reboot now")

def on_shutdown(self, message):
if self.indicator:
self.indicator.turn_off()
os.system("shutdown --poweroff now")

def on_reboot(self, message):
# Example of using the self.speak() helper function
self.speak("I'll be right back")
sleep(5)
if self.indicator:
self.indicator.turn_off()
os.system("shutdown --reboot now")

@@ -0,0 +1,59 @@
#!/usr/bin/env python
##########################################################################
# AIY_Enclosure.sh
#
# Copyright 2019, Stephen Penrod
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

# This file defines a simple custom enclosure for your Picroft. By default
# it supports:
# * The AIY button connected as a "Stop"
# * The AIY button's LED as an activity indicator
# * Reboot and shutdown administrative actions
#
# Feel free to modify this code to your own purposes. Changes will not be
# overwritten by the update process will not overwrite it. This code monitors
# the messagebus for system events, listens to GPIOs, and can do just about
# anything you'd like.
#
# Changes made to the file will restart the enclosure process automatically
# but be careful -- syntax errors will require manual relaunching or reboot
# after the error is fixed. Relaunch manually via:
# cd ~/enclosure
# python ~/my_enclosure.py

from lib.picroft_enclosure import Picroft_Enclosure
import lib.file_watchdog as watchdog
import lib.GPIO_Button
import lib.GPIO_LED

watchdog.watch(__file__)

##########################################################################

class AIY_Enclosure(Picroft_Enclosure):

def __init__(self):
super().__init__()

# Support the standard AIY button
self.stop_button = lib.GPIO_Button(self.bus, GPIO_BCM=23)

# Support the standard AIY button's LED as an activity indicator
self.visual = lib.GPIO_LED(GPIO_BCM=25)


enc = AIY_Enclosure()
enc.run()
@@ -0,0 +1,48 @@
#!/usr/bin/env python
##########################################################################
# Generic_Enclosure.py
#
# Copyright 2019, Stephen Penrod
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

# This file defines a basic enclosure for your Picroft. By default it supports:
# * LED activity light on GPIO-21
# * A button connected to the GPIO-23 (and ground) as a Stop button
# * Administrative actions such as reboot and shutdown
#
# Feel free to modify this code to your own purposes. Changes will not be
# overwritten by the update process will not change it. This code monitors
# the messagebus for system events, listens to GPIOs, and can do just about
# anything you'd like.
#
# Changes to my_enclosure.py will restart the enclosure process automatically
# but be careful -- syntax errors will require manual relaunching or reboot
# after the error is fixed.

from lib.picroft_enclosure import Picroft_Enclosure
from time import sleep

import lib.file_watchdog as watchdog
watchdog.watch(__file__)

##########################################################################

class Generic_Enclosure(Picroft_Enclosure):

def __init__(self):
super().__init__(button_gpio_bcm=23, led_gpio_bcm=21)

enc = Generic_Enclosure()
enc.run()
@@ -0,0 +1,53 @@
#!/usr/bin/env python
##########################################################################
# Matrix_Enclosure.sh
#
# Copyright 2019, Stephen Penrod
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

# This file defines a simple custom enclosure for your Picroft. By default
# it supports:
# * A button connected to the GPIO-23 (and ground) as a Stop button
# * LED activity light on GPIO-21
# * Administrative actions such as reboot and shutdown
#
# Feel free to modify this code to your own purposes. Changes will not be
# overwritten by the update process will not change it. This code monitors
# the messagebus for system events, listens to GPIOs, and can do just about
# anything you'd like.
#
# Changes made to the file will restart the enclosure process automatically
# but be careful -- syntax errors will require manual relaunching or reboot
# after the error is fixed. Relaunch manually via:
# cd ~/enclosure
# python ~/my_enclosure.py

from lib.picroft_enclosure import Picroft_Enclosure
import lib.file_watchdog as watchdog

watchdog.watch(__file__)

##########################################################################

class Matrix_Enclosure(Picroft_Enclosure):

def __init__(self):
super().__init__()

# TODO: Use the Matrix light array to indicate status


enc = Matrix_Enclosure()
enc.run()
@@ -0,0 +1,120 @@
#!/usr/bin/env python
##########################################################################
# ReSpeaker_Enclosure.py
#
# Copyright 2019, Stephen Penrod
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##########################################################################

# This file defines a custom enclosure for your Picroft using a Seeed ReSpeaker
# Mic Array. By default it supports:
# * Animations on the pixel ring on the mic array
# * A button connected to the GPIO-23 as a Stop button
# * Reboot and shutdown administrative actions
#
# Feel free to modify this code to your own purposes. Changes will not be
# overwritten by the update process will not overwrite it. This code monitors
# the messagebus for system events, listens to GPIOs, and can do just about
# anything you'd like.
#
# Changes to my_enclosure.py will restart the enclosure process automatically
# but be careful -- syntax errors will require manual relaunching or reboot
# after the error is fixed.

from lib.picroft_enclosure import Picroft_Enclosure
from time import sleep

# The pixel_ring code is installed from Github in the home directory
import sys
sys.path.insert(1, '/home/pi/usb_4_mic_array/pixel_ring')
from pixel_ring import pixel_ring

import lib.file_watchdog as watchdog
watchdog.watch(__file__)

##########################################################################

class ReSpeaker_Enclosure(Picroft_Enclosure):

def __init__(self):
super().__init__(led_gpio_bcm=None) # Pixel Ring is used to show state
# instead of a LED on a GPIO

def indicate_booting(self):
# Visual indication that system is booting
pixel_ring.set_brightness(2)
pixel_ring.spin()

def indicate_booting_done(self, message):
# Boot has completed, turn off booting visualization
pixel_ring.off()

def indicate_sleeping(self, message):
# Turn lights orange when asleep
pixel_ring.set_color(False, 250, 60, 0)
self.asleep = True

def indicate_waking(self, message):
# Restore lights when woken
pixel_ring.spin()
sleep(2)
pixel_ring.off()
self.asleep = False

######################################################################
# Interaction sequence indicators. The trypical sequence is:
# - indicate_listening()
# - indicate_listening_done()
# - indicate_thinking()
# - indicate_talking()
# - indicate_talking_done()
# - indicate_thinking_done()
# There are variations on this, for example an inadvertant recording might never
# begin a handler sequence. Or there might be multiple output begin/end pairs
# within the handler.

# Illuminate lights when listening
def indicate_listening(self, message):
if self.asleep:
return
pixel_ring.listen()

def indicate_listening_done(self, message):
if self.asleep:
return
pixel_ring.off()

def indicate_thinking(self, message):
if self.asleep:
return
pixel_ring.think()

def indicate_thinking_done(self, message):
if self.asleep:
return
pixel_ring.off()

def indicate_talking(self, message):
if self.asleep:
return
pixel_ring.speak()

def indicate_talking_done(self, message):
if self.asleep:
return
pixel_ring.off()


enc = ReSpeaker_Enclosure()
enc.run()