forked from splitti/oled_phoniebox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patholed_phoniebox.py
542 lines (434 loc) · 21.8 KB
/
oled_phoniebox.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PIL import ImageFont, Image, ImageDraw
from luma.core.render import canvas
from luma.core import cmdline, error
from luma.core.image_composition import ImageComposition, ComposableImage
from time import sleep
from datetime import timedelta
from mpd import MPDClient
import base64
import re
import os
import io
import sys
import signal
import configparser
import math
import RPi.GPIO as GPIO
# GPIO16 mutes the power stage.
# GPIO26 shuts down the power stage.
# $ raspi-gpio get | egrep '16|26'
# GPIO 16: level=1 fsel=0 func=INPUT
# GPIO 26: level=1 fsel=0 func=INPUT
# off
# raspi-gpio set 16 op # output
# raspi-gpio set 26 op
# on
# raspi-gpio set 26 ip
# raspi-gpio set 16 ip # input
# disable warning when a pin is not in IN state on first use. gpio configuration is out of scope of this script and bad initial state does happen
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# if the pins are not configured, do nothing
def disable_hifiberry():
if not "mute" in config["HIFIBERRY"] or not "power" in config["HIFIBERRY"]:
return
GPIO.setup(config["HIFIBERRY"]["mute"], GPIO.OUT)
sleep(0.5)
GPIO.setup(config["HIFIBERRY"]["power"], GPIO.OUT)
def enable_hifiberry():
if not "mute" in config["HIFIBERRY"] or not "power" in config["HIFIBERRY"]:
return
GPIO.setup(config["HIFIBERRY"]["power"], GPIO.IN)
sleep(0.5)
GPIO.setup(config["HIFIBERRY"]["mute"], GPIO.IN)
# LED control. This is a non-feature really. Originally there was a startup service to control the button leds,
# which used sleep timing during startup to display sort of an animation until mpd was up. What a horrible idea.
# More so on a single core RPi zero. Even more so with a status display at hand. Wasting 5 GPIO pins and hugely complicating the interior in the process.
# Wiring them together (even in 2 or 3 groups) and using PWM to 'animate' something would have been acceptable. Maybe.
# The other issue with those LEDs is... you can choose between too bright at night or too dim during the day with a hardwired resistor. Or lean towards the right brightness
# in daylight (but which one, indoors, outdoors, summer, winter....) and then PWM them down. But based on what? An extra photodiode? Not even my phone gets it 100% right. Extra Buttons?
# Remember kids should be able to do that on their own. And wanting to.
# I could imagine wiring them together and hooking them up directly to the usb battery with a potentiometer dial somewhere on the side, going full analog and removing the need to deal with it on the RPi.
# But at the end of the day, why bother. They add nothing in terms of UI. Not even illumination itself is a feature. I could operate my philips roller in pitch black when i was 3 years old.
# https://upload.wikimedia.org/wikipedia/commons/a/a7/Philips-roller.jpg
# Using no LEDs is the best option IMHO. Still, here we are.
def enable_leds():
for pin in config["LEDS"]:
GPIO.setup(config["LEDS"][pin], GPIO.OUT)
GPIO.output(config["LEDS"][pin], GPIO.HIGH)
def disable_leds():
for pin in config["LEDS"]:
GPIO.setup(config["LEDS"][pin], GPIO.IN)
# used googles material for the logos https://fonts.google.com/icons?preview.text=%E2%8F%BB&preview.text_type=custom&icon.set=Material+Icons&icon.query=power
# then IrfanView to convert them:
# image->show channel->alpha (or any)
# ctrl + r height 64
# shift v 128x64 center
# image->decrease color depth 2
# saved as png
# then did base64 -w 0 on the images
def get_logo():
logo = {
"card": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAUUlEQVQ4jWNgGMyA8T8YNMAFmCECB+AC7BCBB3ABfojAB0oE5CECP6isAkigqgALkKaCGu7Y//8vxe5gYGClQ4gNWRWEBTASLkbSxkj8gxEAAIJlKJ3Y53v8AAAAAElFTkSuQmCC",
"music_note": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAYklEQVQ4jWNgoCdg/P+/AV3gAIoAMzEqDqCraCCognJbfjCgAmZ0gVEV+FUw/0YT4P+PJiCPLlBPUOA/mgAjLQSYidHygUQBoNMfoArUoyU5BntMgQZUAf5/qHwGpi8MdAUATb1mLPOSjm4AAAAASUVORK5CYII=",
"pause": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAJklEQVQ4jWNgGFpA/v8P/v8/kATs//+Q//9nVGBUYFgIICftoQAAWYK9iYl2zxoAAAAASUVORK5CYII=",
"pause_circle": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAr0lEQVQ4ja2TQQ7DIAwEQTlw5Al5Sp6WPC1P4Qkcc6i6hVAp3iVtqiq+eWTwYtbO/R1ewfzkPAArgQhsBCbgQQAlqEcFiwFDBYnvBLIC22Y6BVbrrAAQIQp8A4eyoYH1K4jZai/jCUlAVBCyfV1sFQTuqthuqLhXadCK9zwuJkbg+l+6r3QfwJH3htktZa08ngLrws64Qa3tWajr16P25aUb+c79vYmAkxMlFgW/xwuNGicFlFNGbAAAAABJRU5ErkJggg==",
"play": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAgUlEQVQ4jdXSwQnAIAxA0QxQcIHSrOpojuIIHj2IqR7zU3rXm4/wUVTk9HUTUoBMqABtnBiESTBU1QonGmEQUFVDNRmqCxrBV1djcsJXNxSCq66GdU4EqL/R3eA5wsE6wV9OP64vgEnwzdUIz1AIGcDHVjQlhQ9TCGjKg71chBPXC+guVzpvSA/2AAAAAElFTkSuQmCC",
"play_circle": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAA9ElEQVQ4jbWTMQ7DIAxFbWVgzNgxvUF7gtIjdewQNRyNozB2zJAhQ1RqEkpsM3SKpUTi6dsY/AE4MLq3XOMQJTAxBpkR4yyAnS4fAeIdRRFcAAbHa1KBllc1tDAjA62nNA46yke+7y39egZe5ZejL7Ica3qnQcsaHeFMYG8VAzypMwF6AZoAloDfgU9nbSQISfcLEtsRUCoWoXBAY0CZEh0qhdcg/E3hu2zbqpRZK4I+i+cAq+PrC6I7fWhwFZcM65zZGOpBVaOshm2LLEdXCuVIlmq4pUzYvh1oWybjWtYoWdtJa9fmr55H9YAwqicGpwkOjC8IWW9N6FgJuAAAAABJRU5ErkJggg==",
"power": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAsklEQVQ4jc3RQQrDIBAFUCWLWXoEb1IvFqxH8yhzBJcupFOni/CnEEug0EoQfCTfCd+5n66tvgHx96EQezz7SrwVnIKJCSehRhwwJSg0BM1AiAodQa9FSDrYWEFWyCvYFW4rGArpEnSFuILPn7x+I12CXTccPR/bGaQj6Aximf2aGursF0F7JWxOD6Zsmq9HhO3h3L0CeCnzAXDSguDZJZFhIIp0AyTCBrzYzBliI/5wPQFov1oTW3AKDgAAAABJRU5ErkJggg==",
"volume": "iVBORw0KGgoAAAANSUhEUgAAAIAAAABAAQMAAADoGO08AAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB7CAAAewgFu0HU+AAAAw0lEQVQ4jcWTvQ0DIQyFD1FQMgKjsFkwUiQ2Shk5FWswwpVXnOKQjmeXkS50fDLPf49t++8hdXeVFXjtCEI9NXgjSFVQNpOgauE0AAhFSOOE/LECL+Qgb5iSAOJMeoMyJshQhrQVuIdIp7TmEGkjEYI9GsAA+hFUxBkwoh0a6Ihunljgx88apg5detfNtaHbB/CcgJcRfofccepJ7rgXs6igd+uFERg7GMNshfIAkLkwAGPLWAXu1tpem99VlLAf6OLzAfmrobheyJl4AAAAAElFTkSuQmCC",
}
for img in logo:
logo[img] = Image.open(io.BytesIO(base64.b64decode(logo[img]))).convert(device.mode)
return logo
def get_config(file):
base_path = os.path.abspath(os.path.dirname(__file__))
font_path = os.path.join(base_path, "fonts", "Bitstream Vera Sans Mono Roman.ttf") # Tried Inconsolata and VT323, but this one looked better
config = configparser.ConfigParser()
config.read(os.path.join(base_path, file))
config_dict = {"PATH": {}, "FONT": {}}
for section in config.sections():
config_dict[section] = {}
for key in config.options(section):
config_dict[section][key] = config.get(section, key)
config_dict["PATH"]["base_path"] = base_path
config_dict["PATH"]["images"] = os.path.join(base_path, "images")
config_dict["FONT"]["standard"] = ImageFont.truetype(font_path, 12)
config_dict["FONT"]["small"] = ImageFont.truetype(font_path, 10)
config_dict["DISPLAY"]["refresh"] = int(config_dict["DISPLAY"]["refresh"])
for section in "HIFIBERRY", "LEDS":
for key in config_dict[section]:
config_dict[section][key] = int(config_dict[section][key])
return config_dict
def get_device(deviceName):
actual_args = ["-d", deviceName]
parser = cmdline.create_parser(description="luma.examples arguments")
args = parser.parse_args(actual_args)
if args.config:
config = cmdline.load_config(args.config)
args = parser.parse_args(config + actual_args)
try:
device = cmdline.create_device(args)
except error.Error as e:
parser.error(e)
return device
def get_wifi():
wififile = "/proc/net/wireless"
if not os.path.exists(wififile):
return "--"
wifirateFile = open(wififile)
wifiline = wifirateFile.readlines()[2] # last line
wifirateFile.close()
return int(math.ceil(float(re.split(r"\s+", wifiline)[3])))
def sigterm_handler(*_):
disable_leds()
draw_logo("power")
sys.exit(0)
def draw_logo(image_name):
device.display(logo[image_name])
sleep(config["DISPLAY"]["refresh"])
def time_convert(s):
result = re.search(r"^\d+:([^.]+)\.*", str(timedelta(seconds=float(s))))
return result.groups()[0]
def mpc_state_convert(s):
state = {
"play": 2,
"pause": 1,
"stop": 0,
}
return state[s]
def mpc_file_convert(s):
name = {
#
"artist": "",
"title": "",
"album": "",
}
result = re.search(r"^(.+)/([^/]+)\.[^.]+$", s) # Kinderlieder/Kinderlieder Klassiker/1/Track.02.mp3
if result.groups()[0].startswith("http"):
return name
name["artist"] = result.groups()[0]
name["title"] = result.groups()[1]
return name
def mpc_get_data(key, data, altdata):
if key in data:
return data[key]
if key in altdata:
return altdata[key]
return ""
def mpc_get_alt_data(data):
alt_data = {
"song": -1,
"playlistlength": 0,
"elapsed": 0,
"duration": 1, # cant be zero (division). also 0 would mean 100%. with 1, its 0%
"file": "/dev/null",
"artist": "",
"title": "",
"album": "",
}
if "file" in data:
alt_data["file"] = data["file"]
alt_data.update(mpc_file_convert(data["file"]))
return alt_data
def mpc_get_track_num_current(key, data, alt_data):
return int(mpc_get_data(key, data, alt_data)) + 1
def mpc_get_track_num_total(key, data, alt_data):
return int(mpc_get_data(key, data, alt_data))
def mpc_get_track_time(key, data, alt_data):
return time_convert(mpc_get_data(key, data, alt_data))
def mpc_get_track_time_percent(data, alt_data):
current_seconds = float(mpc_get_data("elapsed", data, alt_data))
total_seconds = float(mpc_get_data("duration", data, alt_data))
percent = 100 / total_seconds * current_seconds
return percent
def mpc_client():
mpdc.connect(config["MPD"]["socket"])
# {'volume': '30', 'repeat': '0', 'random': '0', 'single': '0', 'consume': '0', 'partition': 'default', 'playlist': '12', 'playlistlength': '5', 'mixrampdb': '0.000000', 'state': 'play', 'song': '0', 'songid': '56',
# 'time': '26:79', 'elapsed': '26.377', 'bitrate': '320', 'duration': '78.968', 'audio': '44100:24:2', 'nextsong': '1', 'nextsongid': '57'}
# of those, volume, playlistlength, state (play, pause, stop), song (currently playing song in list, starts with 0), elapsed and duration are of interest.
status = mpdc.status()
# {'file': 'Kinderlieder/Kinderlieder Klassiker/1/Track.05.mp3', 'last-modified': '2021-11-07T09:51:56Z', 'time': '87', 'duration': '87.222', 'pos': '4', 'id': '60'}
# {'file': 'Musik/2008 For Emma, Forever Ago (L)/01. Flume.mp3', 'last-modified': '2013-07-02T12:56:55Z', 'time': '219', 'duration': '219.062', 'pos': '0', 'id': '61',
# 'artist': 'Bon Iver', 'title': 'Flume', 'album': 'For Emma, Forever Ago', 'track': '1', 'date': '2008', 'genre': 'Folk-rock, Indie folk'}
# first row is always present. all in all file, artist, title, album are of interest.
song = mpdc.currentsong()
mpdc.close()
mpdc.disconnect()
alt_data = mpc_get_alt_data(song)
return {
"status": mpc_state_convert(status["state"]),
"volume": status["volume"],
"track_num_current": mpc_get_track_num_current("song", status, alt_data),
"track_num_total": mpc_get_track_num_total("playlistlength", status, alt_data),
"track_time_elapsed": mpc_get_track_time("elapsed", status, alt_data),
"track_time_total": mpc_get_track_time("duration", status, alt_data),
"track_time_percent": mpc_get_track_time_percent(status, alt_data),
"file_path": mpc_get_data("file", song, alt_data),
"artist": mpc_get_data("artist", song, alt_data),
"title": mpc_get_data("title", song, alt_data),
"album": mpc_get_data("album", song, alt_data),
}
class TextImage:
def __init__(self, text, font):
draw = ImageDraw.Draw(Image.new(device.mode, (device.width, device.height)))
self.left, self.top, self.right, self.bottom = draw.textbbox((0, 0), text, font=font)
self.image = Image.new(device.mode, (self.right, self.bottom))
draw = ImageDraw.Draw(self.image)
draw.text((0, 0), text, font=font, fill="white")
self.width = self.right
self.height = self.bottom
del draw
def compose_text(text, cords):
return ComposableImage(TextImage(text, cords[2]).image, position=(cords[0], cords[1]))
def get_coordinates():
font_std = config["FONT"]["standard"]
font_small = config["FONT"]["small"]
cords = {
# horizontal dividers. 64 pixels (0-63) divided in 4 sections a 16 pixels
"section0_y": 0, # start
"section1_y": 15, # TITLE
"section2_y": 31, # ARTIST
"section3_y": 47, # ALBUM
"section4_y": 63, # STATUS. no need to draw
# vertical dividers for status section. 128 pixels (0-127) divided in 4 sections. The first two need 5 chars, the other two 4 chars.
# this makes 36 pixels for the left and 28 for the right side
"section4_x0": 0, # TIME START
"section4_x1": 35, # TIME
"section4_x2": 71, # TRACK
"section4_x3": 99, # VOLUME
"section4_x4": 127, # WIFI. no need to draw
"scroll": 10, # pixel advancement per tick
}
cords["title"] = [0, cords["section0_y"] + 1, font_std]
cords["artist"] = [0, cords["section1_y"] + 1, font_std]
cords["album"] = [0, cords["section2_y"] + 1, font_std]
cords["track_time_elapsed"] = [cords["section4_x0"], cords["section3_y"] + 2, font_small]
cords["track"] = [cords["section4_x1"] + 2, cords["section3_y"] + 1, font_small]
cords["volume"] = [cords["section4_x2"] + 2, cords["section3_y"] + 1, font_small]
cords["wifi"] = [cords["section4_x3"] + 2, cords["section3_y"] + 1, font_small]
cords["progress1_start"] = [cords["section4_x0"], cords["section4_y"]]
cords["progress2_start"] = [cords["section4_x0"], cords["section4_y"] - 1]
return cords
def get_outlines(cords):
return [
# horizontal dividers
# [0, cords["section1_y"], device.width, cords["section1_y"]], # x,y to x,y
# [0, cords["section2_y"], device.width, cords["section2_y"]],
[0, cords["section3_y"], device.width, cords["section3_y"]],
# vertical dividers
[cords["section4_x1"], cords["section3_y"], cords["section4_x1"], device.height],
[cords["section4_x2"], cords["section3_y"], cords["section4_x2"], device.height],
[cords["section4_x3"], cords["section3_y"], cords["section4_x3"], device.height],
]
def get_scroll_count(image_width, screen_width, scroll_tick):
if image_width <= screen_width:
return 0
offscreen = image_width - screen_width
return math.ceil(offscreen / scroll_tick)
def add_image(current, coordinates, image_composition, key, text):
image = compose_text(text, coordinates[key])
current[key] = {
#
"text": text,
"image": image,
}
image_composition.add_image(image)
current[key]["max_scroll"] = get_scroll_count(image.width, device.width, coordinates["scroll"])
current[key]["cur_scroll"] = 0
def update_images(current, image_composition, coordinates, new):
# if there is a content update, remove the old image, render and add the new content
for key in new:
if not key in coordinates:
continue
if not key in current: # first iteration
add_image(current, coordinates, image_composition, key, new[key])
if current[key]["text"] != new[key]: # updated content
image_composition.remove_image(current[key]["image"])
add_image(current, coordinates, image_composition, key, new[key])
continue
if current[key]["max_scroll"] != 0: # scrolling
if current[key]["cur_scroll"] == current[key]["max_scroll"]:
# reset image
current[key]["image"].offset = (0, 0)
current[key]["cur_scroll"] = 0
continue
# scroll image
current[key]["image"].offset = (current[key]["image"].offset[0] + coordinates["scroll"], 0)
current[key]["cur_scroll"] += 1
return current
def update_counter(max_count, count):
if count == max_count:
return 0, 1
count += 1
return count, 0
def update_state(state):
state["count"], do_update = update_counter(2, state["count"])
if not do_update:
return state
mpc = mpc_client()
# the if statements here indicate distinct events, where actions could be added
if mpc["status"] != state["status"]:
state["wifi"] = get_wifi() # a state change might be a good time to update wifi signal
state["status"] = mpc["status"]
if mpc["volume"] != state["volume"]:
state["volume"] = mpc["volume"]
current_id = mpc["artist"] + mpc["title"] + str(mpc["track_num_current"]) + mpc["track_time_total"]
if current_id != state["id"]: # track change
state["id"] = current_id
state["album"] = mpc["album"]
state["title"] = mpc["title"]
state["artist"] = mpc["artist"]
# below are non-events
if mpc["file_path"].startswith("http"): # what is in track_time_percent or others when there is a stream running? i doubt this file check is good
state["progress"] = device.width
else:
state["progress"] = int(math.ceil(device.width * mpc["track_time_percent"] / 100))
state["track_time_elapsed"] = mpc["track_time_elapsed"]
state["track_num_current"] = mpc["track_num_current"]
state["track_num_total"] = mpc["track_num_total"]
return state
def pad_state(state):
if "volume" in state:
padding = " "
if state["volume"] == 100:
padding = ""
state["volume"] = "V" + padding + str(state["volume"])
if state["status"] == 1:
state["track_time_elapsed"] = "PAUSE"
else:
if len(state["track_time_elapsed"]) == 4:
state["track_time_elapsed"] = " " + state["track_time_elapsed"]
if "track_num_current" in state and "track_num_total" in state:
track_cur = str(state["track_num_current"])
track_total = str(state["track_num_total"])
if len(track_cur) == 1:
track_cur = "0" + track_cur
if len(track_total) == 1:
track_total = "0" + track_total
state["track"] = track_cur + "/" + track_total
if "wifi" in state:
wifi = str(state["wifi"])
padding = " "
if len(wifi) == 3:
padding = ""
state["wifi"] = "W" + padding + wifi
return state
# The script utilizes 5-10% CPU on a RPi zero when updating the display. lets try to be as aggressive with power saving as possible.
# Little delays are acceptable (sound and display). The device will mostly be idle, or playing continously without anyone interacting.
# So I opt for saving power at all costs when idle and not going overboard during playback.
def save_power(state):
if state["status"] != 2 and state["save_power"] == 1: # no need to render old state if nothing happens
return 1
if state["status"] != 2 and state["save_power"] == 0:
state["hifiberry_shutdown_wait"], state["save_power"] = update_counter(10, state["hifiberry_shutdown_wait"])
if state["save_power"] == 1:
disable_hifiberry()
if state["status"] == 0:
draw_logo("card")
return 1
if state["status"] == 2 and state["save_power"] == 1: # reenable sound
enable_hifiberry()
state["save_power"] = 0
state["hifiberry_shutdown_wait"] = 0
return 0
def main():
image_composition = ImageComposition(device)
coordinates = get_coordinates()
current_display = {}
current_state = {
#
"status": 0,
"volume": 0,
"track_num_current": 0,
"track_num_total": 0,
"track_time_elapsed": "00:00",
"track_time_total": "00:00",
"track_time_percent": 0,
"file_path": "",
"artist": "",
"title": "",
"album": "",
"progress": 0,
"id": ".",
"count": 0,
"hifiberry_shutdown_wait": 0,
"save_power": 0,
}
try:
while True:
current_state = update_state(current_state)
if save_power(current_state):
sleep(config["DISPLAY"]["refresh"]) # take a nap. continue would skip that otherwise.
continue
current_display = update_images(current_display, image_composition, coordinates, pad_state(current_state.copy()))
with canvas(device, background=image_composition()) as draw:
image_composition.refresh()
for line in get_outlines(coordinates):
draw.line(line[0:4], fill="white")
# progress bar
draw.line(
(coordinates["progress1_start"][0], coordinates["progress1_start"][1], current_state["progress"], coordinates["progress1_start"][1]),
fill="white",
)
draw.line(
(coordinates["progress2_start"][0], coordinates["progress2_start"][1], current_state["progress"], coordinates["progress2_start"][1]),
fill="white",
)
sleep(1)
except KeyboardInterrupt:
pass
return
if __name__ == "__main__":
config = get_config("oled_phoniebox.conf")
enable_leds()
device = get_device(config["DISPLAY"]["controller"])
device.contrast(int(config["DISPLAY"]["contrast"]))
logo = get_logo()
mpdc = MPDClient()
signal.signal(signal.SIGTERM, sigterm_handler)
draw_logo("music_note")
main()
disable_leds()