musikautomat.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!./bin/python3
  2. import os
  3. import sys
  4. import threading
  5. import time
  6. import traceback
  7. from typing import Dict
  8. import pygame # type: ignore
  9. import tornado.ioloop, tornado.web
  10. import asyncio
  11. import RPi.GPIO as GPIO # Support for Raspberry PI GPIO input
  12. from display import Display
  13. from player import Player
  14. class WebHandler(tornado.web.RequestHandler):
  15. def initialize(self, musikautomat):
  16. self.musikautomat = musikautomat
  17. def get(self, a):
  18. if a.startswith("play"):
  19. if a == "play/dir":
  20. path = self.get_argument('path')
  21. self.musikautomat.play({
  22. "type" : "dir",
  23. "name" : self.get_argument('name', path),
  24. "path" : path
  25. }, start=True)
  26. class Musikautomat:
  27. def __init__(self, pydsp, config: Dict):
  28. # Initialise state objects
  29. self._pydsp = pydsp
  30. self._display = Display(config, self._pydsp)
  31. self._display.update()
  32. self._eventSink = self._display
  33. self._player = Player(config, self._pydsp)
  34. self._dx = config.get("dimx", 128)
  35. threading.Thread(target=self.runWebServer, daemon=True).start()
  36. GPIO.setmode(GPIO.BOARD) # Use physical pin numbering
  37. GPIO.setwarnings(False) # Disable warnings
  38. GPIO.setup(3, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
  39. GPIO.setup(5, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
  40. GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  41. GPIO.setup(11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  42. def runWebServer(self):
  43. '''
  44. Run event loop for web interface.
  45. '''
  46. urls = [
  47. (r"/api/(.*)", WebHandler, dict(musikautomat=self)),
  48. (r"/(.*)", tornado.web.StaticFileHandler, {"path" : "./web",
  49. "default_filename" : "index.html" }),
  50. ]
  51. asyncio.set_event_loop(asyncio.new_event_loop())
  52. app = tornado.web.Application(urls, debug=False)
  53. app.listen(8080)
  54. tornado.ioloop.IOLoop.instance().start()
  55. def eventUp(self):
  56. self._eventSink.action("up")
  57. def eventDown(self):
  58. self._eventSink.action("down")
  59. def eventLeft(self):
  60. # Get back to display
  61. self._eventSink = self._display
  62. self._display.update()
  63. def eventRight(self):
  64. if self._eventSink == self._player:
  65. self._player.togglePlay()
  66. else:
  67. self.play(self._display.currentItem())
  68. def runDisplay(self):
  69. '''
  70. Run event loop for display and buttons.
  71. '''
  72. clk = pygame.time.Clock()
  73. gpio_event_timer = 0
  74. while True:
  75. clk.tick(10) # We only need 10 FPS
  76. if gpio_event_timer > 1:
  77. if GPIO.input(3) == 0: # Up
  78. gpio_event_timer=0
  79. self.eventUp()
  80. elif GPIO.input(5) == 0: # LEFT
  81. gpio_event_timer=0
  82. self.eventLeft()
  83. elif GPIO.input(7) == 0: # RIGHT
  84. gpio_event_timer=0
  85. self.eventRight()
  86. elif GPIO.input(11) == 0: # DOWN
  87. gpio_event_timer=0
  88. self.eventDown()
  89. else:
  90. gpio_event_timer+=1
  91. try:
  92. for event in pygame.event.get():
  93. # Handle exit event
  94. if event.type == pygame.QUIT:
  95. pygame.quit()
  96. sys.exit()
  97. # Handle key events
  98. if event.type == pygame.KEYDOWN:
  99. if event.key == pygame.K_UP:
  100. self.eventUp()
  101. elif event.key == pygame.K_LEFT:
  102. self.eventLeft()
  103. elif event.key == pygame.K_RIGHT:
  104. self.eventRight()
  105. elif event.key == pygame.K_DOWN:
  106. self.eventDown()
  107. except Exception as e:
  108. # Display error
  109. traceback.print_exc()
  110. self._pydsp.fill((0,0,0))
  111. font = pygame.font.Font('freesansbold.ttf', 12)
  112. s = str(e)
  113. n = int(self._dx / 6)
  114. for i, start in enumerate(range(0, len(s), n)):
  115. text = font.render(s[start:start+n], True, (255, 0, 0) , (0, 0, 0))
  116. self._pydsp.blit(text, (0, i * 12))
  117. self._eventSink = self._display
  118. pygame.display.update()
  119. def play(self, item, start=False):
  120. '''
  121. Play an item.
  122. '''
  123. print("Player data:", item)
  124. self._eventSink = self._player
  125. t = item.get("type")
  126. if t == "dir":
  127. self.playDir(item)
  128. else:
  129. raise Exception("Unknown type: %s" % t)
  130. if start:
  131. self._player.togglePlay()
  132. def playDir(self, item):
  133. '''
  134. Play all files in a directory.
  135. '''
  136. path = item.get("path")
  137. files = os.listdir(path)
  138. def toDisplayName(s):
  139. s = os.path.splitext(s)[0]
  140. return s.replace("_", " ").title()
  141. self._player.setPlaylist(sorted([{
  142. "name" : toDisplayName(f),
  143. "path" : os.path.join(path, f)
  144. } for f in files], key=lambda i: i["name"]))
  145. self._player.update()