123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- #!/usr/bin/env python3
-
- # IP address code taken from:
- # https://github.com/rm-hull/luma.examples/blob/master/examples/sys_info_extended.py
- #
- # ----------------------------------------------------------------------------
- # Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
- # Copyright (c) 2024 Philipp Schönberger (mail@phschoen.de)
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # See <http://www.gnu.org/licenses/>.
- # ----------------------------------------------------------------------------
-
- import sys
- import os
- import random
- import subprocess
- import signal
- import glob
- import socket
- from collections import OrderedDict
- import time
-
- import pisugar
- from luma.core.interface.serial import i2c
- from luma.core.render import canvas
- from luma.oled.device import ssd1306
- from luma.core.error import DeviceNotFoundError
- import psutil
- import RPi.GPIO as GPIO
- from PIL import ImageFont
-
- basevol = "70"
- debouncems = 100
-
- #fontfile = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"
- fontfile = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
- fontsize = 11
-
- LCD_REFRESH = 5.0
-
- BTN_ARTIST = 16
- BTN_NEXT = 26
-
- currentplaying = None
- lcd = None
- font = None
- bat = None
- songlist = None
- currentfile = None
- lasttime = None
- currvol = basevol
-
- vol_list = [
- ("Harley_Matthews", "100"),
- ("xythobuz", "50"),
- ]
-
- basedir = sys.argv[1]
- if basedir.endswith("/"):
- basedir = basedir.removesuffix("/")
-
- def get_artist(fn):
- global currvol
- parts = fn.replace(basedir + "/", "").split(os.sep)
- artist = parts[0].replace("_", " ")
- currvol = basevol
- for i in vol_list:
- if i[0] in fn:
- currvol = i[1]
- break
- return artist
-
- originalsongs = []
- originalartists = []
- for fn in glob.iglob(os.path.join(basedir, '**', '*.wav'), recursive=True):
- originalsongs.append(fn)
-
- artist = get_artist(fn)
- if artist not in originalartists:
- originalartists.append(artist)
-
- artists = originalartists.copy()
- random.shuffle(artists)
- currentartist = artists[0]
-
- def find_single_ipv4_address(addrs):
- for addr in addrs:
- if addr.family == socket.AddressFamily.AF_INET: # IPv4
- return addr.address
-
- def get_ipv4_address(interface_name=None):
- if_addrs = psutil.net_if_addrs()
- if isinstance(interface_name, str) and interface_name in if_addrs:
- addrs = if_addrs.get(interface_name)
- address = find_single_ipv4_address(addrs)
- return address if isinstance(address, str) else ""
- else:
- if_stats = psutil.net_if_stats()
- if_stats_filtered = {key: if_stats[key] for key, stat in if_stats.items() if "loopback" not in stat.flags}
- if_names_sorted = [stat[0] for stat in sorted(if_stats_filtered.items(), key=lambda x: (x[1].isup, x[1].duplex), reverse=True)]
- if_addrs_sorted = OrderedDict((key, if_addrs[key]) for key in if_names_sorted if key in if_addrs)
- for _, addrs in if_addrs_sorted.items():
- address = find_single_ipv4_address(addrs)
- if isinstance(address, str):
- return address
- return ""
-
- def status(filename):
- try:
- with canvas(lcd) as draw:
- f = filename.replace(".wav", "")
- f = f.replace(basedir + "/", "")
- f = f.replace("/", "\n")
- f = f.replace("_", " ")
-
- f += "\n\n"
- f += "Bat: {:.0f}% {:.2f}V {:.2f}A".format(bat.get_battery_level(), bat.get_battery_voltage(), bat.get_battery_current())
-
- ip = get_ipv4_address()
- if len(ip) > 0:
- f += "\n"
- f += "IP: %s" % (ip)
-
- with open("/proc/asound/card0/pcm0p/sub0/hw_params", "r") as rf:
- for line in rf:
- if line.startswith("rate:"):
- rate = int(line.split(" ")[1])
-
- f += "\n"
- f += "Rate: {:.0f}kHz".format(rate / 1000)
-
- draw.multiline_text((0, 0), f, font=font, fill="white", spacing=-1)
- except Exception as e:
- pass
-
- def stop():
- global currentplaying
-
- if running():
- try:
- print("Stopping running player")
- os.kill(currentplaying.pid, signal.SIGINT)
- if not currentplaying.poll():
- currentplaying = None
- else:
- print("Error stopping player")
- except ProcessLookupError as e:
- currentplaying = None
- else:
- currentplaying = None
-
- def play(filename):
- global currentplaying
- global lcd
- global basedir
- global bat
- global currentfile
-
- stop()
-
- print('Now playing "' + filename + '"')
- currentfile = filename
- status(currentfile)
-
- print("volume %s " % currvol)
- currentplaying = subprocess.Popen(["ffplay", "-hide_banner", "-nostats", "-nodisp", "-autoexit", "-volume", currvol, filename])
-
- def running():
- global currentplaying
-
- if currentplaying != None:
- if currentplaying.poll() == None:
- return True
- return False
-
- def playlist():
- global songlist
- global lasttime
-
- if not running():
- while True:
- if (songlist == None) or (len(songlist) <= 0):
- switch_artist()
- songlist = originalsongs.copy()
- random.shuffle(songlist)
-
- song = songlist.pop()
- artist = get_artist(song)
- if artist == currentartist:
- play(song)
- lasttime = time.time()
- break
- else:
- if (time.time() - lasttime) >= LCD_REFRESH:
- status(currentfile)
- lasttime = time.time()
-
- def switch_artist():
- global artists
- global currentartist
-
- if len(artists) <= 0:
- artists = originalartists.copy()
- random.shuffle(artists)
-
- currentartist = artists.pop()
-
- switch_track()
-
- def switch_track():
- stop()
-
- def button(ch):
- val = not GPIO.input(ch)
-
- #name = "Unknown"
- #if ch == BTN_ARTIST:
- # name = "BTN_ARTIST"
- #elif ch == BTN_NEXT:
- # name = "BTN_NEXT"
- #print(name + " is now " + str(val))
-
- if val:
- if ch == BTN_ARTIST:
- switch_artist()
- elif ch == BTN_NEXT:
- switch_track()
-
- def main():
- global lcd
- global font
- global bat
- global currentfile
-
- if len(sys.argv) <= 1:
- print("Usage:")
- print("\t" + sys.argv[0] + " PATH")
- sys.exit(1)
-
- os.system("killall ffplay")
-
- GPIO.setmode(GPIO.BCM)
- for b in [ BTN_ARTIST, BTN_NEXT ]:
- GPIO.setup(b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
- GPIO.add_event_detect(b, GPIO.BOTH, callback=button, bouncetime=debouncems)
-
- try:
- bus = i2c(port=1, address=0x3C)
- lcd = ssd1306(bus)
- font = ImageFont.truetype(fontfile, fontsize)
- except DeviceNotFoundError as E:
- print("No LCD connected")
- lcd = None
-
- conn, event_conn = pisugar.connect_tcp()
- bat = pisugar.PiSugarServer(conn, event_conn)
- print(bat.get_model() + " " + bat.get_version())
- print(str(bat.get_battery_level()) + "% " + str(bat.get_battery_voltage()) + "V " + str(bat.get_battery_current()) + "A")
- print("Plug=" + str(bat.get_battery_power_plugged()) + " Charge=" + str(bat.get_battery_charging()))
-
- try:
- while True:
- playlist()
- time.sleep(0.05)
- except KeyboardInterrupt:
- print("Bye")
- GPIO.cleanup()
- sys.exit(0)
-
- if __name__ == "__main__":
- main()
|