Ingen beskrivning
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

osci-pi.py 7.6KB

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