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