import subprocess import sys, inspect, os import wiringpi import time from datetime import datetime from time import sleep from evdev import InputDevice, categorize, ecodes import random import mpd import requests from xml.etree import ElementTree as ET import evdev from pyudmx import pyudmx import talkey # HTTP Server from twisted.web import server, resource from twisted.internet import reactor class Simple(resource.Resource): isLeaf = True def render_GET(self, request): statustext = "" if inUse: statustext = "true" else: statustext = "false" html = '[{"inuse":"%s"}]' % statustext return html.encode('utf-8') # Preparing IR Device def get_ir_device(): devices = [evdev.InputDevice(path) for path in evdev.list_devices()] for device in devices: if (device.name == "gpio_ir_recv"): print("Using device", device.path, "\n") return device print("No device found!") ir_dev = get_ir_device() remote_keys = { 0x100028: 'mode_disco', # phono start 0x100029: 'mode_work', # phono stop 0x10000f: 'mode_power', # tuner band 0x100000: 'mode_music_play_1', # tuner 1 0x100001: 'mode_music_play_2', # tuner 2 0x100002: 'mode_music_play_3', # tuner 3 0x100003: 'mode_music_play_4', # tuner 4 0x100004: 'mode_music_play_5', # tuner 5 0x100033: 'mode_music_rewind', # tape fast rewind 0x100037: 'mode_music_back', # tape slow rewind 0x100032: 'mode_music_forward', # tape slow forward 0x100034: 'mode_music_fastforward', # tape fast forward 0x100038: 'mode_music_stop', # tape stop 0x100039: 'mode_music_pause', # tape pause 0x10003f: 'mode_music_info', # tape stoprec 0x110032: 'mode_music_play', # cd play 0x110030: 'mode_music_previous', # cd previous 0x110031: 'mode_music_next', # cd next 0x110038: 'mode_music_stop', # cd stop 0x100025: 'mode_news', # source cd 0x10001e: 'mode_play_fm4', # source video 2 0x100022: 'mode_play_oe1', # source video 1 0x100012: 'mode_volume_up', # vol up 0x100013: 'mode_volume_down', # vol down 0x100014: 'mode_music_mute', # vol mute } 0x100000 # GPIO pin_kugel = 2 # Input: dico ball + DMX on/off pin_sun = 4 # Input: light bulb on/off pin_pir = 0 # Output: PIR sensor pin_door = 11 # Output: door open/closed sensor # for system stuff dmxScenes = { "fadecolors":[255,255,255,255,255,192], "plain-red":[255,255,0,0,0,0], "strobe":[190,255,255,255,0,0], "nini":[120,0,255,255,0,224], "black":[0,0,0,0,0,0] } bye_sayings = [ "Goodbye!", "Bye!", "Bye bye!", "See you!", #"Ciao!", "Have a nice day!", "Thank's for using!", #"I'm off!", "Take it easy!", "I look forward to our next meeting!", "Take care!", "See you later!", "This was nice. See you!", "Peace!" ] dmxUserScenes = [ [255,255,255,255,255,192], [255,0,180,180,0,0], [255,255,0,0,0,0], [255,0,255,0,0,0], [255,0,0,255,0,0], [190,255,255,255,0,0], [120,0,255,255,0,224] ] # no strobe etc. dmxStartupScenes = [ [255,255,255,255,255,192], [255,0,180,180,0,0], [255,255,0,0,0,0], [255,0,255,0,0,0], [255,0,0,255,0,0], [120,0,255,255,0,224] ] def interpretRemoteInputs(): events = ir_dev.read() try: ir_event_list = [event.value for event in events] print("Received commands:", ir_event_list) if ir_event_list and len(ir_event_list) > 0: remote_key = ir_event_list[0] print(remote_key) code = None try: code = remote_keys[remote_key] except KeyError: print('received invalid signal: ' + str(remote_key)) lastUsed = time.time() # user is active! hadUserInteraction = True inUse = True print('received code:', str(code)) if(code == "mode_disco"): setDiscoMode() if(code == "mode_work"): setWorkingMode() if(code == "mode_power"): setOnOff() if(code == "mode_music_play_1"): startMusic('1') if(code == "mode_music_play_2"): startMusic('2') if(code == "mode_music_play_3"): startMusic('3') if(code == "mode_music_play_4"): startMusic('4') if(code == "mode_music_play_5"): startMusic('5') if(code == "mode_play_fm4"): startMusic('https://orf-live.ors-shoutcast.at/fm4-q2a') if(code == "mode_play_oe1"): startMusic('https://orf-live.ors-shoutcast.at/oe1-q2a') if(code == "mode_music_previous"): previousSong() if(code == "mode_music_next"): nextSong() if(code == "mode_music_play"): playMusic() if(code == "mode_music_stop"): stopMusic() if(code == "mode_music_mute"): muteMusic() if(code == "mode_music_pause"): pauseMusic() if(code == "mode_music_rewind"): seek('-30') if(code == "mode_music_back"): seek('-5') if(code == "mode_music_forward"): seek('+5') if(code == "mode_music_fastforward"): seek('+30') if(code == "mode_volume_up"): changeVolume(5) if(code == "mode_volume_down"): changeVolume(-5) if(code == "mode_music_info"): getTrackInfo() if(code == "mode_record"): say("I'm sorry, I'm afraid I can't do that!") if(code == "mode_news"): oejournalUrl = getNewestPodcastUrl('https://files.orf.at/podcast/oe1/oe1_journale.xml') startMusic(oejournalUrl) except BlockingIOError: # ir_event_list = None print("No command received.\n") # Set a dmx scene by name def setDmxScene(scene): # a universe of zeros cv = [0 for v in range(0, 512)] errorcode = [240,255,0,0,0,0] for index, val in enumerate(dmxScenes.get(scene,errorcode)): cv[index] = val dev.send_multi_value(1, cv) # Set random startup dmx scene def setStartupDmxScene(): # a universe of zeros cv = [0 for v in range(0, 512)] errorcode = [240,255,0,0,0,0] # get a random scene index scene = random.choice(list(enumerate(dmxStartupScenes)))[0] print(scene) for index, val in enumerate(dmxStartupScenes[scene]): cv[index] = val dev.send_multi_value(1, cv) # Switch betweeb user dmx scenes def setUserDmxScene(): # loop through scenes global dmxScene if dmxScene < len(dmxUserScenes)-1: dmxScene += 1 else: dmxScene = 0 # setup the universe cv = [0 for v in range(0, 512)] for index, val in enumerate(dmxUserScenes[dmxScene]): cv[index] = val dev.send_multi_value(1, cv) def setKugel(state): if state == 'on': wiringpi.digitalWrite(pin_kugel, 0) if state == 'off': wiringpi.digitalWrite(pin_kugel, 1) def setSun(state): if state == 'off': wiringpi.digitalWrite(pin_sun, 0) if state == 'on': wiringpi.digitalWrite(pin_sun, 1) def startMusic(playlist, single=False, shuffle=True, repeat=True): try: client.clear() # clear playlist except Exception: client.connect("localhost", 6600) client.clear() # clear playlist client.add(playlist) # add file/directory to playlist if shuffle: client.shuffle() # shuffle playlist if repeat: client.repeat(1) # set playback mode repeat else: client.repeat(0) # set playback mode repeat if single: client.repeat(0) # set playback mode repeat client.single(1) # set playback mode single else: client.single(0) # set playback mode single client.setvol(80)# set volume client.play() # play def getNewestPodcastUrl(xml): podcast = xml podcast_string = requests.get(podcast).text tree = ET.fromstring(podcast_string) return tree.find('.//enclosure').get('url') def playMusic(): try: client.play() except Exception: client.connect("localhost", 6600) client.play() def pauseMusic(): try: client.pause() except Exception: client.connect("localhost", 6600) client.pause() def stopMusic(): try: client.stop() except Exception: client.connect("localhost", 6600) client.stop() def nextSong(): try: client.next() except Exception: client.connect("localhost", 6600) client.next() def previousSong(): try: client.previous() except Exception: client.connect("localhost", 6600) client.previous() def muteMusic(): global uservolume if getMpdVolume() != 0: # if not muted setMpdVolume(0) else: setMpdVolume(uservolume) def changeVolume(change=5): global uservolume global volume newvol = uservolume + change if newvol > 100: newvol = 100 if newvol < 0: newvol = 0 try: client.setvol(newvol) except Exception: client.connect("localhost", 6600) client.setvol(newvol) uservolume = newvol volume = newvol def setMode(string): global mode global inUse mode = string if mode == "off": inUse = False def setDiscoMode(startup=False): setKugel('on') if startup: setStartupDmxScene() else: setUserDmxScene() sleep(0.3) setSun('off') setMode('disco') def getMpdVolume(): try: vol = int(client.status()['volume']) except Exception: client.connect("localhost", 6600) vol = int(client.status()['volume']) if vol != 0: #only if not muted global uservolume uservolume = vol return vol def setMpdVolume(vol): try: client.setvol(vol) except Exception: client.connect("localhost", 6600) client.setvol(vol) if vol != 0: #only if not muted global uservolume uservolume = vol return True def seek(secs): try: client.seekcur(secs) except Exception: client.connect("localhost", 6600) client.seekcur(secs) return True def getTrackInfo(): try: currentsong = client.currentsong() except Exception: client.connect("localhost", 6600) currentsong = client.currentsong() print(currentsong) volume = getMpdVolume() setMpdVolume(10) try: tts.say(currentsong['artist'] + ', ' + currentsong['title']) except Exception: tts.say('Willkommen am Discoklo!', 'de') setMpdVolume(volume) def setWorkingMode(): setSun('on') sleep(0.3) setKugel('off') setDmxScene('black') setMode('work') def startTimeoutCountdown(): global lastUsed global inUse tts.say('Timeout in') countdown = [5,4,3,2,1] #10,9,8,7,6, for sec in countdown: tts.say(str(sec)) sleep(0.5) remotesignal = lirc.nextcode() if wiringpi.digitalRead(pin_pir) == 1 or remotesignal: lastUsed = time.time() tts.say('Timeout cancelled!') inUse = True toilet = lirc.nextcode() break if not inUse: tts.say('Shutting down now.', 'en') closeService() def inactiveShutdown(): closeService() def closeService(sleepsecs=0): setSun('off') sleep(0.3) setKugel('off') setDmxScene('black') for x in range(0, 20): changeVolume(-5) sleep(0.1) stopMusic() inUseBefore = False # Pfusch pfusch! hadUserInteraction = False # reset variable setMode('off') sleep(sleepsecs) # function when user arrives def initService(): startMusic('0', True) # start intro music setDiscoMode(True) global volume global defaultvolume global uservolume try: client.setvol(defaultvolume) except Exception: client.connect("0.0.0.0", 6600) client.setvol(defaultvolume) volume = defaultvolume uservolume = defaultvolume global starttime starttime = time.time() def say(text, lang="en"): originalvol = getMpdVolume() setMpdVolume(10) tts.say(text, lang) setMpdVolume(originalvol) def setOnOff(): global mode stopMusic() if mode != 'work': setWorkingMode() else: initService() def doorShutdown(): #tts.say(random.choice(bye_sayings), "en") # sleep to give user some time to close the door # (pir sensor also stays up for 2 sec) closeService(5) def bootstrap(): wiringpi.wiringPiSetup() wiringpi.pinMode(pin_kugel, 1) # set Relay Disokugel mode to OUTPUT wiringpi.pinMode(pin_door, 0) # set Circuit Door mode to INPUT wiringpi.pinMode(pin_sun, 1) # set Relay Sun mode to OUTPUT # TODO: Set pin! wiringpi.pinMode(pin_pir, 0) # set PIR Sensor mode to INPUT def timestamp(stamp=time.time()): return datetime.fromtimestamp(stamp).strftime('%Y-%m-%d %H:%M:%S') dmxScene = 0 bootstrap() dev = pyudmx.uDMXDevice() dev.open() print('dmx device: ' + str(dev)) client = mpd.MPDClient() client.connect("localhost", 6600) tts = talkey.Talkey( preferred_languages=['en'], engine_preference=['pico'], ) site = server.Site(Simple()) reactor.listenTCP(8080, site) reactor.startRunning(False) starttime = time.time() # helper for doorshutdown lastUsed = time.time() # helper for timeout inUse = False # is toilet in use? inUseBefore = False # helper to check statechanges mode = "off" # can be: disco, work, off timeout = 5 * 60 - 5 # timeout since last user interaction defaultvolume = 90 # Volume when user enters the toilet volume = 90 # Global for actual volume uservolume = 90 # Global for user volume (ignores mute state) hadUserInteraction = False # setSun('off') print(timestamp(), "Ready!") #lirc.init("disco", "~/discobert/lircrc", blocking=False) # Main event loop ... while True: print("start new loop round") sleep(0.3) pirstate = wiringpi.digitalRead(pin_pir) doorstate = wiringpi.digitalRead(pin_door) #print('pirstate: ', pirstate) #print('doorstate: ', doorstate) # 0 => door closed # 1 => door open if doorstate == 1: # don't do a doorShutdown when user comes in # or when door stays open after user left if (time.time() > (starttime + 15) and inUse == True): doorShutdown() if pirstate == 1: lastUsed = time.time() inUse = True else: # Auto timeout if(time.time() > lastUsed + timeout and not hadUserInteraction): inUse = False interpretRemoteInputs() if(inUseBefore != inUse): print(timestamp(), "State change inUse:", inUseBefore, inUse) if inUse: initService() else: inactiveShutdown() #startTimeoutCountdown() inUseBefore = inUse # Webserver reactor.iterate() #lirc.deinit() # Clean up lirc # dev.close()