Toggle Navigation
Hatchery
Eggs
Monster++
__init__.py
Users
Badges
Login
Register
MCH2022 badge?
go to mch2022.badge.team
__init__.py
raw
Content
""" This project is based on collins watch++ which was inspired by yrlfs digiclk and base functionality (clock) is taken from there. https://badge.team/projects/watch https://github.com/Ferdi265/card10-digiclk Also inspired by and copied code from: * [36C3 Clock](https://badge.team/projects/36c3_clock) * [adventure timer](https://badge.team/projects/adventure_timer) * [Binary Clock](https://badge.team/projects/binary_clock) * [Control Center](https://badge.team/projects/control_center) * [DigiClk](https://badge.team/projects/digiclk) * [Engelsystem](https://badge.team/projects/engelsystem) * [Fahrplan](https://badge.team/projects/fahrplan) * [picview](https://badge.team/projects/picview) * [simple flashlight](https://badge.team/projects/simple_flashlight) * [Tiny Typer](https://badge.team/projects/tiny_typer) * [Timer](https://badge.team/projects/timer) * [watch++](https://badge.team/projects/watch) * card10 standard apps * Menu * Bluetooth * Personal State * [Exposure Notifications](https://card10.badge.events.ccc.de/en/exposure_notifications) Thanks to lortas for the battery rendering code """ import buttons import display import os import utime import light_sensor import power import leds import personal_state #from Color import color import ujson import vibra import struct import interrupt import sys_ble import time import config EXNOSTAT_ENABLED = True leds.dim_top(0) leds.set_rocket(0,0) leds.set_rocket(1,0) leds.set_rocket(2,0) leds.set_flashlight(0) # ---------------------------------------------------------------------------- # "THE TSCHUNK-WARE LICENSE" (Revision 23): # Niklas Roy wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a tschunk in return # ---------------------------------------------------------------------------- mini_alphabet=( ((0,0,0,0,0,0,0), # 32:' ' (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0)), ((0,0,1,0,0,0,0), #! (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,0,0,0,0,0), (0,0,1,0,0,0,0)), ((0,0,1,0,1,0,0), #" (0,0,1,0,1,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0)), ((0,0,1,0,1,0,0), ## (0,0,1,0,1,0,0), (1,1,1,1,1,1,1), (0,0,1,0,1,0,0), (1,1,1,1,1,1,1), (0,0,1,0,1,0,0), (0,0,1,0,1,0,0)), ((0,0,0,1,0,0,0), #$ (1,1,1,1,1,1,1), (1,0,0,1,0,0,0), (1,1,1,1,1,1,1), (0,0,0,1,0,0,1), (1,1,1,1,1,1,1), (0,0,0,1,0,0,0)), ((0,0,0,0,0,0,1), #% (0,1,0,0,0,1,0), (0,0,0,0,1,0,0), (0,0,0,1,0,0,0), (0,0,1,0,0,0,0), (0,1,0,0,0,1,0), (1,0,0,0,0,0,0)), ((0,0,1,1,0,0,0), #& (0,1,0,0,1,0,0), (0,0,1,0,1,0,0), (0,1,1,1,1,0,1), (1,0,0,0,1,1,0), (1,0,0,0,1,1,0), (0,1,1,1,0,0,1)), ((0,0,0,1,0,0,0), #' (0,0,0,1,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0)), ((0,0,0,1,0,0,0), #( (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,1,0,0,0,0), (0,0,0,1,0,0,0)), ((0,0,0,1,0,0,0), #) (0,0,0,0,1,0,0), (0,0,0,0,1,0,0), (0,0,0,0,1,0,0), (0,0,0,0,1,0,0), (0,0,0,0,1,0,0), (0,0,0,1,0,0,0)), ((0,0,0,1,0,0,0), #* (0,1,0,1,0,1,0), (0,0,1,1,1,0,0), (1,1,1,1,1,1,1), (0,0,1,1,1,0,0), (0,1,0,1,0,1,0), (0,0,0,1,0,0,0)), ((0,0,0,1,0,0,0), #+ (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (1,1,1,1,1,1,1), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((0,0,0,0,0,0,0), #, (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((0,0,0,0,0,0,0), #- (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (1,1,1,1,1,1,1), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0)), ((0,0,0,0,0,0,0), #. (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0)), ((0,0,0,0,0,0,1), #/ (0,0,0,0,0,1,0), (0,0,0,0,1,0,0), (0,0,0,1,0,0,0), (0,0,1,0,0,0,0), (0,1,0,0,0,0,0), (1,0,0,0,0,0,0)), ((1,1,1,1,1,1,1), #0 (1,0,0,0,0,1,1), (1,0,0,0,1,0,1), (1,0,0,1,0,0,1), (1,0,1,0,0,0,1), (1,1,0,0,0,0,1), (1,1,1,1,1,1,1)), ((0,1,1,1,0,0,0), #1 (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #2 (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,1,1,1,1,1,1), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #3 (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (0,0,1,1,1,1,1), (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,0,0,0,0,0,0), #4 (1,0,0,1,0,0,0), (1,0,0,1,0,0,0), (1,1,1,1,1,1,1), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((1,1,1,1,1,1,1), #5 (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1), (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #6 (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #7 (0,0,0,0,0,1,0), (0,0,0,0,1,0,0), (0,1,1,1,1,1,0), (0,0,1,0,0,0,0), (0,1,0,0,0,0,0), (1,0,0,0,0,0,0)), ((1,1,1,1,1,1,1), #8 (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #9 (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1), (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((0,0,0,0,0,0,0), #: (0,0,0,1,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0), (0,0,0,0,0,0,0)), ((0,0,0,0,0,0,0), #; (0,0,0,0,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((0,0,0,0,1,0,0), #< (0,0,0,1,0,0,0), (0,0,1,0,0,0,0), (0,1,0,0,0,0,0), (0,0,1,0,0,0,0), (0,0,0,1,0,0,0), (0,0,0,0,1,0,0)), ((0,0,0,0,0,0,0), #= (0,0,0,0,0,0,0), (1,1,1,1,1,1,1), (0,0,0,0,0,0,0), (1,1,1,1,1,1,1), (0,0,0,0,0,0,0), (0,0,0,0,0,0,0)), ((0,0,1,0,0,0,0), #> (0,0,0,1,0,0,0), (0,0,0,0,1,0,0), (0,0,0,0,0,1,0), (0,0,0,0,1,0,0), (0,0,0,1,0,0,0), (0,0,1,0,0,0,0)), ((1,1,1,1,1,1,1), #? (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (0,0,0,1,1,1,1), (0,0,0,1,0,0,0), (0,0,0,0,0,0,0), (0,0,0,1,0,0,0)), ((1,1,1,1,1,1,1), #@ (1,0,0,0,0,0,1), (1,0,1,1,1,0,1), (1,0,1,0,1,0,1), (1,0,1,1,1,1,1), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1)), ((0,0,0,1,0,0,0), #A (0,0,1,0,1,0,0), (0,1,0,0,0,1,0), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1)), ((1,1,1,1,1,0,0), #B (1,0,0,0,1,0,0), (1,0,0,0,1,0,0), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((0,0,1,1,1,1,1), #C (0,1,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (0,1,0,0,0,0,0), (0,0,1,1,1,1,1)), ((1,1,1,1,1,0,0), #D (1,0,0,0,0,1,0), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,1,0), (1,1,1,1,1,0,0)), ((1,1,1,1,1,1,1), #E (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #F (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0)), ((1,1,1,1,1,1,1), #G (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,0,0,0,0,0,1), #H (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1)), ((1,1,1,1,1,1,1), #I (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #J (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,0,0,0,0,0,1), (0,1,0,0,0,1,0), (0,0,1,1,1,0,0)), ((1,0,0,0,0,0,1), #K (1,0,0,0,0,1,0), (1,0,0,0,1,0,0), (1,1,1,1,1,1,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1)), ((1,0,0,0,0,0,0), #L (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1)), ((1,0,0,0,0,0,1), #M (1,1,0,0,0,1,1), (1,0,1,0,1,0,1), (1,0,0,1,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1)), ((1,0,0,0,0,0,1), #N (1,1,0,0,0,0,1), (1,0,1,0,0,0,1), (1,0,0,1,0,0,1), (1,0,0,0,1,0,1), (1,0,0,0,0,1,1), (1,0,0,0,0,0,1)), ((1,1,1,1,1,1,1), #O (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #P (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,0,0,0,0,0,0)), ((1,1,1,1,1,1,1), #Q (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,1,0,1), (1,0,0,0,0,1,0), (1,1,1,1,1,0,1)), ((1,1,1,1,1,1,1), #R (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1), (1,0,0,0,1,0,0), (1,0,0,0,0,1,0), (1,0,0,0,0,0,1)), ((1,1,1,1,1,1,1), #S (1,0,0,0,0,0,0), (1,0,0,0,0,0,0), (1,1,1,1,1,1,1), (0,0,0,0,0,0,1), (0,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,1,1,1,1,1,1), #T (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((1,0,0,0,0,0,1), #U (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,1,1,1,1,1,1)), ((1,0,0,0,0,0,1), #V (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (0,1,0,0,0,1,0), (0,0,1,0,1,0,0), (0,0,0,1,0,0,0)), ((1,0,0,0,0,0,1), #W (1,0,0,0,0,0,1), (1,0,0,0,0,0,1), (1,0,0,1,0,0,1), (1,0,1,0,1,0,1), (1,1,0,0,0,1,1), (1,0,0,0,0,0,1)), ((1,0,0,0,0,0,1), #X (0,1,0,0,0,1,0), (0,0,1,0,1,0,0), (0,0,0,1,0,0,0), (0,0,1,0,1,0,0), (0,1,0,0,0,1,0), (1,0,0,0,0,0,1)), ((1,0,0,0,0,0,1), #Y (0,1,0,0,0,1,0), (0,0,1,0,1,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0), (0,0,0,1,0,0,0)), ((1,1,1,1,1,1,1), #Z (0,0,0,0,0,1,0), (0,0,0,0,1,0,0), (0,0,0,1,0,0,0), (0,0,1,0,0,0,0), (0,1,0,0,0,0,0), (1,1,1,1,1,1,1)), ((1,0,1,0,1,0,1), #CURSOR (0,1,0,1,0,1,0), (1,0,1,0,1,0,1), (0,1,0,1,0,1,0), (1,0,1,0,1,0,1), (0,1,0,1,0,1,0), (1,0,1,0,1,0,1))) #-------------------------------------------------------------- mini text def mini_text(disp,text,x_text,y_text,text_col): for i in range(len(text)): c = ord(text[i]) if c>96 and c<123: # a->A ... z->Z c-=32 c-=32 if c<0 or c>59: c=0 if c==59 and saved == False: text_col=(255,64,64) # indicate unsaved file by red cursor if x_text < 160: for x in range(7): # show character for y in range(7): if (mini_alphabet[c][y][x]==1): x_px = x + x_text y_px = y + y_text if x_px>=0 and x_px<160 and y_px>=0 and y_px<80: disp.pixel(x_px, y_px,col=text_col) x_text += 10 # increment start position of next character #-------------------------------------------------------------- End of "THE TSCHUNK-WARE LICENSE" DM_ADV_TYPE_FLAGS = 0x01 DM_ADV_TYPE_16_UUID = 0x03 DM_ADV_TYPE_SERVICE_DATA = 0x16 UUID = b"\x6f\xfd" TIMEOUT = 99 seen = {} display_stats = 0 MODE_OFF = 0 MODE_ON_NEW_MAC = 1 MODE_ON_RX = 2 MODE_BOTH = 3 vib_mode = MODE_BOTH led_mode = MODE_BOTH def parse_advertisement_data(data): ads = {} l = len(data) p = 0 while p < l: ad_len = data[p] p += 1 if ad_len > 0: ad_type = data[p] ad_data = b"" p += 1 if ad_len > 1: ad_data = data[p : p + ad_len - 1] p += ad_len - 1 ads[ad_type] = ad_data return ads def bytes2hex(bin, sep=""): return sep.join(["%02x" % x for x in bin]) def process_covid_data(mac, service_data, rssi, flags): global display_stats global vib_mode global led_mode if vib_mode in [MODE_ON_RX, MODE_BOTH]: vibra.vibrate(5) if vib_mode in [MODE_ON_NEW_MAC, MODE_BOTH] and mac not in seen: vibra.vibrate(25) if led_mode in [MODE_ON_RX, MODE_BOTH]: leds.flash_rocket(1, 1, 10) if led_mode in [MODE_ON_NEW_MAC, MODE_BOTH] and mac not in seen: leds.flash_rocket(1, 31, 200) display_stats = 1 print(bytes2hex(mac, ":"), rssi, bytes2hex(service_data), flags) # try to produce a small int last_rx_time = time.time() - t0 seen[mac] = [int(last_rx_time), flags] def prune(): global seen seen_pruned = {} now = time.time() - t0 for mac in seen: if seen[mac][0] + TIMEOUT > now: seen_pruned[mac] = seen[mac] seen = seen_pruned def process_scan_report(scan_report): ads = parse_advertisement_data(scan_report[0]) mac = scan_report[4] mac = bytes([mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]]) rssi = scan_report[1] # print(bytes2hex(mac, ':'), rssi, bytes2hex(scan_report[0]), ads) # According to spec there is no other service announced and the # service is always listed in the complete list of 16 bit services if DM_ADV_TYPE_16_UUID in ads: if ads[DM_ADV_TYPE_16_UUID] == UUID: if DM_ADV_TYPE_SERVICE_DATA in ads: flags = None if DM_ADV_TYPE_FLAGS in ads: flags = ads[DM_ADV_TYPE_FLAGS] # service data contains another copy of the service UUID process_covid_data(mac, ads[DM_ADV_TYPE_SERVICE_DATA][2:], rssi, flags) def ble_callback(_): event = sys_ble.get_event() if event == sys_ble.EVENT_SCAN_REPORT: while True: scan_report = sys_ble.get_scan_report() if scan_report == None: break process_scan_report(scan_report) prune() def show_stats(disp): seen_google = 0 seen_apple = 0 t = time.time() - t0 t_min = t # Make a copy as `seen` could change in between # and we're not locking it seen_copy = seen.copy() for mac in seen_copy: info = seen_copy[mac] if info[1]: seen_apple += 1 else: seen_google += 1 if info[0] < t_min: t_min = info[0] seen_total = seen_google + seen_apple window = t - t_min if len(seen_copy) > 0 else min(TIMEOUT, t - last_rx_time) mini_text(disp,"%2d:%2dG%2dA%3dT" % (window, seen_google, seen_apple, seen_total), 5 ,73 ,color.WHITE) last_rx_time = 0 t0 = time.time() if EXNOSTAT_ENABLED: interrupt.set_callback(interrupt.BLE, ble_callback) interrupt.enable_callback(interrupt.BLE) sys_ble.scan_start() try: vib_mode = int(config.get_string("exno_vib_mode")) except: pass try: led_mode = int(config.get_string("exno_led_mode")) except: pass SWITCHTOTIMER = 0 if 'timer' in os.listdir('/apps'): if 'variables.json' in os.listdir('/apps/timer'): f = open('/apps/timer/variables.json', 'r') try: variables = ujson.loads(f.read()) if "mode" and "runstamp" in variables: if variables["mode"] == 1: SWITCHTOTIMER = variables["runstamp"] leds.set_rocket(2,31) except: pass f.close() alarm_hour = -1 alarm_minute = -1 if 'control_center' in os.listdir('/apps'): if 'cc_settings.txt' in os.listdir('/apps/control_center'): try: with open('/apps/control_center/cc_settings.txt') as cc_settings: trash = int(cc_settings.read(4)) #I don't need the firs 4 chars if int(cc_settings.read(1)) != 0: alarm_hour = int(cc_settings.read(2)) alarm_minute = int(cc_settings.read(2)) except: pass shifts = ujson.loads('[]') if 'shifts.json' in os.listdir(''): f = open('shifts.json', 'r') shifts = ujson.loads(f.read()) f.close() PreAlertMins = 15 PreAlertVibrate = 250 StartVibrate = 100 EndVibrate = 100 COLOR_DESTRUCTION = [254,80,0] COLOR_HOPE = [0,187,49] MINUTE_COLOR = [204, 102, 0] MINUTE_COLOR_DARK = [1, 0, 0] HOUR_COLOR = [127,154,10] HOUR_COLOR_DARK = [0, 1, 0] PATH = "/apps/monster" TOP_LED_BRIGHTNESS = 1 # 1 .. 8 MONTH_STRING = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] DAY_STRING = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"] #MONTH_STRING = ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"] #DAY_STRING = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"] BATTERY_COLOR_GOOD = [0, 128, 0] BATTERY_COLOR_OK = [128, 100, 0] BATTERY_COLOR_BAD = [128, 0, 0] states = [ ("No State", personal_state.NO_STATE), ("No Contact", personal_state.NO_CONTACT), ("Chaos", personal_state.CHAOS), ("Communication", personal_state.COMMUNICATION), ("Camp", personal_state.CAMP), ] NONE = -1 DISPLAY = 0 CHANGE_SECONDS = 1 CHANGE_HOURS = 3 CHANGE_MINUTES = 2 CHANGE_YEAR = 4 CHANGE_MONTH = 5 CHANGE_DAY = 6 MODE = DISPLAY FLASHLIGHT = 0 BACKLIGHT = 23 LIGHTMODE = 0 CONFIG_NAME = "ble.txt" ACTIVE_STRING = "active=true" INACTIVE_STRING = "active=false" PICS = list( [ (PATH+"/{}").format(fn) for fn in os.listdir(PATH) if fn.lower().endswith(".bmp") ] ) def TZtoSEC(timezone): return int(timezone[0:3])*3600+int(timezone[4:6])*60 def next_angel_time(timestamp = utime.unix_time()): nextshift = -1 if len(shifts) > 0: nextshiftend = timestamp+86400 for shift in range(len(shifts)): if (shifts[shift]["end"]) >= timestamp and (shifts[shift]["end"]) < nextshiftend: nextshiftend = shifts[shift]["end"] nextshift = shift return nextshift def usb_mode(): os.usbconfig(os.USB_FLASH) leds.set_rocket(0,31) with display.open() as d: d.clear(color.CHAOSBLUE) d.print("USB Storage", posx=3, posy=20, fg=color.CHAOSBLUE_DARK) d.print("open", posx=52, posy=40, fg=color.CHAOSBLUE_DARK) d.update() d.backlight(23) # Wait for any button to be pressed and disable USB storage again while buttons.read(0xFF) == 0: pass os.usbconfig(os.USB_SERIAL) leds.set_rocket(0,0) os.exec(PATH+"/__init__.py") #Restart app def bin_clock(hour, minute): if hour == alarm_hour and minute == alarm_minute: with display.open() as d: d.clear() d.print(" Switching ", posx=3, posy=10, fg=color.WHITE) d.print("to Control ", posx=3, posy=30, fg=color.WHITE) d.print("Center app.", posx=3, posy=50, fg=color.WHITE) d.update() d.backlight(23) os.exec("apps/control_center/__init__.py") for i in range(0, 6): leds.set(i, MINUTE_COLOR if (minute & (1 << i)) else MINUTE_COLOR_DARK) for i in range(0, 5): leds.set(i + 6, HOUR_COLOR if (hour & (1 << i)) else HOUR_COLOR_DARK) def init(): if CONFIG_NAME not in os.listdir("."): with open(CONFIG_NAME, "w") as f: f.write(INACTIVE_STRING) def is_active(): with open(CONFIG_NAME, "r") as f: state = f.readlines()[0] if len(state) < len(ACTIVE_STRING): return False state = state[0 : len(ACTIVE_STRING)] return state == ACTIVE_STRING def toggle_ble(): content = INACTIVE_STRING if is_active() else ACTIVE_STRING with open(CONFIG_NAME, "w") as f: f.write(content) if is_active(): leds.set_rocket(1,31) else: leds.set_rocket(0,31) utime.sleep_ms(100) leds.set_rocket(1,0) leds.set_rocket(0,0) def ceil_div(a, b): return (a + (b - 1)) // b def tip_height(w): return ceil_div(w, 2) - 1 def draw_tip(d, x, y, w, c, invert=False, swapAxes=False): h = tip_height(w) for dy in range(h): for dx in range(dy + 1, w - 1 - dy): px = x + dx py = y + dy if not invert else y + h - 1 - dy if swapAxes: px, py = py, px d.pixel(px, py, col=c) def draw_seg(d, x, y, w, h, c, swapAxes=False): tip_h = tip_height(w) body_h = h - 2 * tip_h draw_tip(d, x, y, w, c, invert=True, swapAxes=swapAxes) px1, px2 = x, x + (w - 1) py1, py2 = y + tip_h, y + tip_h + (body_h - 1) if swapAxes: px1, px2, py1, py2 = py1, py2, px1, px2 d.rect(px1, py1, px2, py2, col=c) draw_tip(d, x, y + tip_h + body_h, w, c, invert=False, swapAxes=swapAxes) def draw_Vseg(d, x, y, w, l, c): draw_seg(d, x, y, w, l, c) def draw_Hseg(d, x, y, w, l, c): draw_seg(d, y, x, w, l, c, swapAxes=True) def draw_grid_seg(d, x, y, w, l, c, swapAxes=False): sw = w - 2 tip_h = tip_height(sw) x = x * w y = y * w l = (l - 1) * w draw_seg(d, x + 1, y + tip_h + 3, sw, l - 3, c, swapAxes=swapAxes) def draw_grid_Vseg(d, x, y, w, l, c): draw_grid_seg(d, x, y, w, l, c) def draw_grid_Hseg(d, x, y, w, l, c): draw_grid_seg(d, y, x, w, l, c, swapAxes=True) def draw_grid(d, x1, y1, x2, y2, w, c): for x in range(x1 * w, x2 * w): for y in range(y1 * w, y2 * w): if x % w == 0 or x % w == w - 1 or y % w == 0 or y % w == w - 1: d.pixel(x, y, col=c) def draw_grid_7seg(d, x, y, w, segs, c): if segs[0]: draw_grid_Hseg(d, x, y, w, 4, c) if segs[1]: draw_grid_Vseg(d, x + 3, y, w, 4, c) if segs[2]: draw_grid_Vseg(d, x + 3, y + 3, w, 4, c) if segs[3]: draw_grid_Hseg(d, x, y + 6, w, 4, c) if segs[4]: draw_grid_Vseg(d, x, y + 3, w, 4, c) if segs[5]: draw_grid_Vseg(d, x, y, w, 4, c) if segs[6]: draw_grid_Hseg(d, x, y + 3, w, 4, c) DIGITS = [ (True, True, True, True, True, True, False), (False, True, True, False, False, False, False), (True, True, False, True, True, False, True), (True, True, True, True, False, False, True), (False, True, True, False, False, True, True), (True, False, True, True, False, True, True), (True, False, True, True, True, True, True), (True, True, True, False, False, False, False), (True, True, True, True, True, True, True), (True, True, True, True, False, True, True) ] def get_bat_color(v): if v > 3.8: return BATTERY_COLOR_GOOD if v > 3.6: return BATTERY_COLOR_OK return BATTERY_COLOR_BAD def render_battery(display, pos_x=142, pos_y=72): v = os.read_battery() c = get_bat_color(v) if not c: return display.rect(pos_x, pos_y, pos_x + 15, pos_y + 7, filled=True, col=c) display.rect(pos_x + 15, pos_y + 2, pos_x + 17, pos_y + 5, filled=True, col=c) if v < 4.0: display.rect(pos_x + 11, pos_y + 1, pos_x + 14, pos_y + 6, filled=True, col=[0, 0, 0]) if v < 3.8: display.rect(pos_x + 6, pos_y + 1, pos_x + 11, pos_y + 6, filled=True, col=[0, 0, 0]) if v < 3.6: display.rect(pos_x + 1, pos_y + 1, pos_x + 6, pos_y + 6, filled=True, col=[0, 0, 0]) render_charging(display, pos_x + 6, pos_y) def render_charging(display, pos_x, pos_y): v_in = power.read_chargein_voltage() if v_in > 4.0: c = [0, 0, 0] c_shade = [120, 120, 120] display.pixel(pos_x + 1, pos_y, col=c) display.pixel(pos_x + 1, pos_y, col=c) display.pixel(pos_x + 2, pos_y, col=c_shade) display.pixel(pos_x + 1, pos_y + 1, col=c) display.pixel(pos_x, pos_y + 1, col=c_shade) display.pixel(pos_x + 1, pos_y + 2, col=c) display.pixel(pos_x, pos_y + 2, col=c) display.pixel(pos_x, pos_y + 3, col=c) display.pixel(pos_x + 1, pos_y + 3, col=c) display.pixel(pos_x + 2, pos_y + 3, col=c) display.pixel(pos_x + 3, pos_y + 3, col=c_shade) display.pixel(pos_x + 2, pos_y + 4, col=c) display.pixel(pos_x + 3, pos_y + 4, col=c) display.pixel(pos_x + 4, pos_y + 4, col=c) display.pixel(pos_x + 1, pos_y + 4, col=c_shade) display.pixel(pos_x + 3, pos_y + 5, col=c) display.pixel(pos_x + 4, pos_y + 5, col=c) display.pixel(pos_x + 3, pos_y + 6, col=c) display.pixel(pos_x + 4, pos_y + 6, col=c_shade) display.pixel(pos_x + 3, pos_y + 7, col=c) display.pixel(pos_x + 2, pos_y + 7, col=c_shade) def render_num(d, num, x, Color): draw_grid_7seg(d, x, 0, 7, DIGITS[num // 10], Color) draw_grid_7seg(d, x + 5, 0, 7, DIGITS[num % 10], Color) def render_colon(d, Color): draw_grid_Vseg(d, 11, 2, 7, 2, Color) draw_grid_Vseg(d, 11, 4, 7, 2, Color) def render_text(d, text, Color, blankidx=None): bs = bytearray(text) if blankidx is not None: bs[blankidx:blankidx + 1] = b'_' if MODE == DISPLAY: d.print(bs.decode(), fg=Color, bg=None, posx=3, posy=52) else: fg_color = (0, 255, 128) if MODE in (CHANGE_YEAR, CHANGE_MONTH, CHANGE_DAY) else (0, 128, 128) fg_color = Color d.print(MODES[MODE], fg=fg_color, bg=None, posx=3, posy=54) def render_bar(d, num, Color): global display_stats if (display_stats): d.rect(0, 72, 140, 80, col=(int(Color[0]*1/3),int(Color[1]*1/3),int(Color[2]*1/3))) d.rect(0, 72, num, 80, col=(int(Color[0]*2/3),int(Color[1]*2/3),int(Color[2]*2/3))) show_stats(d) else: # d.rect(0, 72, num+1, 80, col=color.BLACK) d.rect(0, 72, num, 80, col=Color) def render(d): global picloaded global Color rendertime = utime.time() rendertime_unix = utime.unix_time() year, month, mday, hour, min, sec, wday, yday = utime.localtime(rendertime) Color = color.from_hsv(((hour*60+min)*360/1439+240), 1, 1) d.clear() picloaded = 1 bin_clock(hour, min); ctrl_backlight(d) if MODE == CHANGE_YEAR: render_num(d, year // 100, 1,Color) render_num(d, year % 100, 13,Color) elif MODE == CHANGE_MONTH: render_num(d, month, 13,Color) elif MODE == CHANGE_DAY: render_num(d, mday, 13,Color) elif MODE == CHANGE_SECONDS: render_num(d, sec, 13,Color) else: render_num(d, hour, 1,Color) render_num(d, min, 13,Color) if MODE not in (CHANGE_YEAR, CHANGE_MONTH, CHANGE_DAY): render_colon(d,Color) nextshift = next_angel_time(utime.unix_time()); if nextshift >= 0: if (shifts[nextshift]["start"]<=rendertime_unix): TextColor = COLOR_DESTRUCTION else: TextColor = COLOR_HOPE startyear, startmonth, startmday, starthour, startmin, startsec, startwday, startyday = utime.localtime(shifts[nextshift]["start"]+TZtoSEC(shifts[nextshift]["timezone"])) endyear, endmonth, endmday, endhour, endmin, endsec, endwday, endyday = utime.localtime(shifts[nextshift]["end"]+TZtoSEC(shifts[nextshift]["timezone"])) line_text = "{:02}:".format(starthour) + "{:02}-".format(startmin) + "{:02}:".format(endhour)+ "{:02}".format(endmin) else: line_text = DAY_STRING[wday] + "-{:02}.".format(mday) + MONTH_STRING[month - 1] + str(year)[2:] TextColor = Color render_text(d, line_text,TextColor , None) render_battery(d) d.update() oldbar = -1 def render_nice(display): global PICS global picloaded global oldbar global Color bar = int(139*(utime.time_ms()%60000)/57000) if bar != oldbar: oldbar = bar year, month, mday, hour, min, sec, wday, yday = utime.localtime() if bar <= 139: if picloaded == 1: picloaded = 0 bin_clock(hour, min); render_bar(display, bar, Color) display.update() ctrl_backlight(display) else: if picloaded == 0: rendertime = utime.time()+3 rendertime_unix = utime.unix_time()+3 year, month, mday, hour, min, sec, wday, yday = utime.localtime(rendertime) with open(PICS[os.urandom(1)[0] % len(PICS)], "rb+") as bmpfile: header, filesize, _, _, data_offset = struct.unpack("<HIHHI", bmpfile.read(14)) bmpinfo = bmpfile.read(data_offset - 14) width, height, _, bpp, compr = struct.unpack("<IIHHI", bmpinfo[4:20]) pad = (width * -3) % 4 BACKGROUND = bmpfile.read(512) i = 2 for y in range(height - 1, -1, -1): for x in range(width): if i >= len(BACKGROUND): i = 2 + (i - len(BACKGROUND)) BACKGROUND = BACKGROUND[-2:] + bmpfile.read(512) display.pixel(x, y, col=(BACKGROUND[i], BACKGROUND[i - 1], BACKGROUND[i - 2])) i += 3 i += pad # skip padding bs = check_buttons() CTRL_FNS[MODE](bs) ctrl_backlight(display) Color = color.from_hsv(((hour*60+min)*360/1439+240), 1, 1) render_num(display, hour, 1,Color) render_colon(display ,Color) render_num(display, min, 13,Color) nextshift = next_angel_time(utime.unix_time()); print(rendertime_unix) if nextshift >= 0: if shifts[nextshift]["start"]-PreAlertMins*60 == rendertime_unix: vibra.vibrate(PreAlertVibrate) if shifts[nextshift]["start"] == rendertime_unix: vibra.vibrate(StartVibrate) if shifts[nextshift]["end"] == rendertime_unix: vibra.vibrate(EndVibrate) if (shifts[nextshift]["start"]<=rendertime_unix): TextColor = COLOR_DESTRUCTION else: TextColor = COLOR_HOPE startyear, startmonth, startmday, starthour, startmin, startsec, startwday, startyday = utime.localtime(shifts[nextshift]["start"]+TZtoSEC(shifts[nextshift]["timezone"])) endyear, endmonth, endmday, endhour, endmin, endsec, endwday, endyday = utime.localtime(shifts[nextshift]["end"]+TZtoSEC(shifts[nextshift]["timezone"])) line_text = "{:02}:".format(starthour) + "{:02}-".format(startmin) + "{:02}:".format(endhour)+ "{:02}".format(endmin) else: line_text = DAY_STRING[wday] + "-{:02}.".format(mday) + MONTH_STRING[month - 1] + str(year)[2:] TextColor = Color render_text(display, line_text,TextColor , None) render_battery(display) picloaded = 1 BUTTON_SEL = 1 << 0 BUTTON_UP = 1 << 1 BUTTON_DOWN = 1 << 2 BUTTON_SEL_LONG = 1 << 3 BUTTON_UP_LONG = 1 << 4 BUTTON_DOWN_LONG = 1 << 5 pressed_prev = 0 button_sel_time = 0 button_up_time = 0 button_down_time = 0 def check_buttons(): global MODE global pressed_prev, button_sel_time, button_up_time, button_down_time t = utime.time_ms() pressed = buttons.read(buttons.BOTTOM_LEFT | buttons.TOP_RIGHT | buttons.BOTTOM_RIGHT) cur_buttons = 0 if pressed & buttons.TOP_RIGHT and not pressed_prev & buttons.TOP_RIGHT: button_sel_time = t elif not pressed & buttons.TOP_RIGHT and pressed_prev & buttons.TOP_RIGHT: if button_sel_time+500 < t: cur_buttons |= BUTTON_SEL_LONG elif button_sel_time+250 < t: with display.open() as d: d.clear() d.print(" Switching ", posx=3, posy=20, fg=color.WHITE) d.print(" to Timer. ", posx=3, posy=40, fg=color.WHITE) d.update() d.backlight(23) os.exec("/apps/timer/__init__.py") else: cur_buttons |= BUTTON_SEL elif pressed & buttons.TOP_RIGHT and button_sel_time+1000 < t: with display.open() as d: d.clear() d.print(" Switching ", posx=3, posy=10, fg=color.WHITE) d.print(" to Cyber- ", posx=3, posy=30, fg=color.WHITE) d.print(" band app. ", posx=3, posy=50, fg=color.WHITE) d.update() d.backlight(23) os.exec("/apps/cyberband/__init__.py") if pressed and button_sel_time+500 >= t and button_sel_time+400 <= t : vibra.vibrate(25) button_sel_time -=100 if pressed & buttons.BOTTOM_RIGHT and not pressed_prev & buttons.BOTTOM_RIGHT: button_up_time = t elif not pressed & buttons.BOTTOM_RIGHT and pressed_prev & buttons.BOTTOM_RIGHT: if button_up_time+500 < t: cur_buttons |= BUTTON_UP_LONG else: cur_buttons |= BUTTON_UP elif pressed & buttons.BOTTOM_RIGHT and button_up_time+1000 < t: os.reset() if pressed and button_up_time+500 >= t and button_up_time+400 <= t : vibra.vibrate(25) button_up_time -=100 if pressed & buttons.BOTTOM_LEFT and not pressed_prev & buttons.BOTTOM_LEFT: button_down_time = t elif not pressed & buttons.BOTTOM_LEFT and pressed_prev & buttons.BOTTOM_LEFT: if button_down_time+500 < t: cur_buttons |= BUTTON_DOWN_LONG elif (button_down_time+250 < t) and MODE == DISPLAY: with display.open() as d: d.clear() d.print(" Switching ", posx=3, posy=10, fg=color.WHITE) d.print("to Control ", posx=3, posy=30, fg=color.WHITE) d.print("Center app.", posx=3, posy=50, fg=color.WHITE) d.update() d.backlight(23) os.exec("apps/control_center/__init__.py") else: cur_buttons |= BUTTON_DOWN elif pressed & buttons.BOTTOM_LEFT and button_down_time+1000 < t: with display.open() as d: d.clear() d.print(" Switching ", posx=3, posy=20, fg=color.WHITE) d.print("to ECG app.", posx=3, posy=40, fg=color.WHITE) d.update() d.backlight(23) os.exec("/apps/ecg/__init__.py") if pressed and button_down_time+500 >= t and button_down_time+400 <= t : vibra.vibrate(25) button_down_time -=100 pressed_prev = pressed return cur_buttons def modTime(yrs, mth, day, hrs, mns, sec): ltime = utime.localtime() new = utime.mktime( (ltime[0] + yrs, ltime[1] + mth, ltime[2] + day, ltime[3] + hrs, ltime[4] + mns, ltime[5] + sec, None, None)) utime.set_time(new) def ctrl_display(bs): global MODE global FLASHLIGHT global BACKLIGHT global LIGHTMODE global RENDERSECOND if bs & BUTTON_SEL_LONG: MODE = CHANGE_SECONDS BACKLIGHT = 23 leds.dim_top(TOP_LED_BRIGHTNESS) LIGHTMODE = 1 if bs & BUTTON_SEL: LIGHTMODE = (LIGHTMODE+1)%10 if LIGHTMODE == 0: leds.dim_top(0) if LIGHTMODE == 1: BACKLIGHT = 23 leds.dim_top(TOP_LED_BRIGHTNESS) if LIGHTMODE == 2: BACKLIGHT = 1 leds.dim_top(0) if LIGHTMODE == 3: BACKLIGHT = 0 leds.dim_top(0) if LIGHTMODE == 4: BACKLIGHT = 0 leds.dim_top(TOP_LED_BRIGHTNESS) if LIGHTMODE == 5: BACKLIGHT = 42 leds.dim_top(TOP_LED_BRIGHTNESS) if LIGHTMODE == 6: BACKLIGHT = 255 leds.dim_top(TOP_LED_BRIGHTNESS) if LIGHTMODE == 7: BACKLIGHT = 255 leds.dim_top(8) if LIGHTMODE == 8: BACKLIGHT = 255 leds.dim_top(0) if LIGHTMODE == 9: BACKLIGHT = 23 leds.dim_top(0) year, month, mday, hour, min, sec, wday, yday = utime.localtime() bin_clock(hour, min) if bs & BUTTON_DOWN: numstates = len(states) current, _ = personal_state.get() current = (current + 1) % numstates state = states[current] personal_state.set(state[1], True) if bs & BUTTON_UP: if FLASHLIGHT == 0: leds.set_flashlight(1) FLASHLIGHT = 1 else: leds.set_flashlight(0) FLASHLIGHT = 0 if bs & BUTTON_UP_LONG: toggle_ble() if bs & BUTTON_DOWN_LONG: usb_mode() def ctrl_chg_hrs(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_MINUTES if bs & BUTTON_UP: modTime(0, 0, 0, 1, 0, 0) if bs & BUTTON_UP_LONG: modTime(0, 0, 0, 10, 0, 0) if bs & BUTTON_DOWN: modTime(0, 0, 0, -1, 0, 0) if bs & BUTTON_DOWN_LONG: modTime(0, 0, 0, -10, 0, 0) def ctrl_chg_mns(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_YEAR if bs & BUTTON_UP: modTime(0, 0, 0, 0, 1, 0) if bs & BUTTON_UP_LONG: modTime(0, 0, 0, 0, 10, 0) if bs & BUTTON_DOWN: modTime(0, 0, 0, 0, -1, 0) if bs & BUTTON_DOWN_LONG: modTime(0, 0, 0, 0, -10, 0) def ctrl_chg_sec(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_HOURS if bs & BUTTON_UP: modTime(0, 0, 0, 0, 0, 1) if bs & BUTTON_UP_LONG: modTime(0, 0, 0, 0, 0, 10) if bs & BUTTON_DOWN: modTime(0, 0, 0, 0, 0, -1) if bs & BUTTON_DOWN_LONG: modTime(0, 0, 0, 0, 0, -10) def ctrl_chg_yrs(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_MONTH if bs & BUTTON_UP: modTime(1, 0, 0, 0, 0, 0) if bs & BUTTON_UP_LONG: modTime(10, 0, 0, 0, 0, 0) if bs & BUTTON_DOWN: modTime(-1, 0, 0, 0, 0, 0) if bs & BUTTON_DOWN_LONG: modTime(-10, 0, 0, 0, 0, 0) def ctrl_chg_mth(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_DAY if bs & BUTTON_UP: modTime(0, 1, 0, 0, 0, 0) if bs & BUTTON_UP_LONG: modTime(0, 10, 0, 0, 0, 0) if bs & BUTTON_DOWN: modTime(0, -1, 0, 0, 0, 0) if bs & BUTTON_DOWN_LONG: modTime(0, -10, 0, 0, 0, 0) def ctrl_chg_day(bs): global MODE if bs & BUTTON_SEL_LONG: MODE = DISPLAY if bs & BUTTON_SEL: MODE = CHANGE_SECONDS if bs & BUTTON_UP: modTime(0, 0, 1, 0, 0, 0) if bs & BUTTON_UP_LONG: modTime(0, 0, 10, 0, 0, 0) if bs & BUTTON_DOWN: modTime(0, 0, -1, 0, 0, 0) if bs & BUTTON_DOWN_LONG: modTime(0, 0, -10, 0, 0, 0) def ctrl_backlight(d): global LIGHTMODE if LIGHTMODE == 0: light = light_sensor.get_reading() # display_brightness = int(light // 4) if light >= 4 else 1 display_brightness = int(light-12) if light >= 14 else 1 display_brightness = 100 if light > 300 else display_brightness d.backlight(display_brightness) else: d.backlight(BACKLIGHT) MODES = { DISPLAY: '---', CHANGE_HOURS: '>-----HOURS', CHANGE_MINUTES: '>---MINUTES', CHANGE_SECONDS: '>---SECONDS', CHANGE_YEAR: '>------YEAR', CHANGE_MONTH: '>-----MONTH', CHANGE_DAY: '>-------DAY', } CTRL_FNS = { DISPLAY: ctrl_display, CHANGE_HOURS: ctrl_chg_hrs, CHANGE_MINUTES: ctrl_chg_mns, CHANGE_SECONDS: ctrl_chg_sec, CHANGE_YEAR: ctrl_chg_yrs, CHANGE_MONTH: ctrl_chg_mth, CHANGE_DAY: ctrl_chg_day, } def main(): lastmode = NONE light_sensor.start() with display.open() as d: while True: if SWITCHTOTIMER > 0 and SWITCHTOTIMER <= utime.time_ms()+3000: os.exec("apps/timer/__init__.py") bs = check_buttons() CTRL_FNS[MODE](bs) if MODE == DISPLAY and lastmode == DISPLAY: render_nice(d) else: render(d) lastmode = MODE init() main()