You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

608 lines
15 KiB

  1. import subprocess
  2. import sys, inspect, os
  3. import wiringpi
  4. import time
  5. from datetime import datetime
  6. from time import sleep
  7. from evdev import InputDevice, categorize, ecodes
  8. import random
  9. import mpd
  10. import requests
  11. from xml.etree import ElementTree as ET
  12. import evdev
  13. from pyudmx import pyudmx
  14. import talkey
  15. # HTTP Server
  16. from twisted.web import server, resource
  17. from twisted.internet import reactor
  18. class Simple(resource.Resource):
  19. isLeaf = True
  20. def render_GET(self, request):
  21. statustext = ""
  22. if inUse:
  23. statustext = "true"
  24. else:
  25. statustext = "false"
  26. html = '[{"inuse":"%s"}]' % statustext
  27. return html.encode('utf-8')
  28. # Preparing IR Device
  29. def get_ir_device():
  30. devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
  31. for device in devices:
  32. if (device.name == "gpio_ir_recv"):
  33. print("Using device", device.path, "\n")
  34. return device
  35. print("No device found!")
  36. ir_dev = get_ir_device()
  37. remote_keys = {
  38. 0x100028: 'mode_disco', # phono start
  39. 0x100029: 'mode_work', # phono stop
  40. 0x10000f: 'mode_power', # tuner band
  41. 0x100000: 'mode_music_play_1', # tuner 1
  42. 0x100001: 'mode_music_play_2', # tuner 2
  43. 0x100002: 'mode_music_play_3', # tuner 3
  44. 0x100003: 'mode_music_play_4', # tuner 4
  45. 0x100004: 'mode_music_play_5', # tuner 5
  46. 0x100033: 'mode_music_rewind', # tape fast rewind
  47. 0x100037: 'mode_music_back', # tape slow rewind
  48. 0x100032: 'mode_music_forward', # tape slow forward
  49. 0x100034: 'mode_music_fastforward', # tape fast forward
  50. 0x100038: 'mode_music_stop', # tape stop
  51. 0x100039: 'mode_music_pause', # tape pause
  52. 0x10003f: 'mode_music_info', # tape stoprec
  53. 0x110032: 'mode_music_play', # cd play
  54. 0x110030: 'mode_music_previous', # cd previous
  55. 0x110031: 'mode_music_next', # cd next
  56. 0x110038: 'mode_music_stop', # cd stop
  57. 0x100025: 'mode_news', # source cd
  58. 0x10001e: 'mode_play_fm4', # source video 2
  59. 0x100022: 'mode_play_oe1', # source video 1
  60. 0x100012: 'mode_volume_up', # vol up
  61. 0x100013: 'mode_volume_down', # vol down
  62. 0x100014: 'mode_music_mute', # vol mute
  63. }
  64. 0x100000
  65. # GPIO
  66. pin_kugel = 2 # Input: dico ball + DMX on/off
  67. pin_sun = 4 # Input: light bulb on/off
  68. pin_pir = 0 # Output: PIR sensor
  69. pin_door = 11 # Output: door open/closed sensor
  70. # for system stuff
  71. dmxScenes = {
  72. "fadecolors":[255,255,255,255,255,192],
  73. "plain-red":[255,255,0,0,0,0],
  74. "strobe":[190,255,255,255,0,0],
  75. "nini":[120,0,255,255,0,224],
  76. "black":[0,0,0,0,0,0]
  77. }
  78. bye_sayings = [
  79. "Goodbye!",
  80. "Bye!",
  81. "Bye bye!",
  82. "See you!",
  83. #"Ciao!",
  84. "Have a nice day!",
  85. "Thank's for using!",
  86. #"I'm off!",
  87. "Take it easy!",
  88. "I look forward to our next meeting!",
  89. "Take care!",
  90. "See you later!",
  91. "This was nice. See you!",
  92. "Peace!"
  93. ]
  94. dmxUserScenes = [
  95. [255,255,255,255,255,192],
  96. [255,0,180,180,0,0],
  97. [255,255,0,0,0,0],
  98. [255,0,255,0,0,0],
  99. [255,0,0,255,0,0],
  100. [190,255,255,255,0,0],
  101. [120,0,255,255,0,224]
  102. ]
  103. # no strobe etc.
  104. dmxStartupScenes = [
  105. [255,255,255,255,255,192],
  106. [255,0,180,180,0,0],
  107. [255,255,0,0,0,0],
  108. [255,0,255,0,0,0],
  109. [255,0,0,255,0,0],
  110. [120,0,255,255,0,224]
  111. ]
  112. def interpretRemoteInputs():
  113. events = ir_dev.read()
  114. try:
  115. ir_event_list = [event.value for event in events]
  116. print("Received commands:", ir_event_list)
  117. if ir_event_list and len(ir_event_list) > 0:
  118. remote_key = ir_event_list[0]
  119. print(remote_key)
  120. code = None
  121. try:
  122. code = remote_keys[remote_key]
  123. except KeyError:
  124. print('received invalid signal: ' + str(remote_key))
  125. lastUsed = time.time() # user is active!
  126. hadUserInteraction = True
  127. inUse = True
  128. print('received code:', str(code))
  129. if(code == "mode_disco"):
  130. setDiscoMode()
  131. if(code == "mode_work"):
  132. setWorkingMode()
  133. if(code == "mode_power"):
  134. setOnOff()
  135. if(code == "mode_music_play_1"):
  136. startMusic('1')
  137. if(code == "mode_music_play_2"):
  138. startMusic('2')
  139. if(code == "mode_music_play_3"):
  140. startMusic('3')
  141. if(code == "mode_music_play_4"):
  142. startMusic('4')
  143. if(code == "mode_music_play_5"):
  144. startMusic('5')
  145. if(code == "mode_play_fm4"):
  146. startMusic('https://orf-live.ors-shoutcast.at/fm4-q2a')
  147. if(code == "mode_play_oe1"):
  148. startMusic('https://orf-live.ors-shoutcast.at/oe1-q2a')
  149. if(code == "mode_music_previous"):
  150. previousSong()
  151. if(code == "mode_music_next"):
  152. nextSong()
  153. if(code == "mode_music_play"):
  154. playMusic()
  155. if(code == "mode_music_stop"):
  156. stopMusic()
  157. if(code == "mode_music_mute"):
  158. muteMusic()
  159. if(code == "mode_music_pause"):
  160. pauseMusic()
  161. if(code == "mode_music_rewind"):
  162. seek('-30')
  163. if(code == "mode_music_back"):
  164. seek('-5')
  165. if(code == "mode_music_forward"):
  166. seek('+5')
  167. if(code == "mode_music_fastforward"):
  168. seek('+30')
  169. if(code == "mode_volume_up"):
  170. changeVolume(5)
  171. if(code == "mode_volume_down"):
  172. changeVolume(-5)
  173. if(code == "mode_music_info"):
  174. getTrackInfo()
  175. if(code == "mode_record"):
  176. say("I'm sorry, I'm afraid I can't do that!")
  177. if(code == "mode_news"):
  178. oejournalUrl = getNewestPodcastUrl('https://files.orf.at/podcast/oe1/oe1_journale.xml')
  179. startMusic(oejournalUrl)
  180. except BlockingIOError:
  181. # ir_event_list = None
  182. print("No command received.\n")
  183. # Set a dmx scene by name
  184. def setDmxScene(scene):
  185. # a universe of zeros
  186. cv = [0 for v in range(0, 512)]
  187. errorcode = [240,255,0,0,0,0]
  188. for index, val in enumerate(dmxScenes.get(scene,errorcode)):
  189. cv[index] = val
  190. dev.send_multi_value(1, cv)
  191. # Set random startup dmx scene
  192. def setStartupDmxScene():
  193. # a universe of zeros
  194. cv = [0 for v in range(0, 512)]
  195. errorcode = [240,255,0,0,0,0]
  196. # get a random scene index
  197. scene = random.choice(list(enumerate(dmxStartupScenes)))[0]
  198. print(scene)
  199. for index, val in enumerate(dmxStartupScenes[scene]):
  200. cv[index] = val
  201. dev.send_multi_value(1, cv)
  202. # Switch betweeb user dmx scenes
  203. def setUserDmxScene():
  204. # loop through scenes
  205. global dmxScene
  206. if dmxScene < len(dmxUserScenes)-1:
  207. dmxScene += 1
  208. else:
  209. dmxScene = 0
  210. # setup the universe
  211. cv = [0 for v in range(0, 512)]
  212. for index, val in enumerate(dmxUserScenes[dmxScene]):
  213. cv[index] = val
  214. dev.send_multi_value(1, cv)
  215. def setKugel(state):
  216. if state == 'on':
  217. wiringpi.digitalWrite(pin_kugel, 0)
  218. if state == 'off':
  219. wiringpi.digitalWrite(pin_kugel, 1)
  220. def setSun(state):
  221. if state == 'off':
  222. wiringpi.digitalWrite(pin_sun, 0)
  223. if state == 'on':
  224. wiringpi.digitalWrite(pin_sun, 1)
  225. def startMusic(playlist, single=False, shuffle=True, repeat=True):
  226. try:
  227. client.clear() # clear playlist
  228. except Exception:
  229. client.connect("localhost", 6600)
  230. client.clear() # clear playlist
  231. client.add(playlist) # add file/directory to playlist
  232. if shuffle:
  233. client.shuffle() # shuffle playlist
  234. if repeat:
  235. client.repeat(1) # set playback mode repeat
  236. else:
  237. client.repeat(0) # set playback mode repeat
  238. if single:
  239. client.repeat(0) # set playback mode repeat
  240. client.single(1) # set playback mode single
  241. else:
  242. client.single(0) # set playback mode single
  243. client.setvol(80)# set volume
  244. client.play() # play
  245. def getNewestPodcastUrl(xml):
  246. podcast = xml
  247. podcast_string = requests.get(podcast).text
  248. tree = ET.fromstring(podcast_string)
  249. return tree.find('.//enclosure').get('url')
  250. def playMusic():
  251. try:
  252. client.play()
  253. except Exception:
  254. client.connect("localhost", 6600)
  255. client.play()
  256. def pauseMusic():
  257. try:
  258. client.pause()
  259. except Exception:
  260. client.connect("localhost", 6600)
  261. client.pause()
  262. def stopMusic():
  263. try:
  264. client.stop()
  265. except Exception:
  266. client.connect("localhost", 6600)
  267. client.stop()
  268. def nextSong():
  269. try:
  270. client.next()
  271. except Exception:
  272. client.connect("localhost", 6600)
  273. client.next()
  274. def previousSong():
  275. try:
  276. client.previous()
  277. except Exception:
  278. client.connect("localhost", 6600)
  279. client.previous()
  280. def muteMusic():
  281. global uservolume
  282. if getMpdVolume() != 0: # if not muted
  283. setMpdVolume(0)
  284. else:
  285. setMpdVolume(uservolume)
  286. def changeVolume(change=5):
  287. global uservolume
  288. global volume
  289. newvol = uservolume + change
  290. if newvol > 100:
  291. newvol = 100
  292. if newvol < 0:
  293. newvol = 0
  294. try:
  295. client.setvol(newvol)
  296. except Exception:
  297. client.connect("localhost", 6600)
  298. client.setvol(newvol)
  299. uservolume = newvol
  300. volume = newvol
  301. def setMode(string):
  302. global mode
  303. global inUse
  304. mode = string
  305. if mode == "off":
  306. inUse = False
  307. def setDiscoMode(startup=False):
  308. setKugel('on')
  309. if startup:
  310. setStartupDmxScene()
  311. else:
  312. setUserDmxScene()
  313. sleep(0.3)
  314. setSun('off')
  315. setMode('disco')
  316. def getMpdVolume():
  317. try:
  318. vol = int(client.status()['volume'])
  319. except Exception:
  320. client.connect("localhost", 6600)
  321. vol = int(client.status()['volume'])
  322. if vol != 0: #only if not muted
  323. global uservolume
  324. uservolume = vol
  325. return vol
  326. def setMpdVolume(vol):
  327. try:
  328. client.setvol(vol)
  329. except Exception:
  330. client.connect("localhost", 6600)
  331. client.setvol(vol)
  332. if vol != 0: #only if not muted
  333. global uservolume
  334. uservolume = vol
  335. return True
  336. def seek(secs):
  337. try:
  338. client.seekcur(secs)
  339. except Exception:
  340. client.connect("localhost", 6600)
  341. client.seekcur(secs)
  342. return True
  343. def getTrackInfo():
  344. try:
  345. currentsong = client.currentsong()
  346. except Exception:
  347. client.connect("localhost", 6600)
  348. currentsong = client.currentsong()
  349. print(currentsong)
  350. volume = getMpdVolume()
  351. setMpdVolume(10)
  352. try:
  353. tts.say(currentsong['artist'] + ', ' + currentsong['title'])
  354. except Exception:
  355. tts.say('Willkommen am Discoklo!', 'de')
  356. setMpdVolume(volume)
  357. def setWorkingMode():
  358. setSun('on')
  359. sleep(0.3)
  360. setKugel('off')
  361. setDmxScene('black')
  362. setMode('work')
  363. def startTimeoutCountdown():
  364. global lastUsed
  365. global inUse
  366. tts.say('Timeout in')
  367. countdown = [5,4,3,2,1] #10,9,8,7,6,
  368. for sec in countdown:
  369. tts.say(str(sec))
  370. sleep(0.5)
  371. remotesignal = lirc.nextcode()
  372. if wiringpi.digitalRead(pin_pir) == 1 or remotesignal:
  373. lastUsed = time.time()
  374. tts.say('Timeout cancelled!')
  375. inUse = True
  376. toilet = lirc.nextcode()
  377. break
  378. if not inUse:
  379. tts.say('Shutting down now.', 'en')
  380. closeService()
  381. def inactiveShutdown():
  382. closeService()
  383. def closeService(sleepsecs=0):
  384. setSun('off')
  385. sleep(0.3)
  386. setKugel('off')
  387. setDmxScene('black')
  388. for x in range(0, 20):
  389. changeVolume(-5)
  390. sleep(0.1)
  391. stopMusic()
  392. inUseBefore = False # Pfusch pfusch!
  393. hadUserInteraction = False # reset variable
  394. setMode('off')
  395. sleep(sleepsecs)
  396. # function when user arrives
  397. def initService():
  398. startMusic('0', True) # start intro music
  399. setDiscoMode(True)
  400. global volume
  401. global defaultvolume
  402. global uservolume
  403. try:
  404. client.setvol(defaultvolume)
  405. except Exception:
  406. client.connect("0.0.0.0", 6600)
  407. client.setvol(defaultvolume)
  408. volume = defaultvolume
  409. uservolume = defaultvolume
  410. global starttime
  411. starttime = time.time()
  412. def say(text, lang="en"):
  413. originalvol = getMpdVolume()
  414. setMpdVolume(10)
  415. tts.say(text, lang)
  416. setMpdVolume(originalvol)
  417. def setOnOff():
  418. global mode
  419. stopMusic()
  420. if mode != 'work':
  421. setWorkingMode()
  422. else:
  423. initService()
  424. def doorShutdown():
  425. #tts.say(random.choice(bye_sayings), "en")
  426. # sleep to give user some time to close the door
  427. # (pir sensor also stays up for 2 sec)
  428. closeService(5)
  429. def bootstrap():
  430. wiringpi.wiringPiSetup()
  431. wiringpi.pinMode(pin_kugel, 1) # set Relay Disokugel mode to OUTPUT
  432. wiringpi.pinMode(pin_door, 0) # set Circuit Door mode to INPUT
  433. wiringpi.pinMode(pin_sun, 1) # set Relay Sun mode to OUTPUT # TODO: Set pin!
  434. wiringpi.pinMode(pin_pir, 0) # set PIR Sensor mode to INPUT
  435. def timestamp(stamp=time.time()):
  436. return datetime.fromtimestamp(stamp).strftime('%Y-%m-%d %H:%M:%S')
  437. dmxScene = 0
  438. bootstrap()
  439. dev = pyudmx.uDMXDevice()
  440. dev.open()
  441. print('dmx device: ' + str(dev))
  442. client = mpd.MPDClient()
  443. client.connect("localhost", 6600)
  444. tts = talkey.Talkey(
  445. preferred_languages=['en'],
  446. engine_preference=['pico'],
  447. )
  448. site = server.Site(Simple())
  449. reactor.listenTCP(8080, site)
  450. reactor.startRunning(False)
  451. starttime = time.time() # helper for doorshutdown
  452. lastUsed = time.time() # helper for timeout
  453. inUse = False # is toilet in use?
  454. inUseBefore = False # helper to check statechanges
  455. mode = "off" # can be: disco, work, off
  456. timeout = 5 * 60 - 5 # timeout since last user interaction
  457. defaultvolume = 90 # Volume when user enters the toilet
  458. volume = 90 # Global for actual volume
  459. uservolume = 90 # Global for user volume (ignores mute state)
  460. hadUserInteraction = False #
  461. setSun('off')
  462. print(timestamp(), "Ready!")
  463. #lirc.init("disco", "~/discobert/lircrc", blocking=False)
  464. # Main event loop ...
  465. while True:
  466. print("start new loop round")
  467. sleep(0.3)
  468. pirstate = wiringpi.digitalRead(pin_pir)
  469. doorstate = wiringpi.digitalRead(pin_door)
  470. #print('pirstate: ', pirstate)
  471. #print('doorstate: ', doorstate)
  472. # 0 => door closed
  473. # 1 => door open
  474. if doorstate == 1:
  475. # don't do a doorShutdown when user comes in
  476. # or when door stays open after user left
  477. if (time.time() > (starttime + 15) and inUse == True):
  478. doorShutdown()
  479. if pirstate == 1:
  480. lastUsed = time.time()
  481. inUse = True
  482. else:
  483. # Auto timeout
  484. if(time.time() > lastUsed + timeout and not hadUserInteraction):
  485. inUse = False
  486. interpretRemoteInputs()
  487. if(inUseBefore != inUse):
  488. print(timestamp(), "State change inUse:", inUseBefore, inUse)
  489. if inUse:
  490. initService()
  491. else:
  492. inactiveShutdown()
  493. #startTimeoutCountdown()
  494. inUseBefore = inUse
  495. # Webserver
  496. reactor.iterate()
  497. #lirc.deinit() # Clean up lirc
  498. # dev.close()