From 7d7355e04b95f563a9df9265024c4c7b30a9f768 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Mon, 20 Dec 2021 18:23:01 -0500 Subject: [PATCH 01/26] change already updated message --- timemachine/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timemachine/main.py b/timemachine/main.py index 3c41681..9a7880e 100644 --- a/timemachine/main.py +++ b/timemachine/main.py @@ -428,8 +428,8 @@ def stop_button_longpress(button, state): scr.wake_up() scr.show_text("Updating\nCode\n\nStand By...", force=True) sleep(20) - scr.show_text("No Update\nAvailable...", clear=True, force=True) - sleep(10) + scr.show_text("Already\nat Latest\nRelease", clear=True, force=True) + sleep(5) scr.image.frombytes(pixels) scr.refresh(force=True) # exit() From 4691dc66bbeb674fafa4e03fbd16c95cb96fb59a Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 16:14:24 -0500 Subject: [PATCH 02/26] adding a calibration service, to speed up screen turning on after bootup --- timemachine/bin/services.sh | 4 +- timemachine/connect_network.py | 187 ++------------------------------- 2 files changed, 9 insertions(+), 182 deletions(-) diff --git a/timemachine/bin/services.sh b/timemachine/bin/services.sh index 9cbcdd5..3741441 100755 --- a/timemachine/bin/services.sh +++ b/timemachine/bin/services.sh @@ -9,6 +9,7 @@ sudo cp $SCRIPT_DIR/timemachine.service /etc/systemd/system/. sudo cp $SCRIPT_DIR/connect_network.service /etc/systemd/system/. sudo cp $SCRIPT_DIR/serve_options.service /etc/systemd/system/. sudo cp $SCRIPT_DIR/update.service /etc/systemd/system/. +sudo cp $SCRIPT_DIR/calibrate.service /etc/systemd/system/. echo "changing permissions on wpa_supplicant" sudo chmod 644 /etc/wpa_supplicant/wpa_supplicant.conf @@ -17,8 +18,9 @@ sudo chgrp root /etc/wpa_supplicant/wpa_supplicant.conf echo "reenabling services" sudo systemctl daemon-reload -sudo systemctl enable timemachine.service +sudo systemctl enable calibrate.service sudo systemctl enable connect_network.service +sudo systemctl enable timemachine.service sudo systemctl enable serve_options.service sudo systemctl disable update.service diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index 1ba54b4..e3ca441 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -210,98 +210,6 @@ def year_button(button): scr.clear() -def get_knob_orientation(knob, label): - m_knob_event.clear() - d_knob_event.clear() - y_knob_event.clear() - knob.steps = 0 - before_value = knob.steps - scr.show_text("Calibrating knobs", font=scr.smallfont, force=False, clear=True) - scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) - if label == "month": - m_knob_event.wait() - elif label == "day": - d_knob_event.wait() - elif label == "year": - y_knob_event.wait() - after_value = knob.steps - return after_value > before_value - - -def save_knob_sense(parms): - knob_senses = [get_knob_orientation(knob, label) for knob, label in [(m, "month"), (d, "day"), (y, "year")]] - knob_sense = 0 - for i in range(len(knob_senses)): - knob_sense += 1 << i if knob_senses[i] else 0 - f = open(parms.knob_sense_path, 'w') - f.write(str(knob_sense)) - f.close() - scr.show_text("Knobs\nCalibrated", font=scr.boldsmall, color=(0, 255, 255), force=False, clear=True) - scr.show_text(F" {knob_sense}", font=scr.boldsmall, loc=(0, 60), force=True) - - -def test_buttons(event, label): - event.clear() - scr.show_text("Testing Buttons", font=scr.smallfont, force=False, clear=True) - scr.show_text(F"Press {label}", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) - event.wait() - - -def default_options(): - d = {} - d['COLLECTIONS'] = 'GratefulDead' - d['SCROLL_VENUE'] = 'true' - d['FAVORED_TAPER'] = 'miller' - d['AUTO_UPDATE_ARCHIVE'] = 'false' - d['DEFAULT_START_TIME'] = '15:00:00' - d['TIMEZONE'] = 'America/New_York' - return d - - -def configure_collections(parms): - """ is this a GratefulDead or a Phish Time Machine? """ - if (not parms.test) and os.path.exists(parms.knob_sense_path): - return - collection = select_option("Collection\nTurn Year, Select", ['GratefulDead', 'Phish', 'GratefulDead,Phish', 'other']) - if collection == 'other': - collection = select_chars("Collection?\nSelect. Stop to end", character_set=string.printable[36:62]) - scr.show_text(f"Collection:\n{collection}", font=scr.smallfont, force=True, clear=True) - #envs = get_envs() - sleep(2) - tmpd = default_options() - try: - tmpd = json.load(open(parms.options_path, 'r')) - except Exception as e: - logger.warning(F"Failed to read options from {parms.options_path}. Using defaults") - tmpd['COLLECTIONS'] = collection - try: - with open(parms.options_path, 'w') as outfile: - optd = json.dump(tmpd, outfile, indent=1) - except Exception as e: - logger.warning(F"Failed to write options to {parms.options_path}") - - -def test_sound(parms): - """ test that sound works """ - if (not parms.test) and os.path.exists(parms.knob_sense_path): - return - try: - cmd = f'mpv --really-quiet ~/test_sound.ogg &' - os.system(cmd) - except Exception as e: - logger.warning("Failed to play sound file ~/test_sound.ogg") - - -def test_all_buttons(parms): - """ test that every button on the board works """ - if (not parms.test) and os.path.exists(parms.knob_sense_path): - return - _ = [test_buttons(e, l) for e, l in - [(done_event, "stop"), (rewind_event, "rewind"), (ffwd_event, "ffwd"), (select_event, "select"), - (play_pause_event, "play/pause"), (m_event, "month"), (d_event, "day"), (y_event, "year")]] - scr.show_text("Testing Buttons\nSucceeded!", font=scr.smallfont, force=True, clear=True) - - def select_option(message, chooser): if type(chooser) == type(lambda: None): choices = chooser() else: @@ -454,9 +362,9 @@ def wifi_connected(max_attempts=1): attempt = 0 while not connected and attempt < max_attempts: if attempt > 0: - cmd = "sudo killall -HUP wpa_supplicant" + cmd2 = "sudo killall -HUP wpa_supplicant" if not parms.test: - os.system(cmd) + os.system(cmd2) sleep(2*parms.sleep_time) attempt = attempt + 1 raw = subprocess.check_output(cmd, shell=True) @@ -559,102 +467,19 @@ def get_wifi_params(): return wifi, passkey, extra_dict -def check_factory_build(): - home = os.getenv('HOME') - envs = get_envs() - if '.factory_env' not in envs: # create one - logger.info("creating factory build") - srcdir = os.path.join(home, envs[0]) - destdir = os.path.join(home, '.factory_env') - cmd = f'cp -r {srcdir} {destdir}' - os.system(cmd) - else: - logger.info("factory build present") - return - - -def get_envs(): - home = os.getenv('HOME') - current_env = os.path.basename(os.readlink(os.path.join(home, 'timemachine'))) - envs = [x for x in os.listdir(home) if os.path.isdir(os.path.join(home, x)) and (x.startswith('env_') or x == '.factory_env')] - envs = sorted(envs, reverse=True) - envs.insert(0, envs.pop(envs.index(current_env))) # put current_env first in the list. - return envs - - -def change_environment(): - home = os.getenv('HOME') - envs = get_envs() - new_env = select_option("Select an environment to use", envs) - if new_env == envs[0]: - return - if new_env == '.factory_env': - factory_dir = os.path.join(home, new_env) - # new_factory = f'env_{datetime.datetime.now().strftime("%Y%m%d.%H%M%S")}' - new_factory_tmp = 'env_recent_copy_tmp' # Create a tmp dir, in case reboot occurs during the copy. - new_factory = 'env_recent_copy' # by using a static name I avoid cleaning up old directories. - new_dir_tmp = os.path.join(home, new_factory_tmp) - new_dir = os.path.join(home, new_factory) - scr.show_text("Resetting Factory\nenvironment", font=scr.smallfont, force=True, clear=True) - os.system(f'rm -rf {new_dir_tmp}') - cmd = f'cp -r {factory_dir} {new_dir_tmp}' - fail = os.system(cmd) - if fail != 0: - scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) - return - cmd = f'rm -rf {new_dir}' - os.system(cmd) - cmd = f'mv {new_dir_tmp} {new_dir}' - os.system(cmd) - else: - new_dir = os.path.join(home, new_env) - if os.path.isdir(new_dir): - make_link_cmd = f"ln -sfn {new_dir} {os.path.join(home,'timemachine')}" - fail = os.system(make_link_cmd) - if fail == 0: - cmd = "sudo reboot" - os.system(cmd) - sys.exit(-1) - scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) - return - - -def welcome_alternatives(): - scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) - check_factory_build() - button = button_event.wait(0.2*parms.sleep_time) - button_event.clear() - if rewind_event.is_set(): - rewind_event.clear() - return True - if done_event.is_set(): - change_environment() - done_event.clear() - return False - - def main(): try: + scr.show_text("Connecting\nto WiFi", font=scr.font, force=True, clear=True) cmd = "sudo rfkill unblock wifi" os.system(cmd) cmd = "sudo ifconfig wlan0 up" os.system(cmd) - reconnect = welcome_alternatives() - scr.show_text(" . . . . ", font=scr.font, force=True, clear=True) - connected = wifi_connected() - - if parms.test or reconnect or not connected: - try: - test_sound(parms) - test_all_buttons(parms) - configure_collections(parms) - save_knob_sense(parms) - except Exception: - logger.info("Failed to save knob sense...continuing") + connected = wifi_connected(max_attempts=3) + eth_mac_address = get_mac_address() scr.show_text(F"MAC addresses\neth0\n{eth_mac_address}", color=(0, 255, 255), font=scr.smallfont, force=True, clear=True) sleep(1) - if parms.test or reconnect or not connected: + if parms.test or not connected: wifi, passkey, extra_dict = get_wifi_params() scr.show_text(F"wifi:\n{wifi}\npasskey:\n{passkey}", loc=(0, 0), color=(255, 255, 255), font=scr.oldfont, force=True, clear=True) update_wpa_conf(parms.wpa_path, wifi, passkey, extra_dict) From bc96609e60f250846c9b1efee02ef41586b349a2 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 16:19:04 -0500 Subject: [PATCH 03/26] handle calibration --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7f15c95..8b17d2a 100644 --- a/setup.py +++ b/setup.py @@ -43,12 +43,13 @@ 'metadata/silence600.ogg', 'metadata/silence300.ogg', 'options.txt', '.latest_tag']}, entry_points={'console_scripts': ['connect_network=timemachine.connect_network:main', + 'calibrate=timemachine.calibrate:main', 'serve_options=timemachine.serve_options:main', 'timemachine=timemachine.main:main', 'timemachine_test_update=timemachine.main:main_test_update']}, scripts=['timemachine/bin/services.sh', 'timemachine/bin/update.sh', 'timemachine/bin/board_version.sh', 'timemachine/bin/timemachine.service', 'timemachine/bin/update.service', 'timemachine/bin/connect_network.service', - 'timemachine/bin/serve_options.service'], + 'timemachine/bin/serve_options.service', 'timemachine/bin/calibrate.service'], license_files=('LICENSE',), license='GNU General Public License v3 (GPLv3)' ) From eafc477c8670708b8ad3d9ab98242cffc14a6d59 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 16:19:36 -0500 Subject: [PATCH 04/26] calibration service --- timemachine/bin/calibrate.service | 9 + timemachine/calibrate.py | 551 ++++++++++++++++++++++++++++++ 2 files changed, 560 insertions(+) create mode 100644 timemachine/bin/calibrate.service create mode 100644 timemachine/calibrate.py diff --git a/timemachine/bin/calibrate.service b/timemachine/bin/calibrate.service new file mode 100644 index 0000000..373c221 --- /dev/null +++ b/timemachine/bin/calibrate.service @@ -0,0 +1,9 @@ +[Unit] +Description=Calibrate Time Machine +[Service] +Type=oneshot +User=deadhead +ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && calibrate --debug 0" +RemainAfterExit=yes +[Install] +WantedBy=multi-user.target diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py new file mode 100644 index 0000000..cc3c39a --- /dev/null +++ b/timemachine/calibrate.py @@ -0,0 +1,551 @@ +import datetime +import json +import logging +import optparse +import os +import re +import string +import subprocess +import sys +from threading import Event +from time import sleep + +from gpiozero import RotaryEncoder, Button +from tenacity import retry +from tenacity.stop import stop_after_delay +from typing import Callable + +from timemachine import config, controls + + +parser = optparse.OptionParser() +parser.add_option('--knob_sense_path', + dest='knob_sense_path', + type="string", + default=os.path.join(os.getenv('HOME'), ".knob_sense"), + help="path to file describing knob directions [default %default]") +parser.add_option('-d', '--debug', + dest='debug', + type="int", + default=0, + help="If > 0, don't run the main script on loading [default %default]") +parser.add_option('--options_path', + dest='options_path', + default=os.path.join(os.getenv('HOME'), '.timemachine_options.txt'), + help="path to options file [default %default]") +parser.add_option('--test', + dest='test', + action="store_true", + default=False, + help="Force reconnection (for testing) [default %default]") +parser.add_option('--sleep_time', + dest='sleep_time', + type="int", + default=10, + help="how long to sleep before checking network status [default %default]") +parser.add_option('-v', '--verbose', + dest='verbose', + action="store_true", + default=False, + help="Print more verbose information [default %default]") +parms, remainder = parser.parse_args() + +logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)s: %(name)s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') +logger = logging.getLogger(__name__) +controlsLogger = logging.getLogger('timemachine.controls') +if parms.verbose: + logger.setLevel(logging.DEBUG) + controlsLogger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.DEBUG) + controlsLogger.setLevel(logging.INFO) + +for k in parms.__dict__.keys(): + print(F"{k:20s} : {parms.__dict__[k]}") + +button_event = Event() +rewind_event = Event() +done_event = Event() # stop button +ffwd_event = Event() +play_pause_event = Event() +select_event = Event() +m_event = Event() +d_event = Event() +y_event = Event() +m_knob_event = Event() +d_knob_event = Event() +y_knob_event = Event() + + +@retry(stop=stop_after_delay(10)) +def retry_call(callable: Callable, *args, **kwargs): + """Retry a call.""" + return callable(*args, **kwargs) + + +class decade_counter(): + def __init__(self, tens: RotaryEncoder, ones: RotaryEncoder, bounds=(None, None)): + self.bounds = bounds + self.tens = tens + self.ones = ones + self.set_value(tens.steps, ones.steps) + + def set_value(self, tens_val, ones_val): + self.value = tens_val*10 + ones_val + if self.bounds[0] is not None: + self.value = max(self.value, self.bounds[0]) + if self.bounds[1] is not None: + self.value = min(self.value, self.bounds[1]) + self.tens.steps, self.ones.steps = divmod(self.value, 10) + return self.value + + def get_value(self): + return self.value + + +def decade_knob(knob: RotaryEncoder, label, counter: decade_counter): + if knob.is_active: + print(f"Knob {label} steps={knob.steps} value={knob.value}") + else: + if knob.steps < knob.threshold_steps[0]: + if label == "year" and d.steps > d.threshold_steps[0]: + knob.steps = knob.threshold_steps[1] + d.steps = max(d.threshold_steps[0], d.steps - 1) + else: + knob.steps = knob.threshold_steps[0] + if knob.steps > knob.threshold_steps[1]: + if label == "year" and d.steps < d.threshold_steps[1]: + knob.steps = knob.threshold_steps[0] + d.steps = min(d.threshold_steps[1], d.steps + 1) + else: + knob.steps = knob.threshold_steps[1] + print(f"Knob {label} is inactive") + counter.set_value(d.steps, y.steps) + if label == "month": + m_knob_event.set() + if label == "day": + d_knob_event.set() + if label == "year": + y_knob_event.set() + + +def rewind_button(button): + logger.debug("pressing or holding rewind") + button_event.set() + rewind_event.set() + + +def select_button(button): + logger.debug("pressing select") + select_event.set() + + +def stop_button(button): + logger.debug("pressing stop") + button_event.set() + done_event.set() + + +def ffwd_button(button): + logger.debug("pressing ffwd") + ffwd_event.set() + + +def play_pause_button(button): + logger.debug("pressing ffwd") + play_pause_event.set() + + +def month_button(button): + logger.debug("pressing or holding rewind") + m_event.set() + + +def day_button(button): + logger.debug("pressing or holding rewind") + d_event.set() + + +def year_button(button): + logger.debug("pressing or holding rewind") + y_event.set() + + +max_choices = len(string.printable) +m = retry_call(RotaryEncoder, config.month_pins[1], config.month_pins[0], max_steps=0, threshold_steps=(0, 9)) +d = retry_call(RotaryEncoder, config.day_pins[1], config.day_pins[0], max_steps=0, threshold_steps=(0, 1+divmod(max_choices-1, 10)[0])) +y = retry_call(RotaryEncoder, config.year_pins[1], config.year_pins[0], max_steps=0, threshold_steps=(0, 9)) +counter = decade_counter(d, y, bounds=(0, 100)) + +m_button = retry_call(Button, config.month_pins[2]) +d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) +y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) + +m.when_rotated = lambda x: decade_knob(m, "month", counter) +d.when_rotated = lambda x: decade_knob(d, "day", counter) +y.when_rotated = lambda x: decade_knob(y, "year", counter) + +rewind = retry_call(Button, config.rewind_pin) +ffwd = retry_call(Button, config.ffwd_pin) +play_pause = retry_call(Button, config.play_pause_pin) +select = retry_call(Button, config.select_pin, hold_time=2, hold_repeat=True) +stop = retry_call(Button, config.stop_pin) + +rewind.when_pressed = lambda x: rewind_button(x) +rewind.when_held = lambda x: rewind_button(x) +ffwd.when_pressed = lambda x: ffwd_button(x) +play_pause.when_pressed = lambda x: play_pause_button(x) +y_button.when_pressed = lambda x: year_button(x) +m_button.when_pressed = lambda x: month_button(x) +d_button.when_pressed = lambda x: day_button(x) +select.when_pressed = lambda x: select_button(x) +stop.when_pressed = lambda x: stop_button(x) + +scr = controls.screen(upside_down=False) +scr.clear() + + +def get_knob_orientation(knob, label): + m_knob_event.clear() + d_knob_event.clear() + y_knob_event.clear() + knob.steps = 0 + before_value = knob.steps + scr.show_text("Calibrating knobs", font=scr.smallfont, force=False, clear=True) + scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) + if label == "month": + m_knob_event.wait() + elif label == "day": + d_knob_event.wait() + elif label == "year": + y_knob_event.wait() + after_value = knob.steps + return after_value > before_value + + +def save_knob_sense(parms): + knob_senses = [get_knob_orientation(knob, label) for knob, label in [(m, "month"), (d, "day"), (y, "year")]] + knob_sense = 0 + for i in range(len(knob_senses)): + knob_sense += 1 << i if knob_senses[i] else 0 + f = open(parms.knob_sense_path, 'w') + f.write(str(knob_sense)) + f.close() + scr.show_text("Knobs\nCalibrated", font=scr.boldsmall, color=(0, 255, 255), force=False, clear=True) + scr.show_text(F" {knob_sense}", font=scr.boldsmall, loc=(0, 60), force=True) + + +def test_buttons(event, label): + event.clear() + scr.show_text("Testing Buttons", font=scr.smallfont, force=False, clear=True) + scr.show_text(F"Press {label}", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) + event.wait() + + +def default_options(): + d = {} + d['COLLECTIONS'] = 'GratefulDead' + d['SCROLL_VENUE'] = 'true' + d['FAVORED_TAPER'] = 'miller' + d['AUTO_UPDATE_ARCHIVE'] = 'false' + d['DEFAULT_START_TIME'] = '15:00:00' + d['TIMEZONE'] = 'America/New_York' + return d + + +def configure_collections(parms): + """ is this a GratefulDead or a Phish Time Machine? """ + collection = select_option("Collection\nTurn Year, Select", ['GratefulDead', 'Phish', 'GratefulDead,Phish', 'other']) + if collection == 'other': + collection = select_chars("Collection?\nSelect. Stop to end", character_set=string.printable[36:62]) + scr.show_text(f"Collection:\n{collection}", font=scr.smallfont, force=True, clear=True) + #envs = get_envs() + sleep(2) + tmpd = default_options() + try: + tmpd = json.load(open(parms.options_path, 'r')) + except Exception as e: + logger.warning(F"Failed to read options from {parms.options_path}. Using defaults") + tmpd['COLLECTIONS'] = collection + try: + with open(parms.options_path, 'w') as outfile: + optd = json.dump(tmpd, outfile, indent=1) + except Exception as e: + logger.warning(F"Failed to write options to {parms.options_path}") + + +def test_sound(parms): + """ test that sound works """ + try: + cmd = f'mpv --really-quiet ~/test_sound.ogg &' + os.system(cmd) + except Exception as e: + logger.warning("Failed to play sound file ~/test_sound.ogg") + + +def test_all_buttons(parms): + """ test that every button on the board works """ + _ = [test_buttons(e, l) for e, l in + [(done_event, "stop"), (rewind_event, "rewind"), (ffwd_event, "ffwd"), (select_event, "select"), + (play_pause_event, "play/pause"), (m_event, "month"), (d_event, "day"), (y_event, "year")]] + scr.show_text("Testing Buttons\nSucceeded!", font=scr.smallfont, force=True, clear=True) + + +def select_option(message, chooser): + if type(chooser) == type(lambda: None): choices = chooser() + else: + choices = chooser + scr.clear() + counter.set_value(0, 0) + selected = None + screen_height = 5 + screen_width = 14 + update_now = scr.update_now + scr.update_now = False + done_event.clear() + rewind_event.clear() + select_event.clear() + + scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) + (text_width, text_height) = scr.smallfont.getsize(message) + + text_height = text_height + 1 + y_origin = text_height*(1+message.count('\n')) + selection_bbox = controls.Bbox(0, y_origin, 160, 128) + + while not select_event.is_set(): + if rewind_event.is_set(): + if type(chooser) == type(lambda: None): choices = chooser() + else: + choices = chooser + rewind_event.clear() + scr.clear_area(selection_bbox, force=False) + x_loc = 0 + y_loc = y_origin + step = divmod(counter.value, len(choices))[1] + + text = '\n'.join(choices[max(0, step-int(screen_height/2)):step]) + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=False) + y_loc = y_loc + text_height*(1+text.count('\n')) + + if len(choices[step]) > screen_width: + text = '>' + '..' + choices[step][-13:] + else: + text = '>' + choices[step] + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, color=(0, 0, 255), force=False) + y_loc = y_loc + text_height + + text = '\n'.join(choices[step+1:min(step+screen_height, len(choices))]) + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=True) + + sleep(0.01) + select_event.clear() + selected = choices[step] + # scr.show_text(F"So far: \n{selected}",loc=selected_bbox.origin(),color=(255,255,255),font=scr.smallfont,force=True) + + logger.info(F"word selected {selected}") + scr.update_now = update_now + return selected + + +def select_chars(message, message2="So Far", character_set=string.printable): + scr.clear() + selected = '' + counter.set_value(0, 1) + screen_width = 12 + update_now = scr.update_now + scr.update_now = False + done_event.clear() + select_event.clear() + + scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) + (text_width, text_height) = scr.smallfont.getsize(message) + + y_origin = text_height*(1+message.count('\n')) + selection_bbox = controls.Bbox(0, y_origin, 160, y_origin+22) + selected_bbox = controls.Bbox(0, y_origin+21, 160, 128) + + while not done_event.is_set(): + while not select_event.is_set() and not done_event.is_set(): + scr.clear_area(selection_bbox, force=False) + # scr.draw.rectangle((0,0,scr.width,scr.height),outline=0,fill=(0,0,0)) + x_loc = 0 + y_loc = y_origin + + text = 'DEL' + (text_width, text_height) = scr.oldfont.getsize(text) + if counter.value == 0: # we are deleting + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) + scr.show_text(character_set[:screen_width], loc=(x_loc + text_width, y_loc), font=scr.oldfont, force=True) + continue + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) + x_loc = x_loc + text_width + + # print the white before the red, if applicable + text = character_set[max(0, -1+counter.value-int(screen_width/2)):-1+counter.value] + for x in character_set[94:]: + text = text.replace(x, u'\u25A1') + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) + x_loc = x_loc + text_width + + # print the red character + text = character_set[-1+min(counter.value, len(character_set))] + if text == ' ': + text = "SPC" + elif text == '\t': + text = "\\t" + elif text == '\n': + text = "\\n" + elif text == '\r': + text = "\\r" + elif text == '\x0b': + text = "\\v" + elif text == '\x0c': + text = "\\f" + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) + x_loc = x_loc + text_width + + # print the white after the red, if applicable + text = character_set[counter.value:min(-1+counter.value+screen_width, len(character_set))] + for x in character_set[94:]: + text = text.replace(x, u'\u25A1') + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=True) + x_loc = x_loc + text_width + + sleep(0.1) + select_event.clear() + if done_event.is_set(): + continue + if counter.value == 0: + selected = selected[:-1] + scr.clear_area(selected_bbox, force=False) + else: + selected = selected + character_set[-1+counter.value] + scr.clear_area(selected_bbox, force=False) + scr.show_text(F"{message2}:\n{selected[-screen_width:]}", loc=selected_bbox.origin(), color=(255, 255, 255), font=scr.oldfont, force=True) + + logger.info(F"word selected {selected}") + scr.update_now = update_now + return selected + + +def exit_success(status=0, sleeptime=5): + sleep(sleeptime) + scr.clear() + sys.exit(status) + + +def check_factory_build(): + home = os.getenv('HOME') + envs = get_envs() + if '.factory_env' not in envs: # create one + logger.info("creating factory build") + srcdir = os.path.join(home, envs[0]) + destdir = os.path.join(home, '.factory_env') + cmd = f'cp -r {srcdir} {destdir}' + os.system(cmd) + else: + logger.info("factory build present") + return + + +def get_envs(): + home = os.getenv('HOME') + current_env = os.path.basename(os.readlink(os.path.join(home, 'timemachine'))) + envs = [x for x in os.listdir(home) if os.path.isdir(os.path.join(home, x)) and (x.startswith('env_') or x == '.factory_env')] + envs = sorted(envs, reverse=True) + envs.insert(0, envs.pop(envs.index(current_env))) # put current_env first in the list. + return envs + + +def change_environment(): + home = os.getenv('HOME') + envs = get_envs() + new_env = select_option("Select an environment to use", envs) + if new_env == envs[0]: + return + if new_env == '.factory_env': + factory_dir = os.path.join(home, new_env) + # new_factory = f'env_{datetime.datetime.now().strftime("%Y%m%d.%H%M%S")}' + new_factory_tmp = 'env_recent_copy_tmp' # Create a tmp dir, in case reboot occurs during the copy. + new_factory = 'env_recent_copy' # by using a static name I avoid cleaning up old directories. + new_dir_tmp = os.path.join(home, new_factory_tmp) + new_dir = os.path.join(home, new_factory) + scr.show_text("Resetting Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + os.system(f'rm -rf {new_dir_tmp}') + cmd = f'cp -r {factory_dir} {new_dir_tmp}' + fail = os.system(cmd) + if fail != 0: + scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + return + cmd = f'rm -rf {new_dir}' + os.system(cmd) + cmd = f'mv {new_dir_tmp} {new_dir}' + os.system(cmd) + else: + new_dir = os.path.join(home, new_env) + if os.path.isdir(new_dir): + make_link_cmd = f"ln -sfn {new_dir} {os.path.join(home,'timemachine')}" + fail = os.system(make_link_cmd) + if fail == 0: + cmd = "sudo reboot" + os.system(cmd) + sys.exit(-1) + scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + return + + +def welcome_alternatives(): + scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) + check_factory_build() + button = button_event.wait(0.2*parms.sleep_time) + button_event.clear() + if rewind_event.is_set(): + rewind_event.clear() + # remove wpa_supplicant.conf file + return True + if ffwd_event.is_set(): + scr.show_text("recalibrating ", font=scr.font, force=True, clear=True) + return True + if done_event.is_set(): + change_environment() + done_event.clear() + return False + + +def main(): + try: + scr.show_text("Booting\nUp ...", font=scr.font, force=True, clear=True) + cmd = "sudo rfkill unblock wifi" + os.system(cmd) + cmd = "sudo ifconfig wlan0 up" + os.system(cmd) + reconnect = welcome_alternatives() + + if reconnect or (parms.test) or not os.path.exists(parms.knob_sense_path): + try: + test_sound(parms) + test_all_buttons(parms) + configure_collections(parms) + save_knob_sense(parms) + + cmd = f'killall mpv' + os.system(cmd) + except Exception: + logger.info("Failed to save knob sense...continuing") + except Exception: + sys.exit(-1) + finally: + scr.clear() + + exit_success(sleeptime=0) + + +if __name__ == "__main__" and parms.debug == 0: + main() From ac0c183f28a5a21c3c10c033fe75e34c26e080a6 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 16:36:27 -0500 Subject: [PATCH 05/26] tuning calibration step --- timemachine/bin/connect_network.service | 1 + timemachine/calibrate.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/timemachine/bin/connect_network.service b/timemachine/bin/connect_network.service index c79871b..47e91ce 100644 --- a/timemachine/bin/connect_network.service +++ b/timemachine/bin/connect_network.service @@ -2,6 +2,7 @@ Description=Check Network Exists or Setup Network Wants=network.target After=systemd-user-sessions.service +After=calibrate.service [Service] Type=oneshot User=deadhead diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index cc3c39a..f909335 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -202,7 +202,6 @@ def year_button(button): stop.when_pressed = lambda x: stop_button(x) scr = controls.screen(upside_down=False) -scr.clear() def get_knob_orientation(knob, label): @@ -436,8 +435,8 @@ def select_chars(message, message2="So Far", character_set=string.printable): def exit_success(status=0, sleeptime=5): + scr.show_text("\n Please\n Stand By\n . . . ", color=(0, 255, 255), force=True, clear=True) sleep(sleeptime) - scr.clear() sys.exit(status) @@ -541,10 +540,8 @@ def main(): logger.info("Failed to save knob sense...continuing") except Exception: sys.exit(-1) - finally: - scr.clear() - exit_success(sleeptime=0) + exit_success(sleeptime=10) if __name__ == "__main__" and parms.debug == 0: From 06ba9d486e6a8cabfb9187dfa26c8afe4a637cb2 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 16:48:12 -0500 Subject: [PATCH 06/26] remove wpa_supplicant when required --- timemachine/calibrate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index f909335..dce97e1 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -19,6 +19,11 @@ parser = optparse.OptionParser() +parser.add_option('--wpa_path', + dest='wpa_path', + type="string", + default='/etc/wpa_supplicant/wpa_supplicant.conf', + help="path to wpa_supplicant file [default %default]") parser.add_option('--knob_sense_path', dest='knob_sense_path', type="string", @@ -508,6 +513,8 @@ def welcome_alternatives(): if rewind_event.is_set(): rewind_event.clear() # remove wpa_supplicant.conf file + cmd = F"sudo rm {parms.wpa_path}" + _ = subprocess.check_output(cmd, shell=True) return True if ffwd_event.is_set(): scr.show_text("recalibrating ", font=scr.font, force=True, clear=True) From 9801821a82ebe50e27a0cb2b4277e20344102885 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 23:36:02 -0500 Subject: [PATCH 07/26] don't clear screen upon exit --- timemachine/connect_network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index e3ca441..bcecca6 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -437,7 +437,6 @@ def get_ip(): def exit_success(status=0, sleeptime=5): sleep(sleeptime) - scr.clear() sys.exit(status) From f94b115ca53a14d69f2e6d8b2d40ee14f5086590 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 23:39:00 -0500 Subject: [PATCH 08/26] Welcome earlier --- timemachine/calibrate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index dce97e1..92ef2ad 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -527,12 +527,11 @@ def welcome_alternatives(): def main(): try: - scr.show_text("Booting\nUp ...", font=scr.font, force=True, clear=True) + reconnect = welcome_alternatives() cmd = "sudo rfkill unblock wifi" os.system(cmd) cmd = "sudo ifconfig wlan0 up" os.system(cmd) - reconnect = welcome_alternatives() if reconnect or (parms.test) or not os.path.exists(parms.knob_sense_path): try: From 5880df105928fa6bc319ce2f4c2f0957850e9e55 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Wed, 22 Dec 2021 23:54:21 -0500 Subject: [PATCH 09/26] try getting ip address outside of tenacity retry --- timemachine/connect_network.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index bcecca6..cfc0f9b 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -495,7 +495,14 @@ def main(): scr.clear() if wifi_connected(): - ip = retry_call(get_ip) + ip = None + i = 0 + while ip is None and i < 5: + try: + ip = get_ip() + i = i + 1 + except: + sleep(2) logger.info(F"Wifi connected\n{ip}") scr.show_text(F"Wifi connected\n{ip}", font=scr.smallfont, force=True, clear=True) exit_success(sleeptime=0.5*parms.sleep_time) From d0e0d394f33fc54245e4545e9cc7fec27d08ef80 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 10:54:08 -0500 Subject: [PATCH 10/26] moving to controls.Time_Machine_Board object --- timemachine/calibrate.py | 341 +++++++-------------------------------- timemachine/controls.py | 244 +++++++++++++++++++++++++++- 2 files changed, 305 insertions(+), 280 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 92ef2ad..af77c33 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -68,19 +68,6 @@ for k in parms.__dict__.keys(): print(F"{k:20s} : {parms.__dict__[k]}") -button_event = Event() -rewind_event = Event() -done_event = Event() # stop button -ffwd_event = Event() -play_pause_event = Event() -select_event = Event() -m_event = Event() -d_event = Event() -y_event = Event() -m_knob_event = Event() -d_knob_event = Event() -y_knob_event = Event() - @retry(stop=stop_after_delay(10)) def retry_call(callable: Callable, *args, **kwargs): @@ -88,161 +75,102 @@ def retry_call(callable: Callable, *args, **kwargs): return callable(*args, **kwargs) -class decade_counter(): - def __init__(self, tens: RotaryEncoder, ones: RotaryEncoder, bounds=(None, None)): - self.bounds = bounds - self.tens = tens - self.ones = ones - self.set_value(tens.steps, ones.steps) - - def set_value(self, tens_val, ones_val): - self.value = tens_val*10 + ones_val - if self.bounds[0] is not None: - self.value = max(self.value, self.bounds[0]) - if self.bounds[1] is not None: - self.value = min(self.value, self.bounds[1]) - self.tens.steps, self.ones.steps = divmod(self.value, 10) - return self.value - - def get_value(self): - return self.value - - -def decade_knob(knob: RotaryEncoder, label, counter: decade_counter): - if knob.is_active: - print(f"Knob {label} steps={knob.steps} value={knob.value}") - else: - if knob.steps < knob.threshold_steps[0]: - if label == "year" and d.steps > d.threshold_steps[0]: - knob.steps = knob.threshold_steps[1] - d.steps = max(d.threshold_steps[0], d.steps - 1) - else: - knob.steps = knob.threshold_steps[0] - if knob.steps > knob.threshold_steps[1]: - if label == "year" and d.steps < d.threshold_steps[1]: - knob.steps = knob.threshold_steps[0] - d.steps = min(d.threshold_steps[1], d.steps + 1) - else: - knob.steps = knob.threshold_steps[1] - print(f"Knob {label} is inactive") - counter.set_value(d.steps, y.steps) - if label == "month": - m_knob_event.set() - if label == "day": - d_knob_event.set() - if label == "year": - y_knob_event.set() - - def rewind_button(button): logger.debug("pressing or holding rewind") - button_event.set() - rewind_event.set() + TMB.button_event.set() + TMB.rewind_event.set() def select_button(button): logger.debug("pressing select") - select_event.set() + TMB.select_event.set() def stop_button(button): logger.debug("pressing stop") - button_event.set() - done_event.set() + TMB.button_event.set() + TMB.stop_event.set() def ffwd_button(button): logger.debug("pressing ffwd") - ffwd_event.set() + TMB.ffwd_event.set() def play_pause_button(button): logger.debug("pressing ffwd") - play_pause_event.set() + TMB.play_pause_event.set() def month_button(button): logger.debug("pressing or holding rewind") - m_event.set() + TMB.m_event.set() def day_button(button): logger.debug("pressing or holding rewind") - d_event.set() + TMB.d_event.set() def year_button(button): logger.debug("pressing or holding rewind") - y_event.set() + TMB.y_event.set() max_choices = len(string.printable) -m = retry_call(RotaryEncoder, config.month_pins[1], config.month_pins[0], max_steps=0, threshold_steps=(0, 9)) -d = retry_call(RotaryEncoder, config.day_pins[1], config.day_pins[0], max_steps=0, threshold_steps=(0, 1+divmod(max_choices-1, 10)[0])) -y = retry_call(RotaryEncoder, config.year_pins[1], config.year_pins[0], max_steps=0, threshold_steps=(0, 9)) -counter = decade_counter(d, y, bounds=(0, 100)) - -m_button = retry_call(Button, config.month_pins[2]) -d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) -y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) - -m.when_rotated = lambda x: decade_knob(m, "month", counter) -d.when_rotated = lambda x: decade_knob(d, "day", counter) -y.when_rotated = lambda x: decade_knob(y, "year", counter) - -rewind = retry_call(Button, config.rewind_pin) -ffwd = retry_call(Button, config.ffwd_pin) -play_pause = retry_call(Button, config.play_pause_pin) -select = retry_call(Button, config.select_pin, hold_time=2, hold_repeat=True) -stop = retry_call(Button, config.stop_pin) - -rewind.when_pressed = lambda x: rewind_button(x) -rewind.when_held = lambda x: rewind_button(x) -ffwd.when_pressed = lambda x: ffwd_button(x) -play_pause.when_pressed = lambda x: play_pause_button(x) -y_button.when_pressed = lambda x: year_button(x) -m_button.when_pressed = lambda x: month_button(x) -d_button.when_pressed = lambda x: day_button(x) -select.when_pressed = lambda x: select_button(x) -stop.when_pressed = lambda x: stop_button(x) - -scr = controls.screen(upside_down=False) + +TMB = controls.Time_Machine_Board(max_choices) + +TMB.rewind.when_pressed = lambda x: rewind_button(x) +TMB.rewind.when_held = lambda x: rewind_button(x) +TMB.ffwd.when_pressed = lambda x: ffwd_button(x) +TMB.play_pause.when_pressed = lambda x: play_pause_button(x) +TMB.y_button.when_pressed = lambda x: year_button(x) +TMB.m_button.when_pressed = lambda x: month_button(x) +TMB.d_button.when_pressed = lambda x: day_button(x) +TMB.select.when_pressed = lambda x: select_button(x) +TMB.stop.when_pressed = lambda x: stop_button(x) + +counter = controls.decade_counter(TMB.d, TMB.y, bounds=(0, 100)) +TMB.m.when_rotated = lambda x: TMB.decade_knob(TMB.m, "month", counter) +TMB.d.when_rotated = lambda x: TMB.decade_knob(TMB.d, "day", counter) +TMB.y.when_rotated = lambda x: TMB.decade_knob(TMB.y, "year", counter) def get_knob_orientation(knob, label): - m_knob_event.clear() - d_knob_event.clear() - y_knob_event.clear() + TMB.m_knob_event.clear() + TMB.d_knob_event.clear() + TMB.y_knob_event.clear() knob.steps = 0 before_value = knob.steps - scr.show_text("Calibrating knobs", font=scr.smallfont, force=False, clear=True) - scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) + TMB.scr.show_text("Calibrating knobs", font=TMB.scr.smallfont, force=False, clear=True) + TMB.scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=TMB.scr.boldsmall, color=(0, 255, 255), force=True) if label == "month": - m_knob_event.wait() + TMB.m_knob_event.wait() elif label == "day": - d_knob_event.wait() + TMB.d_knob_event.wait() elif label == "year": - y_knob_event.wait() + TMB.y_knob_event.wait() after_value = knob.steps return after_value > before_value def save_knob_sense(parms): - knob_senses = [get_knob_orientation(knob, label) for knob, label in [(m, "month"), (d, "day"), (y, "year")]] + knob_senses = [get_knob_orientation(knob, label) for knob, label in [(TMB.m, "month"), (TMB.d, "day"), (TMB.y, "year")]] knob_sense = 0 for i in range(len(knob_senses)): knob_sense += 1 << i if knob_senses[i] else 0 f = open(parms.knob_sense_path, 'w') f.write(str(knob_sense)) f.close() - scr.show_text("Knobs\nCalibrated", font=scr.boldsmall, color=(0, 255, 255), force=False, clear=True) - scr.show_text(F" {knob_sense}", font=scr.boldsmall, loc=(0, 60), force=True) + TMB.scr.show_text("Knobs\nCalibrated", font=TMB.scr.boldsmall, color=(0, 255, 255), force=False, clear=True) + TMB.scr.show_text(F" {knob_sense}", font=TMB.scr.boldsmall, loc=(0, 60), force=True) def test_buttons(event, label): event.clear() - scr.show_text("Testing Buttons", font=scr.smallfont, force=False, clear=True) - scr.show_text(F"Press {label}", loc=(0, 40), font=scr.boldsmall, color=(0, 255, 255), force=True) + TMB.scr.show_text("Testing Buttons", font=TMB.scr.smallfont, force=False, clear=True) + TMB.scr.show_text(F"Press {label}", loc=(0, 40), font=TMB.scr.boldsmall, color=(0, 255, 255), force=True) event.wait() @@ -259,10 +187,10 @@ def default_options(): def configure_collections(parms): """ is this a GratefulDead or a Phish Time Machine? """ - collection = select_option("Collection\nTurn Year, Select", ['GratefulDead', 'Phish', 'GratefulDead,Phish', 'other']) + collection = controls.select_option(TMB, counter, "Collection\nTurn Year, Select", ['GratefulDead', 'Phish', 'GratefulDead,Phish', 'other']) if collection == 'other': - collection = select_chars("Collection?\nSelect. Stop to end", character_set=string.printable[36:62]) - scr.show_text(f"Collection:\n{collection}", font=scr.smallfont, force=True, clear=True) + collection = controls.select_chars(TMB, counter, "Collection?\nSelect. Stop to end", character_set=string.printable[36:62]) + TMB.scr.show_text(f"Collection:\n{collection}", font=TMB.scr.smallfont, force=True, clear=True) #envs = get_envs() sleep(2) tmpd = default_options() @@ -290,157 +218,13 @@ def test_sound(parms): def test_all_buttons(parms): """ test that every button on the board works """ _ = [test_buttons(e, l) for e, l in - [(done_event, "stop"), (rewind_event, "rewind"), (ffwd_event, "ffwd"), (select_event, "select"), - (play_pause_event, "play/pause"), (m_event, "month"), (d_event, "day"), (y_event, "year")]] - scr.show_text("Testing Buttons\nSucceeded!", font=scr.smallfont, force=True, clear=True) - - -def select_option(message, chooser): - if type(chooser) == type(lambda: None): choices = chooser() - else: - choices = chooser - scr.clear() - counter.set_value(0, 0) - selected = None - screen_height = 5 - screen_width = 14 - update_now = scr.update_now - scr.update_now = False - done_event.clear() - rewind_event.clear() - select_event.clear() - - scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) - (text_width, text_height) = scr.smallfont.getsize(message) - - text_height = text_height + 1 - y_origin = text_height*(1+message.count('\n')) - selection_bbox = controls.Bbox(0, y_origin, 160, 128) - - while not select_event.is_set(): - if rewind_event.is_set(): - if type(chooser) == type(lambda: None): choices = chooser() - else: - choices = chooser - rewind_event.clear() - scr.clear_area(selection_bbox, force=False) - x_loc = 0 - y_loc = y_origin - step = divmod(counter.value, len(choices))[1] - - text = '\n'.join(choices[max(0, step-int(screen_height/2)):step]) - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=False) - y_loc = y_loc + text_height*(1+text.count('\n')) - - if len(choices[step]) > screen_width: - text = '>' + '..' + choices[step][-13:] - else: - text = '>' + choices[step] - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, color=(0, 0, 255), force=False) - y_loc = y_loc + text_height - - text = '\n'.join(choices[step+1:min(step+screen_height, len(choices))]) - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=True) - - sleep(0.01) - select_event.clear() - selected = choices[step] - # scr.show_text(F"So far: \n{selected}",loc=selected_bbox.origin(),color=(255,255,255),font=scr.smallfont,force=True) - - logger.info(F"word selected {selected}") - scr.update_now = update_now - return selected - - -def select_chars(message, message2="So Far", character_set=string.printable): - scr.clear() - selected = '' - counter.set_value(0, 1) - screen_width = 12 - update_now = scr.update_now - scr.update_now = False - done_event.clear() - select_event.clear() - - scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) - (text_width, text_height) = scr.smallfont.getsize(message) - - y_origin = text_height*(1+message.count('\n')) - selection_bbox = controls.Bbox(0, y_origin, 160, y_origin+22) - selected_bbox = controls.Bbox(0, y_origin+21, 160, 128) - - while not done_event.is_set(): - while not select_event.is_set() and not done_event.is_set(): - scr.clear_area(selection_bbox, force=False) - # scr.draw.rectangle((0,0,scr.width,scr.height),outline=0,fill=(0,0,0)) - x_loc = 0 - y_loc = y_origin - - text = 'DEL' - (text_width, text_height) = scr.oldfont.getsize(text) - if counter.value == 0: # we are deleting - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) - scr.show_text(character_set[:screen_width], loc=(x_loc + text_width, y_loc), font=scr.oldfont, force=True) - continue - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) - x_loc = x_loc + text_width - - # print the white before the red, if applicable - text = character_set[max(0, -1+counter.value-int(screen_width/2)):-1+counter.value] - for x in character_set[94:]: - text = text.replace(x, u'\u25A1') - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) - x_loc = x_loc + text_width - - # print the red character - text = character_set[-1+min(counter.value, len(character_set))] - if text == ' ': - text = "SPC" - elif text == '\t': - text = "\\t" - elif text == '\n': - text = "\\n" - elif text == '\r': - text = "\\r" - elif text == '\x0b': - text = "\\v" - elif text == '\x0c': - text = "\\f" - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) - x_loc = x_loc + text_width - - # print the white after the red, if applicable - text = character_set[counter.value:min(-1+counter.value+screen_width, len(character_set))] - for x in character_set[94:]: - text = text.replace(x, u'\u25A1') - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=True) - x_loc = x_loc + text_width - - sleep(0.1) - select_event.clear() - if done_event.is_set(): - continue - if counter.value == 0: - selected = selected[:-1] - scr.clear_area(selected_bbox, force=False) - else: - selected = selected + character_set[-1+counter.value] - scr.clear_area(selected_bbox, force=False) - scr.show_text(F"{message2}:\n{selected[-screen_width:]}", loc=selected_bbox.origin(), color=(255, 255, 255), font=scr.oldfont, force=True) - - logger.info(F"word selected {selected}") - scr.update_now = update_now - return selected + [(TMB.stop_event, "stop"), (TMB.rewind_event, "rewind"), (TMB.ffwd_event, "ffwd"), (TMB.select_event, "select"), + (TMB.play_pause_event, "play/pause"), (TMB.m_event, "month"), (TMB.d_event, "day"), (TMB.y_event, "year")]] + TMB.scr.show_text("Testing Buttons\nSucceeded!", font=TMB.scr.smallfont, force=True, clear=True) def exit_success(status=0, sleeptime=5): - scr.show_text("\n Please\n Stand By\n . . . ", color=(0, 255, 255), force=True, clear=True) + TMB.scr.show_text("Please\n Stand By\n . . . ", color=(0, 255, 255), force=True, clear=True) sleep(sleeptime) sys.exit(status) @@ -471,7 +255,7 @@ def get_envs(): def change_environment(): home = os.getenv('HOME') envs = get_envs() - new_env = select_option("Select an environment to use", envs) + new_env = controls.select_option(TMB, counter, "Select an environment to use", envs) if new_env == envs[0]: return if new_env == '.factory_env': @@ -481,12 +265,12 @@ def change_environment(): new_factory = 'env_recent_copy' # by using a static name I avoid cleaning up old directories. new_dir_tmp = os.path.join(home, new_factory_tmp) new_dir = os.path.join(home, new_factory) - scr.show_text("Resetting Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("Resetting Factory\nenvironment", font=TMB.scr.smallfont, force=True, clear=True) os.system(f'rm -rf {new_dir_tmp}') cmd = f'cp -r {factory_dir} {new_dir_tmp}' fail = os.system(cmd) if fail != 0: - scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("Failed to\nReset Factory\nenvironment", font=TMB.scr.smallfont, force=True, clear=True) return cmd = f'rm -rf {new_dir}' os.system(cmd) @@ -501,27 +285,27 @@ def change_environment(): cmd = "sudo reboot" os.system(cmd) sys.exit(-1) - scr.show_text("Failed to\nReset Factory\nenvironment", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("Failed to\nReset Factory\nenvironment", font=TMB.scr.smallfont, force=True, clear=True) return def welcome_alternatives(): - scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) + TMB.scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) check_factory_build() - button = button_event.wait(0.2*parms.sleep_time) - button_event.clear() - if rewind_event.is_set(): - rewind_event.clear() + button = TMB.button_event.wait(parms.sleep_time) + TMB.button_event.clear() + if TMB.rewind_event.is_set(): + TMB.rewind_event.clear() # remove wpa_supplicant.conf file cmd = F"sudo rm {parms.wpa_path}" _ = subprocess.check_output(cmd, shell=True) return True - if ffwd_event.is_set(): - scr.show_text("recalibrating ", font=scr.font, force=True, clear=True) + if TMB.ffwd_event.is_set(): + TMB.scr.show_text("recalibrating ", font=TMB.scr.font, force=True, clear=True) return True - if done_event.is_set(): + if TMB.stop_event.is_set(): change_environment() - done_event.clear() + TMB.stop_event.clear() return False @@ -540,14 +324,13 @@ def main(): configure_collections(parms) save_knob_sense(parms) - cmd = f'killall mpv' - os.system(cmd) + os.system('killall mpv') except Exception: logger.info("Failed to save knob sense...continuing") except Exception: sys.exit(-1) - exit_success(sleeptime=10) + exit_success(sleeptime=5) if __name__ == "__main__" and parms.debug == 0: diff --git a/timemachine/controls.py b/timemachine/controls.py index e4c11b6..68db5c0 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -17,8 +17,9 @@ import datetime import logging import os +import string from time import sleep -from threading import BoundedSemaphore +from threading import BoundedSemaphore, Event import adafruit_rgb_display.st7735 as st7735 import board @@ -26,6 +27,9 @@ from adafruit_rgb_display import color565 from gpiozero import Button, LED, RotaryEncoder from PIL import Image, ImageDraw, ImageFont +from tenacity import retry +from tenacity.stop import stop_after_delay +from typing import Callable import pkg_resources from timemachine import config @@ -42,6 +46,12 @@ QUIESCENT_TIME = 20 +@retry(stop=stop_after_delay(10)) +def retry_call(callable: Callable, *args, **kwargs): + """Retry a call.""" + return callable(*args, **kwargs) + + def with_state_semaphore(func): def inner(*args, **kwargs): try: @@ -168,6 +178,238 @@ def next_date(self): return self.date +class decade_counter(): + def __init__(self, tens: RotaryEncoder, ones: RotaryEncoder, bounds=(None, None)): + self.bounds = bounds + self.tens = tens + self.ones = ones + self.set_value(tens.steps, ones.steps) + + def set_value(self, tens_val, ones_val): + self.value = tens_val*10 + ones_val + if self.bounds[0] is not None: + self.value = max(self.value, self.bounds[0]) + if self.bounds[1] is not None: + self.value = min(self.value, self.bounds[1]) + self.tens.steps, self.ones.steps = divmod(self.value, 10) + return self.value + + def get_value(self): + return self.value + + +class Time_Machine_Board(): + """ TMB class describes and addresses the hardware of the Time Machine Board """ + + def __init__(self, max_choices, upside_down=False): + self.setup_events() + self.setup_buttons(max_choices) + self.setup_screen(upside_down) + + def setup_buttons(self, max_choices): + self.m = retry_call(RotaryEncoder, config.month_pins[1], config.month_pins[0], max_steps=0, threshold_steps=(0, 9)) + self.d = retry_call(RotaryEncoder, config.day_pins[1], config.day_pins[0], max_steps=0, threshold_steps=(0, 1+divmod(max_choices-1, 10)[0])) + self.y = retry_call(RotaryEncoder, config.year_pins[1], config.year_pins[0], max_steps=0, threshold_steps=(0, 9)) + + self.m_button = retry_call(Button, config.month_pins[2]) + self.d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) + self.y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) + + self.rewind = retry_call(Button, config.rewind_pin) + self.ffwd = retry_call(Button, config.ffwd_pin) + self.play_pause = retry_call(Button, config.play_pause_pin) + self.select = retry_call(Button, config.select_pin, hold_time=2, hold_repeat=True) + self.stop = retry_call(Button, config.stop_pin) + + def setup_screen(self, upside_down=False): + self.scr = screen(upside_down) + + def setup_events(self): + self.button_event = Event() + self.rewind_event = Event() + self.stop_event = Event() # stop button + self.ffwd_event = Event() + self.play_pause_event = Event() + self.select_event = Event() + self.m_event = Event() + self.d_event = Event() + self.y_event = Event() + self.m_knob_event = Event() + self.d_knob_event = Event() + self.y_knob_event = Event() + + def decade_knob(self, knob: RotaryEncoder, label, counter: decade_counter): + if knob.is_active: + print(f"Knob {label} steps={knob.steps} value={knob.value}") + else: + if knob.steps < knob.threshold_steps[0]: + if label == "year" and self.d.steps > self.d.threshold_steps[0]: + knob.steps = knob.threshold_steps[1] + self.d.steps = max(self.d.threshold_steps[0], self.d.steps - 1) + else: + knob.steps = knob.threshold_steps[0] + if knob.steps > knob.threshold_steps[1]: + if label == "year" and self.d.steps < self.d.threshold_steps[1]: + knob.steps = knob.threshold_steps[0] + self.d.steps = min(d.threshold_steps[1], self.d.steps + 1) + else: + knob.steps = knob.threshold_steps[1] + print(f"Knob {label} is inactive") + counter.set_value(self.d.steps, self.y.steps) + if label == "month": + self.m_knob_event.set() + if label == "day": + self.d_knob_event.set() + if label == "year": + self.y_knob_event.set() + + +def select_option(TMB, counter, message, chooser): + if type(chooser) == type(lambda: None): choices = chooser() + else: + choices = chooser + scr = TMB.scr + scr.clear() + counter.set_value(0, 0) + selected = None + screen_height = 5 + screen_width = 14 + update_now = scr.update_now + scr.update_now = False + TMB.stop_event.clear() + TMB.rewind_event.clear() + TMB.select_event.clear() + + scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) + (text_width, text_height) = scr.smallfont.getsize(message) + + text_height = text_height + 1 + y_origin = text_height*(1+message.count('\n')) + selection_bbox = Bbox(0, y_origin, 160, 128) + + while not TMB.select_event.is_set(): + if TMB.rewind_event.is_set(): + if type(chooser) == type(lambda: None): choices = chooser() + else: + choices = chooser + TMB.rewind_event.clear() + scr.clear_area(selection_bbox, force=False) + x_loc = 0 + y_loc = y_origin + step = divmod(counter.value, len(choices))[1] + + text = '\n'.join(choices[max(0, step-int(screen_height/2)):step]) + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=False) + y_loc = y_loc + text_height*(1+text.count('\n')) + + if len(choices[step]) > screen_width: + text = '>' + '..' + choices[step][-13:] + else: + text = '>' + choices[step] + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, color=(0, 0, 255), force=False) + y_loc = y_loc + text_height + + text = '\n'.join(choices[step+1:min(step+screen_height, len(choices))]) + (text_width, text_height) = scr.smallfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=True) + + sleep(0.01) + TMB.select_event.clear() + selected = choices[step] + # scr.show_text(F"So far: \n{selected}",loc=selected_bbox.origin(),color=(255,255,255),font=scr.smallfont,force=True) + + logger.info(F"word selected {selected}") + scr.update_now = update_now + return selected + + +def select_chars(TMB, counter, message, message2="So Far", character_set=string.printable): + scr = TMB.scr + scr.clear() + selected = '' + counter.set_value(0, 1) + screen_width = 12 + update_now = scr.update_now + scr.update_now = False + TMB.stop_event.clear() + TMB.select_event.clear() + + scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) + (text_width, text_height) = scr.smallfont.getsize(message) + + y_origin = text_height*(1+message.count('\n')) + selection_bbox = Bbox(0, y_origin, 160, y_origin+22) + selected_bbox = Bbox(0, y_origin+21, 160, 128) + + while not TMB.stop_event.is_set(): + while not TMB.select_event.is_set() and not TMB.stop_event.is_set(): + scr.clear_area(selection_bbox, force=False) + # scr.draw.rectangle((0,0,scr.width,scr.height),outline=0,fill=(0,0,0)) + x_loc = 0 + y_loc = y_origin + + text = 'DEL' + (text_width, text_height) = scr.oldfont.getsize(text) + if counter.value == 0: # we are deleting + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) + scr.show_text(character_set[:screen_width], loc=(x_loc + text_width, y_loc), font=scr.oldfont, force=True) + continue + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) + x_loc = x_loc + text_width + + # print the white before the red, if applicable + text = character_set[max(0, -1+counter.value-int(screen_width/2)):-1+counter.value] + for x in character_set[94:]: + text = text.replace(x, u'\u25A1') + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) + x_loc = x_loc + text_width + + # print the red character + text = character_set[-1+min(counter.value, len(character_set))] + if text == ' ': + text = "SPC" + elif text == '\t': + text = "\\t" + elif text == '\n': + text = "\\n" + elif text == '\r': + text = "\\r" + elif text == '\x0b': + text = "\\v" + elif text == '\x0c': + text = "\\f" + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) + x_loc = x_loc + text_width + + # print the white after the red, if applicable + text = character_set[counter.value:min(-1+counter.value+screen_width, len(character_set))] + for x in character_set[94:]: + text = text.replace(x, u'\u25A1') + (text_width, text_height) = scr.oldfont.getsize(text) + scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=True) + x_loc = x_loc + text_width + + sleep(0.1) + TMB.select_event.clear() + if TMB.stop_event.is_set(): + continue + if counter.value == 0: + selected = selected[:-1] + scr.clear_area(selected_bbox, force=False) + else: + selected = selected + character_set[-1+counter.value] + scr.clear_area(selected_bbox, force=False) + scr.show_text(F"{message2}:\n{selected[-screen_width:]}", loc=selected_bbox.origin(), color=(255, 255, 255), font=scr.oldfont, force=True) + + logger.info(F"word selected {selected}") + scr.update_now = update_now + return selected + + class Bbox: def __init__(self, x0, y0, x1, y1): self.corners = (x0, y0, x1, y1) From 29af3cc973c319911d073f117438535307f8d8a5 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 11:58:37 -0500 Subject: [PATCH 11/26] calibration using TMB object --- timemachine/calibrate.py | 40 ++++++++++++++++++++++------------------ timemachine/controls.py | 24 ++++++++++++++++++------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index af77c33..737cb17 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -24,11 +24,6 @@ type="string", default='/etc/wpa_supplicant/wpa_supplicant.conf', help="path to wpa_supplicant file [default %default]") -parser.add_option('--knob_sense_path', - dest='knob_sense_path', - type="string", - default=os.path.join(os.getenv('HOME'), ".knob_sense"), - help="path to file describing knob directions [default %default]") parser.add_option('-d', '--debug', dest='debug', type="int", @@ -55,6 +50,8 @@ help="Print more verbose information [default %default]") parms, remainder = parser.parse_args() +knob_sense_path = os.path.join(os.getenv('HOME'), ".knob_sense") + logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)s: %(name)s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') logger = logging.getLogger(__name__) controlsLogger = logging.getLogger('timemachine.controls') @@ -119,7 +116,7 @@ def year_button(button): max_choices = len(string.printable) -TMB = controls.Time_Machine_Board(max_choices) +TMB = controls.Time_Machine_Board(mdy_bounds=[(0, 9), (0, 1+divmod(max_choices-1, 10)[0]), (0, 9)]) TMB.rewind.when_pressed = lambda x: rewind_button(x) TMB.rewind.when_held = lambda x: rewind_button(x) @@ -152,19 +149,24 @@ def get_knob_orientation(knob, label): elif label == "year": TMB.y_knob_event.wait() after_value = knob.steps - return after_value > before_value + TMB.m_knob_event.clear() + TMB.d_knob_event.clear() + TMB.y_knob_event.clear() + bounds = knob.threshold_steps + return abs(after_value - bounds[0]) < abs(after_value - bounds[1]) -def save_knob_sense(parms): +def save_knob_sense(): knob_senses = [get_knob_orientation(knob, label) for knob, label in [(TMB.m, "month"), (TMB.d, "day"), (TMB.y, "year")]] + knob_sense_orig = TMB.get_knob_sense() knob_sense = 0 for i in range(len(knob_senses)): - knob_sense += 1 << i if knob_senses[i] else 0 - f = open(parms.knob_sense_path, 'w') - f.write(str(knob_sense)) + knob_sense += 1 << i if not knob_senses[i] else 0 + f = open(knob_sense_path, 'w') + f.write(str(knob_sense ^ knob_sense_orig)) f.close() TMB.scr.show_text("Knobs\nCalibrated", font=TMB.scr.boldsmall, color=(0, 255, 255), force=False, clear=True) - TMB.scr.show_text(F" {knob_sense}", font=TMB.scr.boldsmall, loc=(0, 60), force=True) + TMB.scr.show_text(F" {knob_sense ^ knob_sense_orig}", font=TMB.scr.boldsmall, loc=(0, 60), force=True) def test_buttons(event, label): @@ -297,10 +299,12 @@ def welcome_alternatives(): if TMB.rewind_event.is_set(): TMB.rewind_event.clear() # remove wpa_supplicant.conf file - cmd = F"sudo rm {parms.wpa_path}" - _ = subprocess.check_output(cmd, shell=True) + remove_wpa = controls.select_option(TMB, counter, "Forget WiFi?", ["No", "Yes"]) + if remove_wpa == "Yes": + cmd = F"sudo rm {parms.wpa_path}" + _ = subprocess.check_output(cmd, shell=True) return True - if TMB.ffwd_event.is_set(): + if TMB.play_pause_event.is_set(): TMB.scr.show_text("recalibrating ", font=TMB.scr.font, force=True, clear=True) return True if TMB.stop_event.is_set(): @@ -311,18 +315,18 @@ def welcome_alternatives(): def main(): try: - reconnect = welcome_alternatives() + recalibrate = welcome_alternatives() cmd = "sudo rfkill unblock wifi" os.system(cmd) cmd = "sudo ifconfig wlan0 up" os.system(cmd) - if reconnect or (parms.test) or not os.path.exists(parms.knob_sense_path): + if (parms.test) or not os.path.exists(knob_sense_path): try: test_sound(parms) test_all_buttons(parms) configure_collections(parms) - save_knob_sense(parms) + save_knob_sense() os.system('killall mpv') except Exception: diff --git a/timemachine/controls.py b/timemachine/controls.py index 68db5c0..2dfdd18 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -201,15 +201,16 @@ def get_value(self): class Time_Machine_Board(): """ TMB class describes and addresses the hardware of the Time Machine Board """ - def __init__(self, max_choices, upside_down=False): + def __init__(self, mdy_bounds=[(0, 9), (0, 9), (0, 9)], upside_down=False): self.setup_events() - self.setup_buttons(max_choices) + self.setup_buttons(mdy_bounds) self.setup_screen(upside_down) - def setup_buttons(self, max_choices): - self.m = retry_call(RotaryEncoder, config.month_pins[1], config.month_pins[0], max_steps=0, threshold_steps=(0, 9)) - self.d = retry_call(RotaryEncoder, config.day_pins[1], config.day_pins[0], max_steps=0, threshold_steps=(0, 1+divmod(max_choices-1, 10)[0])) - self.y = retry_call(RotaryEncoder, config.year_pins[1], config.year_pins[0], max_steps=0, threshold_steps=(0, 9)) + def setup_buttons(self, mdy_bounds): + knob_sense = self.get_knob_sense() + self.m = retry_call(RotaryEncoder, config.month_pins[knob_sense & 1], config.month_pins[~knob_sense & 1], max_steps=0, threshold_steps=mdy_bounds[0]) + self.d = retry_call(RotaryEncoder, config.day_pins[(knob_sense >> 1) & 1], config.day_pins[~(knob_sense >> 1) & 1], max_steps=0, threshold_steps=mdy_bounds[1]) + self.y = retry_call(RotaryEncoder, config.year_pins[(knob_sense >> 2) & 1], config.year_pins[~(knob_sense >> 2) & 1], max_steps=0, threshold_steps=mdy_bounds[2]) self.m_button = retry_call(Button, config.month_pins[2]) self.d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) @@ -263,6 +264,17 @@ def decade_knob(self, knob: RotaryEncoder, label, counter: decade_counter): if label == "year": self.y_knob_event.set() + def get_knob_sense(self): + knob_sense_path = os.path.join(os.getenv('HOME'), ".knob_sense") + try: + kfile = open(knob_sense_path, 'r') + knob_sense = int(kfile.read()) + kfile.close() + except Exception: + knob_sense = 0 + finally: + return knob_sense + def select_option(TMB, counter, message, chooser): if type(chooser) == type(lambda: None): choices = chooser() From 2dde47fd0384397b38c1167bd1522fe2a0a4d609 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 12:24:07 -0500 Subject: [PATCH 12/26] flake8 recommendations --- timemachine/calibrate.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 737cb17..3aaa645 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -1,21 +1,17 @@ -import datetime import json import logging import optparse import os -import re import string import subprocess import sys -from threading import Event from time import sleep -from gpiozero import RotaryEncoder, Button from tenacity import retry from tenacity.stop import stop_after_delay from typing import Callable -from timemachine import config, controls +from timemachine import controls parser = optparse.OptionParser() @@ -139,7 +135,6 @@ def get_knob_orientation(knob, label): TMB.d_knob_event.clear() TMB.y_knob_event.clear() knob.steps = 0 - before_value = knob.steps TMB.scr.show_text("Calibrating knobs", font=TMB.scr.smallfont, force=False, clear=True) TMB.scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=TMB.scr.boldsmall, color=(0, 255, 255), force=True) if label == "month": @@ -148,12 +143,11 @@ def get_knob_orientation(knob, label): TMB.d_knob_event.wait() elif label == "year": TMB.y_knob_event.wait() - after_value = knob.steps TMB.m_knob_event.clear() TMB.d_knob_event.clear() TMB.y_knob_event.clear() bounds = knob.threshold_steps - return abs(after_value - bounds[0]) < abs(after_value - bounds[1]) + return abs(knob.steps - bounds[0]) < abs(knob.steps - bounds[1]) def save_knob_sense(): @@ -193,7 +187,7 @@ def configure_collections(parms): if collection == 'other': collection = controls.select_chars(TMB, counter, "Collection?\nSelect. Stop to end", character_set=string.printable[36:62]) TMB.scr.show_text(f"Collection:\n{collection}", font=TMB.scr.smallfont, force=True, clear=True) - #envs = get_envs() + # envs = get_envs() sleep(2) tmpd = default_options() try: @@ -203,7 +197,7 @@ def configure_collections(parms): tmpd['COLLECTIONS'] = collection try: with open(parms.options_path, 'w') as outfile: - optd = json.dump(tmpd, outfile, indent=1) + json.dump(tmpd, outfile, indent=1) except Exception as e: logger.warning(F"Failed to write options to {parms.options_path}") @@ -211,7 +205,7 @@ def configure_collections(parms): def test_sound(parms): """ test that sound works """ try: - cmd = f'mpv --really-quiet ~/test_sound.ogg &' + cmd = 'mpv --really-quiet ~/test_sound.ogg &' os.system(cmd) except Exception as e: logger.warning("Failed to play sound file ~/test_sound.ogg") @@ -294,7 +288,7 @@ def change_environment(): def welcome_alternatives(): TMB.scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) check_factory_build() - button = TMB.button_event.wait(parms.sleep_time) + TMB.button_event.wait(parms.sleep_time) TMB.button_event.clear() if TMB.rewind_event.is_set(): TMB.rewind_event.clear() @@ -321,7 +315,7 @@ def main(): cmd = "sudo ifconfig wlan0 up" os.system(cmd) - if (parms.test) or not os.path.exists(knob_sense_path): + if recalibrate or (parms.test) or not os.path.exists(knob_sense_path): try: test_sound(parms) test_all_buttons(parms) From 190fa765f501cd5c775121b984a1637fa8862836 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 12:45:03 -0500 Subject: [PATCH 13/26] moving connect_network to new TMB object --- timemachine/connect_network.py | 317 +++++---------------------------- timemachine/controls.py | 2 +- 2 files changed, 46 insertions(+), 273 deletions(-) diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index cfc0f9b..1226247 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -24,20 +24,11 @@ type="string", default='/etc/wpa_supplicant/wpa_supplicant.conf', help="path to wpa_supplicant file [default %default]") -parser.add_option('--knob_sense_path', - dest='knob_sense_path', - type="string", - default=os.path.join(os.getenv('HOME'), ".knob_sense"), - help="path to file describing knob directions [default %default]") parser.add_option('-d', '--debug', dest='debug', type="int", default=1, help="If > 0, don't run the main script on loading [default %default]") -parser.add_option('--options_path', - dest='options_path', - default=os.path.join(os.getenv('HOME'), '.timemachine_options.txt'), - help="path to options file [default %default]") parser.add_option('--test', dest='test', action="store_true", @@ -68,19 +59,6 @@ for k in parms.__dict__.keys(): print(F"{k:20s} : {parms.__dict__[k]}") -button_event = Event() -rewind_event = Event() -done_event = Event() # stop button -ffwd_event = Event() -play_pause_event = Event() -select_event = Event() -m_event = Event() -d_event = Event() -y_event = Event() -m_knob_event = Event() -d_knob_event = Event() -y_knob_event = Event() - @retry(stop=stop_after_delay(10)) def retry_call(callable: Callable, *args, **kwargs): @@ -88,274 +66,69 @@ def retry_call(callable: Callable, *args, **kwargs): return callable(*args, **kwargs) -class decade_counter(): - def __init__(self, tens: RotaryEncoder, ones: RotaryEncoder, bounds=(None, None)): - self.bounds = bounds - self.tens = tens - self.ones = ones - self.set_value(tens.steps, ones.steps) - - def set_value(self, tens_val, ones_val): - self.value = tens_val*10 + ones_val - if self.bounds[0] is not None: - self.value = max(self.value, self.bounds[0]) - if self.bounds[1] is not None: - self.value = min(self.value, self.bounds[1]) - self.tens.steps, self.ones.steps = divmod(self.value, 10) - return self.value - - def get_value(self): - return self.value - - -def decade_knob(knob: RotaryEncoder, label, counter: decade_counter): - if knob.is_active: - print(f"Knob {label} steps={knob.steps} value={knob.value}") - else: - if knob.steps < knob.threshold_steps[0]: - if label == "year" and d.steps > d.threshold_steps[0]: - knob.steps = knob.threshold_steps[1] - d.steps = max(d.threshold_steps[0], d.steps - 1) - else: - knob.steps = knob.threshold_steps[0] - if knob.steps > knob.threshold_steps[1]: - if label == "year" and d.steps < d.threshold_steps[1]: - knob.steps = knob.threshold_steps[0] - d.steps = min(d.threshold_steps[1], d.steps + 1) - else: - knob.steps = knob.threshold_steps[1] - print(f"Knob {label} is inactive") - counter.set_value(d.steps, y.steps) - if label == "month": - m_knob_event.set() - if label == "day": - d_knob_event.set() - if label == "year": - y_knob_event.set() - - def rewind_button(button): logger.debug("pressing or holding rewind") - button_event.set() - rewind_event.set() + TMB.button_event.set() + TMB.rewind_event.set() def select_button(button): logger.debug("pressing select") - select_event.set() + TMB.select_event.set() def stop_button(button): logger.debug("pressing stop") - button_event.set() - done_event.set() + TMB.button_event.set() + TMB.stop_event.set() def ffwd_button(button): logger.debug("pressing ffwd") - ffwd_event.set() + TMB.ffwd_event.set() def play_pause_button(button): logger.debug("pressing ffwd") - play_pause_event.set() + TMB.play_pause_event.set() def month_button(button): logger.debug("pressing or holding rewind") - m_event.set() + TMB.m_event.set() def day_button(button): logger.debug("pressing or holding rewind") - d_event.set() + TMB.d_event.set() def year_button(button): logger.debug("pressing or holding rewind") - y_event.set() + TMB.y_event.set() max_choices = len(string.printable) -m = retry_call(RotaryEncoder, config.month_pins[1], config.month_pins[0], max_steps=0, threshold_steps=(0, 9)) -d = retry_call(RotaryEncoder, config.day_pins[1], config.day_pins[0], max_steps=0, threshold_steps=(0, 1+divmod(max_choices-1, 10)[0])) -y = retry_call(RotaryEncoder, config.year_pins[1], config.year_pins[0], max_steps=0, threshold_steps=(0, 9)) -counter = decade_counter(d, y, bounds=(0, 100)) - -m_button = retry_call(Button, config.month_pins[2]) -d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) -y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) - -m.when_rotated = lambda x: decade_knob(m, "month", counter) -d.when_rotated = lambda x: decade_knob(d, "day", counter) -y.when_rotated = lambda x: decade_knob(y, "year", counter) - -rewind = retry_call(Button, config.rewind_pin) -ffwd = retry_call(Button, config.ffwd_pin) -play_pause = retry_call(Button, config.play_pause_pin) -select = retry_call(Button, config.select_pin, hold_time=2, hold_repeat=True) -stop = retry_call(Button, config.stop_pin) - -rewind.when_pressed = lambda x: rewind_button(x) -rewind.when_held = lambda x: rewind_button(x) -ffwd.when_pressed = lambda x: ffwd_button(x) -play_pause.when_pressed = lambda x: play_pause_button(x) -y_button.when_pressed = lambda x: year_button(x) -m_button.when_pressed = lambda x: month_button(x) -d_button.when_pressed = lambda x: day_button(x) -select.when_pressed = lambda x: select_button(x) -stop.when_pressed = lambda x: stop_button(x) - -scr = controls.screen(upside_down=False) -scr.clear() - - -def select_option(message, chooser): - if type(chooser) == type(lambda: None): choices = chooser() - else: - choices = chooser - scr.clear() - counter.set_value(0, 0) - selected = None - screen_height = 5 - screen_width = 14 - update_now = scr.update_now - scr.update_now = False - done_event.clear() - rewind_event.clear() - select_event.clear() - - scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) - (text_width, text_height) = scr.smallfont.getsize(message) - - text_height = text_height + 1 - y_origin = text_height*(1+message.count('\n')) - selection_bbox = controls.Bbox(0, y_origin, 160, 128) - - while not select_event.is_set(): - if rewind_event.is_set(): - if type(chooser) == type(lambda: None): choices = chooser() - else: - choices = chooser - rewind_event.clear() - scr.clear_area(selection_bbox, force=False) - x_loc = 0 - y_loc = y_origin - step = divmod(counter.value, len(choices))[1] - - text = '\n'.join(choices[max(0, step-int(screen_height/2)):step]) - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=False) - y_loc = y_loc + text_height*(1+text.count('\n')) - - if len(choices[step]) > screen_width: - text = '>' + '..' + choices[step][-13:] - else: - text = '>' + choices[step] - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, color=(0, 0, 255), force=False) - y_loc = y_loc + text_height - - text = '\n'.join(choices[step+1:min(step+screen_height, len(choices))]) - (text_width, text_height) = scr.smallfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.smallfont, force=True) - - sleep(0.01) - select_event.clear() - selected = choices[step] - # scr.show_text(F"So far: \n{selected}",loc=selected_bbox.origin(),color=(255,255,255),font=scr.smallfont,force=True) - - logger.info(F"word selected {selected}") - scr.update_now = update_now - return selected - - -def select_chars(message, message2="So Far", character_set=string.printable): - scr.clear() - selected = '' - counter.set_value(0, 1) - screen_width = 12 - update_now = scr.update_now - scr.update_now = False - done_event.clear() - select_event.clear() - - scr.show_text(message, loc=(0, 0), font=scr.smallfont, color=(0, 255, 255), force=True) - (text_width, text_height) = scr.smallfont.getsize(message) - - y_origin = text_height*(1+message.count('\n')) - selection_bbox = controls.Bbox(0, y_origin, 160, y_origin+22) - selected_bbox = controls.Bbox(0, y_origin+21, 160, 128) - - while not done_event.is_set(): - while not select_event.is_set() and not done_event.is_set(): - scr.clear_area(selection_bbox, force=False) - # scr.draw.rectangle((0,0,scr.width,scr.height),outline=0,fill=(0,0,0)) - x_loc = 0 - y_loc = y_origin - - text = 'DEL' - (text_width, text_height) = scr.oldfont.getsize(text) - if counter.value == 0: # we are deleting - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) - scr.show_text(character_set[:screen_width], loc=(x_loc + text_width, y_loc), font=scr.oldfont, force=True) - continue - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) - x_loc = x_loc + text_width - - # print the white before the red, if applicable - text = character_set[max(0, -1+counter.value-int(screen_width/2)):-1+counter.value] - for x in character_set[94:]: - text = text.replace(x, u'\u25A1') - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=False) - x_loc = x_loc + text_width - - # print the red character - text = character_set[-1+min(counter.value, len(character_set))] - if text == ' ': - text = "SPC" - elif text == '\t': - text = "\\t" - elif text == '\n': - text = "\\n" - elif text == '\r': - text = "\\r" - elif text == '\x0b': - text = "\\v" - elif text == '\x0c': - text = "\\f" - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, color=(0, 0, 255), force=False) - x_loc = x_loc + text_width - - # print the white after the red, if applicable - text = character_set[counter.value:min(-1+counter.value+screen_width, len(character_set))] - for x in character_set[94:]: - text = text.replace(x, u'\u25A1') - (text_width, text_height) = scr.oldfont.getsize(text) - scr.show_text(text, loc=(x_loc, y_loc), font=scr.oldfont, force=True) - x_loc = x_loc + text_width - - sleep(0.1) - select_event.clear() - if done_event.is_set(): - continue - if counter.value == 0: - selected = selected[:-1] - scr.clear_area(selected_bbox, force=False) - else: - selected = selected + character_set[-1+counter.value] - scr.clear_area(selected_bbox, force=False) - scr.show_text(F"{message2}:\n{selected[-screen_width:]}", loc=selected_bbox.origin(), color=(255, 255, 255), font=scr.oldfont, force=True) +TMB = controls.Time_Machine_Board(mdy_bounds=[(0, 9), (0, 1+divmod(max_choices-1, 10)[0]), (0, 9)]) + +TMB.rewind.when_pressed = lambda x: rewind_button(x) +TMB.rewind.when_held = lambda x: rewind_button(x) +TMB.ffwd.when_pressed = lambda x: ffwd_button(x) +TMB.play_pause.when_pressed = lambda x: play_pause_button(x) +TMB.y_button.when_pressed = lambda x: year_button(x) +TMB.m_button.when_pressed = lambda x: month_button(x) +TMB.d_button.when_pressed = lambda x: day_button(x) +TMB.select.when_pressed = lambda x: select_button(x) +TMB.stop.when_pressed = lambda x: stop_button(x) - logger.info(F"word selected {selected}") - scr.update_now = update_now - return selected +counter = controls.decade_counter(TMB.d, TMB.y, bounds=(0, 100)) +TMB.m.when_rotated = lambda x: TMB.decade_knob(TMB.m, "month", counter) +TMB.d.when_rotated = lambda x: TMB.decade_knob(TMB.d, "day", counter) +TMB.y.when_rotated = lambda x: TMB.decade_knob(TMB.y, "year", counter) def wifi_connected(max_attempts=1): - scr.show_text("Checking for\nWifi connection", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("Checking for\nWifi connection", font=TMB.scr.smallfont, force=True, clear=True) logger.info("Checking if Wifi connected") cmd = "iwconfig" connected = False @@ -442,33 +215,33 @@ def exit_success(status=0, sleeptime=5): def get_wifi_params(): extra_dict = {} - country_code = select_option("Country Code\nTurn Year, Select", ['US', 'CA', 'GB', 'AU', 'FR', 'other']) + country_code = controls.select_option(TMB, counter, "Country Code\nTurn Year, Select", ['US', 'CA', 'GB', 'AU', 'FR', 'other']) if country_code == 'other': - country_code = select_chars("2 Letter\ncountry code\nSelect. Stop to end", character_set=string.printable[36:62]) + country_code = controls.select_chars(TMB, counter, "2 Letter\ncountry code\nSelect. Stop to end", character_set=string.printable[36:62]) extra_dict['country'] = country_code - scr.show_text("scanning networks\nPress rewind\nat any time\nto re-scan", - font=scr.smallfont, color=(0, 255, 255), force=True, clear=True) + TMB.scr.show_text("scanning networks\nPress rewind\nat any time\nto re-scan", + font=TMB.scr.smallfont, color=(0, 255, 255), force=True, clear=True) sleep(1) - wifi = select_option("Select Wifi Name\nTurn Year, Select", get_wifi_choices) + wifi = controls.select_option(TMB, counter, "Select Wifi Name\nTurn Year, Select", get_wifi_choices) if wifi == 'HIDDEN_WIFI': - wifi = select_chars("Input Wifi Name\nSelect. Stop to end") - passkey = select_chars("Passkey:Turn Year\nSelect. Stop to end", message2=wifi) + wifi = controls.select_chars(TMB, counter, "Input Wifi Name\nSelect. Stop to end") + passkey = controls.select_chars(TMB, counter, "Passkey:Turn Year\nSelect. Stop to end", message2=wifi) need_extra_fields = 'no' - need_extra_fields = select_option("Extra Fields\nRequired?", ['no', 'yes']) + need_extra_fields = controls.select_option(TMB, counter, "Extra Fields\nRequired?", ['no', 'yes']) while need_extra_fields == 'yes': fields = ['priority', 'scan_ssid', 'key_mgmt', 'bssid', 'mode', 'proto', 'auth_alg', 'pairwise', 'group', 'eapol_flags', 'eap', 'other'] - field_name = select_option("Field Name\nTurn Year, Select", fields) + field_name = controls.select_option(TMB, counter, "Field Name\nTurn Year, Select", fields) if field_name == 'other': - field_name = select_chars("Field Name:Turn Year\nSelect. Stop to end") - field_value = select_chars("Field Value:Turn Year\nSelect. Stop to end", message2=field_name) + field_name = controls.select_chars(TMB, counter, "Field Name:Turn Year\nSelect. Stop to end") + field_value = controls.select_chars(TMB, counter, "Field Value:Turn Year\nSelect. Stop to end", message2=field_name) extra_dict[field_name] = field_value - need_extra_fields = select_option("More Fields\nRequired?", ['no', 'yes']) + need_extra_fields = controls.select_option(TMB, counter, "More Fields\nRequired?", ['no', 'yes']) return wifi, passkey, extra_dict def main(): try: - scr.show_text("Connecting\nto WiFi", font=scr.font, force=True, clear=True) + TMB.scr.show_text("Connecting\nto WiFi", font=TMB.scr.font, force=True, clear=True) cmd = "sudo rfkill unblock wifi" os.system(cmd) cmd = "sudo ifconfig wlan0 up" @@ -476,23 +249,23 @@ def main(): connected = wifi_connected(max_attempts=3) eth_mac_address = get_mac_address() - scr.show_text(F"MAC addresses\neth0\n{eth_mac_address}", color=(0, 255, 255), font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text(F"MAC addresses\neth0\n{eth_mac_address}", color=(0, 255, 255), font=TMB.scr.smallfont, force=True, clear=True) sleep(1) if parms.test or not connected: wifi, passkey, extra_dict = get_wifi_params() - scr.show_text(F"wifi:\n{wifi}\npasskey:\n{passkey}", loc=(0, 0), color=(255, 255, 255), font=scr.oldfont, force=True, clear=True) + TMB.scr.show_text(F"wifi:\n{wifi}\npasskey:\n{passkey}", loc=(0, 0), color=(255, 255, 255), font=TMB.scr.oldfont, force=True, clear=True) update_wpa_conf(parms.wpa_path, wifi, passkey, extra_dict) cmd = "sudo killall -HUP wpa_supplicant" if not parms.test: os.system(cmd) else: print(F"not issuing command {cmd}") - scr.show_text("wifi connecting\n...", loc=(0, 0), color=(255, 255, 255), font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("wifi connecting\n...", loc=(0, 0), color=(255, 255, 255), font=TMB.scr.smallfont, force=True, clear=True) sleep(parms.sleep_time) except Exception: sys.exit(-1) finally: - scr.clear() + TMB.scr.clear() if wifi_connected(): ip = None @@ -504,10 +277,10 @@ def main(): except: sleep(2) logger.info(F"Wifi connected\n{ip}") - scr.show_text(F"Wifi connected\n{ip}", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text(F"Wifi connected\n{ip}", font=TMB.scr.smallfont, force=True, clear=True) exit_success(sleeptime=0.5*parms.sleep_time) else: - scr.show_text("Wifi connection\n\n\nRebooting", font=scr.smallfont, force=True, clear=True) + TMB.scr.show_text("Wifi connection\n\n\nRebooting", font=TMB.scr.smallfont, force=True, clear=True) cmd = "sudo reboot" os.system(cmd) sys.exit(-1) diff --git a/timemachine/controls.py b/timemachine/controls.py index 2dfdd18..23361e0 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -252,7 +252,7 @@ def decade_knob(self, knob: RotaryEncoder, label, counter: decade_counter): if knob.steps > knob.threshold_steps[1]: if label == "year" and self.d.steps < self.d.threshold_steps[1]: knob.steps = knob.threshold_steps[0] - self.d.steps = min(d.threshold_steps[1], self.d.steps + 1) + self.d.steps = min(self.d.threshold_steps[1], self.d.steps + 1) else: knob.steps = knob.threshold_steps[1] print(f"Knob {label} is inactive") From 56a973dff0487bcff6426fab9f79b0790a92332a Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 12:46:52 -0500 Subject: [PATCH 14/26] flake8 changes --- timemachine/connect_network.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index 1226247..43d45fb 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -1,5 +1,3 @@ -import datetime -import json import logging import optparse import os @@ -7,15 +5,13 @@ import string import subprocess import sys -from threading import Event from time import sleep -from gpiozero import RotaryEncoder, Button from tenacity import retry from tenacity.stop import stop_after_delay from typing import Callable -from timemachine import config, controls +from timemachine import controls parser = optparse.OptionParser() @@ -179,7 +175,7 @@ def update_wpa_conf(wpa_path, wifi, passkey, extra_dict): f = open(new_wpa_path, 'w') f.write('\n'.join(wpa)) cmd = F"sudo mv {new_wpa_path} {wpa_path}" - raw = subprocess.check_output(cmd, shell=True) + _ = subprocess.check_output(cmd, shell=True) cmd = F"sudo chown root {wpa_path}" _ = subprocess.check_output(cmd, shell=True) cmd = F"sudo chgrp root {wpa_path}" @@ -262,7 +258,7 @@ def main(): print(F"not issuing command {cmd}") TMB.scr.show_text("wifi connecting\n...", loc=(0, 0), color=(255, 255, 255), font=TMB.scr.smallfont, force=True, clear=True) sleep(parms.sleep_time) - except Exception: + except Exception as e: sys.exit(-1) finally: TMB.scr.clear() @@ -274,7 +270,7 @@ def main(): try: ip = get_ip() i = i + 1 - except: + except Exception as e: sleep(2) logger.info(F"Wifi connected\n{ip}") TMB.scr.show_text(F"Wifi connected\n{ip}", font=TMB.scr.smallfont, force=True, clear=True) From 5eafe97add9ab99cd6a4a9f6953fb40abd51e160 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 13:43:26 -0500 Subject: [PATCH 15/26] moving main.py to TMB object --- timemachine/controls.py | 21 +++- timemachine/main.py | 266 ++++++++++++++++++---------------------- 2 files changed, 138 insertions(+), 149 deletions(-) diff --git a/timemachine/controls.py b/timemachine/controls.py index 23361e0..e42ecdb 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -203,15 +203,19 @@ class Time_Machine_Board(): def __init__(self, mdy_bounds=[(0, 9), (0, 9), (0, 9)], upside_down=False): self.setup_events() - self.setup_buttons(mdy_bounds) + self.setup_knobs(mdy_bounds) + self.setup_buttons() self.setup_screen(upside_down) - def setup_buttons(self, mdy_bounds): + def setup_knobs(self, mdy_bounds): + if mdy_bounds is None: + return knob_sense = self.get_knob_sense() self.m = retry_call(RotaryEncoder, config.month_pins[knob_sense & 1], config.month_pins[~knob_sense & 1], max_steps=0, threshold_steps=mdy_bounds[0]) self.d = retry_call(RotaryEncoder, config.day_pins[(knob_sense >> 1) & 1], config.day_pins[~(knob_sense >> 1) & 1], max_steps=0, threshold_steps=mdy_bounds[1]) self.y = retry_call(RotaryEncoder, config.year_pins[(knob_sense >> 2) & 1], config.year_pins[~(knob_sense >> 2) & 1], max_steps=0, threshold_steps=mdy_bounds[2]) + def setup_buttons(self): self.m_button = retry_call(Button, config.month_pins[2]) self.d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) self.y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) @@ -227,6 +231,8 @@ def setup_screen(self, upside_down=False): def setup_events(self): self.button_event = Event() + self.knob_event = Event() + self.screen_event = Event() self.rewind_event = Event() self.stop_event = Event() # stop button self.ffwd_event = Event() @@ -239,6 +245,17 @@ def setup_events(self): self.d_knob_event = Event() self.y_knob_event = Event() + def twist_knob(self, knob: RotaryEncoder, label, date_reader: date_knob_reader): + if knob.is_active: + logger.debug(f"Knob {label} steps={knob.steps} value={knob.value}") + else: + if knob.steps < knob.threshold_steps[0]: + knob.steps = knob.threshold_steps[0] + if knob.steps > knob.threshold_steps[1]: + knob.steps = knob.threshold_steps[1] + logger.debug(f"Knob {label} is inactive") + date_reader.update() + def decade_knob(self, knob: RotaryEncoder, label, counter: decade_counter): if knob.is_active: print(f"Knob {label} steps={knob.steps} value={knob.value}") diff --git a/timemachine/main.py b/timemachine/main.py index 9a7880e..fe78697 100644 --- a/timemachine/main.py +++ b/timemachine/main.py @@ -50,11 +50,6 @@ dest='options_path', default=os.path.join(os.getenv('HOME'), '.timemachine_options.txt'), help="path to options file [default %default]") -parser.add_option('--knob_sense_path', - dest='knob_sense_path', - type="string", - default=os.path.join(os.getenv('HOME'), ".knob_sense"), - help="path to file describing knob directions [default %default]") parser.add_option('--test_update', dest='test_update', action="store_true", @@ -77,6 +72,8 @@ help="Print more verbose information [default %default]") parms, remainder = parser.parse_args() +knob_sense_path = os.path.join(os.getenv('HOME'), ".knob_sense") + logging.basicConfig(format='%(asctime)s.%(msecs)03d %(levelname)s: %(name)s %(message)s', level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') @@ -88,15 +85,9 @@ controlsLogger.setLevel(logging.WARN) stagedate_event = Event() -select_event = Event() track_event = Event() playstate_event = Event() -# busy_event = Event() free_event = Event() -stop_event = Event() -knob_event = Event() -button_event = Event() -screen_event = Event() stop_update_event = Event() stop_loop_event = Event() venue_counter = (0, 0) @@ -227,16 +218,8 @@ def load_options(parms): def twist_knob(knob: RotaryEncoder, label, date_reader: controls.date_knob_reader): - if knob.is_active: - logger.debug(f"Knob {label} steps={knob.steps} value={knob.value}") - else: - if knob.steps < knob.threshold_steps[0]: - knob.steps = knob.threshold_steps[0] - if knob.steps > knob.threshold_steps[1]: - knob.steps = knob.threshold_steps[1] - logger.debug(f"Knob {label} is inactive") - date_reader.update() - knob_event.set() + TMB.twist_knob(knob, label, date_reader) + TMB.knob_event.set() stagedate_event.set() @@ -270,7 +253,7 @@ def select_tape(tape, state, autoplay=False): logger.debug(F"current state {current}") if autoplay and not EOT: logger.debug("Autoplaying tape") - scr.show_playstate(staged_play=True, force=True) + TMB.scr.show_playstate(staged_play=True, force=True) state.player.play() current['PLAY_STATE'] = config.PLAYING playstate_event.set() @@ -282,13 +265,13 @@ def select_current_date(state, autoplay=False): date_reader = state.date_reader if not date_reader.tape_available(): return - scr.show_playstate(staged_play=True, force=True) + TMB.scr.show_playstate(staged_play=True, force=True) tapes = date_reader.archive.tape_dates[date_reader.fmtdate()] tape = tapes[date_reader.shownum] state = select_tape(tape, state, autoplay=autoplay) logger.debug(F"current state after selecting tape {state}") - select_event.set() + TMB.select_event.set() return state @@ -303,7 +286,7 @@ def select_button(button, state): if current['ON_TOUR'] and current['TOUR_STATE'] in [config.READY, config.PLAYING]: return state = select_current_date(state, autoplay=AUTO_PLAY) - scr.wake_up() + TMB.scr.wake_up() logger.debug(F"current state after select button {state}") return state @@ -328,13 +311,13 @@ def select_button_longpress(button, state): else: for i in range(0, max(1, len(tape_id)), 2): show_venue_text(tapes[itape], color=id_color, show_id=True, offset=i, force=True) - #scr.show_venue(tape_id[i:], color=id_color, force=True) + #TMB.scr.show_venue(tape_id[i:], color=id_color, force=True) if not button.is_held: break - scr.show_venue(tape_id, color=id_color) + TMB.scr.show_venue(tape_id, color=id_color) tape = tapes[itape] state = select_tape(tape, state, autoplay=AUTO_PLAY) - select_event.set() + TMB.select_event.set() @sequential @@ -358,9 +341,9 @@ def play_pause_button(button, state): current['PLAY_STATE'] = config.PAUSED elif current['PLAY_STATE'] in [config.PAUSED, config.STOPPED, config.READY]: current['PLAY_STATE'] = config.PLAYING - scr.wake_up() - screen_event.set() - scr.show_playstate(staged_play=True, force=True) # show that we've registered the button-press before blocking call. + TMB.scr.wake_up() + TMB.screen_event.set() + TMB.scr.show_playstate(staged_play=True, force=True) # show that we've registered the button-press before blocking call. state.player.play() # this is a blocking call. I could move the "wait_until_playing" to the event handler. state.set(current) playstate_event.set() @@ -373,7 +356,7 @@ def play_pause_button_longpress(button, state): current = state.get_current() if current['EXPERIENCE']: current['EXPERIENCE'] = False - scr.show_playstate(staged_play=True, force=True) # show that we've registered the button-press before blocking call. + TMB.scr.show_playstate(staged_play=True, force=True) # show that we've registered the button-press before blocking call. new_date = random.choice(state.date_reader.archive.dates) tape = state.date_reader.archive.best_tape(new_date) current['DATE'] = to_date(new_date) @@ -392,7 +375,7 @@ def play_pause_button_longpress(button, state): state.player.play() # this is a blocking call. I could move the "wait_until_playing" to the event handler. state.set(current) - select_event.set() + TMB.select_event.set() stagedate_event.set() playstate_event.set() @@ -406,7 +389,7 @@ def stop_button(button, state): return if current['PLAY_STATE'] in [config.READY, config.INIT, config.STOPPED]: return - button_event.set() + TMB.button_event.set() state.player.stop() current['PLAY_STATE'] = config.STOPPED current['PAUSED_AT'] = datetime.datetime.now() @@ -417,21 +400,21 @@ def stop_button(button, state): @sequential def stop_button_longpress(button, state): logger.debug(" longpress of stop button -- loading options menu") - pixels = scr.image.tobytes() - scr.show_experience(text="Hold 5s to Update\nCode and Restart", force=True) + pixels = TMB.scr.image.tobytes() + TMB.scr.show_experience(text="Hold 5s to Update\nCode and Restart", force=True) sleep(5) if button.is_held: - scr.clear() + TMB.scr.clear() cmd = "sudo service update start" os.system(cmd) - stop_event.set() - scr.wake_up() - scr.show_text("Updating\nCode\n\nStand By...", force=True) + TMB.stop_event.set() + TMB.scr.wake_up() + TMB.scr.show_text("Updating\nCode\n\nStand By...", force=True) sleep(20) - scr.show_text("Already\nat Latest\nRelease", clear=True, force=True) + TMB.scr.show_text("Already\nat Latest\nRelease", clear=True, force=True) sleep(5) - scr.image.frombytes(pixels) - scr.refresh(force=True) + TMB.scr.image.frombytes(pixels) + TMB.scr.refresh(force=True) # exit() @@ -511,7 +494,7 @@ def day_button(button, state): def day_button_longpress(button, state): logger.debug("long-pressing day button") - scr.sleep() + TMB.scr.sleep() @sequential @@ -549,7 +532,7 @@ def year_button_longpress(button, state): if not button.is_held: return ip_address = get_ip() - scr.show_experience(text=F"{ip_address}:9090\nto configure", force=True) + TMB.scr.show_experience(text=F"{ip_address}:9090\nto configure", force=True) sleep(2*button._hold_time) if not button.is_held: sleep(2*button._hold_time) @@ -557,20 +540,20 @@ def year_button_longpress(button, state): logger.debug(" longpress of year button") current = state.get_current() if current['ON_TOUR']: - scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\nHold 3s to exit", force=True) + TMB.scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\nHold 3s to exit", force=True) sleep(3) if button.is_held: logger.info(" EXITING ON_TOUR mode") current['ON_TOUR'] = False current['TOUR_YEAR'] = None current['TOUR_STATE'] = config.INIT - scr.show_experience(text=F"ON_TOUR: Finished\n{ip_address}", force=True) + TMB.scr.show_experience(text=F"ON_TOUR: Finished\n{ip_address}", force=True) elif config.optd['ON_TOUR_ALLOWED']: current['ON_TOUR'] = True current['TOUR_YEAR'] = state.date_reader.date.year current['TOUR_STATE'] = config.INIT logger.info(F" ---> ON_TOUR:{current['TOUR_YEAR']}") - scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\n{ip_address}", force=True) + TMB.scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\n{ip_address}", force=True) sleep(3) track_event.set() state.set(current) @@ -579,12 +562,12 @@ def year_button_longpress(button, state): def update_tracks(state): current = state.get_current() if current['EXPERIENCE']: - scr.show_experience() + TMB.scr.show_experience() elif current['ON_TOUR'] and current['TOUR_STATE'] in [config.READY, config.PLAYING]: - scr.show_experience(text=F"Hold Year to\nExit TOUR {current['TOUR_YEAR']}") + TMB.scr.show_experience(text=F"Hold Year to\nExit TOUR {current['TOUR_YEAR']}") else: - scr.show_track(current['TRACK_TITLE'], 0) - scr.show_track(current['NEXT_TRACK_TITLE'], 1) + TMB.scr.show_track(current['TRACK_TITLE'], 0) + TMB.scr.show_track(current['NEXT_TRACK_TITLE'], 1) def to_date(d): @@ -621,7 +604,7 @@ def play_on_tour(tape, state, seek_to=0): else: state.player.play() state.set(current) - select_event.set() + TMB.select_event.set() return @@ -689,15 +672,15 @@ def refresh_venue(state): # logger.debug(F"display_string is {display_string}") if not config.optd['SCROLL_VENUE']: - scr.show_venue(display_string, color=id_color) + TMB.scr.show_venue(display_string, color=id_color) return else: display_offset = min(max(0, len(display_string)-(screen_width-1)), screen_width*venue_counter[1]) if venue_counter[1] < n_subfields-1: display_offset = 0 if (display_offset < screen_width) else display_offset - scr.show_venue(display_string[display_offset:], color=id_color) + TMB.scr.show_venue(display_string[display_offset:], color=id_color) else: - scr.show_venue(display_string[-1*(screen_width-1):], color=id_color) + TMB.scr.show_venue(display_string[-1*(screen_width-1):], color=id_color) div, mod = divmod(venue_counter[1]+1, n_subfields) venue_counter = (divmod(venue_counter[0] + div, n_fields)[1], mod) @@ -716,28 +699,28 @@ def test_update(state): date_reader = state.date_reader last_sdevent = datetime.datetime.now() clear_stagedate = False - scr.update_now = False + TMB.scr.update_now = False free_event.set() stagedate_event.set() - knob_event.clear() - button_event.clear() - scr.clear() + TMB.knob_event.clear() + TMB.button_event.clear() + TMB.scr.clear() try: if parms.pid_to_kill is not None: os.system(F"kill {parms.pid_to_kill}") except Exception: pass try: - scr.show_text("Turn Any\nKnob", force=True) - if knob_event.wait(600): - knob_event.clear() - scr.clear() + TMB.scr.show_text("Turn Any\nKnob", force=True) + if TMB.knob_event.wait(600): + TMB.knob_event.clear() + TMB.scr.clear() else: sys.exit(-1) - scr.show_text("Press Stop\nButton", force=True) - if button_event.wait(120): - button_event.clear() - scr.show_text("Passed! ", force=True, clear=True) + TMB.scr.show_text("Press Stop\nButton", force=True) + if TMB.button_event.wait(120): + TMB.button_event.clear() + TMB.scr.show_text("Passed! ", force=True, clear=True) sys.exit(0) else: sys.exit(-1) @@ -769,13 +752,13 @@ def show_venue_text(arg, color=(0, 255, 255), show_id=False, offset=0, force=Fal venue_name = venue_name[offset:] artist_name = tape.artist num_events = 1 - scr.clear_area(scr.venue_bbox) - scr.show_text(venue_name, scr.venue_bbox.origin(), font=scr.boldsmall, color=color, force=force) + TMB.scr.clear_area(TMB.scr.venue_bbox) + TMB.scr.show_text(venue_name, TMB.scr.venue_bbox.origin(), font=TMB.scr.boldsmall, color=color, force=force) if len(config.optd['COLLECTIONS']) > 1: - scr.clear_area(scr.track1_bbox) - scr.show_text(artist_name, scr.track1_bbox.origin(), font=scr.boldsmall, color=color, force=True) + TMB.scr.clear_area(TMB.scr.track1_bbox) + TMB.scr.show_text(artist_name, TMB.scr.track1_bbox.origin(), font=TMB.scr.boldsmall, color=color, force=True) if num_events > 1: - scr.show_nevents(str(num_events), force=force) + TMB.scr.show_nevents(str(num_events), force=force) def event_loop(state, lock): @@ -792,10 +775,10 @@ def event_loop(state, lock): refresh_times = [4, 9, 14, 19, 24, 29, 34, 39, 44, 49] max_second_hand = 50 clear_stagedate = False - scr.update_now = False + TMB.scr.update_now = False free_event.set() stagedate_event.set() - scr.clear() + TMB.scr.clear() try: while not stop_loop_event.wait(timeout=0.001): @@ -823,7 +806,7 @@ def event_loop(state, lock): state.player.stop() current['TAPE_ID'] = None start_time = state.date_reader.archive.tape_start_time(then_time, default_start=default_start) - scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\nWaiting for show", force=True) + TMB.scr.show_experience(text=F"ON_TOUR:{current['TOUR_YEAR']}\nWaiting for show", force=True) then_date = then_time.date() random.seed(then_date.year + then_date.month + then_date.day) wait_time = random.randrange(60, 600) @@ -841,40 +824,40 @@ def event_loop(state, lock): track_event.set() logger.debug(F" ENDED!! TOUR_STATE is {current['TOUR_STATE']}, default_start: {default_start}") - if screen_event.is_set(): - scr.refresh() - screen_event.clear() + if TMB.screen_event.is_set(): + TMB.scr.refresh() + TMB.screen_event.clear() if stagedate_event.is_set(): last_sdevent = now q_counter = True - scr.show_staged_date(date_reader.date) + TMB.scr.show_staged_date(date_reader.date) show_venue_text(date_reader) # if clear_stagedate: stagedate_event.clear() # clear_stagedate = not clear_stagedate # only clear stagedate event after updating twice stagedate_event.clear() - scr.wake_up() - screen_event.set() + TMB.scr.wake_up() + TMB.screen_event.set() if track_event.is_set(): update_tracks(state) track_event.clear() - screen_event.set() - if select_event.is_set(): + TMB.screen_event.set() + if TMB.select_event.is_set(): current = state.get_current() - scr.show_selected_date(current['DATE']) + TMB.scr.show_selected_date(current['DATE']) update_tracks(state) - select_event.clear() - scr.wake_up() - screen_event.set() + TMB.select_event.clear() + TMB.scr.wake_up() + TMB.screen_event.set() if playstate_event.is_set(): - scr.show_playstate() + TMB.scr.show_playstate() playstate_event.clear() - screen_event.set() + TMB.screen_event.set() if q_counter and config.DATE and idle_seconds > QUIESCENT_TIME: logger.debug(F"Reverting staged date back to selected date {idle_seconds}> {QUIESCENT_TIME}") - scr.show_staged_date(config.DATE) - scr.show_venue(config.VENUE) + TMB.scr.show_staged_date(config.DATE) + TMB.scr.show_venue(config.VENUE) q_counter = False - screen_event.set() + TMB.screen_event.set() if idle_second_hand in refresh_times and idle_second_hand != last_idle_second_hand: last_idle_second_hand = idle_second_hand # if now.minute != last_idle_minute: @@ -893,16 +876,16 @@ def event_loop(state, lock): if current['PLAY_STATE'] != config.PLAYING: # deal with overnight pauses, which freeze the alsa player. if (now - config.PAUSED_AT).seconds > SLEEP_AFTER_SECONDS and state.player.get_prop('audio-device') != 'null': logger.debug(F"Paused at {config.PAUSED_AT}, sleeping after {SLEEP_AFTER_SECONDS}, now {now}") - scr.sleep() + TMB.scr.sleep() state.player._set_property('audio-device', 'null') state.player.wait_for_property('audio-device', lambda x: x == 'null') state.set(current) playstate_event.set() elif (now - current['WOKE_AT']).seconds > SLEEP_AFTER_SECONDS: - scr.sleep() + TMB.scr.sleep() if idle_seconds > QUIESCENT_TIME: if config.DATE: - scr.show_staged_date(config.DATE) + TMB.scr.show_staged_date(config.DATE) try: if current['PLAY_STATE'] > config.INIT: refresh_venue(state) @@ -910,9 +893,9 @@ def event_loop(state, lock): raise e logger.warning(f'event_loop, error refreshing venue {e}') else: - scr.show_staged_date(date_reader.date) + TMB.scr.show_staged_date(date_reader.date) show_venue_text(date_reader) - screen_event.set() + TMB.screen_event.set() lock.release() except KeyError as e: @@ -944,27 +927,20 @@ def get_ip(): config.PAUSED_AT = datetime.datetime.now() config.WOKE_AT = datetime.datetime.now() -scr = controls.screen(upside_down=False) +TMB = controls.Time_Machine_Board(mdy_bounds=None) ip_address = get_ip() -scr.show_text("Time\n Machine\n Loading...", color=(0, 255, 255), force=False, clear=True) -scr.show_text(F"{ip_address}", loc=(0, 100), font=scr.smallfont, color=(255, 255, 255)) +TMB.scr.show_text("Time\n Machine\n Loading...", color=(0, 255, 255), force=False, clear=True) +TMB.scr.show_text(F"{ip_address}", loc=(0, 100), font=TMB.scr.smallfont, color=(255, 255, 255)) if parms.test_update: config.optd = default_options() # no weirdness during update testing -# Define the buttons here -select = retry_call(Button, config.select_pin, hold_time=0.5, hold_repeat=False) -play_pause = retry_call(Button, config.play_pause_pin, hold_time=7) -ffwd = retry_call(Button, config.ffwd_pin, hold_time=0.5, hold_repeat=False) -rewind = retry_call(Button, config.rewind_pin, hold_time=0.5, hold_repeat=False) -stop = retry_call(Button, config.stop_pin, hold_time=7) - reload_ids = False -if rewind.is_pressed: - scr.show_text("Reloading\nfrom\narchive.org...", color=(0, 255, 255), force=True, clear=True) +if TMB.rewind.is_pressed: + TMB.scr.show_text("Reloading\nfrom\narchive.org...", color=(0, 255, 255), force=True, clear=True) logger.info('Reloading from archive.org') # reload_ids = True -if stop.is_pressed: +if TMB.stop.is_pressed: logger.info('Resetting to factory archive -- nyi') archive = Archivary.Archivary(parms.dbpath, reload_ids=reload_ids, with_latest=False, collection_list=config.optd['COLLECTIONS']) @@ -987,7 +963,7 @@ def my_handler(event): try: - kfile = open(parms.knob_sense_path, 'r') + kfile = open(knob_sense_path, 'r') knob_sense = int(kfile.read()) kfile.close() except Exception: @@ -995,56 +971,52 @@ def my_handler(event): year_list = archive.year_list() num_years = max(year_list) - min(year_list) -m = retry_call(RotaryEncoder, config.month_pins[knob_sense & 1], config.month_pins[~knob_sense & 1], max_steps=0, threshold_steps=(1, 12)) -d = retry_call(RotaryEncoder, config.day_pins[(knob_sense >> 1) & 1], config.day_pins[~(knob_sense >> 1) & 1], max_steps=0, threshold_steps=(1, 31)) -y = retry_call(RotaryEncoder, config.year_pins[(knob_sense >> 2) & 1], config.year_pins[~(knob_sense >> 2) & 1], max_steps=0, threshold_steps=(0, num_years)) +TMB.setup_knobs(mdy_bounds=[(1, 12), (1, 31), (0, num_years)]) + if 'GratefulDead' in archive.collection_list: - m.steps = 8 - d.steps = 13 - y.steps = min(max(0, 1975 - min(year_list)), num_years) + TMB.m.steps = 8 + TMB.d.steps = 13 + TMB.y.steps = min(max(0, 1975 - min(year_list)), num_years) else: - m.steps = 1 - d.steps = 1 - y.steps = 0 + TMB.m.steps = 1 + TMB.d.steps = 1 + TMB.y.steps = 0 -date_reader = controls.date_knob_reader(y, m, d, archive) +date_reader = controls.date_knob_reader(TMB.y, TMB.m, TMB.d, archive) if not 'GratefulDead' in archive.collection_list: date_reader.set_date(*date_reader.next_show()) state = controls.state(date_reader, player) -m.when_rotated = lambda x: twist_knob(m, "month", date_reader) -d.when_rotated = lambda x: twist_knob(d, "day", date_reader) -y.when_rotated = lambda x: twist_knob(y, "year", date_reader) -m_button = retry_call(Button, config.month_pins[2]) -d_button = retry_call(Button, config.day_pins[2], hold_time=0.3, hold_repeat=False) -y_button = retry_call(Button, config.year_pins[2], hold_time=0.5) +TMB.m.when_rotated = lambda x: twist_knob(TMB.m, "month", date_reader) +TMB.d.when_rotated = lambda x: twist_knob(TMB.d, "day", date_reader) +TMB.y.when_rotated = lambda x: twist_knob(TMB.y, "year", date_reader) -play_pause.when_pressed = lambda button: play_pause_button(button, state) -play_pause.when_held = lambda button: play_pause_button_longpress(button, state) +TMB.play_pause.when_pressed = lambda button: play_pause_button(button, state) +TMB.play_pause.when_held = lambda button: play_pause_button_longpress(button, state) -select.when_pressed = lambda button: select_button(button, state) -select.when_held = lambda button: select_button_longpress(button, state) +TMB.select.when_pressed = lambda button: select_button(button, state) +TMB.select.when_held = lambda button: select_button_longpress(button, state) -ffwd.when_pressed = lambda button: ffwd_button(button, state) -ffwd.when_held = lambda button: ffwd_button_longpress(button, state) +TMB.ffwd.when_pressed = lambda button: ffwd_button(button, state) +TMB.ffwd.when_held = lambda button: ffwd_button_longpress(button, state) -rewind.when_pressed = lambda button: rewind_button(button, state) -rewind.when_held = lambda button: rewind_button_longpress(button, state) +TMB.rewind.when_pressed = lambda button: rewind_button(button, state) +TMB.rewind.when_held = lambda button: rewind_button_longpress(button, state) -stop.when_pressed = lambda button: stop_button(button, state) -stop.when_held = lambda button: stop_button_longpress(button, state) +TMB.stop.when_pressed = lambda button: stop_button(button, state) +TMB.stop.when_held = lambda button: stop_button_longpress(button, state) -m_button.when_pressed = lambda button: month_button(button, state) -d_button.when_pressed = lambda button: day_button(button, state) -y_button.when_pressed = lambda button: year_button(button, state) +TMB.m_button.when_pressed = lambda button: month_button(button, state) +TMB.d_button.when_pressed = lambda button: day_button(button, state) +TMB.y_button.when_pressed = lambda button: year_button(button, state) -d_button.when_held = lambda button: day_button_longpress(button, state) +TMB.d_button.when_held = lambda button: day_button_longpress(button, state) # m_button.when_held = lambda button: month_button_longpress(button,state) -y_button.when_held = lambda button: year_button_longpress(button, state) +TMB.y_button.when_held = lambda button: year_button_longpress(button, state) -scr.clear_area(controls.Bbox(0, 0, 160, 100)) -scr.show_text("Powered by\n archive.org\n & phish.in", color=(0, 255, 255), force=True) -scr.show_text(str(len(archive.collection_list)).rjust(3), font=scr.boldsmall, loc=(120, 100), color=(255, 100, 0), force=True) +TMB.scr.clear_area(controls.Bbox(0, 0, 160, 100)) +TMB.scr.show_text("Powered by\n archive.org\n & phish.in", color=(0, 255, 255), force=True) +TMB.scr.show_text(str(len(archive.collection_list)).rjust(3), font=TMB.scr.boldsmall, loc=(120, 100), color=(255, 100, 0), force=True) if RELOAD_STATE_ON_START: load_saved_state(state) @@ -1055,7 +1027,7 @@ def my_handler(event): def main(): if config.optd['AUTO_UPDATE_ARCHIVE']: - archive_updater = Archivary.Archivary_Updater(state, 3600, stop_update_event, scr=scr, lock=lock) + archive_updater = Archivary.Archivary_Updater(state, 3600, stop_update_event, scr=TMB.scr, lock=lock) archive_updater.start() eloop.run() exit() From b64f8babb6dfdf86c4ea0dfd272c5a31e170e31e Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Thu, 23 Dec 2021 13:49:01 -0500 Subject: [PATCH 16/26] flake8 suggestions --- timemachine/main.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/timemachine/main.py b/timemachine/main.py index fe78697..210fc03 100644 --- a/timemachine/main.py +++ b/timemachine/main.py @@ -25,7 +25,6 @@ import threading import sys import time -from operator import methodcaller from threading import Event, Lock from time import sleep @@ -311,7 +310,7 @@ def select_button_longpress(button, state): else: for i in range(0, max(1, len(tape_id)), 2): show_venue_text(tapes[itape], color=id_color, show_id=True, offset=i, force=True) - #TMB.scr.show_venue(tape_id[i:], color=id_color, force=True) + # TMB.scr.show_venue(tape_id[i:], color=id_color, force=True) if not button.is_held: break TMB.scr.show_venue(tape_id, color=id_color) @@ -486,8 +485,6 @@ def day_button(button, state): if button.is_pressed or button.is_held: return logger.debug("pressing day button") - #new_date = state.date_reader.next_date() - # state.date_reader.set_date(new_date) state.date_reader.set_date(*state.date_reader.next_show()) stagedate_event.set() @@ -624,7 +621,7 @@ def refresh_venue(state): try: vcs = [x.strip() for x in config.VENUE.split(',')] - except: + except Exception: vcs = tape_id if tape_id is not None else '' artist = config.ARTIST if config.ARTIST is not None else '' @@ -698,7 +695,6 @@ def test_update(state): state.set(current) date_reader = state.date_reader last_sdevent = datetime.datetime.now() - clear_stagedate = False TMB.scr.update_now = False free_event.set() stagedate_event.set() @@ -763,6 +759,7 @@ def show_venue_text(arg, color=(0, 255, 255), show_id=False, offset=0, force=Fal def event_loop(state, lock): global venue_counter + key_error_count = 0 date_reader = state.date_reader last_sdevent = datetime.datetime.now() q_counter = False @@ -774,7 +771,7 @@ def event_loop(state, lock): last_idle_minute = now.minute refresh_times = [4, 9, 14, 19, 24, 29, 34, 39, 44, 49] max_second_hand = 50 - clear_stagedate = False + # clear_stagedate = False TMB.scr.update_now = False free_event.set() stagedate_event.set() @@ -953,7 +950,7 @@ def on_track_event(_name, value): if value is None: config.PLAY_STATE = config.ENDED config.PAUSED_AT = datetime.datetime.now() - select_button(select, state) + select_button(TMB.select, state) track_event.set() @@ -983,7 +980,7 @@ def my_handler(event): TMB.y.steps = 0 date_reader = controls.date_knob_reader(TMB.y, TMB.m, TMB.d, archive) -if not 'GratefulDead' in archive.collection_list: +if 'GratefulDead' not in archive.collection_list: date_reader.set_date(*date_reader.next_show()) state = controls.state(date_reader, player) From 71628954b251f77e89dccb7261b120e4d2be29f6 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:11:01 -0500 Subject: [PATCH 17/26] add a stand_by script to leave screen non-black --- setup.py | 6 ++++-- timemachine/bin/calibrate.service | 3 ++- timemachine/calibrate.py | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 8b17d2a..f39ba14 100644 --- a/setup.py +++ b/setup.py @@ -44,12 +44,14 @@ entry_points={'console_scripts': ['connect_network=timemachine.connect_network:main', 'calibrate=timemachine.calibrate:main', + 'stand_by=timemachine.calibrate:stand_by', 'serve_options=timemachine.serve_options:main', 'timemachine=timemachine.main:main', 'timemachine_test_update=timemachine.main:main_test_update']}, scripts=['timemachine/bin/services.sh', 'timemachine/bin/update.sh', 'timemachine/bin/board_version.sh', - 'timemachine/bin/timemachine.service', 'timemachine/bin/update.service', 'timemachine/bin/connect_network.service', - 'timemachine/bin/serve_options.service', 'timemachine/bin/calibrate.service'], + 'timemachine/bin/stand_by.sh', 'timemachine/bin/timemachine.service', 'timemachine/bin/update.service', + 'timemachine/bin/connect_network.service', 'timemachine/bin/serve_options.service', + 'timemachine/bin/calibrate.service'], license_files=('LICENSE',), license='GNU General Public License v3 (GPLv3)' ) diff --git a/timemachine/bin/calibrate.service b/timemachine/bin/calibrate.service index 373c221..f69360e 100644 --- a/timemachine/bin/calibrate.service +++ b/timemachine/bin/calibrate.service @@ -3,7 +3,8 @@ Description=Calibrate Time Machine [Service] Type=oneshot User=deadhead -ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && calibrate --debug 0" +ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && calibrate" +ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && stand_by.sh" RemainAfterExit=yes [Install] WantedBy=multi-user.target diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 3aaa645..9267f40 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -307,6 +307,12 @@ def welcome_alternatives(): return False +def stand_by(): + logger.info("in stand_by function") + TMB.scr.show_text("Standing By\n . . . ", color=(0, 255, 255), force=True, clear=True) + os.system(f'kill {os.getpid()}') + + def main(): try: recalibrate = welcome_alternatives() From 337cfbcae2bbb800bb0526baffa511680704d735 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:18:54 -0500 Subject: [PATCH 18/26] put standing by on the screen and kill process --- timemachine/bin/stand_by.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 timemachine/bin/stand_by.sh diff --git a/timemachine/bin/stand_by.sh b/timemachine/bin/stand_by.sh new file mode 100755 index 0000000..5fa5975 --- /dev/null +++ b/timemachine/bin/stand_by.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +stand_by +exit 0 From 699d495e69a6cb6eee1b51768dac659753f4e6c7 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:40:23 -0500 Subject: [PATCH 19/26] have calibrate kill itself so that screen doesn't black out --- setup.py | 3 +-- timemachine/bin/calibrate.service | 3 +-- timemachine/bin/calibrate.sh | 5 +++++ timemachine/calibrate.py | 13 +++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) create mode 100755 timemachine/bin/calibrate.sh diff --git a/setup.py b/setup.py index f39ba14..e632685 100644 --- a/setup.py +++ b/setup.py @@ -44,12 +44,11 @@ entry_points={'console_scripts': ['connect_network=timemachine.connect_network:main', 'calibrate=timemachine.calibrate:main', - 'stand_by=timemachine.calibrate:stand_by', 'serve_options=timemachine.serve_options:main', 'timemachine=timemachine.main:main', 'timemachine_test_update=timemachine.main:main_test_update']}, scripts=['timemachine/bin/services.sh', 'timemachine/bin/update.sh', 'timemachine/bin/board_version.sh', - 'timemachine/bin/stand_by.sh', 'timemachine/bin/timemachine.service', 'timemachine/bin/update.service', + 'timemachine/bin/calibrate.sh', 'timemachine/bin/timemachine.service', 'timemachine/bin/update.service', 'timemachine/bin/connect_network.service', 'timemachine/bin/serve_options.service', 'timemachine/bin/calibrate.service'], license_files=('LICENSE',), diff --git a/timemachine/bin/calibrate.service b/timemachine/bin/calibrate.service index f69360e..040e0eb 100644 --- a/timemachine/bin/calibrate.service +++ b/timemachine/bin/calibrate.service @@ -3,8 +3,7 @@ Description=Calibrate Time Machine [Service] Type=oneshot User=deadhead -ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && calibrate" -ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && stand_by.sh" +ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && calibrate.sh" RemainAfterExit=yes [Install] WantedBy=multi-user.target diff --git a/timemachine/bin/calibrate.sh b/timemachine/bin/calibrate.sh new file mode 100755 index 0000000..be303ae --- /dev/null +++ b/timemachine/bin/calibrate.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +calibrate + +exit 0 diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 9267f40..3d7465b 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -219,9 +219,12 @@ def test_all_buttons(parms): TMB.scr.show_text("Testing Buttons\nSucceeded!", font=TMB.scr.smallfont, force=True, clear=True) -def exit_success(status=0, sleeptime=5): +def exit_success(status=0, sleeptime=0): TMB.scr.show_text("Please\n Stand By\n . . . ", color=(0, 255, 255), force=True, clear=True) sleep(sleeptime) + if status == 0: + os.system(f'kill {os.getpid()}') # Killing the process like this will leave the message on the screen. + else sys.exit(status) @@ -307,12 +310,6 @@ def welcome_alternatives(): return False -def stand_by(): - logger.info("in stand_by function") - TMB.scr.show_text("Standing By\n . . . ", color=(0, 255, 255), force=True, clear=True) - os.system(f'kill {os.getpid()}') - - def main(): try: recalibrate = welcome_alternatives() @@ -334,7 +331,7 @@ def main(): except Exception: sys.exit(-1) - exit_success(sleeptime=5) + exit_success() if __name__ == "__main__" and parms.debug == 0: From 1299251b62bb38b3101c4bdae1d9b8fac83e39ea Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:40:40 -0500 Subject: [PATCH 20/26] removing file --- timemachine/bin/stand_by.sh | 4 ---- 1 file changed, 4 deletions(-) delete mode 100755 timemachine/bin/stand_by.sh diff --git a/timemachine/bin/stand_by.sh b/timemachine/bin/stand_by.sh deleted file mode 100755 index 5fa5975..0000000 --- a/timemachine/bin/stand_by.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -stand_by -exit 0 From 8147b701bdad66367f8fdffc4a8fc773a3210735 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:41:01 -0500 Subject: [PATCH 21/26] remove comment --- timemachine/bin/connect_network.service | 1 - 1 file changed, 1 deletion(-) diff --git a/timemachine/bin/connect_network.service b/timemachine/bin/connect_network.service index 47e91ce..73f8619 100644 --- a/timemachine/bin/connect_network.service +++ b/timemachine/bin/connect_network.service @@ -6,7 +6,6 @@ After=calibrate.service [Service] Type=oneshot User=deadhead -#ExecStart=/bin/sh -c 'while ! /usr/bin/python3 /home/deadhead/deadstream/timemachine/connect_network.py --debug 0; do sleep 5; done' ExecStart=/bin/bash -c "source /home/deadhead/timemachine/bin/activate && connect_network --debug 0" RemainAfterExit=yes [Install] From 1eb2d644be96e03e88f5035835bb8b5ef407adf4 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:42:49 -0500 Subject: [PATCH 22/26] syntax error fix --- timemachine/calibrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 3d7465b..7a63953 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -224,8 +224,8 @@ def exit_success(status=0, sleeptime=0): sleep(sleeptime) if status == 0: os.system(f'kill {os.getpid()}') # Killing the process like this will leave the message on the screen. - else - sys.exit(status) + else: + sys.exit(status) def check_factory_build(): From 2b2516499008340adaf312963433208d1008abcd Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 10:45:37 -0500 Subject: [PATCH 23/26] modified: .githooks/pre-commit --- .githooks/pre-commit | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index d37ee33..85f5d38 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -4,9 +4,9 @@ import os import sys -mycmd = "git describe --tags --abbrev=0 > timemachine/.latest_tag" -print(f"my command is {mycmd}") -os.system(mycmd) +#mycmd = "git describe --tags --abbrev=0 > timemachine/.latest_tag" +#print(f"my command is {mycmd}") +# os.system(mycmd) # we try our best, but the shebang of this script is difficult to determine: # - macos doesn't ship with python3 From 4bd9b0c0d698a2c66d7857ac240fe0a1e649838d Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 12:45:14 -0500 Subject: [PATCH 24/26] finally, I think the calibration of knobs is correct! --- timemachine/calibrate.py | 43 +++++++++++++++++++++++----------------- timemachine/controls.py | 12 ++++++++++- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 7a63953..121bbd9 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -91,7 +91,7 @@ def ffwd_button(button): def play_pause_button(button): - logger.debug("pressing ffwd") + logger.debug("pressing play_pause") TMB.play_pause_event.set() @@ -134,7 +134,8 @@ def get_knob_orientation(knob, label): TMB.m_knob_event.clear() TMB.d_knob_event.clear() TMB.y_knob_event.clear() - knob.steps = 0 + bounds = knob.threshold_steps + initial_value = knob.steps = int((bounds[0]+bounds[1])/2) TMB.scr.show_text("Calibrating knobs", font=TMB.scr.smallfont, force=False, clear=True) TMB.scr.show_text(F"Rotate {label}\nclockwise", loc=(0, 40), font=TMB.scr.boldsmall, color=(0, 255, 255), force=True) if label == "month": @@ -146,8 +147,8 @@ def get_knob_orientation(knob, label): TMB.m_knob_event.clear() TMB.d_knob_event.clear() TMB.y_knob_event.clear() - bounds = knob.threshold_steps - return abs(knob.steps - bounds[0]) < abs(knob.steps - bounds[1]) + logger.info(f'AFTER: knob {label} is {knob.steps}, initial_value {initial_value}. {knob.steps > initial_value}') + return knob.steps > initial_value def save_knob_sense(): @@ -155,12 +156,13 @@ def save_knob_sense(): knob_sense_orig = TMB.get_knob_sense() knob_sense = 0 for i in range(len(knob_senses)): - knob_sense += 1 << i if not knob_senses[i] else 0 + knob_sense += 1 << i if knob_senses[i] else 0 + new_knob_sense = 7 & ~(knob_sense ^ knob_sense_orig) f = open(knob_sense_path, 'w') - f.write(str(knob_sense ^ knob_sense_orig)) + f.write(str(new_knob_sense)) f.close() TMB.scr.show_text("Knobs\nCalibrated", font=TMB.scr.boldsmall, color=(0, 255, 255), force=False, clear=True) - TMB.scr.show_text(F" {knob_sense ^ knob_sense_orig}", font=TMB.scr.boldsmall, loc=(0, 60), force=True) + TMB.scr.show_text(F" {new_knob_sense}", font=TMB.scr.boldsmall, loc=(0, 60), force=True) def test_buttons(event, label): @@ -292,33 +294,38 @@ def welcome_alternatives(): TMB.scr.show_text("\n Welcome", color=(0, 0, 255), force=True, clear=True) check_factory_build() TMB.button_event.wait(parms.sleep_time) - TMB.button_event.clear() if TMB.rewind_event.is_set(): - TMB.rewind_event.clear() + TMB.clear_events() # remove wpa_supplicant.conf file remove_wpa = controls.select_option(TMB, counter, "Forget WiFi?", ["No", "Yes"]) if remove_wpa == "Yes": cmd = F"sudo rm {parms.wpa_path}" _ = subprocess.check_output(cmd, shell=True) return True - if TMB.play_pause_event.is_set(): - TMB.scr.show_text("recalibrating ", font=TMB.scr.font, force=True, clear=True) - return True if TMB.stop_event.is_set(): + TMB.clear_events() change_environment() - TMB.stop_event.clear() + return True + if TMB.button_event.is_set(): + TMB.clear_events() + TMB.scr.show_text("recalibrating ", font=TMB.scr.font, force=True, clear=True) + return True return False +def unblock_wifi(): + cmd = "sudo rfkill unblock wifi" + os.system(cmd) + cmd = "sudo ifconfig wlan0 up" + os.system(cmd) + + def main(): try: recalibrate = welcome_alternatives() - cmd = "sudo rfkill unblock wifi" - os.system(cmd) - cmd = "sudo ifconfig wlan0 up" - os.system(cmd) + unblock_wifi() - if recalibrate or (parms.test) or not os.path.exists(knob_sense_path): + if recalibrate or parms.test or not os.path.exists(knob_sense_path): try: test_sound(parms) test_all_buttons(parms) diff --git a/timemachine/controls.py b/timemachine/controls.py index e42ecdb..b9e4365 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -202,7 +202,9 @@ class Time_Machine_Board(): """ TMB class describes and addresses the hardware of the Time Machine Board """ def __init__(self, mdy_bounds=[(0, 9), (0, 9), (0, 9)], upside_down=False): + self.events = [] self.setup_events() + self.clear_events() self.setup_knobs(mdy_bounds) self.setup_buttons() self.setup_screen(upside_down) @@ -244,6 +246,11 @@ def setup_events(self): self.m_knob_event = Event() self.d_knob_event = Event() self.y_knob_event = Event() + self.events = [self.button_event, self.knob_event, self.screen_event, self.rewind_event, self.stop_event, self.ffwd_event, self.play_pause_event, + self.select_event, self.m_event, self.d_event, self.y_event, self.m_knob_event, self.d_knob_event, self.y_knob_event] + + def clear_events(self): + _ = [x.clear() for x in self.events] def twist_knob(self, knob: RotaryEncoder, label, date_reader: date_knob_reader): if knob.is_active: @@ -286,8 +293,11 @@ def get_knob_sense(self): try: kfile = open(knob_sense_path, 'r') knob_sense = int(kfile.read()) + if knob_sense > 7 or knob_sense < 0: + raise ValueError kfile.close() - except Exception: + except Exception as e: + logger.warning(f'error in get_knob_sense {e}. Setting knob_sense to 0') knob_sense = 0 finally: return knob_sense From 83e4fc5cf905b6153b24bd04a5a94b829d959aa2 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 12:45:47 -0500 Subject: [PATCH 25/26] the next release will be 1.1.0. But this isn't it until it has the tag --- timemachine/.latest_tag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timemachine/.latest_tag b/timemachine/.latest_tag index b18d465..795460f 100644 --- a/timemachine/.latest_tag +++ b/timemachine/.latest_tag @@ -1 +1 @@ -v1.0.1 +v1.1.0 From f7424e6bd5f6b512f6b54676dc1e5e776dac2647 Mon Sep 17 00:00:00 2001 From: Steve Eichblatt Date: Fri, 24 Dec 2021 13:28:11 -0500 Subject: [PATCH 26/26] move basic button functions to Time_Machine_Board --- timemachine/calibrate.py | 60 +++++---------------------------- timemachine/connect_network.py | 61 ++++++---------------------------- timemachine/controls.py | 40 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 102 deletions(-) diff --git a/timemachine/calibrate.py b/timemachine/calibrate.py index 121bbd9..539d7cc 100644 --- a/timemachine/calibrate.py +++ b/timemachine/calibrate.py @@ -68,61 +68,19 @@ def retry_call(callable: Callable, *args, **kwargs): return callable(*args, **kwargs) -def rewind_button(button): - logger.debug("pressing or holding rewind") - TMB.button_event.set() - TMB.rewind_event.set() - - -def select_button(button): - logger.debug("pressing select") - TMB.select_event.set() - - -def stop_button(button): - logger.debug("pressing stop") - TMB.button_event.set() - TMB.stop_event.set() - - -def ffwd_button(button): - logger.debug("pressing ffwd") - TMB.ffwd_event.set() - - -def play_pause_button(button): - logger.debug("pressing play_pause") - TMB.play_pause_event.set() - - -def month_button(button): - logger.debug("pressing or holding rewind") - TMB.m_event.set() - - -def day_button(button): - logger.debug("pressing or holding rewind") - TMB.d_event.set() - - -def year_button(button): - logger.debug("pressing or holding rewind") - TMB.y_event.set() - - max_choices = len(string.printable) TMB = controls.Time_Machine_Board(mdy_bounds=[(0, 9), (0, 1+divmod(max_choices-1, 10)[0]), (0, 9)]) -TMB.rewind.when_pressed = lambda x: rewind_button(x) -TMB.rewind.when_held = lambda x: rewind_button(x) -TMB.ffwd.when_pressed = lambda x: ffwd_button(x) -TMB.play_pause.when_pressed = lambda x: play_pause_button(x) -TMB.y_button.when_pressed = lambda x: year_button(x) -TMB.m_button.when_pressed = lambda x: month_button(x) -TMB.d_button.when_pressed = lambda x: day_button(x) -TMB.select.when_pressed = lambda x: select_button(x) -TMB.stop.when_pressed = lambda x: stop_button(x) +TMB.rewind.when_pressed = lambda x: TMB.rewind_button(x) +TMB.rewind.when_held = lambda x: TMB.rewind_button(x) +TMB.ffwd.when_pressed = lambda x: TMB.ffwd_button(x) +TMB.play_pause.when_pressed = lambda x: TMB.play_pause_button(x) +TMB.y_button.when_pressed = lambda x: TMB.year_button(x) +TMB.m_button.when_pressed = lambda x: TMB.month_button(x) +TMB.d_button.when_pressed = lambda x: TMB.day_button(x) +TMB.select.when_pressed = lambda x: TMB.select_button(x) +TMB.stop.when_pressed = lambda x: TMB.stop_button(x) counter = controls.decade_counter(TMB.d, TMB.y, bounds=(0, 100)) TMB.m.when_rotated = lambda x: TMB.decade_knob(TMB.m, "month", counter) diff --git a/timemachine/connect_network.py b/timemachine/connect_network.py index 43d45fb..57935fc 100644 --- a/timemachine/connect_network.py +++ b/timemachine/connect_network.py @@ -62,60 +62,18 @@ def retry_call(callable: Callable, *args, **kwargs): return callable(*args, **kwargs) -def rewind_button(button): - logger.debug("pressing or holding rewind") - TMB.button_event.set() - TMB.rewind_event.set() - - -def select_button(button): - logger.debug("pressing select") - TMB.select_event.set() - - -def stop_button(button): - logger.debug("pressing stop") - TMB.button_event.set() - TMB.stop_event.set() - - -def ffwd_button(button): - logger.debug("pressing ffwd") - TMB.ffwd_event.set() - - -def play_pause_button(button): - logger.debug("pressing ffwd") - TMB.play_pause_event.set() - - -def month_button(button): - logger.debug("pressing or holding rewind") - TMB.m_event.set() - - -def day_button(button): - logger.debug("pressing or holding rewind") - TMB.d_event.set() - - -def year_button(button): - logger.debug("pressing or holding rewind") - TMB.y_event.set() - - max_choices = len(string.printable) TMB = controls.Time_Machine_Board(mdy_bounds=[(0, 9), (0, 1+divmod(max_choices-1, 10)[0]), (0, 9)]) -TMB.rewind.when_pressed = lambda x: rewind_button(x) -TMB.rewind.when_held = lambda x: rewind_button(x) -TMB.ffwd.when_pressed = lambda x: ffwd_button(x) -TMB.play_pause.when_pressed = lambda x: play_pause_button(x) -TMB.y_button.when_pressed = lambda x: year_button(x) -TMB.m_button.when_pressed = lambda x: month_button(x) -TMB.d_button.when_pressed = lambda x: day_button(x) -TMB.select.when_pressed = lambda x: select_button(x) -TMB.stop.when_pressed = lambda x: stop_button(x) +TMB.rewind.when_pressed = lambda x: TMB.rewind_button(x) +TMB.rewind.when_held = lambda x: TMB.rewind_button(x) +TMB.ffwd.when_pressed = lambda x: TMB.ffwd_button(x) +TMB.play_pause.when_pressed = lambda x: TMB.play_pause_button(x) +TMB.y_button.when_pressed = lambda x: TMB.year_button(x) +TMB.m_button.when_pressed = lambda x: TMB.month_button(x) +TMB.d_button.when_pressed = lambda x: TMB.day_button(x) +TMB.select.when_pressed = lambda x: TMB.select_button(x) +TMB.stop.when_pressed = lambda x: TMB.stop_button(x) counter = controls.decade_counter(TMB.d, TMB.y, bounds=(0, 100)) TMB.m.when_rotated = lambda x: TMB.decade_knob(TMB.m, "month", counter) @@ -261,6 +219,7 @@ def main(): except Exception as e: sys.exit(-1) finally: + TMB.clear_events() TMB.scr.clear() if wifi_connected(): diff --git a/timemachine/controls.py b/timemachine/controls.py index b9e4365..949a4ba 100644 --- a/timemachine/controls.py +++ b/timemachine/controls.py @@ -302,6 +302,46 @@ def get_knob_sense(self): finally: return knob_sense + def rewind_button(self, button): + logger.debug("pressing or holding rewind") + self.button_event.set() + self.rewind_event.set() + + def select_button(self, button): + logger.debug("pressing select") + self.button_event.set() + self.select_event.set() + + def stop_button(self, button): + logger.debug("pressing stop") + self.button_event.set() + self.stop_event.set() + + def ffwd_button(self, button): + logger.debug("pressing ffwd") + self.button_event.set() + self.ffwd_event.set() + + def play_pause_button(self, button): + logger.debug("pressing play_pause") + self.button_event.set() + self.play_pause_event.set() + + def month_button(self, button): + logger.debug("pressing or holding rewind") + self.button_event.set() + self.m_event.set() + + def day_button(self, button): + logger.debug("pressing or holding rewind") + self.button_event.set() + self.d_event.set() + + def year_button(self, button): + logger.debug("pressing or holding rewind") + self.button_event.set() + self.y_event.set() + def select_option(TMB, counter, message, chooser): if type(chooser) == type(lambda: None): choices = chooser()