No Description
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.

osci-pi.py 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. #!/usr/bin/env python3
  2. # IP address code taken from:
  3. # https://github.com/rm-hull/luma.examples/blob/master/examples/sys_info_extended.py
  4. #
  5. # ----------------------------------------------------------------------------
  6. # Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
  7. # Copyright (c) 2024 Philipp Schönberger (mail@phschoen.de)
  8. #
  9. # This program is free software: you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation, either version 3 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # See <http://www.gnu.org/licenses/>.
  20. # ----------------------------------------------------------------------------
  21. import sys
  22. import os
  23. import random
  24. import subprocess
  25. import signal
  26. import glob
  27. import socket
  28. from collections import OrderedDict
  29. import time
  30. import pisugar
  31. from luma.core.interface.serial import i2c
  32. from luma.core.render import canvas
  33. from luma.oled.device import ssd1306
  34. from luma.core.error import DeviceNotFoundError
  35. import psutil
  36. import RPi.GPIO as GPIO
  37. from PIL import ImageFont
  38. basevol = "70"
  39. debouncems = 100
  40. #fontfile = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"
  41. fontfile = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
  42. fontsize = 11
  43. LCD_REFRESH = 5.0
  44. BTN_ARTIST = 16
  45. BTN_NEXT = 26
  46. currentplaying = None
  47. lcd = None
  48. font = None
  49. bat = None
  50. songlist = None
  51. currentfile = None
  52. lasttime = None
  53. currvol = basevol
  54. vol_list = [
  55. ("Harley_Matthews", "100"),
  56. ("xythobuz", "50"),
  57. ]
  58. basedir = sys.argv[1]
  59. if basedir.endswith("/"):
  60. basedir = basedir.removesuffix("/")
  61. def get_artist(fn):
  62. global currvol
  63. parts = fn.replace(basedir + "/", "").split(os.sep)
  64. artist = parts[0].replace("_", " ")
  65. currvol = basevol
  66. for i in vol_list:
  67. if i[0] in fn:
  68. currvol = i[1]
  69. break
  70. return artist
  71. originalsongs = []
  72. originalartists = []
  73. for fn in glob.iglob(os.path.join(basedir, '**', '*.wav'), recursive=True):
  74. originalsongs.append(fn)
  75. artist = get_artist(fn)
  76. if artist not in originalartists:
  77. originalartists.append(artist)
  78. artists = originalartists.copy()
  79. random.shuffle(artists)
  80. currentartist = artists[0]
  81. def find_single_ipv4_address(addrs):
  82. for addr in addrs:
  83. if addr.family == socket.AddressFamily.AF_INET: # IPv4
  84. return addr.address
  85. def get_ipv4_address(interface_name=None):
  86. if_addrs = psutil.net_if_addrs()
  87. if isinstance(interface_name, str) and interface_name in if_addrs:
  88. addrs = if_addrs.get(interface_name)
  89. address = find_single_ipv4_address(addrs)
  90. return address if isinstance(address, str) else ""
  91. else:
  92. if_stats = psutil.net_if_stats()
  93. if_stats_filtered = {key: if_stats[key] for key, stat in if_stats.items() if "loopback" not in stat.flags}
  94. if_names_sorted = [stat[0] for stat in sorted(if_stats_filtered.items(), key=lambda x: (x[1].isup, x[1].duplex), reverse=True)]
  95. if_addrs_sorted = OrderedDict((key, if_addrs[key]) for key in if_names_sorted if key in if_addrs)
  96. for _, addrs in if_addrs_sorted.items():
  97. address = find_single_ipv4_address(addrs)
  98. if isinstance(address, str):
  99. return address
  100. return ""
  101. def status(filename):
  102. try:
  103. with canvas(lcd) as draw:
  104. f = filename.replace(".wav", "")
  105. f = f.replace(basedir + "/", "")
  106. f = f.replace("/", "\n")
  107. f = f.replace("_", " ")
  108. f += "\n\n"
  109. f += "Bat: {:.0f}% {:.2f}V {:.2f}A".format(bat.get_battery_level(), bat.get_battery_voltage(), bat.get_battery_current())
  110. ip = get_ipv4_address()
  111. if len(ip) > 0:
  112. f += "\n"
  113. f += "IP: %s" % (ip)
  114. with open("/proc/asound/card0/pcm0p/sub0/hw_params", "r") as rf:
  115. for line in rf:
  116. if line.startswith("rate:"):
  117. rate = int(line.split(" ")[1])
  118. f += "\n"
  119. f += "Rate: {:.0f}kHz".format(rate / 1000)
  120. draw.multiline_text((0, 0), f, font=font, fill="white", spacing=-1)
  121. except Exception as e:
  122. pass
  123. def stop():
  124. global currentplaying
  125. if running():
  126. try:
  127. print("Stopping running player")
  128. os.kill(currentplaying.pid, signal.SIGINT)
  129. if not currentplaying.poll():
  130. currentplaying = None
  131. else:
  132. print("Error stopping player")
  133. except ProcessLookupError as e:
  134. currentplaying = None
  135. else:
  136. currentplaying = None
  137. def play(filename):
  138. global currentplaying
  139. global lcd
  140. global basedir
  141. global bat
  142. global currentfile
  143. stop()
  144. print('Now playing "' + filename + '"')
  145. currentfile = filename
  146. status(currentfile)
  147. print("volume %s " % currvol)
  148. currentplaying = subprocess.Popen(["ffplay", "-hide_banner", "-nostats", "-nodisp", "-autoexit", "-volume", currvol, filename])
  149. def running():
  150. global currentplaying
  151. if currentplaying != None:
  152. if currentplaying.poll() == None:
  153. return True
  154. return False
  155. def playlist():
  156. global songlist
  157. global lasttime
  158. if not running():
  159. while True:
  160. if (songlist == None) or (len(songlist) <= 0):
  161. switch_artist()
  162. songlist = originalsongs.copy()
  163. random.shuffle(songlist)
  164. song = songlist.pop()
  165. artist = get_artist(song)
  166. if artist == currentartist:
  167. play(song)
  168. lasttime = time.time()
  169. break
  170. else:
  171. if (time.time() - lasttime) >= LCD_REFRESH:
  172. status(currentfile)
  173. lasttime = time.time()
  174. def switch_artist():
  175. global artists
  176. global currentartist
  177. if len(artists) <= 0:
  178. artists = originalartists.copy()
  179. random.shuffle(artists)
  180. currentartist = artists.pop()
  181. switch_track()
  182. def switch_track():
  183. stop()
  184. def button(ch):
  185. val = not GPIO.input(ch)
  186. #name = "Unknown"
  187. #if ch == BTN_ARTIST:
  188. # name = "BTN_ARTIST"
  189. #elif ch == BTN_NEXT:
  190. # name = "BTN_NEXT"
  191. #print(name + " is now " + str(val))
  192. if val:
  193. if ch == BTN_ARTIST:
  194. switch_artist()
  195. elif ch == BTN_NEXT:
  196. switch_track()
  197. def main():
  198. global lcd
  199. global font
  200. global bat
  201. global currentfile
  202. if len(sys.argv) <= 1:
  203. print("Usage:")
  204. print("\t" + sys.argv[0] + " PATH")
  205. sys.exit(1)
  206. os.system("killall ffplay")
  207. GPIO.setmode(GPIO.BCM)
  208. for b in [ BTN_ARTIST, BTN_NEXT ]:
  209. GPIO.setup(b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  210. GPIO.add_event_detect(b, GPIO.BOTH, callback=button, bouncetime=debouncems)
  211. try:
  212. bus = i2c(port=1, address=0x3C)
  213. lcd = ssd1306(bus)
  214. font = ImageFont.truetype(fontfile, fontsize)
  215. except DeviceNotFoundError as E:
  216. print("No LCD connected")
  217. lcd = None
  218. conn, event_conn = pisugar.connect_tcp()
  219. bat = pisugar.PiSugarServer(conn, event_conn)
  220. print(bat.get_model() + " " + bat.get_version())
  221. print(str(bat.get_battery_level()) + "% " + str(bat.get_battery_voltage()) + "V " + str(bat.get_battery_current()) + "A")
  222. print("Plug=" + str(bat.get_battery_power_plugged()) + " Charge=" + str(bat.get_battery_charging()))
  223. try:
  224. while True:
  225. playlist()
  226. time.sleep(0.05)
  227. except KeyboardInterrupt:
  228. print("Bye")
  229. GPIO.cleanup()
  230. sys.exit(0)
  231. if __name__ == "__main__":
  232. main()