Init
This commit is contained in:
commit
cba3dd96d6
|
@ -0,0 +1,20 @@
|
||||||
|
DESTDIR :=
|
||||||
|
ROOTDIR=/usr/share/pumpkin/
|
||||||
|
SERVICE_PATH=/etc/systemd/system/
|
||||||
|
|
||||||
|
install:
|
||||||
|
pip install RPi.GPIO
|
||||||
|
mkdir -p $(DESTDIR)$(ROOTDIR)
|
||||||
|
mkdir -p $(DESTDIR)$(SERVICE_PATH)
|
||||||
|
cp __main__.py $(DESTDIR)$(ROOTDIR)
|
||||||
|
cp rgb.py $(DESTDIR)$(ROOTDIR)
|
||||||
|
cp waveloader.py $(DESTDIR)$(ROOTDIR)
|
||||||
|
cp config.py $(DESTDIR)$(ROOTDIR)
|
||||||
|
cp pumpkin.py $(DESTDIR)$(ROOTDIR)
|
||||||
|
cp pumpkin.service $(DESTDIR)$(SERVICE_PATH)
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm $(SERVICE_PATH)pumpkin.service
|
||||||
|
rm -R $(ROOTDIR)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
RUN THE INSTALLER BY TYPING IN:
|
||||||
|
./installer
|
||||||
|
|
||||||
|
IF SOME DEPENDENCIES ARE MISSING PLEASE INSTALL THEM WITH YOUR PACKAGE MANAGER.
|
||||||
|
|
||||||
|
IF THE INSTALLER WAS SUCCESSFULL YOU HAVE TO START IT ONCE TO GET A DEFAULT CONFIG FILE:
|
||||||
|
sudo systemctl start pumpkin
|
||||||
|
|
||||||
|
IT WILL CRASH BUT NOW YOU WILL HAVE A CUSTOMIZABLE CONFIG FILE. OPEN IT:
|
||||||
|
sudo nano /etc/pumpkin.conf
|
||||||
|
|
||||||
|
TO MAKE IT START SUCCESSFULLY YOU HAVE TO GIVE THE ABSOLUTE PATH TO YOUR WAVE FOLDER:
|
||||||
|
AUDIO_PATH=/your/wave/folder
|
||||||
|
|
||||||
|
THEN SPECIFY YOUR GPIOS FOR EVERY COLOR. IF THERE ARE MULTIPLE LEDS FOR ONE COLOR SEPARATE THEM WITH , LIKE THAT:
|
||||||
|
GPIOS_RED=11,12,13
|
||||||
|
|
||||||
|
NOW YOU HAVE A BASIC CONFIGURATION AND IT SHOULD RUN. START THE PUMPKIN AGAIN BY TYPING IN
|
||||||
|
sudo systemctl start pumpkin
|
||||||
|
|
||||||
|
TO STOP IT TYPE
|
||||||
|
sudo systemctl stop pumpkin
|
||||||
|
|
||||||
|
IF YOU WANT TO MAKE IT START AUTOMATICALLY AFTER BOOT WITHOUT LOGIN TYPE
|
||||||
|
sudo systemctl enable pumpkin
|
||||||
|
|
||||||
|
TO DELETE THE AUTOSTART TYPE
|
||||||
|
sudo systemctl disable pumpkin
|
||||||
|
|
||||||
|
IF YOU HAVE A FOG MACHINE YOU CAN ALSO SPECIFY ONE GPIO FOR THE EVAPORATOR AND ONE FOR THE PUMP:
|
||||||
|
GPIO_EVAPORATOR=
|
||||||
|
GPIO_PUMP=
|
||||||
|
|
||||||
|
AND NOW HAVE FUN!
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from pumpkin import Pumpkin
|
||||||
|
from config import Config
|
||||||
|
from time import time
|
||||||
|
from time import sleep
|
||||||
|
from os import listdir
|
||||||
|
from os.path import isfile
|
||||||
|
from shutil import copy
|
||||||
|
from sys import argv
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
pumpkin.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_PATH = '/etc/pumpkin.conf'
|
||||||
|
|
||||||
|
# Make sure that a config file exists
|
||||||
|
if not isfile(CONFIG_PATH):
|
||||||
|
parts = argv[0].split('/')
|
||||||
|
pathDefaultConf = '/'.join(parts[:len(parts) - 1]) + '/pumpkin.conf'
|
||||||
|
|
||||||
|
print(pathDefaultConf)
|
||||||
|
copy(pathDefaultConf, CONFIG_PATH)
|
||||||
|
|
||||||
|
config = Config(CONFIG_PATH)
|
||||||
|
|
||||||
|
AUDIO_PATH = config.getString('AUDIO_PATH')
|
||||||
|
|
||||||
|
COLOR_RED = config.getInt('COLOR_RED')
|
||||||
|
COLOR_GREEN = config.getInt('COLOR_GREEN')
|
||||||
|
COLOR_BLUE = config.getInt('COLOR_BLUE')
|
||||||
|
|
||||||
|
COLOR_BACKGROUND_RED = config.getInt('COLOR_BACKGROUND_RED')
|
||||||
|
COLOR_BACKGROUND_GREEN = config.getInt('COLOR_BACKGROUND_GREEN')
|
||||||
|
COLOR_BACKGROUND_BLUE = config.getInt('COLOR_BACKGROUND_BLUE')
|
||||||
|
|
||||||
|
COLOR_FIRE_RED = config.getInt('COLOR_FIRE_RED')
|
||||||
|
COLOR_FIRE_GREEN = config.getInt('COLOR_FIRE_GREEN')
|
||||||
|
COLOR_FIRE_BLUE = config.getInt('COLOR_FIRE_BLUE')
|
||||||
|
|
||||||
|
GPIOS_RED = config.getIntList('GPIOS_RED')
|
||||||
|
GPIOS_GREEN = config.getIntList('GPIOS_GREEN')
|
||||||
|
GPIOS_BLUE = config.getIntList('GPIOS_BLUE')
|
||||||
|
GPIO_TRIGGER = config.getInt('GPIO_TRIGGER')
|
||||||
|
|
||||||
|
GPIO_EVAPORATOR = config.getInt('GPIO_EVAPORATOR')
|
||||||
|
GPIO_PUMP = config.getInt('GPIO_PUMP')
|
||||||
|
FOG_RUNTIME_MAX = config.getFloat('FOG_RUNTIME_MAX')
|
||||||
|
|
||||||
|
GPIO_POWEROFF = config.getInt('GPIO_POWEROFF')
|
||||||
|
RETRIGGER_TIMEOUT = config.getFloat('RETRIGGER_TIMEOUT')
|
||||||
|
|
||||||
|
PWM_FREQUENCY = config.getInt('PWM_FREQUENCY')
|
||||||
|
|
||||||
|
pumpkin = Pumpkin(AUDIO_PATH, GPIOS_RED, GPIOS_GREEN, GPIOS_BLUE, GPIO_TRIGGER, PWM_FREQUENCY)
|
||||||
|
pumpkin.setAudioColor(COLOR_RED, COLOR_GREEN, COLOR_BLUE)
|
||||||
|
pumpkin.setBackgroundColor(COLOR_BACKGROUND_RED, COLOR_BACKGROUND_GREEN, COLOR_BACKGROUND_BLUE)
|
||||||
|
pumpkin.setFireColor(COLOR_FIRE_RED, COLOR_FIRE_GREEN, COLOR_FIRE_BLUE)
|
||||||
|
pumpkin.enableFog(GPIO_EVAPORATOR, GPIO_PUMP, FOG_RUNTIME_MAX)
|
||||||
|
pumpkin.enablePowerOff(GPIO_POWEROFF)
|
||||||
|
|
||||||
|
atexit.register(cleanup)
|
||||||
|
|
||||||
|
retriggerTimeout = RETRIGGER_TIMEOUT
|
||||||
|
t_after_trigger = time() - retriggerTimeout
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
try:
|
||||||
|
if pumpkin.isTriggered() and time() - t_after_trigger > retriggerTimeout:
|
||||||
|
pumpkin.speak()
|
||||||
|
t_after_trigger = time()
|
||||||
|
else:
|
||||||
|
pumpkin.lurk()
|
||||||
|
|
||||||
|
sleep(0.5)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
exit()
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self, file):
|
||||||
|
self.COMMENT_MARK = '#'
|
||||||
|
|
||||||
|
self.path = file
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file, 'r') as conf:
|
||||||
|
config = conf.read().split('\n')
|
||||||
|
except:
|
||||||
|
raise IOError('Couldn\'t read ' + file + '!')
|
||||||
|
|
||||||
|
if not self.isValidFile(config):
|
||||||
|
raise Exception('Error: ' + file + ' is no valid config file!')
|
||||||
|
|
||||||
|
self.values = {}
|
||||||
|
|
||||||
|
for c in config:
|
||||||
|
if self.lineHasValue(c):
|
||||||
|
parts = c.split('=')
|
||||||
|
self.values[parts[0].strip()] = parts[1].strip()
|
||||||
|
|
||||||
|
def getValue(self, key):
|
||||||
|
try:
|
||||||
|
return self.values[key]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getInt(self, key):
|
||||||
|
value = self.getValue(key)
|
||||||
|
|
||||||
|
if value == None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
def getFloat(self, key):
|
||||||
|
return float(self.getValue(key))
|
||||||
|
|
||||||
|
def getBool(self, key):
|
||||||
|
return bool(self.getValue(key))
|
||||||
|
|
||||||
|
def getString(self, key):
|
||||||
|
return self.getValue(key)
|
||||||
|
|
||||||
|
def getList(self, key):
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for v in self.getValue(key).split(','):
|
||||||
|
values.append(v.strip())
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
def getIntList(self, key):
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for i in self.getList(key):
|
||||||
|
values.append(int(i))
|
||||||
|
|
||||||
|
return values
|
||||||
|
|
||||||
|
def lineHasValue(self, line):
|
||||||
|
return not line.startswith(self.COMMENT_MARK) and len(line.replace(' ', '').replace('\n', '')) > 0 and line.count('=') == 1
|
||||||
|
|
||||||
|
def lineHasSyntaxError(self, line):
|
||||||
|
return not line.startswith(self.COMMENT_MARK) and len(line.replace(' ', '').replace('\n', '')) > 0 and line.count('=') != 1
|
||||||
|
|
||||||
|
def isValidFile(self, config):
|
||||||
|
count = 1
|
||||||
|
for line in config:
|
||||||
|
if self.lineHasSyntaxError(line):
|
||||||
|
print(self.path + ' Line ' + str(count) + ': Invalid syntax!')
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return True
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function check_package()
|
||||||
|
{
|
||||||
|
printf "\tChecking for ${1}... "
|
||||||
|
|
||||||
|
${1} --version &> /dev/null
|
||||||
|
|
||||||
|
if [ ! $? == 0 ]
|
||||||
|
then
|
||||||
|
echo "Failed!"
|
||||||
|
echo ""
|
||||||
|
echo "Please install ${1}!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found."
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Checking for dependencies:"
|
||||||
|
|
||||||
|
check_package python3
|
||||||
|
check_package pip3
|
||||||
|
check_package gcc
|
||||||
|
check_package make
|
||||||
|
check_package aplay
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Ready to install."
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
./configure
|
||||||
|
|
||||||
|
if [ $? == 0 ]
|
||||||
|
then
|
||||||
|
echo "Installing..."
|
||||||
|
sudo make install
|
||||||
|
fi
|
|
@ -0,0 +1,58 @@
|
||||||
|
# The GPIO numbers are in board mode so you can count down on your
|
||||||
|
# Raspberry Pi to find the number:
|
||||||
|
# _____________
|
||||||
|
# (1) (2) \
|
||||||
|
# (3) (4) |
|
||||||
|
# (5) (6) |
|
||||||
|
#
|
||||||
|
# and so on...
|
||||||
|
#
|
||||||
|
# To get a view for the numbering install the python module gpiozero the following way:
|
||||||
|
#
|
||||||
|
# sudo pip install gpiozero
|
||||||
|
#
|
||||||
|
# Now if you type pinout you get a nice graphical overview for your pins.
|
||||||
|
# WARNING: Only use GPIOs that are NOT reserved for I²C, SPI or UART!
|
||||||
|
|
||||||
|
PWM_FREQUENCY=60
|
||||||
|
|
||||||
|
# You can specify multiple GPIOS for one color separated by ,
|
||||||
|
GPIOS_RED=11
|
||||||
|
GPIOS_GREEN=12
|
||||||
|
GPIOS_BLUE=13
|
||||||
|
|
||||||
|
# The path to your audio folder (must contain wave files!).
|
||||||
|
AUDIO_PATH=/path/to/audio/files
|
||||||
|
|
||||||
|
# The color for sound visualization
|
||||||
|
COLOR_RED=0
|
||||||
|
COLOR_GREEN=128
|
||||||
|
COLOR_BLUE=255
|
||||||
|
|
||||||
|
# The color for silence parts of your audio visualization.
|
||||||
|
COLOR_BACKGROUND_RED=0
|
||||||
|
COLOR_BACKGROUND_GREEN=0
|
||||||
|
COLOR_BACKGROUND_BLUE=0
|
||||||
|
|
||||||
|
# The color for the flickering candle on standby.
|
||||||
|
COLOR_FIRE_RED=255
|
||||||
|
COLOR_FIRE_GREEN=128
|
||||||
|
COLOR_FIRE_BLUE=0
|
||||||
|
|
||||||
|
# The input pin for your motion sensor.
|
||||||
|
GPIO_TRIGGER=7
|
||||||
|
|
||||||
|
# The seconds the pumpkin waits to be triggered again after he has been triggered.
|
||||||
|
RETRIGGER_TIMEOUT=10
|
||||||
|
|
||||||
|
# The input pin for your evaporator activation.
|
||||||
|
GPIO_EVAPORATOR=0
|
||||||
|
|
||||||
|
# The input pin for your pump activation.
|
||||||
|
GPIO_PUMP=0
|
||||||
|
|
||||||
|
# The maximal runtime for pump and evaporator in seconds during speaking.
|
||||||
|
FOG_RUNTIME_MAX=3
|
||||||
|
|
||||||
|
# The input pin for a poweroff switch to power down the whole system.
|
||||||
|
GPIO_POWEROFF=0
|
|
@ -0,0 +1,184 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import _thread
|
||||||
|
import atexit
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import os
|
||||||
|
from waveloader import WaveLoader
|
||||||
|
from rgb import RGBMatrix
|
||||||
|
from rgb import RGB
|
||||||
|
from subprocess import call
|
||||||
|
from time import time
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
|
||||||
|
class Pumpkin:
|
||||||
|
def __init__(self, soundpath, GPIOS_RED = [11], GPIOS_GREEN = [12], GPIOS_BLUE = [13], GPIO_TRIGGER = 15, PWM_FREQUENCY = 60):
|
||||||
|
self.soundfiles = []
|
||||||
|
self.loadWaveFolder(soundpath)
|
||||||
|
|
||||||
|
if len(self.soundfiles) == 0:
|
||||||
|
raise IOError('No audio files found in ' + soundpath + '!')
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
|
self.GPIOS_RED = GPIOS_RED
|
||||||
|
self.GPIOS_GREEN = GPIOS_GREEN
|
||||||
|
self.GPIOS_BLUE = GPIOS_BLUE
|
||||||
|
self.GPIO_TRIGGER = GPIO_TRIGGER
|
||||||
|
self.GPIO_EVAPORATOR = 0
|
||||||
|
self.GPIO_PUMP = 0
|
||||||
|
self.GPIO_POWEROFF = 0
|
||||||
|
|
||||||
|
self.fogRuntimeMax = 3
|
||||||
|
|
||||||
|
GPIO.setup(self.GPIO_TRIGGER, GPIO.IN)
|
||||||
|
|
||||||
|
self.rgbControl = RGBMatrix(PWM_FREQUENCY)
|
||||||
|
|
||||||
|
for r in self.GPIOS_RED:
|
||||||
|
self.rgbControl.addRed(r)
|
||||||
|
|
||||||
|
for g in self.GPIOS_GREEN:
|
||||||
|
self.rgbControl.addGreen(g)
|
||||||
|
|
||||||
|
for b in self.GPIOS_BLUE:
|
||||||
|
self.rgbControl.addBlue(b)
|
||||||
|
|
||||||
|
self.color = RGB(255, 255, 255)
|
||||||
|
self.colorBackground = RGB(0, 0, 0)
|
||||||
|
self.colorFire = RGB(255, 55, 0)
|
||||||
|
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def enableFog(self, GPIO_EVAPORATOR, GPIO_PUMP, runtimeSecondsMax = 3):
|
||||||
|
self.GPIO_EVAPORATOR = GPIO_EVAPORATOR
|
||||||
|
self.GPIO_PUMP = GPIO_PUMP
|
||||||
|
self.fogRuntimeMax = runtimeSecondsMax
|
||||||
|
|
||||||
|
if self.GPIO_EVAPORATOR != 0:
|
||||||
|
GPIO.setup(self.GPIO_EVAPORATOR, GPIO.OUT)
|
||||||
|
|
||||||
|
if self.GPIO_PUMP != 0:
|
||||||
|
GPIO.setup(self.GPIO_PUMP, GPIO.OUT)
|
||||||
|
|
||||||
|
self.stopFog()
|
||||||
|
|
||||||
|
def enablePowerOff(self, GPIO_POWEROFF):
|
||||||
|
self.GPIO_POWEROFF = GPIO_POWEROFF
|
||||||
|
|
||||||
|
if self.GPIO_POWEROFF != 0:
|
||||||
|
self.powerSwitch = GPIO.setup(self.GPIO_POWEROFF, GPIO.IN)
|
||||||
|
|
||||||
|
def loadWaveFolder(self, path):
|
||||||
|
if not path.endswith('/'):
|
||||||
|
path += '/'
|
||||||
|
|
||||||
|
files = os.listdir(path)
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
if f.endswith('.wav'):
|
||||||
|
self.soundfiles.append(path + f)
|
||||||
|
|
||||||
|
def lurk(self):
|
||||||
|
if self.isTurnedOff():
|
||||||
|
self.poweroff()
|
||||||
|
|
||||||
|
self.rgbControl.candle(self.colorFire.r, self.colorFire.g, self.colorFire.b, 0.5)
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
self.startFog()
|
||||||
|
|
||||||
|
soundfile = self.soundfiles[randint(0, len(self.soundfiles) - 1)]
|
||||||
|
|
||||||
|
sound = WaveLoader(soundfile)
|
||||||
|
|
||||||
|
duration = sound.frameCount / sound.frameRate
|
||||||
|
t_now = 0
|
||||||
|
|
||||||
|
player = _thread.start_new_thread(call, (['aplay', soundfile],))
|
||||||
|
t_start = time()
|
||||||
|
|
||||||
|
while t_now < duration:
|
||||||
|
t_now = time() - t_start
|
||||||
|
amplitude = sound.getCurrentLevelRealtime(t_now)
|
||||||
|
|
||||||
|
if amplitude < 0.1:
|
||||||
|
red = self.colorBackground.r
|
||||||
|
green = self.colorBackground.g
|
||||||
|
blue = self.colorBackground.b
|
||||||
|
else:
|
||||||
|
amplitude = 1
|
||||||
|
red = self.color.r * amplitude
|
||||||
|
green = self.color.g * amplitude
|
||||||
|
blue = self.color.b * amplitude
|
||||||
|
|
||||||
|
self.rgbControl.setColor(red, green, blue)
|
||||||
|
|
||||||
|
if t_now > self.fogRuntimeMax:
|
||||||
|
self.stopFog()
|
||||||
|
|
||||||
|
self.stopFog()
|
||||||
|
|
||||||
|
def startFog(self):
|
||||||
|
if self.GPIO_EVAPORATOR != 0:
|
||||||
|
GPIO.output(self.GPIO_EVAPORATOR, True)
|
||||||
|
|
||||||
|
if self.GPIO_PUMP != 0:
|
||||||
|
GPIO.output(self.GPIO_PUMP, True)
|
||||||
|
|
||||||
|
def stopFog(self):
|
||||||
|
if self.GPIO_EVAPORATOR != 0:
|
||||||
|
GPIO.output(self.GPIO_EVAPORATOR, False)
|
||||||
|
|
||||||
|
if self.GPIO_PUMP != 0:
|
||||||
|
GPIO.output(self.GPIO_PUMP, False)
|
||||||
|
|
||||||
|
def isTriggered(self):
|
||||||
|
return bool(GPIO.input(self.GPIO_TRIGGER))
|
||||||
|
|
||||||
|
def isTurnedOff(self):
|
||||||
|
if self.GPIO_POWEROFF == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return bool(GPIO.input(self.GPIO_POWEROFF))
|
||||||
|
|
||||||
|
def poweroff(self):
|
||||||
|
call(['poweroff'])
|
||||||
|
|
||||||
|
def setAudioColor(self, red, green, blue):
|
||||||
|
self.color = RGB(red, green, blue)
|
||||||
|
|
||||||
|
def setBackgroundColor(self, red, green, blue):
|
||||||
|
self.colorBackground = RGB(red, green, blue)
|
||||||
|
|
||||||
|
def setFireColor(self, red, green, blue):
|
||||||
|
self.colorFire = RGB(red, green, blue)
|
||||||
|
|
||||||
|
def loadAudioFolder(self, path):
|
||||||
|
if os.path.isdir(path):
|
||||||
|
if not path.endswith('/'):
|
||||||
|
path += '/'
|
||||||
|
|
||||||
|
for file in os.listdir(path):
|
||||||
|
if file.endswith('.wav'):
|
||||||
|
audioFiles.append(audioPath + file)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def findAudioFiles():
|
||||||
|
audioFiles = []
|
||||||
|
|
||||||
|
for home in os.listdir('/home'):
|
||||||
|
audioPath = '/home/' + home + '/.config/pumpkin/audio/'
|
||||||
|
|
||||||
|
if os.path.isdir(audioPath):
|
||||||
|
for file in os.listdir(audioPath):
|
||||||
|
audioFiles.append(audioPath + file)
|
||||||
|
|
||||||
|
return audioFiles
|
|
@ -0,0 +1,5 @@
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/share/pumpkin/__main__.py
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,387 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
from time import sleep
|
||||||
|
from time import time
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
|
||||||
|
class RGB:
|
||||||
|
def __init__(self, red, green, blue):
|
||||||
|
self.r = red
|
||||||
|
self.g = green
|
||||||
|
self.b = blue
|
||||||
|
|
||||||
|
def getDutyCycles(self):
|
||||||
|
percent = RGB.rgbToPerc(self.r, self.g, self.b)
|
||||||
|
|
||||||
|
return RGB(percent[0], percent[1], percent[2])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sanitizeColor(value):
|
||||||
|
if value < 0 or value > 255:
|
||||||
|
print('Value must be between 0 and 255! Fixing color value.')
|
||||||
|
return max(0, min(value, 255))
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rgbToPerc(red, green, blue):
|
||||||
|
percent = 100 / 255
|
||||||
|
|
||||||
|
return (red * percent, green * percent, blue * percent)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def percToRGB(red, green, blue):
|
||||||
|
percent = 255 / 100
|
||||||
|
|
||||||
|
return (red * percent, green * percent, blue * percent)
|
||||||
|
|
||||||
|
|
||||||
|
class RGB_Control:
|
||||||
|
|
||||||
|
def __init__(self, r, g, b, freq=60):
|
||||||
|
self.pins_allowed = (7, 11, 12, 13, 15, 16, 18, 19, 21, 22)
|
||||||
|
self.frequency = freq
|
||||||
|
|
||||||
|
if r not in self.pins_allowed:
|
||||||
|
print("Fehler: r wurde ungültigem Pin zugewiesen!")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
if g not in self.pins_allowed:
|
||||||
|
print("Fehler: g wurde ungültigem Pin zugewiesen!")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
if b not in self.pins_allowed:
|
||||||
|
print("Fehler: b wurde ungültigem Pin zugewiesen!")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
GPIO.setup(r, GPIO.OUT)
|
||||||
|
GPIO.setup(g, GPIO.OUT)
|
||||||
|
GPIO.setup(b, GPIO.OUT)
|
||||||
|
except:
|
||||||
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
|
GPIO.setup(r, GPIO.OUT)
|
||||||
|
GPIO.setup(g, GPIO.OUT)
|
||||||
|
GPIO.setup(b, GPIO.OUT)
|
||||||
|
|
||||||
|
self.r = GPIO.PWM(r, freq)
|
||||||
|
self.g = GPIO.PWM(g, freq)
|
||||||
|
self.b = GPIO.PWM(b, freq)
|
||||||
|
|
||||||
|
self.r.start(0)
|
||||||
|
self.g.start(0)
|
||||||
|
self.b.start(0)
|
||||||
|
|
||||||
|
self.r_alt = None
|
||||||
|
self.g_alt = None
|
||||||
|
self.b_alt = None
|
||||||
|
|
||||||
|
self.intensity = 1
|
||||||
|
|
||||||
|
self.current_r = 0
|
||||||
|
self.current_g = 0
|
||||||
|
self.current_b = 0
|
||||||
|
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
GPIO.cleanup()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def enableSecondLED(self, gpioRed, gpioGreen, gpioBlue):
|
||||||
|
GPIO.setup(gpioRed, GPIO.OUT)
|
||||||
|
GPIO.setup(gpioGreen, GPIO.OUT)
|
||||||
|
GPIO.setup(gpioBlue, GPIO.OUT)
|
||||||
|
|
||||||
|
self.r_alt = GPIO.PWM(gpioRed, self.frequency)
|
||||||
|
self.g_alt = GPIO.PWM(gpioGreen, self.frequency)
|
||||||
|
self.b_alt = GPIO.PWM(gpioBlue, self.frequency)
|
||||||
|
|
||||||
|
self.r_alt.start(0)
|
||||||
|
self.g_alt.start(0)
|
||||||
|
self.b_alt.start(0)
|
||||||
|
|
||||||
|
def toggleSpectrum(self,fps=30,speed=1):
|
||||||
|
self.setColor(255,0,0)
|
||||||
|
|
||||||
|
self.transition((255, 255, 0), duration = speed)
|
||||||
|
self.transition((0, 255, 0), duration = speed)
|
||||||
|
self.transition((0, 255, 255), duration = speed)
|
||||||
|
self.transition((0, 0, 255), duration = speed)
|
||||||
|
self.transition((255, 0, 255), duration = speed)
|
||||||
|
self.transition((255, 0, 0), duration = speed)
|
||||||
|
|
||||||
|
|
||||||
|
def flash(self, color = (255, 255, 255)):
|
||||||
|
color_start = (self.current_r, self.current_g, self.current_b)
|
||||||
|
self.setColor(color[0], color[1], color[2])
|
||||||
|
sleep(1/30)
|
||||||
|
self.setColor(color_start[0], color_start[1], color_start[2])
|
||||||
|
|
||||||
|
|
||||||
|
def pulse(self, from_color, to_color, freq=0.5):
|
||||||
|
duration = freq * 0.5
|
||||||
|
|
||||||
|
self.transition(from_color, duration=duration)
|
||||||
|
self.transition(to_color, duration=duration)
|
||||||
|
|
||||||
|
|
||||||
|
def fire(self, wind = 0.5, r = 255, g = 55, b = 0):
|
||||||
|
wind_power = 100 - (100 * wind)
|
||||||
|
|
||||||
|
flicker = randint(int(wind_power), 100) * 0.01
|
||||||
|
|
||||||
|
color_start = (r * flicker, g * flicker, b * flicker)
|
||||||
|
|
||||||
|
duration = randint(10, int(40 - wind * 30)) * 0.01
|
||||||
|
|
||||||
|
self.transition(color_start, duration = duration)
|
||||||
|
|
||||||
|
def panic(self, times = 10, color = (255, 255, 255)):
|
||||||
|
for t in range(times):
|
||||||
|
self.flash(color = color)
|
||||||
|
sleep(1/30)
|
||||||
|
|
||||||
|
def setColor(self, r, g, b):
|
||||||
|
perc = RGB.rgbToPerc(r, g, b)
|
||||||
|
|
||||||
|
self.current_r = r
|
||||||
|
self.current_g = g
|
||||||
|
self.current_b = b
|
||||||
|
|
||||||
|
self.r.ChangeDutyCycle(perc[0] * self.intensity)
|
||||||
|
self.g.ChangeDutyCycle(perc[1] * self.intensity)
|
||||||
|
self.b.ChangeDutyCycle(perc[2] * self.intensity)
|
||||||
|
|
||||||
|
if self.r_alt != None:
|
||||||
|
self.r_alt.ChangeDutyCycle(perc[0] * self.intensity)
|
||||||
|
|
||||||
|
if self.g_alt != None:
|
||||||
|
self.g_alt.ChangeDutyCycle(perc[1] * self.intensity)
|
||||||
|
|
||||||
|
if self.b_alt != None:
|
||||||
|
self.b_alt.ChangeDutyCycle(perc[2] * self.intensity)
|
||||||
|
|
||||||
|
def transition(self, color_finish, duration = 3, fps = 30):
|
||||||
|
start_r = self.current_r
|
||||||
|
start_g = self.current_g
|
||||||
|
start_b = self.current_b
|
||||||
|
|
||||||
|
finish_r = color_finish[0]
|
||||||
|
finish_g = color_finish[1]
|
||||||
|
finish_b = color_finish[2]
|
||||||
|
|
||||||
|
delta_r = finish_r - start_r
|
||||||
|
delta_g = finish_g - start_g
|
||||||
|
delta_b = finish_b - start_b
|
||||||
|
|
||||||
|
|
||||||
|
frames = duration * fps
|
||||||
|
frame_duration = 1 / fps
|
||||||
|
|
||||||
|
# Step per frame for every color
|
||||||
|
stf_r = delta_r / frames
|
||||||
|
stf_g = delta_g / frames
|
||||||
|
stf_b = delta_b / frames
|
||||||
|
|
||||||
|
self.setColor(self.current_r, self.current_g, self.current_b)
|
||||||
|
sleep(frame_duration)
|
||||||
|
|
||||||
|
start_time = time()
|
||||||
|
|
||||||
|
while time() - start_time < duration:
|
||||||
|
|
||||||
|
self.current_r += stf_r
|
||||||
|
self.current_g += stf_g
|
||||||
|
self.current_b += stf_b
|
||||||
|
|
||||||
|
if self.current_r > 255:
|
||||||
|
self.current_r = 255
|
||||||
|
elif self.current_r < 0:
|
||||||
|
self.current_r = 0
|
||||||
|
|
||||||
|
if self.current_g > 255:
|
||||||
|
self.current_g = 255
|
||||||
|
elif self.current_g < 0:
|
||||||
|
self.current_g = 0
|
||||||
|
|
||||||
|
if self.current_b > 255:
|
||||||
|
self.current_b = 255
|
||||||
|
elif self.current_b < 0:
|
||||||
|
self.current_b = 0
|
||||||
|
|
||||||
|
self.setColor(self.current_r, self.current_g, self.current_b)
|
||||||
|
|
||||||
|
sleep(frame_duration)
|
||||||
|
|
||||||
|
|
||||||
|
class RGBMatrix(RGB):
|
||||||
|
|
||||||
|
def __init__(self, PWM_FREQUENCY = 60):
|
||||||
|
self.PWM_FREQUENCY = PWM_FREQUENCY
|
||||||
|
|
||||||
|
self.gpiosRed = []
|
||||||
|
self.gpiosGreen = []
|
||||||
|
self.gpiosBlue = []
|
||||||
|
|
||||||
|
self.currentColor = RGB(0, 0, 0)
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
def setupLED(self, gpio):
|
||||||
|
GPIO.setup(gpio, GPIO.OUT)
|
||||||
|
led = GPIO.PWM(gpio, self.PWM_FREQUENCY)
|
||||||
|
led.start(0)
|
||||||
|
|
||||||
|
return led
|
||||||
|
|
||||||
|
def addRed(self, gpio):
|
||||||
|
led = self.setupLED(gpio)
|
||||||
|
led.ChangeDutyCycle(self.currentColor.getDutyCycles().r)
|
||||||
|
self.gpiosRed.append(led)
|
||||||
|
|
||||||
|
def addGreen(self, gpio):
|
||||||
|
led = self.setupLED(gpio)
|
||||||
|
led.ChangeDutyCycle(self.currentColor.getDutyCycles().g)
|
||||||
|
self.gpiosGreen.append(led)
|
||||||
|
|
||||||
|
def addBlue(self, gpio):
|
||||||
|
led = self.setupLED(gpio)
|
||||||
|
led.ChangeDutyCycle(self.currentColor.getDutyCycles().b)
|
||||||
|
self.gpiosBlue.append(led)
|
||||||
|
|
||||||
|
def setColor(self, red, green, blue):
|
||||||
|
rgb = RGB(red, green, blue)
|
||||||
|
|
||||||
|
colorsDutyCycle = rgb.getDutyCycles()
|
||||||
|
|
||||||
|
for red in self.gpiosRed:
|
||||||
|
red.ChangeDutyCycle(colorsDutyCycle.r)
|
||||||
|
|
||||||
|
for green in self.gpiosGreen:
|
||||||
|
green.ChangeDutyCycle(colorsDutyCycle.g)
|
||||||
|
|
||||||
|
for blue in self.gpiosBlue:
|
||||||
|
blue.ChangeDutyCycle(colorsDutyCycle.b)
|
||||||
|
|
||||||
|
self.currentColor = RGB(rgb.r, rgb.g, rgb.b)
|
||||||
|
|
||||||
|
def fadeToColor(self, red, green, blue, seconds, fps = 30):
|
||||||
|
timeStart = time()
|
||||||
|
timeEnd = timeStart + seconds
|
||||||
|
frameDuration = 1 / fps
|
||||||
|
|
||||||
|
colorStart = self.currentColor
|
||||||
|
colorTarget = RGB(red, green, blue)
|
||||||
|
|
||||||
|
deltaRed = colorTarget.r - colorStart.r
|
||||||
|
deltaGreen = colorTarget.g - colorStart.g
|
||||||
|
deltaBlue = colorTarget.b - colorStart.b
|
||||||
|
|
||||||
|
while time() < timeEnd:
|
||||||
|
frameStart = time()
|
||||||
|
|
||||||
|
progress = (time() - timeStart) / seconds
|
||||||
|
|
||||||
|
self.setColor(
|
||||||
|
colorStart.r + deltaRed * progress,
|
||||||
|
colorStart.g + deltaGreen * progress,
|
||||||
|
colorStart.b + deltaBlue * progress
|
||||||
|
)
|
||||||
|
|
||||||
|
duration = time() - frameStart
|
||||||
|
|
||||||
|
if duration < frameDuration:
|
||||||
|
sleep(frameDuration - duration)
|
||||||
|
|
||||||
|
self.setColor(red, green, blue)
|
||||||
|
|
||||||
|
def candle(self, red, green, blue, wind = 0.5):
|
||||||
|
darkenMax = 100 - wind * 100
|
||||||
|
color = RGB(red, green, blue)
|
||||||
|
|
||||||
|
intensity = randint(darkenMax, 100) / 100
|
||||||
|
seconds = randint(darkenMax, 50) / 100
|
||||||
|
|
||||||
|
targetColor = RGB(color.r * intensity, color.g * intensity, color.b * intensity)
|
||||||
|
self.fadeToColor(targetColor.r, targetColor.g, targetColor.b, seconds)
|
||||||
|
|
||||||
|
# Chance of 5% for flickering
|
||||||
|
flickering = randint(0, 100) < 5
|
||||||
|
|
||||||
|
if flickering:
|
||||||
|
intensityLight = 1
|
||||||
|
intensityDark = 0.9
|
||||||
|
times = randint(4, 10)
|
||||||
|
duration = randint(5, 30) / 100
|
||||||
|
|
||||||
|
colorLight = RGB(color.r * intensityLight, color.g * intensityLight, color.b * intensityLight)
|
||||||
|
colorDark = RGB(color.r * intensityDark, color.g * intensityDark, color.b * intensityDark)
|
||||||
|
|
||||||
|
intensity = 1
|
||||||
|
|
||||||
|
for t in range(times):
|
||||||
|
progress = (1 / times) * t
|
||||||
|
intensity = abs(0.5 - progress) * 2
|
||||||
|
colorDark = RGB(
|
||||||
|
color.r * intensityDark * intensity,
|
||||||
|
color.g * intensityDark * intensity,
|
||||||
|
color.b * intensityDark * intensity
|
||||||
|
)
|
||||||
|
self.fadeToColor(colorLight.r, colorLight.g, colorLight.b, duration)
|
||||||
|
self.fadeToColor(colorDark.r, colorDark.g, colorDark.b, duration)
|
||||||
|
|
||||||
|
def toggleSpectrum(self, seconds):
|
||||||
|
spectrum = [
|
||||||
|
RGB(255, 0, 0),
|
||||||
|
RGB(255, 255, 0),
|
||||||
|
RGB(0, 255, 0),
|
||||||
|
RGB(0, 255, 255),
|
||||||
|
RGB(0, 0, 255),
|
||||||
|
RGB(255, 0, 255)
|
||||||
|
]
|
||||||
|
|
||||||
|
fadeDuration = seconds / len(spectrum)
|
||||||
|
|
||||||
|
for color in spectrum:
|
||||||
|
self.fadeToColor(color.r, color.g, color.b, fadeDuration)
|
||||||
|
|
||||||
|
def halloween(control):
|
||||||
|
|
||||||
|
while True:
|
||||||
|
flash = randint(1, 100)
|
||||||
|
if flash >= 1 and flash <= 5:
|
||||||
|
t = randint(1, 3)
|
||||||
|
control.panic(times = t, color = (200, 255, 255))
|
||||||
|
|
||||||
|
control.fire(wind = 0.8)
|
||||||
|
|
||||||
|
|
||||||
|
def spectrum(control):
|
||||||
|
while True:
|
||||||
|
control.toggleSpectrum()
|
||||||
|
|
||||||
|
|
||||||
|
def fire(control, wind = 0.5):
|
||||||
|
while True:
|
||||||
|
control.fire(wind = wind)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
|
||||||
|
control = RGB_Control(11, 12, 13)
|
||||||
|
|
||||||
|
fire(control, wind = 0.9)
|
||||||
|
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
except:
|
||||||
|
del control
|
|
@ -0,0 +1,107 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import wave
|
||||||
|
|
||||||
|
|
||||||
|
class WaveLoader:
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.soundfile = wave.open(filename, 'rb')
|
||||||
|
|
||||||
|
self.channels = self.soundfile.getnchannels()
|
||||||
|
self.sampleWidth = self.soundfile.getsampwidth()
|
||||||
|
self.frameCount = self.soundfile.getnframes()
|
||||||
|
self.frameRate = self.soundfile.getframerate()
|
||||||
|
self.filename = filename
|
||||||
|
self.amplitudeMax = 2 ** (self.sampleWidth * 8)
|
||||||
|
self.levelData = {}
|
||||||
|
self.amplitudeData = {}
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.soundfile.close()
|
||||||
|
|
||||||
|
def getCurrentLevelRealtime(self, seconds):
|
||||||
|
frameIndex = int(round(seconds * self.frameRate))
|
||||||
|
|
||||||
|
if frameIndex >= self.frameCount:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
self.soundfile.setpos(frameIndex)
|
||||||
|
|
||||||
|
level = int.from_bytes(self.soundfile.readframes(1)[0:self.sampleWidth], 'little')
|
||||||
|
|
||||||
|
if level > self.amplitudeMax * 0.5:
|
||||||
|
level -= self.amplitudeMax
|
||||||
|
|
||||||
|
return abs(level) / (self.amplitudeMax * 0.5)
|
||||||
|
|
||||||
|
def getLevel(self, channel = 0):
|
||||||
|
if channel < self.channels:
|
||||||
|
return self.levelData[channel].copy()
|
||||||
|
|
||||||
|
def getAmplitude(self, channel = 0):
|
||||||
|
if channel < self.channels:
|
||||||
|
return self.amplitudeData[channel].copy()
|
||||||
|
|
||||||
|
def getLevelSum(self):
|
||||||
|
if self.channels == 1:
|
||||||
|
return self.levelData[0].copy()
|
||||||
|
|
||||||
|
levelSum = []
|
||||||
|
|
||||||
|
for f in range(self.frameCount):
|
||||||
|
amplitudeSum = 0
|
||||||
|
|
||||||
|
for c in range(self.channels):
|
||||||
|
amplitudeSum += self.levelData[c][f]
|
||||||
|
|
||||||
|
levelSum.append(amplitudeSum / self.channels)
|
||||||
|
|
||||||
|
return levelSum
|
||||||
|
|
||||||
|
def getCurrentLevel(self, seconds):
|
||||||
|
frameIndex = int(round(seconds * self.frameRate))
|
||||||
|
|
||||||
|
if frameIndex >= self.frameCount:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
level = self.levelData[0][frameIndex]
|
||||||
|
return level
|
||||||
|
|
||||||
|
level = 0
|
||||||
|
|
||||||
|
for c in range(self.channels):
|
||||||
|
level += self.levelData[c][frameIndex]
|
||||||
|
|
||||||
|
return level / self.channels
|
||||||
|
|
||||||
|
def loadData(self):
|
||||||
|
self.levelReset()
|
||||||
|
self.amplitudeReset()
|
||||||
|
soundfile = wave.open(self.filename, 'rb')
|
||||||
|
|
||||||
|
for f in range(self.frameCount):
|
||||||
|
frame = soundfile.readframes(1)
|
||||||
|
|
||||||
|
for c in range(self.channels):
|
||||||
|
byteOffset = c * self.sampleWidth
|
||||||
|
amplitude = int.from_bytes(frame[byteOffset:byteOffset + self.sampleWidth], 'little')
|
||||||
|
|
||||||
|
if amplitude > self.amplitudeMax * 0.5:
|
||||||
|
amplitude -= self.amplitudeMax
|
||||||
|
|
||||||
|
self.amplitudeData[c].append(amplitude / (self.amplitudeMax * 0.5))
|
||||||
|
self.levelData[c].append(abs(amplitude) / (self.amplitudeMax * 0.5))
|
||||||
|
|
||||||
|
soundfile.close()
|
||||||
|
|
||||||
|
def levelReset(self):
|
||||||
|
self.levelData = {}
|
||||||
|
|
||||||
|
for c in range(self.channels):
|
||||||
|
self.levelData[c] = []
|
||||||
|
|
||||||
|
def amplitudeReset(self):
|
||||||
|
self.amplitudeData = {}
|
||||||
|
|
||||||
|
for c in range(self.channels):
|
||||||
|
self.amplitudeData[c] = []
|
Loading…
Reference in New Issue