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.

discobert.py 13 KiB

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