class ClockApp(ZeroApp, Refresher, CenteredTextRenderer): def __init__(self, i, o, *args, **kwargs): super(ClockApp, self).__init__(i, o) self.menu_name = "Clock" self.refresher = Refresher(self.on_refresh, i, o) default_config = '{}' config_filename = "config.json" self.config = read_or_create_config(local_path(config_filename), default_config, self.menu_name+" app") def draw_analog_clock(self, draw, time, radius="min(*self.o.device.size) / 3", clock_x = "center_x+32", clock_y = "center_y+5", h_len = "radius / 2", m_len = "radius - 5", s_len = "radius - 3", **kwargs): """Draws the analog clock, with parameters configurable through config.txt.""" center_x, center_y = self.get_center(self.o.device.size) clock_x = eval(clock_x) clock_y = eval(clock_y) radius = eval(radius) draw.ellipse((clock_x - radius, clock_y - radius, clock_x + radius, clock_y + radius), fill=False, outline="white") self.draw_needle(draw, 60 - time.second / 60, eval(s_len), clock_x, clock_y, 1) self.draw_needle(draw, 60 - time.minute / 60, eval(m_len), clock_x, clock_y, 1) self.draw_needle(draw, 24 - time.hour / 24, eval(h_len), clock_x, clock_y, 1) def draw_text(self, draw, time, text_x="10", text_y="center_y-5", time_format = "%H:%M:%S", **kwargs): """Draws the digital clock, with parameters configurable through config.txt.""" time_str = time.strftime(time_format) center_x, center_y = self.get_center(self.o.device.size) bounds = self.get_centered_text_bounds(draw, time_str, self.o.device.size) x = eval(text_x) y = eval(text_y) draw.text((x, y), time_str, fill="white") def on_refresh(self): current_time = datetime.now() return self.render_clock(current_time, **self.config) def render_clock(self, time, **kwargs): c = canvas(self.o.device) c.__enter__() width, height = c.device.size draw = c.draw self.draw_text(draw, time, **kwargs) self.draw_analog_clock(draw, time, **kwargs) return c.image def draw_needle(self, draw, progress, radius, x, y, width): # type: (ImageDraw, float, float, float, float, int) -> None hour_angle = math.pi * 2 * progress + math.pi draw.line( ( x, y, x + radius * math.sin(hour_angle), y + radius * math.cos(hour_angle) ), width=width, fill=True ) def on_start(self): super(ClockApp, self).on_start() self.refresher.activate()
class StopwatchApp(ZeroApp): def __init__(self, i, o): super(StopwatchApp, self).__init__(i, o) self.menu_name = "Stopwatch" self.counter = Chronometer() self.refresher = None self.__instructions = [ "", "UP/ENTER to start/pause", "RIGHT : restart", "DOWN : reset" ] def on_start(self): self.refresher = Refresher( self.refresh_function, self.i, self.o, .1, { "KEY_UP": self.counter.toggle, "KEY_RIGHT": self.counter.start, "KEY_ENTER": self.counter.toggle, "KEY_DOWN": self.counter.stop }) self.refresher.activate() def refresh_function(self): self.counter.update() text_rows = [ "{} {}".format(self.get_char(), round(self.counter.elapsed, 2)).center(self.o.cols) ] text_rows.extend([ instruction.center(self.o.cols) for instruction in self.__instructions ]) return text_rows def get_char(self): return "|>" if self.counter.active else "||"
def callback(): keymap = { "KEY_ENTER": pomodoro_options_menu, "KEY_KPENTER": pomodoro_options_menu } refresher = Refresher(status_refresher_data, i, o, 1, keymap, "Pomodoro monitor") refresher.activate()
def status_monitor(): keymap = { "KEY_ENTER": wireless_status, "KEY_RIGHT": lambda: scan(False), "KEY_UP": lambda: reconnect() } refresher = Refresher(status_refresher_data, i, o, 0.5, keymap, "Wireless monitor") refresher.activate()
def detect_loop(self): """ The detect loop. Will exit on RefresherExitException (raised by ``show_chip_status`` or on KEY_LEFT from the user. """ r = Refresher(self.get_current_status_data, self.i, self.o, name="Avrdude chip detect loop") r.activate()
def callback(): global refresher, keys_called keys_called = [] i.set_streaming(process_key) refresher = Refresher(get_keys, i, o, 1, name="Key monitor") refresher.keymap.pop( "KEY_LEFT") #Removing deactivate callback to show KEY_LEFT PrettyPrinter("To exit this app, press the same key 3 times", i, o) refresher.activate() i.remove_streaming()
class LectureHelper(object): refresher = None position = 0 started_at = None def __init__(self, file, interval): self.filename = file with open(self.filename, 'r') as f: self.contents = [ line.rstrip('\n') for line in f.readlines() if line.rstrip('\n') ] self.contents.append("STOP") self.interval = interval #In minutes def move_left(self): if self.position == 0: self.refresher.deactivate() return self.position -= 1 def move_right(self): if self.position == len(self.contents) - 1: return self.position += 1 def get_keymap(self): keymap = {"KEY_LEFT": self.move_left, "KEY_RIGHT": self.move_right} return keymap def get_displayed_data(self): data = [] data_rows = o.rows - 1 current_data = self.contents[self.position] for i in range(data_rows): data.append(current_data[o.cols * i:][:o.cols * (i + 1)]) total_seconds_since_start = (datetime.now() - self.started).total_seconds() total_seconds_till_end = self.interval * 60 - total_seconds_since_start minutes_till_end, seconds_till_end = map( int, (total_seconds_till_end / 60, total_seconds_till_end % 60)) time_str = "{}:{}".format(minutes_till_end, seconds_till_end).center(o.cols) data.append(time_str) return data def start(self): self.started = datetime.now() self.refresher = Refresher(self.get_displayed_data, i, o, 1, self.get_keymap()) self.refresher.activate()
def test_shows_data_on_screen(self): """Tests whether the Refresher outputs data on screen when it's ran""" i = get_mock_input() o = get_mock_output() r = Refresher(lambda: "Hello", i, o, name=r_name) def scenario(): r.refresh() r.deactivate() with patch.object(r, 'idle_loop', side_effect=scenario) as p: r.activate() #The scenario should only be called once assert r.idle_loop.called assert r.idle_loop.call_count == 1 assert o.display_data.called assert o.display_data.call_count == 2 #One in to_foreground, and one in patched idle_loop assert o.display_data.call_args_list[0][0] == ("Hello", ) assert o.display_data.call_args_list[1][0] == ("Hello", )
def test_left_key_exits(self): r = Refresher(lambda: "Hello", get_mock_input(), get_mock_output(), name=r_name) r.refresh = lambda *args, **kwargs: None # This test doesn't actually test whether the Refresher exits # It only tests whether the in_foreground attribute is set # Any ideas? Maybe use some kind of "timeout" library? def scenario(): r.keymap["KEY_LEFT"]() assert not r.in_foreground # If the test fails, either the assert will trigger a test failure, # or the idle loop will just run indefinitely # The exception thrown should protect from the latter raise KeyboardInterrupt with patch.object(r, 'idle_loop', side_effect=scenario) as p: try: r.activate() except KeyboardInterrupt: pass #Test succeeded
def i2c_read_ui(address, reg=None): global last_values if reg == True: reg = UniversalInput(i, o, message="Register:", charmap="hex").activate() if reg is None: # User picked "cancel" return if isinstance(reg, basestring): reg = int(reg, 16) last_values = [] values_on_screen = o.cols def read_value( ): # A helper function to read a value and format it into a list global last_values try: if reg: answer = "{} {}".format( hex(reg), hex(current_bus.read_byte_data(address, reg))) else: answer = hex(current_bus.read_byte(address)) except IOError: answer = "{} error".format(reg) if reg else "error" last_values.append(answer) last_values = last_values[:values_on_screen] return list(reversed(last_values)) r = Refresher(read_value, i, o) def change_interval( ): # A helper function to adjust the Refresher's refresh interval while it's running new_interval = IntegerAdjustInput( r.refresh_interval, i, o, message="Refresh interval:").activate() if new_interval is not None: r.set_refresh_interval(new_interval) r.update_keymap({"KEY_RIGHT": change_interval}) r.activate()
def callback(): keymap = {"KEY_ENTER":pomodoro_options_menu, "KEY_KPENTER":pomodoro_options_menu} refresher = Refresher(status_refresher_data, i, o, 1, keymap, "Pomodoro monitor") refresher.activate()
def activate(self): self.start_time = time() self.counter.start() return Refresher.activate(self)
def callback(): global refresher i.set_streaming(record_key_state) refresher = Refresher(show_key_state, i, o, 0.1, name="Key monitor") refresher.activate() i.remove_streaming()
class ClockApp(ZeroApp, Refresher): def __init__(self, i, o, *args, **kwargs): super(ClockApp, self).__init__(i, o) self.menu_name = "Clock" self.countdown = None self.refresher = Refresher(self.on_refresh, i, o, keymap={ "KEY_RIGHT": self.countdown_settings, "KEY_DOWN": self.force_sync_time }) default_config = '{}' config_filename = "config.json" self.config = read_or_create_config(local_path(config_filename), default_config, self.menu_name + " app") def set_context(self, c): self.context = c c.register_firstboot_action( FirstBootAction("set_timezone", self.set_timezone, depends=None, not_on_emulator=True)) c.register_firstboot_action( FirstBootAction("force_sync_time", self.force_sync_time, depends=["set_timezone", "check_connectivity"], not_on_emulator=True)) def force_sync_time(self): Printer("Syncing time", self.i, self.o, 0) try: output = check_output(["sntp", "-S", "pool.ntp.org"]) except CalledProcessError: logger.exception("Failed to sync time!") Printer("Failed to sync time!", self.i, self.o, 1) return False except OSError: logger.exception("Failed to sync time - sntp not installed!") Printer("Failed to sync time (no sntp)!", self.i, self.o, 1) return False else: Printer("Synced time successfully!", self.i, self.o, 1) return True def format_countdown(self): if not self.countdown: return None h, m, s, sign = self.get_countdown_time_left() if sign: return None return "{}m".format(h * 60 + m) def get_countdown_time_left(self): delta = self.countdown["time"] - datetime.now() print(delta) seconds = delta.seconds sign = None if delta.days < 0: seconds = -seconds sign = "+" hours, remainder = divmod(seconds, 3600) minutes, seconds = divmod(remainder, 60) if sign == "+": hours = hours + 24 return hours, minutes, seconds, sign def countdown_settings(self): # Setting an absolute countdown is not yet possible # because we don't yet have a TimePicker UI element def gmc(): #get menu contents countdown_label = self.format_countdown() contents = [] if countdown_label: contents.append(["Countdown: {}".format(countdown_label)]) #contents.append(["Set absolute", lambda: self.set_countdown(absolute=True)]) contents.append(["Set relative", self.set_countdown]) # Add an option for setting the timezone contents.append(["Set timezone", self.set_timezone]) return contents Menu([], self.i, self.o, "Countdown settings menu", contents_hook=gmc).activate() def set_countdown(self, absolute=False): if absolute: raise NotImplementedError # Needs a TimePicker or something like that rel_start = 0 message = "After (in minutes):" if self.countdown: # A countdown is already active # Using it as a starting point h, m, s, _ = self.get_countdown_time_left() rel_start = h * 60 + m offset = IntegerAdjustInput(rel_start, self.i, self.o, message=message).activate() if offset is not None: countdown = {"time": datetime.now() + timedelta(minutes=offset)} self.countdown = countdown # Shows a menu of available timezones, accept new TZ by pressing ENTER def set_timezone(self): try: with open('/etc/timezone', "r") as f: current_timezone = f.readline().strip() except: logger.exception("Can't get current timezone!") current_timezone = None else: logger.info("Current timezone: {}".format(repr(current_timezone))) lc = [] with LoadingBar(self.i, self.o, message="Getting timezones"): for k in ZoneInfoFile(getzoneinfofile_stream()).zones.keys(): lc.append([k]) lc = sorted(lc) choice = Listbox(lc, self.i, self.o, "Timezone selection listbox", selected=current_timezone).activate() if choice: # Setting timezone using timedatectl try: check_output(["timedatectl", "set-timezone", choice]) except CalledProcessError as e: logger.exception( "Can't set timezone using timedatectl! Return code: {}, output: {}" .format(e.returncode, repr(e.output))) return False else: logger.info("Set timezone successfully") return True else: return None def draw_analog_clock(self, c, time, radius="min(*c.size) / 3", clock_x="center_x+32", clock_y="center_y+5", h_len="radius / 2", m_len="radius - 5", s_len="radius - 3", **kwargs): """Draws the analog clock, with parameters configurable through config.json.""" center_x, center_y = c.get_center() clock_x = eval(clock_x) clock_y = eval(clock_y) radius = eval(radius) c.ellipse((clock_x - radius, clock_y - radius, clock_x + radius, clock_y + radius), fill=False, outline="white") self.draw_needle(c, 60 - time.second / 60, eval(s_len), clock_x, clock_y, 1) self.draw_needle(c, 60 - time.minute / 60, eval(m_len), clock_x, clock_y, 1) self.draw_needle(c, 24 - time.hour / 24, eval(h_len), clock_x, clock_y, 1) def draw_countdown(self, c, countdown_x="(center_x/2)-10", countdown_y="center_y/2*3", **kwargs): """Draws the digital clock, with parameters configurable through config.json.""" h, m, s, sign = self.get_countdown_time_left() hz, mz, sz = map(lambda x: str(x).zfill(2), (h, m, s)) string = "{}:{}".format(mz, sz) if h: string = hz + ":" + string if sign: string = sign + string center_x, center_y = c.get_center() centered_coords = c.get_centered_text_bounds(string) x = eval(countdown_x) y = eval(countdown_y) c.text((x, y), string, fill="white") def draw_text(self, c, time, text_x="10", text_y="center_y-5", time_format="%H:%M:%S", **kwargs): """Draws the digital clock, with parameters configurable through config.json.""" time_str = time.strftime(time_format) center_x, center_y = c.get_center() centered_coords = c.get_centered_text_bounds(time_str) x = eval(text_x) y = eval(text_y) c.text(time_str, (x, y)) def on_refresh(self): current_time = datetime.now() return self.render_clock(current_time, **self.config) def render_clock(self, time, **kwargs): c = Canvas(self.o) width, height = c.size self.draw_text(c, time, **kwargs) self.draw_analog_clock(c, time, **kwargs) if self.countdown: self.draw_countdown(c, **kwargs) return c.get_image() def draw_needle(self, c, progress, radius, x, y, width): # type: (Canvas, float, float, float, float, int) -> None hour_angle = math.pi * 2 * progress + math.pi c.line((int(x), int(y), int(x + radius * math.sin(hour_angle)), int(y + radius * math.cos(hour_angle))), width=width, fill=True) def on_start(self): self.refresher.activate()
class ClockApp(ZeroApp, Refresher): def __init__(self, i, o, *args, **kwargs): super(ClockApp, self).__init__(i, o) self.menu_name = "Clock" self.countdown = None self.refresher = Refresher( self.on_refresh, i, o, keymap={"KEY_RIGHT": self.countdown_settings}) default_config = '{}' config_filename = "config.json" self.config = read_or_create_config(local_path(config_filename), default_config, self.menu_name + " app") def format_countdown(self): if not self.countdown: return None h, m, s, sign = self.get_countdown_time_left() if sign: return None return "{}m".format(h * 60 + m) def get_countdown_time_left(self): delta = self.countdown["time"] - datetime.now() print(delta) seconds = delta.seconds sign = None if delta.days < 0: seconds = -seconds sign = "+" hours, remainder = divmod(seconds, 3600) minutes, seconds = divmod(remainder, 60) if sign == "+": hours = hours + 24 return hours, minutes, seconds, sign def countdown_settings(self): # Setting an absolute countdown is not yet possible # because we don't yet have a TimePicker UI element def gmc(): #get menu contents countdown_label = self.format_countdown() contents = [] if countdown_label: contents.append(["Countdown: {}".format(countdown_label)]) #contents.append(["Set absolute", lambda: self.set_countdown(absolute=True)]) contents.append(["Set relative", self.set_countdown]) return contents Menu([], self.i, self.o, "Countdown settings menu", contents_hook=gmc).activate() def set_countdown(self, absolute=False): if absolute: raise NotImplementedError # Needs a TimePicker or something like that rel_start = 0 message = "After (in minutes):" if self.countdown: # A countdown is already active # Using it as a starting point h, m, s, _ = self.get_countdown_time_left() rel_start = h * 60 + m offset = IntegerAdjustInput(rel_start, self.i, self.o, message=message).activate() if offset is not None: countdown = {"time": datetime.now() + timedelta(minutes=offset)} self.countdown = countdown def draw_analog_clock(self, c, time, radius="min(*c.size) / 3", clock_x="center_x+32", clock_y="center_y+5", h_len="radius / 2", m_len="radius - 5", s_len="radius - 3", **kwargs): """Draws the analog clock, with parameters configurable through config.json.""" center_x, center_y = c.get_center() clock_x = eval(clock_x) clock_y = eval(clock_y) radius = eval(radius) c.ellipse((clock_x - radius, clock_y - radius, clock_x + radius, clock_y + radius), fill=False, outline="white") self.draw_needle(c, 60 - time.second / 60, eval(s_len), clock_x, clock_y, 1) self.draw_needle(c, 60 - time.minute / 60, eval(m_len), clock_x, clock_y, 1) self.draw_needle(c, 24 - time.hour / 24, eval(h_len), clock_x, clock_y, 1) def draw_countdown(self, c, countdown_x="(center_x/2)-10", countdown_y="center_y/2*3", **kwargs): """Draws the digital clock, with parameters configurable through config.json.""" h, m, s, sign = self.get_countdown_time_left() hz, mz, sz = map(lambda x: str(x).zfill(2), (h, m, s)) string = "{}:{}".format(mz, sz) if h: string = hz + ":" + string if sign: string = sign + string center_x, center_y = c.get_center() centered_coords = c.get_centered_text_bounds(string) x = eval(countdown_x) y = eval(countdown_y) c.text((x, y), string, fill="white") def draw_text(self, c, time, text_x="10", text_y="center_y-5", time_format="%H:%M:%S", **kwargs): """Draws the digital clock, with parameters configurable through config.json.""" time_str = time.strftime(time_format) center_x, center_y = c.get_center() centered_coords = c.get_centered_text_bounds(time_str) x = eval(text_x) y = eval(text_y) c.text(time_str, (x, y)) def on_refresh(self): current_time = datetime.now() return self.render_clock(current_time, **self.config) def render_clock(self, time, **kwargs): c = Canvas(self.o) width, height = c.size self.draw_text(c, time, **kwargs) self.draw_analog_clock(c, time, **kwargs) if self.countdown: self.draw_countdown(c, **kwargs) return c.get_image() def draw_needle(self, c, progress, radius, x, y, width): # type: (Canvas, float, float, float, float, int) -> None hour_angle = math.pi * 2 * progress + math.pi c.line((int(x), int(y), int(x + radius * math.sin(hour_angle)), int(y + radius * math.cos(hour_angle))), width=width, fill=True) def on_start(self): super(ClockApp, self).on_start() self.refresher.activate()
def show_totp(name, secret): display_func = lambda: render_totp(name, secret) r = Refresher(display_func, i, o, 1) delete_func = lambda: delete_config_entry(name, secret, r) r.set_keymap({"KEY_RIGHT": delete_func}) r.activate()
class KeyboardFallbackApp(ZeroApp): do_not_activate_events = ["usb_keyboard_connected"] def __init__(self, *args, **kwargs): ZeroApp.__init__(self, *args, **kwargs) self.active = Event() self.pop_on_event = Event() self.pop_on_event.set() self.c = Canvas(self.o) device_manager.register_monitor_callback(self.process_dm_event) self.i.set_streaming(self.deactivate) self.state = None self.status_image = "No image" self.r = Refresher(self.get_status_image, self.i, self.o, name="Keyboard fallback status refresher") def deactivate(self, keyname, *args, **kwargs): # Upon receiving *any* key when active, it's our hint from the user # to not appear again ;-P # Unless the keyboard was just connected, of course if self.state == "usb_keyboard_connected": self.r.deactivate() self.context.signal_background() else: self.pop_on_event.clear() def go_into_foreground(self): if not self.pop_on_event.isSet(): return False return self.context.request_switch() def process_dm_event(self, event): self.c.clear() self.state = event if not self.context.is_active() and self.pop_on_event.isSet() \ and event not in self.do_not_activate_events: if not self.go_into_foreground(): return if event == "usb_keyboard_connected": self.c.centered_text("USB kb connected") elif event == "custom_i2c_disconnected": self.c.centered_text("Keypad not found!") try: dcdc = USB_DCDC() dcdc.on() except: logger.exception("Can't turn the ZP USB DC-DC on!") elif event == "looking_for_usb_keyboard": self.c.centered_text("Keypad not found!", ch=16) self.c.centered_text("Looking for USB kb") elif event == "usb_keyboard_found": self.c.centered_text("USB keyboard found") elif event == "usb_keyboard_disconnected": self.c.centered_text("USB kb disconnected!") self.status_image = self.c.get_image() if self.state == "usb_keyboard_connected": sleep(2) if self.state == "usb_keyboard_connected": self.r.deactivate() self.context.signal_background() def set_context(self, c): self.context = c c.set_target(self.show_status) def get_status_image(self): return self.status_image def show_status(self): self.r.activate()
def status_monitor(): keymap = {"KEY_ENTER":wireless_status, "KEY_KPENTER":wireless_status} refresher = Refresher(status_refresher_data, i, o, 0.5, keymap, "Wireless monitor") refresher.activate()
def status_monitor(): keymap = {"KEY_ENTER": wireless_status, "KEY_KPENTER": wireless_status} refresher = Refresher(status_refresher_data, i, o, 0.5, keymap, "Wireless monitor") refresher.activate()