Add random phrases when an optional pi camera detects motion.

Fix LED paths for more recent pi-image operating systems.

FossilOrigin-Name: 95bda0bf25a4c8760c07217545106e4a5116a2a6e01463d5189551eb83bfa0f7
This commit is contained in:
Mahlon E. Smith 2023-05-24 03:08:39 +00:00
parent 65ac4ce493
commit 74bfdfac57
9 changed files with 135 additions and 19 deletions

View file

@ -1,7 +1,7 @@
# RPi Theremin # RPi Theremin
This is a quick-n-dirty Theremin daemon for use on the RaspberryPi. This is a quick-n-dirty "Theremin" daemon for use on the RaspberryPi.
It is designed to be used with a couple of these distance sensors: It is designed to be used with a couple of these distance sensors:
@ -9,12 +9,25 @@ It is designed to be used with a couple of these distance sensors:
Use 'raspi-config' to configure your default sound output (HDMI, 3.5", etc). Use 'raspi-config' to configure your default sound output (HDMI, 3.5", etc).
This is intended to be run from daemontools or runit. Quick setup as root:
This is intended to be run from daemontools or runit. Quick setup: # apt install ruby espeak fluidsynth runit motion netcat
# systemctl stop/disable/mask motion
# apt install ruby espeak fluidsynth runit
# gem install -Ng # gem install -Ng
# ln -s /etc/service /service
# ln -s /path/to/here/theremin /etc/service # ln -s /path/to/here/theremin /etc/service
# ln -s /path/to/here/motion /etc/motion
# ln -s /path/to/here/fluidsynth /etc/service # ln -s /path/to/here/fluidsynth /etc/service
For this project, there was an available Pi camera (v2). After enabling it in
raspi-config, I also fired up 'motion' with the included configuration file, to
capture people using the theremin and to say hello.
# ln -s /path/to/here/motion /etc/service
# (enable motion support in the config file)
It will save images for 2 weeks with the current motion.conf. Season to taste.
This was mounted on a manniquin named "Maude" (the bod), as a goofy/fun
interactive instrument.

50
motion/motion.conf Normal file
View file

@ -0,0 +1,50 @@
daemon off
setup_mode off
log_level 6
log_file /dev/null
stream_localhost off
stream_port 8080
stream_motion on
stream_preview_method 4
videodevice /dev/video0
camera_name MaudeCam
camera_id 1
width 1024
height 768
locate_motion_mode on
framerate 5
text_left MAUDECAM!!
text_changes on
text_scale 3
text_right %Y-%m-%d\n%T-%q
emulate_motion off
threshold 2500 # Threshold for number of changed pixels that triggers motion.
threshold_maximum 2050000
lightswitch_percent 40
lightswitch_frames 15
; noise_level 32 # Noise threshold for the motion detection.
despeckle_filter EedDl
minimum_motion_frames 3 # Number of images that must contain motion to trigger an event.
event_gap 30 # Gap in seconds of no motion detected that triggers the end of an event.
pre_capture 2 # The number of pre-captured (buffered) pictures from before motion.
post_capture 0 # Number of frames to capture after motion is no longer detected.
on_event_start echo 1 | nc localhost 62877
on_event_end find /service/motion/capture -type f -mtime +12 -depth 1 -delete
picture_output first
picture_filename capture/%Y%m%d%H%M%S-%q
#timelapse_mode weekly
#timelapse_interval 1800
#timelapse_filename timelapse/%Y%m%d
movie_output off
#movie_max_time 60 # Maximum length of movie in seconds.
#movie_quality 0 # The encoding quality of the movie. (0=use bitrate. 1=worst quality, 100=best)
#movie_codec mkv
#movie_filename motion/%t-%v-%Y%m%d%H%M%S

5
motion/run Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
exec 2>&1
exec /usr/bin/motion -c /service/motion/motion.conf

View file

@ -4,23 +4,35 @@
--- ---
logging: logging:
__default__: info (color) __default__: info (color)
theremin: debug (color) # theremin: debug (color)
# gpio: debug (color) # gpio: debug (color)
# See: https://pinout.xyz/
#
theremin: theremin:
# See: https://pinout.xyz/ - GPIO labels, not physical
pitch_trigger: 27 pitch_trigger: 27
pitch_echo: 22 pitch_echo: 22
gain_trigger: 23 gain_trigger: 23
gain_echo: 24 gain_echo: 24
# Fine tune range sensors
distance_max: 2 distance_max: 2
# sample_interval: 0.5
# slew: 5
# Speech
startup_message: Hello. I'm Maude the bod.
use_motion: true
motion_messages:
- Lets make beautiful music together.
- Oh yeah!
- Yes yes yes yes yessss yyyyyyyyyyyyyyyyyyyyyyyy yes!
- oooooooooooooooooooooookay!
- Let's rock.
# Alter synth sounds from defaults.
cancel_after: 3 cancel_after: 3
startup_message: Hello. I'm Maude the bod. Lets make beautiful music together.
# font: /usr/share/sounds/sf2/FluidR3_GM.sf2 # font: /usr/share/sounds/sf2/FluidR3_GM.sf2
# glide: 1 # glide: 1
# slew: 5
# sample_interval: 0.5
# reverb: false # reverb: false
# chorus: false # chorus: false
# font_index: 2 # font_index: 2

0
theremin/lib/gpio.rb Executable file → Normal file
View file

39
theremin/lib/theremin.rb Executable file → Normal file
View file

@ -8,6 +8,7 @@ require 'loggability'
require 'gpio' require 'gpio'
require 'fluidsynth' require 'fluidsynth'
require 'open3' require 'open3'
require 'socket'
# A class that coordinates GPIO events and translates # A class that coordinates GPIO events and translates
@ -130,6 +131,12 @@ class Theremin
## ESpeak TTL options. ## ESpeak TTL options.
setting :espeak_flags, default: %w[ -k20 -v f5 -s 160 -a 150 ] setting :espeak_flags, default: %w[ -k20 -v f5 -s 160 -a 150 ]
## Watch for camera motion?
setting :use_motion, default: false
## A list of phrases to randomly say when motion is detected.
setting :motion_messages, default: []
end end
@ -163,6 +170,9 @@ class Theremin
# An IO to stream to espeak. # An IO to stream to espeak.
attr_reader :speech_io attr_reader :speech_io
# A TCPServer for motion IPC
attr_reader :motion_socket
# Is this Theremin currently running? # Is this Theremin currently running?
attr_reader :running attr_reader :running
@ -181,10 +191,11 @@ class Theremin
self.chorus( self.class.chorus ) self.chorus( self.class.chorus )
self.instrument( self.class.font_index, self.class.instrument_index ) self.instrument( self.class.font_index, self.class.instrument_index )
self.speak( self.class.startup_message ) if self.class.startup_message
@threads << self.note_thread @threads << self.note_thread
@threads << self.gain_thread @threads << self.gain_thread
@threads << self.motion_thread if self.class.use_motion
self.led( :green ) self.led( :green )
self.speak( self.class.startup_message ) if self.class.startup_message
self.log.warn "Waiting for interaction, or ctrl-c to exit." self.log.warn "Waiting for interaction, or ctrl-c to exit."
@threads.map( &:join ) @threads.map( &:join )
end end
@ -196,6 +207,7 @@ class Theremin
@running = false @running = false
self.pins.each {|pin| self.class.send( pin ).unexport rescue nil } self.pins.each {|pin| self.class.send( pin ).unexport rescue nil }
self.synth.socket.close self.synth.socket.close
self.motion_socket.close
self.speech_io.close if self.speech_io && !self.speech_io.closed? self.speech_io.close if self.speech_io && !self.speech_io.closed?
self.led( :red ) self.led( :red )
end end
@ -271,6 +283,23 @@ class Theremin
end end
### Watch the motion fifo, say a random phrase when motion seen.
###
def motion_thread
phrases = self.class.motion_messages
return if phrases.empty?
@motion_socket = TCPServer.new( 62877 )
return self.running_thread do
client = @motion_socket.accept
self.log.info "Camera motion detected."
client.close
self.speak( phrases.sample )
rescue IOError
end
end
### Open a pipe to espeak, and wait on input. ### Open a pipe to espeak, and wait on input.
## ##
def setup_speech def setup_speech
@ -375,11 +404,11 @@ class Theremin
def led( color ) def led( color )
case color case color
when :red when :red
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(0) } File.open( '/sys/class/leds/PWR/brightness', 'w' ){|l| l.puts(1) }
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(1) } File.open( '/sys/class/leds/ACT/brightness', 'w' ){|l| l.puts(0) }
when :green when :green
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(1) } File.open( '/sys/class/leds/PWR/brightness', 'w' ){|l| l.puts(0) }
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(0) } File.open( '/sys/class/leds/ACT/brightness', 'w' ){|l| l.puts(1) }
end end
end end

View file

5
theremin/log/run Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
exec 2>&1
exec svlogd ./main

View file

@ -15,5 +15,7 @@ Signal.trap( "INT" ) do
theremin.stop if theremin.running theremin.stop if theremin.running
end end
$stderr.sync = true
$stdout.sync = true
theremin.start theremin.start