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:
parent
65ac4ce493
commit
74bfdfac57
9 changed files with 135 additions and 19 deletions
21
README.md
21
README.md
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
# 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:
|
||||
|
||||
|
|
@ -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).
|
||||
|
||||
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
|
||||
# apt install ruby espeak fluidsynth runit motion netcat
|
||||
# systemctl stop/disable/mask motion
|
||||
# gem install -Ng
|
||||
# ln -s /etc/service /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
|
||||
|
||||
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
50
motion/motion.conf
Normal 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
5
motion/run
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec 2>&1
|
||||
exec /usr/bin/motion -c /service/motion/motion.conf
|
||||
|
||||
|
|
@ -4,23 +4,35 @@
|
|||
---
|
||||
logging:
|
||||
__default__: info (color)
|
||||
theremin: debug (color)
|
||||
# theremin: debug (color)
|
||||
# gpio: debug (color)
|
||||
|
||||
# See: https://pinout.xyz/
|
||||
#
|
||||
theremin:
|
||||
# See: https://pinout.xyz/ - GPIO labels, not physical
|
||||
pitch_trigger: 27
|
||||
pitch_echo: 22
|
||||
gain_trigger: 23
|
||||
gain_echo: 24
|
||||
|
||||
# Fine tune range sensors
|
||||
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
|
||||
startup_message: Hello. I'm Maude the bod. Lets make beautiful music together.
|
||||
# font: /usr/share/sounds/sf2/FluidR3_GM.sf2
|
||||
# glide: 1
|
||||
# slew: 5
|
||||
# sample_interval: 0.5
|
||||
# reverb: false
|
||||
# chorus: false
|
||||
# font_index: 2
|
||||
|
|
|
|||
0
theremin/lib/gpio.rb
Executable file → Normal file
0
theremin/lib/gpio.rb
Executable file → Normal file
45
theremin/lib/theremin.rb
Executable file → Normal file
45
theremin/lib/theremin.rb
Executable file → Normal file
|
|
@ -8,6 +8,7 @@ require 'loggability'
|
|||
require 'gpio'
|
||||
require 'fluidsynth'
|
||||
require 'open3'
|
||||
require 'socket'
|
||||
|
||||
|
||||
# A class that coordinates GPIO events and translates
|
||||
|
|
@ -130,6 +131,12 @@ class Theremin
|
|||
|
||||
## ESpeak TTL options.
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -163,6 +170,9 @@ class Theremin
|
|||
# An IO to stream to espeak.
|
||||
attr_reader :speech_io
|
||||
|
||||
# A TCPServer for motion IPC
|
||||
attr_reader :motion_socket
|
||||
|
||||
# Is this Theremin currently running?
|
||||
attr_reader :running
|
||||
|
||||
|
|
@ -181,10 +191,11 @@ class Theremin
|
|||
self.chorus( self.class.chorus )
|
||||
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.gain_thread
|
||||
@threads << self.motion_thread if self.class.use_motion
|
||||
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."
|
||||
@threads.map( &:join )
|
||||
end
|
||||
|
|
@ -196,14 +207,15 @@ class Theremin
|
|||
@running = false
|
||||
self.pins.each {|pin| self.class.send( pin ).unexport rescue nil }
|
||||
self.synth.socket.close
|
||||
self.motion_socket.close
|
||||
self.speech_io.close if self.speech_io && !self.speech_io.closed?
|
||||
self.led( :red )
|
||||
end
|
||||
|
||||
|
||||
#########
|
||||
protected
|
||||
#########
|
||||
#########
|
||||
protected
|
||||
#########
|
||||
|
||||
### Measure pitch distance, and manage notes sent to the synth.
|
||||
###
|
||||
|
|
@ -271,6 +283,23 @@ class Theremin
|
|||
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.
|
||||
##
|
||||
def setup_speech
|
||||
|
|
@ -375,11 +404,11 @@ class Theremin
|
|||
def led( color )
|
||||
case color
|
||||
when :red
|
||||
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(0) }
|
||||
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(1) }
|
||||
File.open( '/sys/class/leds/PWR/brightness', 'w' ){|l| l.puts(1) }
|
||||
File.open( '/sys/class/leds/ACT/brightness', 'w' ){|l| l.puts(0) }
|
||||
when :green
|
||||
File.open( '/sys/class/leds/led0/brightness', 'w' ){|l| l.puts(1) }
|
||||
File.open( '/sys/class/leds/led1/brightness', 'w' ){|l| l.puts(0) }
|
||||
File.open( '/sys/class/leds/PWR/brightness', 'w' ){|l| l.puts(0) }
|
||||
File.open( '/sys/class/leds/ACT/brightness', 'w' ){|l| l.puts(1) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
0
theremin/log/main/.placeholder
Normal file
0
theremin/log/main/.placeholder
Normal file
5
theremin/log/run
Executable file
5
theremin/log/run
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec 2>&1
|
||||
exec svlogd ./main
|
||||
|
||||
|
|
@ -15,5 +15,7 @@ Signal.trap( "INT" ) do
|
|||
theremin.stop if theremin.running
|
||||
end
|
||||
|
||||
$stderr.sync = true
|
||||
$stdout.sync = true
|
||||
theremin.start
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue