#      ----------------------------------------------------------------------------
#      "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
#      ----------------------------------------------------------------------------
#
#      2020-06-15: stuff has been done with this stuff.

import buttons
import display
import utime
import bhi160
import bme680
import math
import power
import vibra
import leds
import light_sensor
import ujson

# factory settings
s = {'gesture_threshold': 20, 'sleep_timeout': 12, 'weather_update_interval': 12, 'alarm': {'active': False, 'hour': 0, 'minute': 0}, 'volt_max': 4.1, 'volt_min': 3.6}

# building blocks for big numbers and small lettering
fiver = (
    (False, False, False, False, False),
    (False, False, False, False, True),
    (False, False, False, True, False),
    (False, False, True, False, False),
    (False, False, True, False, True),
    (False, False, True, True, False),
    (False, False, True, True, True),
    (False, True, False, False, False),
    (False, True, False, True, False),
    (False, True, True, False, False),
    (False, True, True, False, True),
    (False, True, True, True, False),
    (False, True, True, True, True),
    (True, False, False, False, False),
    (True, False, False, False, True),
    (True, False, False, True, False),
    (True, False, False, True, True),
    (True, False, True, False, False),
    (True, False, True, False, True),
    (True, False, True, True, False),
    (True, False, True, True, True),
    (True, True, False, False, True),
    (True, True, False, True, True),
    (True, True, True, False, False),
    (True, True, True, True, False),
    (True, True, True, True, True),
)

# building blocks for mini_text
sevener = (
    (False, False, False, False, False, False, False),
    (False, False, False, False, False, False, True),
    (False, False, False, False, False, True, False),
    (True, True, True, True, True, True, True),
    (False, False, False, False, True, False, False),
    (True, True, True, True, True, False, True),
    (True, True, True, True, True, False, False),
    (True, True, False, False, False, True, True),
    (False, False, False, True, False, False, False),
    (False, False, False, True, False, False, True),
    (True, True, False, False, False, False, True),
    (True, False, True, True, True, True, True),
    (True, False, True, True, True, False, True),
    (True, False, True, False, True, False, True),
    (True, False, True, False, False, False, True),
    (False, False, False, True, True, True, True),
    (False, False, True, False, False, False, False),
    (True, False, False, True, True, True, True),
    (True, False, False, True, False, False, True),
    (True, False, False, True, False, False, False),
    (False, False, True, False, True, False, False),
    (True, False, False, False, True, True, False),
    (True, False, False, False, True, False, True),
    (True, False, False, False, True, False, False),
    (False, False, True, True, False, False, False),
    (True, False, False, False, False, True, True),
    (True, False, False, False, False, True, False),
    (True, False, False, False, False, False, True),
    (False, False, True, True, True, False, False),
    (True, False, False, False, False, False, False),
    (False, True, True, True, True, True, False),
    (False, False, True, True, True, True, True),
    (False, True, False, False, False, False, False),
    (False, True, True, True, True, False, True),
    (False, True, False, False, False, True, False),
    (False, True, True, True, False, False, True),
    (False, True, False, False, True, False, False),
    (False, True, False, True, False, True, False),
    (False, True, True, True, False, False, False),
)

# mapping the fivers to ascii codes
micro_alphabet = (
    (0, 0, 0, 0, 0,),
    (7, 7, 7, 0, 7,),
    (8, 8, 0, 0, 0,),
    (8, 25, 8, 25, 8,),
    (11, 17, 11, 4, 11,),
    (14, 2, 3, 7, 14,),
    (11, 8, 4, 8, 10,),
    (3, 0, 0, 0, 0,),
    (2, 3, 3, 3, 2,),
    (7, 3, 3, 3, 7,),
    (18, 11, 25, 11, 18,),
    (3, 3, 25, 3, 3,),
    (0, 0, 0, 7, 7,),
    (0, 0, 25, 0, 0,),
    (0, 0, 0, 0, 13,),
    (1, 2, 3, 7, 13,),
    (11, 14, 14, 14, 11,),
    (3, 9, 17, 3, 25,),
    (11, 1, 11, 13, 25,),
    (24, 1, 5, 1, 24,),
    (13, 15, 25, 2, 2,),
    (25, 13, 24, 1, 11,),
    (12, 13, 24, 14, 11,),
    (25, 2, 3, 7, 13,),
    (11, 14, 11, 14, 11,),
    (11, 14, 12, 1, 11,),
    (0, 7, 0, 7, 0,),
    (0, 7, 0, 7, 7,),
    (2, 3, 7, 3, 2,),
    (0, 25, 0, 25, 0,),
    (7, 3, 2, 3, 7,),
    (11, 1, 5, 0, 3,),
    (11, 18, 19, 13, 11,),
    (11, 14, 25, 14, 14,),
    (24, 14, 24, 14, 24,),
    (12, 13, 13, 13, 12,),
    (24, 14, 14, 14, 24,),
    (25, 13, 24, 13, 25,),
    (25, 13, 24, 13, 13,),
    (11, 13, 20, 14, 11,),
    (14, 14, 25, 14, 14,),
    (25, 3, 3, 3, 25,),
    (25, 1, 1, 14, 11,),
    (14, 15, 25, 14, 14,),
    (13, 13, 13, 13, 25,),
    (14, 22, 18, 14, 14,),
    (14, 21, 18, 16, 14,),
    (11, 14, 14, 14, 11,),
    (24, 14, 24, 13, 13,),
    (11, 14, 17, 15, 10,),
    (24, 14, 24, 15, 14,),
    (12, 13, 11, 1, 24,),
    (25, 3, 3, 3, 3,),
    (14, 14, 14, 14, 11,),
    (14, 14, 14, 8, 3,),
    (14, 14, 18, 22, 14,),
    (14, 8, 3, 8, 14,),
    (14, 8, 3, 3, 3,),
    (25, 2, 3, 7, 25,),
)

# mapping the fivers to numbers
big_numbers = (
    (25, 14, 14, 14, 14, 14, 25,),
    (23, 3, 3, 3, 3, 3, 25,),
    (25, 1, 1, 25, 13, 13, 25,),
    (25, 1, 1, 12, 1, 1, 25,),
    (13, 15, 15, 25, 2, 2, 2,),
    (25, 13, 13, 25, 1, 1, 25,),
    (25, 13, 13, 25, 14, 14, 25,),
    (25, 1, 1, 6, 1, 1, 1,),
    (25, 14, 14, 25, 14, 14, 25,),
    (25, 14, 14, 25, 1, 1, 25,),
)

# mapping the seveners to ascii codes
mini_alphabet = (
    (0, 0, 0, 0, 0, 0, 0,),
    (16, 16, 16, 16, 16, 0, 16,),
    (20, 20, 0, 0, 0, 0, 0,),
    (20, 20, 3, 20, 3, 20, 20,),
    (8, 3, 19, 3, 9, 3, 8,),
    (1, 34, 4, 8, 16, 34, 29,),
    (24, 36, 20, 33, 21, 21, 35,),
    (8, 8, 0, 0, 0, 0, 0,),
    (4, 8, 16, 16, 16, 8, 4,),
    (16, 8, 4, 4, 4, 8, 16,),
    (8, 37, 28, 3, 28, 37, 8,),
    (8, 8, 8, 3, 8, 8, 8,),
    (0, 0, 0, 0, 0, 8, 8,),
    (0, 0, 0, 3, 0, 0, 0,),
    (0, 0, 0, 0, 0, 0, 29,),
    (1, 2, 4, 8, 16, 32, 29,),
    (3, 25, 22, 18, 14, 10, 3,),
    (38, 8, 8, 8, 8, 8, 3,),
    (3, 1, 1, 3, 29, 29, 3,),
    (3, 1, 1, 31, 1, 1, 3,),
    (29, 19, 19, 3, 8, 8, 8,),
    (3, 29, 29, 3, 1, 1, 3,),
    (3, 29, 29, 3, 27, 27, 3,),
    (3, 2, 4, 30, 16, 32, 29,),
    (3, 27, 27, 3, 27, 27, 3,),
    (3, 27, 27, 3, 1, 1, 3,),
    (0, 8, 0, 0, 0, 8, 0,),
    (0, 0, 0, 8, 0, 8, 8,),
    (4, 8, 16, 32, 16, 8, 4,),
    (0, 0, 3, 0, 3, 0, 0,),
    (16, 8, 4, 2, 4, 8, 16,),
    (3, 1, 1, 15, 8, 0, 8,),
    (3, 27, 12, 13, 11, 29, 3,),
    (8, 20, 34, 3, 27, 27, 27,),
    (6, 23, 23, 3, 27, 27, 3,),
    (31, 32, 29, 29, 29, 32, 31,),
    (6, 26, 27, 27, 27, 26, 6,),
    (3, 29, 29, 6, 29, 29, 3,),
    (3, 29, 29, 6, 29, 29, 29,),
    (3, 29, 29, 17, 27, 27, 3,),
    (27, 27, 27, 3, 27, 27, 27,),
    (3, 8, 8, 8, 8, 8, 3,),
    (3, 1, 1, 1, 27, 34, 28,),
    (27, 26, 23, 3, 27, 27, 27,),
    (29, 29, 29, 29, 29, 29, 3,),
    (27, 7, 13, 18, 27, 27, 27,),
    (27, 10, 14, 18, 22, 25, 27,),
    (3, 27, 27, 27, 27, 27, 3,),
    (3, 27, 27, 3, 29, 29, 29,),
    (3, 27, 27, 27, 22, 26, 5,),
    (3, 27, 27, 3, 23, 26, 27,),
    (3, 29, 29, 3, 1, 1, 3,),
    (3, 8, 8, 8, 8, 8, 8,),
    (27, 27, 27, 27, 27, 27, 3,),
    (27, 27, 27, 27, 34, 20, 8,),
    (27, 27, 27, 18, 13, 7, 27,),
    (27, 34, 20, 8, 20, 34, 27,),
    (27, 34, 20, 8, 8, 8, 8,),
    (3, 2, 4, 8, 16, 32, 3,),
)

base_color = (255, 212, 198)
data_colors = {'pressure': (180, 255, 180), 'humidity': (161, 161, 255), 'temperature': (255, 180, 120), 'gas_resistance': (127, 127, 127)}
darker_data_colors = {'pressure': (90, 127, 90), 'humidity': (80, 80, 127), 'temperature': (127, 90, 60), 'gas_resistance': (60, 60, 60)}
selected_red = (255, 64, 64)
charging_color = (161, 161, 255)

VOID = 0
WATCH = 1
DATE = 2
BATTERY = 3
WEATHER = 4
MAIN = 5
ALARM_SCREEN = 9

screen = MAIN
p_screen = VOID
highlighted = VOID

display_brightness = 0
target_brightness = 0

flashlight_on = False
leds_on = False

charge = 0.0
plugged_in = False
charging = False
p_charge = 0.0
p_plugged_in = False

orientation = bhi160.BHI160Orientation(sample_rate=4, sample_buffer_len=8)
current_orientation_x = 0.0
current_orientation_y = 0.0
averaged_orientation_x = 0.0
averaged_orientation_y = 0.0
orientation_status = 0

set_minute = 0
set_hour = 0
set_year = 0
set_month = 0
set_day = 0
EXIT = 0
SET_TIME = 1
SET_DATE = 2
SET_ALARM = 3
SET_TIME_HH = 4
SET_TIME_MM = 5
SET_DATE_YEAR = 6
SET_DATE_MONTH = 7
SET_DATE_DAY = 8
SET_ALARM_ACTIVE = 9
SET_ALARM_HOUR = 10
SET_ALARM_MINUTE = 11

watch_state = EXIT
watch_highlight = EXIT
alarm_count = 0

days = ("MON","TUE","WED","THU","FRI","SAT","SUN")
months = ("JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC")

calendar_day = 1
calendar_month = 1
calendar_year = 2020

weather_detail = 'pressure'
battery_screen = 'log'

current_weather = 0

weather = {'pressure': [], 'humidity': [], 'temperature': [], 'gas_resistance': []}
weatherfiles = {}

battery_log = []

smooth_voltage = 3.7

p_minute = 0

p_button_state = 0
button_state = 0
button_left_pressed = False
button_right_pressed = False
button_select_pressed = False
repeat_millis = 1000000000

def mini_text(text, x_text, y_text, text_color):

    for c in text.upper():
        if ord(c) > 90:
            c = '.'
        if c == ' ':
            x_text += 5  # increment start position of next character
        else:
            for x in range(7):  # show character
                for y in range(7):
                    if sevener[mini_alphabet[ord(c)-32][y]][x]:
                        disp.pixel(x + x_text, y + y_text, col=text_color)

            if c == '.':
                x_text += 4  # increment start position of next character after '.'
            else:
                x_text += 10  # increment start position of next character

def micro_text(text, x_text, y_text, text_color):

    for c in text.upper():

        if ord(c) > 90:
            c = '.'

        for x in range(5):  # show character
            for y in range(5):
                if fiver[micro_alphabet[ord(c)-32][y]][x]:
                    disp.pixel(x + x_text, y + y_text, col=text_color)
        if c == '.':
            x_text += 3  # increment start position of next character after '.'
        else:
            x_text += 7  # increment start position of next character
    return


def draw_flash(x_pos, y_pos, color=base_color):
    x_pos += 4
    for i in range(5):
        disp.line(x_pos, y_pos, x_pos - i, y_pos + 10, col=color)
        disp.line(x_pos, y_pos + 19, x_pos + i, y_pos + 9, col=color)
    return

def constrain(value, minimum, maximum):
    if value < minimum:
        value = minimum
    if value > maximum:
        value = maximum
    return value


def rot_z(p=(1, 1, 1), a=0):
    x = math.cos(a) * p[0] + math.sin(a) * p[1]
    y = -math.sin(a) * p[0] + math.cos(a) * p[1]
    z = p[2]
    return (x, y, z)

def read_bhi():

    global current_orientation_x
    global current_orientation_y
    global averaged_orientation_x
    global averaged_orientation_y
    global orientation_status

    samples = orientation.read()

    if not samples:
        return
    else:
        for s in samples:
            averaged_orientation_x = (averaged_orientation_x + s.x) / 2
            averaged_orientation_y = (averaged_orientation_y + s.y) / 2

        current_orientation_x = samples[-1].x
        current_orientation_y = samples[-1].y
        orientation_status = samples[-1].status

    return

def show_time(x_offset = 0, y_offset = 0, selected = False):

    hh = hour
    mm = minute

    if selected:
        hh_color = selected_red
        mm_color = selected_red
        colon_color = selected_red
    elif watch_state == SET_TIME_HH:
        hh_color = selected_red
        mm_color = base_color
        colon_color = base_color
        hh = set_hour
        mm = set_minute
    elif watch_state == SET_TIME_MM:
        hh_color = base_color
        mm_color = selected_red
        colon_color = base_color
        hh = set_hour
        mm = set_minute
    else:
        hh_color = base_color
        mm_color = base_color
        colon_color = base_color

    disp.rect(x_offset, y_offset, x_offset + 122, y_offset + 29, col=(0, 0, 0))

    for c in "%02d"%hh:
        for x in range(5):  # show character
            for y in range(7):
                if fiver[big_numbers[ord(c)-48][y]][x]:
                    disp.rect(x * 4 + x_offset, y * 4 + y_offset, x * 4 + x_offset + 4, y * 4 + y_offset + 4, col=hh_color)
        x_offset += 28

    # colon
    disp.rect(x_offset + 3, y_offset + 6, x_offset + 7, y_offset + 10, col=colon_color)
    disp.rect(x_offset + 3, y_offset + 18, x_offset + 7, y_offset + 22, col=colon_color)
    x_offset += 17

    for c in "%02d"%mm:
        for x in range(5):  # show character
            for y in range(7):
                if fiver[big_numbers[ord(c)-48][y]][x]:
                    disp.rect(x * 4 + x_offset, y * 4 + y_offset, x * 4 + x_offset + 4, y * 4 + y_offset + 4, col=mm_color)
        x_offset += 28

    return


def show_date(selected=False):

    if selected:
        color = selected_red
    else:
        color = base_color

    disp.rect(130, 0, 156, 28, col=(0, 0, 0))
    mini_text(days[time_all[6]], 130, 0, color)
    mini_text(months[time_all[1] - 1], 130, 10, color)
    mini_text("%02d" % time_all[2], 130, 20, color)

    return


def show_compass():

    xc = 144
    yc = 48

    disp.rect(xc - 15, yc - 15, xc + 15, yc + 15, col=(0, 0, 0))

    steps = 2
    north = (current_orientation_x + 450) / 180 * math.pi

    if orientation_status == 3:  # indicate full trust in compass by drawing circle undashed
        disp.circ(xc, yc, 15, col=base_color, filled=False)
    else:
        for i in range(0, 24, steps):  # dashed circle segments
            ri = math.pi / 12 * i
            ri += north
            ric1 = rot_z((14.5, 0, 0), ri - math.pi / 24)
            ric2 = rot_z((14.5, 0, 0), ri + math.pi / 24)
            disp.line(int(xc + ric1[0]), int(yc + ric1[1]), int(xc + ric2[0]), int(yc + ric2[1]), col=base_color)

    angle = north

    line_begin = rot_z((11, 0, 0), angle)
    line_end = rot_z((-9, 4, 0), angle)

    disp.line(
        int(xc + line_begin[0]),
        int(yc + line_begin[1]),
        int(xc + line_end[0]),
        int(yc + line_end[1]),
        col=base_color,
    )

    line_begin = rot_z((11, 0, 0), angle)
    line_end = rot_z((-9, -4, 0), angle)

    disp.line(
        int(xc + line_begin[0]),
        int(yc + line_begin[1]),
        int(xc + line_end[0]),
        int(yc + line_end[1]),
        col=base_color,
    )

    line_begin = rot_z((-9, -4, 0), angle)
    line_end = rot_z((-9, 4, 0), angle)

    disp.line(
        int(xc + line_begin[0]),
        int(yc + line_begin[1]),
        int(xc + line_end[0]),
        int(yc + line_end[1]),
        col=base_color,
    )

    return


def volt_to_percent(voltage):
    global s
    if s['volt_max'] - s['volt_min'] == 0:
        s['volt_max'] = s['volt_max'] + 0.01
    return ((voltage - s['volt_min']) / ((s['volt_max']-0.05) - s['volt_min']))


def show_battery_log():

    disp.clear()

    charge_color = (
        constrain(int(55 * charge) + 200, 0, 255),
        constrain(int(530 * charge)- 53, 0, 212),
        constrain(int(666 * charge) - 200, 0, 198),
    )

    if battery_screen == 'log':

        disp.line(0, 9, 160, 9, col=base_color)
        disp.line(0, 71, 160, 71, col=base_color)
        disp.rect(0, 10, 160, 70, col=(0, 0, 0))
        disp.rect(0, 0, 160, 8, col=(0, 0, 0))
        disp.rect(0, 72, 160, 80, col=(0, 0, 0))

        mini_text('CHARGE LOG', 0, 0, charge_color)
        mini_text('%.1f %%'%(charge*100), 0, 73, charge_color)

        for i in range(10, 70, 4):
            disp.pixel(52, i, col=base_color)
            disp.pixel(88, i, col=base_color)
            disp.pixel(124, i, col=base_color)
        for i in range(0, 160, 4):
            disp.pixel(i, 40, col=base_color)

        max_index = len(battery_log)

        log_floor = s['volt_min']
        log_ceiling = s['volt_max']

        log_span = log_ceiling - log_floor
        
        if log_span == 0:
            log_span = 1

        log_scale = 60 / log_span

        v1 = int((battery_log[-1][1]-log_floor)*log_scale)
        v2 = int((battery_log[-1][1]-log_floor)*log_scale)

        for i in range(1, max_index):
            v2 = int((battery_log[-(i+1)][1]-log_floor)*log_scale)
            td = battery_log[-i][0] - battery_log[-(i+1)][0]
            if td > 600:
                disp.line(160 - i, 10, 160 -i, 70, col=(constrain(int(td/100), 30, 180), constrain(int(td/100), 30, 150), constrain(int(td/100), 30, 140)))
            disp.line(160 - (i-1), 70 - v1, 160 -i, 70 - v2, col=(255 if(v1<v2) else 222, 212 if(v1>v2) else 183, 172))
            v1 = v2

        micro_text('0H', 145, 65, base_color)
        micro_text('12H', 78, 65, base_color)
        micro_text('24H', 16, 65, base_color)

        upper_text = '%.2f V' % log_ceiling
        lower_text = '%.2f V' % log_floor
        micro_text(upper_text, (160 - len(upper_text)*7), 0, base_color)
        micro_text(lower_text, (160 - len(lower_text)*7), 73, base_color)

    else:

        mini_text("BATTERY:", 6, 14, charge_color)

        mini_text("%.2f V" % smooth_voltage, 101, 8, charge_color)
        mini_text("%.2f A" % power.read_battery_current(), 101, 20, charge_color)

        mini_text("USB:", 6, 54, charging_color if plugged_in else (60,60,60))

        mini_text("%.2f V" % power.read_chargein_voltage(), 101, 48, charging_color if plugged_in else (60,60,60))
        mini_text("%.2f A" % power.read_chargein_current(), 101, 60, charging_color if plugged_in else (60,60,60))

        if plugged_in:
            draw_flash(69, 48, base_color)

    return

def show_battery(selected=False):

    if selected:
        disp.rect(127, 69, 157, 79, col=(255, 64, 64))
        disp.rect(158, 73, 160, 76, col=(255, 64, 64))
        if charge > 0.99:
            micro_text("FULL", 129, 72, (0,0,0))
        else:
            micro_text("%02d%%"%int(charge*100), 132, 72, (0,0,0))
    else:
        charge_color = (
            constrain(int(55 * charge) + 200, 0, 255),
            constrain(int(530 * charge)- 53, 0, 212),
            constrain(int(666 * charge) - 200, 0, 198),
        )

        if plugged_in:
            charge_color = charging_color

        disp.rect(127, 69, 160, 80, col=(0,0,0))
        disp.rect(127, 69, 157, 79, col=charge_color, filled=False)
        disp.rect(158, 73, 160, 76, col=charge_color)

        if charge > 0.99:
            micro_text("FULL", 129, 72, charge_color)
        else:
            micro_text("%02d%%"%int(charge*100), 132, 72, charge_color)

    return


def show_weather_log():

    global weather
    global weatherfiles
    global weather_detail
    global current_weather

    global last_bme_measurement_ms

    if last_bme_measurement_ms + 3000 < utime.monotonic_ms():

        with bme680.Bme680() as environment:
            if weather_detail == 'pressure':
                current_weather = environment.pressure()
            elif weather_detail == 'humidity':
                current_weather = environment.humidity()
            elif weather_detail == 'temperature':
                current_weather = environment.temperature()
            elif weather_detail == 'gas_resistance':
                current_weather = environment.gas_resistance() / 1000

        last_bme_measurement_ms = utime.monotonic_ms()

    disp.clear()

    weather_units = {'pressure': 'hPa', 'humidity': '%', 'temperature': 'C', 'gas_resistance': 'kOhm'}

    disp.line(0, 9, 160, 9, col=base_color)
    disp.line(0, 71, 160, 71, col=base_color)
    disp.rect(0, 10, 160, 70, col=(0, 0, 0))
    disp.rect(0, 0, 160, 8, col=(0, 0, 0))
    disp.rect(0, 72, 160, 80, col=(0, 0, 0))

    micro_text('0H', 145, 65, base_color)
    micro_text('%02dH'%int(4/3 * s['weather_update_interval']), 72, 65, base_color)
    micro_text('%02dH'%int((8/3) * s['weather_update_interval']), 0, 65, base_color)

    mini_text('%s'%(weather_detail,), 0, 0, data_colors[weather_detail])
    mini_text('%.2f %s'%(current_weather, weather_units[weather_detail]), 0, 73, data_colors[weather_detail])

    max_index = len(weather[weather_detail])

    log_floor = min(weather[weather_detail])
    log_ceiling = max(weather[weather_detail])

    if weather_detail == 'humidity':
        log_floor = 0.0
        log_ceiling = 100.0

    log_span = log_ceiling - log_floor
    if log_span == 0:
        log_span = 1

    log_scale = 60 / log_span

    for i in range(10, 70, 4):
        disp.pixel(40, i, col=base_color)
        disp.pixel(80, i, col=base_color)
        disp.pixel(120, i, col=base_color)
    for i in range(0, 160, 4):
        disp.pixel(i, 40, col=base_color)

    for i in range(1, max_index):
        disp.line(160 - (i-1), 70 - int((weather[weather_detail][-i]-log_floor)*log_scale), 160 -i, 70 - int((weather[weather_detail][-(i+1)]-log_floor)*log_scale), col=data_colors[weather_detail])

    upper_text = '%.1f' % log_ceiling
    lower_text = '%.1f' % log_floor
    micro_text(upper_text, (160 - len(upper_text)*7), 0, base_color)
    micro_text(lower_text, (160 - len(lower_text)*7), 73, base_color)

    return

def show_weather(selected=False):

    x_text = 24

    if selected:
        disp.line(0, 33, 121, 33, col=selected_red)
        disp.line(0, 73, 121, 73, col=selected_red)
        return
    else:
        disp.line(0, 33, 121, 33, col=base_color)
        disp.line(0, 73, 121, 73, col=base_color)

    disp.rect(0, 34, 122, 72, col=(0, 0, 0))
    disp.rect(0, 74, 122, 80, col=(0, 0, 0))

    micro_text('0H', 110, 75, base_color)
    micro_text('%02dH'%(s['weather_update_interval'] * 2), 0, 75, base_color)

    for wk in ['humidity', 'temperature', 'pressure', 'gas_resistance']:

        max_index = len(weather[wk])
        if max_index > 122:
            max_index = 122

        log_floor = min(weather[wk][-max_index:])
        log_ceiling = max(weather[wk][-max_index:])

        log_span = log_ceiling - log_floor
        if log_span == 0:
            log_span = 1

        log_scale = 38 / log_span

        for i in range(37, 72, 4):
            disp.pixel(41, i, col=base_color)
            disp.pixel(82, i, col=base_color)

        if wk == 'pressure':
            for i in range(1, max_index):
                disp.line(122 - (i-1), 72 - int((weather[wk][-i]-log_floor)*log_scale), 122 -i, 72 - int((weather[wk][-(i+1)]-log_floor)*log_scale), col=data_colors[wk])
        elif wk == 'temperature':
            for i in range(1, max_index):
                disp.pixel(122 - (i-1), 72 - int((weather[wk][-i]-log_floor)*log_scale), col=data_colors[wk])
        elif wk == 'gas_resistance':
            for i in range(1, max_index):
                disp.pixel(122 - (i-1), 72 - int((weather[wk][-i]-log_floor)*log_scale), col=data_colors[wk])
        elif wk == 'humidity':
            log_scale = 0.38
            for i in range(1, max_index):
                disp.line(122 - (i-1), 72 - int(weather[wk][-i]*log_scale), 122 - (i-1), 72, col=(40,40,64))

        if wk == 'pressure':
            min_barometric_height = (
                288.15 / 0.0065 * (1 - (log_ceiling / 1013.25) ** (1 / 5.255))
            )  # formula taken from 'barometric altimeter' by Nubesik
            max_barometric_height = (
                288.15 / 0.0065 * (1 - (log_floor / 1013.25) ** (1 / 5.255))
            )
            log_string = "%iM" % (int(max_barometric_height - min_barometric_height))
        elif wk == 'humidity':
            log_string = "%02d%%" % int(weather['humidity'][-1])
        elif wk == 'temperature':
            log_string = "%02dC" % int(weather['temperature'][-1])
        elif wk == 'gas_resistance':
            log_string = "%02d" % int(weather['gas_resistance'][-1])
        micro_text(log_string, x_text, 75, data_colors[wk])
        x_text = x_text + len(log_string)*7 + 2

    return

def update_weather_logs():

    global weather
    global weatherfiles

    global last_bme_measurement_ms

    i = 0

    for logname in weather.keys():

        leds.set(i,darker_data_colors[logname])

        if len(weather[logname]) > 320:
            with open("/apps/control_centre/cc_%s_%s%s%s.log"%(logname, time_all[0], time_all[1], time_all[2]), "w") as archivefile:
                for w in weather[logname][:-160]:
                    archivefile.write("%s\n"%w)

            weatherfiles[logname].close()

            weather[logname] = weather[logname][-160:]
            with open("/apps/control_centre/cc_%s.log"%logname, "w") as logfile:
                for w in weather[logname]:
                    logfile.write("%s\n"%w)

            weatherfiles[logname] = open("/apps/control_centre/cc_%s.log"%logname, "a")


        with bme680.Bme680() as environment:
            weatherdata = environment.get_data()
            if logname == 'pressure':
                weather['pressure'].append(weatherdata.pressure)
                weatherfiles['pressure'].write("%s\n"%weather['pressure'][-1])
            elif logname == 'humidity':
                weather['humidity'].append(weatherdata.humidity)
                weatherfiles['humidity'].write("%s\n"%weather['humidity'][-1])
            elif logname == 'temperature':
                weather['temperature'].append(weatherdata.temperature)
                weatherfiles['temperature'].write("%s\n"%weather['temperature'][-1])
            elif logname == 'gas_resistance':
                weather['gas_resistance'].append(weatherdata.gas_resistance/1000)
                weatherfiles['gas_resistance'].write("%s\n"%weather['gas_resistance'][-1])

        last_bme_measurement_ms = utime.monotonic_ms()

        leds.set(i,(0,0,0))

        i += 1

    return

def load_weather_logs():

    global weather
    global weatherfiles

    global last_bme_measurement_ms

    i = 0

    for logname in weather.keys():

        leds.set(i, darker_data_colors[logname])

        try:
            with open("/apps/control_centre/cc_%s.log"%logname, "r") as logfile:
                for line in logfile:
                        weather[logname].append(float(line[:-1]))
            if len(weather[logname]) > 160:
                with open("/apps/control_centre/cc_%s_%04d%02d%02d.log"%(logname, time_all[0], time_all[1], time_all[2]), "w") as archivefile:
                    for w in weather[logname][:-160]:
                        archivefile.write("%s\n"%w)

                weather[logname] = weather[logname][-160:]
                with open("/apps/control_centre/cc_%s.log"%logname, "w") as logfile:
                    for w in weather[logname]:
                        logfile.write("%s\n"%w)

            leds.set(i,(0,0,0))

        except:
            with bme680.Bme680() as environment:
                weatherdata = environment.get_data()
                if logname == 'pressure':
                    weather['pressure'].append(weatherdata.pressure)
                    weather['pressure'].append(weatherdata.pressure)
                if logname == 'humidity':
                    weather['humidity'].append(weatherdata.humidity)
                    weather['humidity'].append(weatherdata.humidity)
                if logname == 'temperature':
                    weather['temperature'].append(weatherdata.temperature)
                    weather['temperature'].append(weatherdata.temperature)
                if logname == 'gas_resistance':
                    weather['gas_resistance'].append(weatherdata.gas_resistance/1000)
                    weather['gas_resistance'].append(weatherdata.gas_resistance/1000)

            last_bme_measurement_ms = utime.monotonic_ms()

            leds.set(i,(127,127,0))
            utime.sleep_ms(100)
            leds.set(i,(0,0,0))

        weatherfiles[logname] = open("/apps/control_centre/cc_%s.log"%logname, "a")
        weatherfiles[logname].write("%s\n%s\n"%(weather[logname][-1], weather[logname][-1]))
        i += 1

    return


def update_battery_log():

    global battery_log
    global batteryfile
    global s

    if s['volt_max'] < max([b[1] for b in battery_log]):
        s['volt_max'] = max([b[1] for b in battery_log])
        save_settings()
    if s['volt_min'] > min([b[1] for b in battery_log]):
        s['volt_min'] = min([b[1] for b in battery_log])
        save_settings()

    leds.set(9, (160,120,0))

    if len(battery_log) > 288:
        with open("/apps/control_centre/cc_battery_%s%s%s.log"%(time_all[0], time_all[1], time_all[2]), "w") as archivefile:
            for b in battery_log[:-144]:
                    archivefile.write("%d,%.2f\n"%b)

        batteryfile.close()

        battery_log = battery_log[-144:]
        with open("/apps/control_centre/cc_battery.log", "w") as logfile:
            for b in battery_log:
                logfile.write("%d,%.2f\n"%b)

        batteryfile = open("/apps/control_centre/cc_battery.log", "a")

    battery_log.append((utime.time(), smooth_voltage))

    batteryfile.write("%d,%.2f\n"%battery_log[-1])

    leds.set(9, (0,0,0))

    return

def load_battery_log():

    global battery_log
    global batteryfile

    leds.set(9, (160,120,0))

    try:
        with open("/apps/control_centre/cc_battery.log", "r") as logfile:
            for line in logfile:
                (time, voltage) = line[:-1].split(",")
                battery_log.append((int(time), float(voltage)))
        if len(battery_log) > 144:
            with open("/apps/control_centre/cc_battery_%s%s%s.log"%(time_all[0], time_all[1], time_all[2]), "w") as archivefile:
                for b in battery_log[:-144]:
                        archivefile.write("%d,%.2f\n"%b)
            battery_log = battery_log[-144:]
            with open("/apps/control_centre/cc_battery.log", "w") as logfile:
                for b in battery_log:
                    logfile.write("%d,%.2f\n"%b)

        leds.set(9,(0,0,0))

    except:
        battery_log.append((utime.time(), power.read_battery_voltage()))
        utime.sleep_ms(200)
        battery_log.append((utime.time(), power.read_battery_voltage()))
        with open("/apps/control_centre/cc_battery.log", "w") as logfile:
            for b in battery_log:
                logfile.write("%d,%.2f\n"%b)

        leds.set(9,(222,202,0))
        utime.sleep_ms(100)
        leds.set(9,(0,0,0))

    batteryfile = open("/apps/control_centre/cc_battery.log", "a")
    smooth_voltage = (battery_log[-1][1] + battery_log[-2][1]) / 2

    return


def load_settings():

    global s

    leds.set(10,(0,0,10))
    utime.sleep_ms(100)
    try:
        with open('/apps/control_centre/cc_settings.json', 'r') as fh:
            s = ujson.load(fh)
        leds.set(10,(0,10,0))
        utime.sleep_ms(100)
    except:
        leds.set(10,(10,0,0))
        utime.sleep_ms(200)
        save_settings()

    leds.set(10,(0,0,0))

    return


def save_settings():

    leds.set(10,(10,10,0))
    utime.sleep_ms(100)

    try:
        with open('/apps/control_centre/cc_settings.json', 'w') as fh:
            ujson.dump(s, fh)
        leds.set(10,(0,10,0))
        utime.sleep_ms(100)
    except:
        leds.set(10,(10,0,0))
        utime.sleep_ms(200)
        leds.set(10,(10,0,0))
        utime.sleep_ms(200)
        leds.set(10,(10,0,0))
        utime.sleep_ms(200)
        leds.set(10,(10,0,0))
        utime.sleep_ms(200)
        save_settings()

    leds.set(10,(0,0,0))

    return


def show_alarm_screen():
    global alarm_count
    alarm_count += 1

    disp.clear().backlight(display_brightness)

    leds.set(11, ((40*alarm_count)%255,(60*alarm_count)%255,(30*alarm_count)%255))
    leds.set(12, ((30*alarm_count)%255,(60*alarm_count)%255,(40*alarm_count)%255))
    leds.set(13, ((30*alarm_count)%255,(40*alarm_count)%255,(60*alarm_count)%255))
    leds.set(14, ((40*alarm_count)%255,(30*alarm_count)%255,(60*alarm_count)%255))

    if (alarm_count // 10) % 4 > 0 or alarm_count % 2 == 1:
        show_time(19, 26, [False, True][alarm_count%2])
    else:
        vibra.vibrate(alarm_count % 40 * 5 + 60)
    return


# day of week algorithm from https://www.hackerearth.com/blog/developers/how-to-find-the-day-of-a-week/
def day_of_week(year, month, day):
    t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
    year -= month < 3
    return (
        year + int(year / 4) - int(year / 100) + int(year / 400) + t[month - 1] + day
    ) % 7


def show_calendar():

    disp.clear()

    mini_text("%s %04d" % (months[calendar_month - 1], calendar_year), 44, 0, base_color)

    for i in range(7):

        micro_text(days[i][:2], i * 22 + 7, 75, base_color)  # write weekdays

        for w in range(6):  # draw boxes for four weeks
            disp.rect(
                i * 22 + 2,
                11 + w * 10,
                (i + 1) * 22 + 2,
                21 + w * 10,
                col=base_color,
                filled=False,
            )

    for i in range(113, 156, 2):  # dashed lines for weekend boxes
        disp.line(i, 11, i, 71, col=(0, 0, 0))
    for i in range(12, 71, 2):
        disp.line(113, i, 156, i, col=(0, 0, 0))

    max_feb = 28
    if calendar_year % 4 == 0:
        max_feb = 29
    max_day = (31, max_feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

    day = 1
    row = 0
    col = (day_of_week(calendar_year, calendar_month, day) - 1) % 7

    for i in range(max_day[calendar_month - 1]):

        if (
            day == calendar_day
            and calendar_month == time_all[1]
            and calendar_year == time_all[0]
        ):  # highlight current day
            disp.rect(
                col * 22 + 1,
                10 + row * 10,
                (col + 1) * 22 + 3,
                22 + row * 10,
                col=(0, 0, 0),
                filled=True,
            )
            disp.rect(
                col * 22 + 2,
                11 + row * 10,
                (col + 1) * 22 + 2,
                21 + row * 10,
                col=(255, 64, 64),
                filled=False,
            )
            micro_text(
                "%2d"%day, col * 22 + 7, row * 10 + 14, (255, 64, 64)
            )  # write each day in box
        else:
            micro_text(
                "%2d"%day, col * 22 + 7, row * 10 + 14, base_color
            )  # write each day in box

        day += 1
        col += 1
        if col == 7:
            col = 0
            row += 1

    return



def show_set_watch_screen():

    global s
    global watch_highlight
    global watch_state
    global set_minute
    global set_hour
    global set_year
    global set_month
    global set_day

    if p_screen != WATCH:
        watch_highlight = EXIT
        watch_state = EXIT

    disp.clear()

    mini_text("SET TIME", 3, 55, (base_color, selected_red)[watch_highlight==SET_TIME])
    mini_text("SET DATE", 83, 55, (base_color, selected_red)[watch_highlight==SET_DATE])
    mini_text("SET ALARM", 3, 69, (base_color, selected_red)[watch_highlight==SET_ALARM])
    mini_text("RETURN", 97, 69, (base_color, selected_red)[watch_highlight==EXIT])
    disp.rect(0, 51, 159, 79, col=base_color, filled=False)
    disp.line(0, 65, 159, 65, col=base_color)  # -
    disp.line(79, 51, 79, 65, col=base_color)  # |top
    disp.line(90, 79, 90, 65, col=base_color)  # |bottom

    if watch_highlight == SET_TIME:
        disp.rect(80, 66, 0, 51, col=(0, 0, 0), filled=False)
        disp.rect(79, 65, 0, 51, col=selected_red, filled=False)
    if watch_highlight == SET_DATE:
        disp.rect(78, 51, 159, 66, col=(0, 0, 0), filled=False)
        disp.rect(79, 51, 159, 65, col=selected_red, filled=False)
    if watch_highlight == SET_ALARM:
        disp.rect(0, 64, 91, 79, col=(0, 0, 0), filled=False)
        disp.rect(0, 65, 90, 79, col=selected_red, filled=False)
    if watch_highlight == EXIT:
        disp.rect(89, 64, 159, 79, col=(0, 0, 0), filled=False)
        disp.rect(90, 65, 159, 79, col=selected_red, filled=False)

    if watch_state == EXIT:

        set_minute = minute
        set_hour = hour
        set_year = time_all[0]
        set_month = time_all[1]
        set_day = time_all[2]

    show_time()

    if watch_state == SET_DATE_YEAR:
        mini_text("SET YEAR:", 1, 36, base_color)
        mini_text("%04d" % (set_year), 93, 36, (255, 64, 64))

    if watch_state == SET_DATE_MONTH:
        mini_text("SET MONTH: ", 1, 36, base_color)
        mini_text(months[set_month - 1], 103, 36, (255, 64, 64))

    if watch_state == SET_DATE_DAY:
        mini_text("SET DAY:", 1, 36, base_color)
        mini_text("%02d" % (set_day), 83, 36, (255, 64, 64))

    if watch_state == SET_ALARM_ACTIVE:
        if s['alarm']['active']:
            mini_text("ALARM IS", 1, 36, base_color)
            mini_text("ON!", 81, 36, (255, 64, 64))
        else:
            mini_text("ALARM IS", 1, 36, base_color)
            mini_text("OFF!", 81, 36, (255, 64, 64))

    if watch_state == SET_ALARM_HOUR:
        mini_text("ALARM HOUR:", 1, 36, base_color)
        mini_text("%02d" % s['alarm']['hour'], 111, 36, (255, 64, 64))

    if watch_state == SET_ALARM_MINUTE:
        mini_text("ALARM MINUTE:", 1, 36, base_color)
        mini_text("%02d" % s['alarm']['minute'], 131, 36, (255, 64, 64))

    if watch_state < SET_DATE_YEAR or watch_state > SET_DATE_DAY:
        mini_text(days[time_all[6]], 130, 0, base_color)
        mini_text(months[time_all[1] - 1], 130, 10, base_color)
        mini_text("%02d" % time_all[2], 130, 20, base_color)

    if watch_state == EXIT or (watch_state in (SET_TIME_HH, SET_TIME_MM)):
        if s['alarm']['active']:
            mini_text("ALARM TIME: %02d:%02d" % (s['alarm']['hour'], s['alarm']['minute']), 1, 36, base_color)
        else:
            mini_text("ALARM IS OFF", 1, 36, base_color)

    return

############
### main ###
############

with display.open() as disp:

    disp.clear()
    light_sensor.start()

    load_settings()
    load_weather_logs()
    load_battery_log()

    last_interaction_ms = utime.monotonic_ms()
    last_bme_measurement_ms = utime.monotonic_ms()

    while True:

        vibra_tock = 0

        time_all = utime.localtime()
        minute = time_all[4]
        hour = time_all[3]
        millis = utime.monotonic_ms()

        if (p_minute != minute) and (minute % s['weather_update_interval'] == 0):
            update_weather_logs()

        if (p_minute != minute) and (minute % 10 ==0):
            update_battery_log()

        smooth_voltage = (power.read_battery_voltage() + smooth_voltage * 5) / 6

        p_charge = charge
        charge = volt_to_percent(smooth_voltage)

        p_plugged_in = plugged_in
        if power.read_chargein_voltage() > 4.2:
            plugged_in = True
            if power.read_chargein_current() > 0.07 :
                charging = True
            else:
                charging = False
        else:
            plugged_in = False
            utime.sleep_ms(100)

        if screen != VOID:
            target_brightness = ( (target_brightness * 10) + (light_sensor.get_reading()-8) ) / 11
            display_brightness = constrain(int(target_brightness), 1, 100)
            disp.backlight(display_brightness).update()

        p_button_state = button_state
        button_state = buttons.read(0xFF)

        button_left_pressed = (
            button_state & buttons.BOTTOM_LEFT != 0 and p_button_state == 0
        )
        button_right_pressed = (
            button_state & buttons.BOTTOM_RIGHT != 0 and p_button_state == 0
        )
        button_select_pressed = (
            button_state & buttons.TOP_RIGHT != 0 and p_button_state == 0
        )

        if p_button_state != button_state:
            last_interaction_ms = millis
            if button_state != 0:
                repeat_millis = millis + 600
                vibra.vibrate(8)
                vibra_tock = 8
            else:
                repeat_millis = millis + 10000000

        if (
            millis > repeat_millis and button_state != 0
        ):  # long button push -> repeated triggers
            last_interaction_ms = millis
            repeat_millis = millis + 200
            vibra_tock += 2
            button_left_pressed = button_state & buttons.BOTTOM_LEFT != 0
            button_right_pressed = button_state & buttons.BOTTOM_RIGHT != 0
            button_select_pressed = button_state & buttons.TOP_RIGHT != 0

        if (
            s['alarm']['active'] == True
            and hour == s['alarm']['hour']
            and minute == s['alarm']['minute']
        ):
            screen = ALARM_SCREEN

        if screen == ALARM_SCREEN and (hour != s['alarm']['hour'] or minute != s['alarm']['minute']):
            screen = VOID

        if screen == VOID:
            read_bhi()
            if plugged_in:
                screen = MAIN
                disp.backlight(display_brightness).update()
            elif (p_button_state != button_state and button_state != 0):
                screen = MAIN
                disp.backlight(display_brightness).update()
                last_interaction_ms = millis
            elif ((averaged_orientation_y - current_orientation_y) > s['gesture_threshold']):
                leds.set_rocket(1,11)
                screen = MAIN
                disp.backlight(display_brightness).update()
                last_interaction_ms = millis
                utime.sleep_ms(222)
                leds.set_rocket(1,0)

        if ((plugged_in == False) and (screen == MAIN)):
            if (
                millis > (last_interaction_ms + (s['sleep_timeout'] * 1000))
                and screen != VOID
            ):
                screen = VOID
                disp.backlight(0).clear()

        if screen != MAIN:
            highlighted = VOID

        if screen == MAIN:

            if (button_right_pressed == True):
                if highlighted == VOID:
                    highlighted = WATCH
                    show_time(selected=True)
                elif highlighted == WATCH:
                    highlighted = DATE
                    show_time()
                    show_date(selected=True)
                elif highlighted == DATE:
                    highlighted = BATTERY
                    show_date()
                    show_battery(selected=True)
                elif highlighted == BATTERY:
                    highlighted = WEATHER
                    show_battery()
                    show_weather(selected=True)
                else:
                    highlighted = VOID
                    show_weather()
                last_interaction_ms = millis

            if (button_left_pressed == True):
                if highlighted == VOID:
                    highlighted = WEATHER
                    show_weather(selected=True)
                elif highlighted == WEATHER:
                    highlighted = BATTERY
                    show_weather()
                    show_battery(selected=True)
                elif highlighted == BATTERY:
                    highlighted = DATE
                    show_battery()
                    show_date(selected=True)
                elif highlighted == DATE:
                    highlighted = WATCH
                    show_date()
                    show_time(selected=True)
                else:
                    highlighted = VOID
                    show_time()
                last_interaction_ms = millis

            if button_select_pressed == True:

                if highlighted == VOID:
                    if not (flashlight_on | leds_on):
                        leds.set_flashlight(True)
                        leds.set_rocket(0,20)
                        leds.set_rocket(1,20)
                        leds.set_rocket(2,20)
                        flashlight_on = True
                    elif flashlight_on:
                        leds.set_flashlight(False)
                        leds.set_rocket(0,0)
                        leds.set_rocket(1,0)
                        leds.set_rocket(2,0)
                        flashlight_on = False
                        leds.set_all([base_color for x in range(15)])
                        leds_on = True
                    elif leds_on:
                        leds.clear()
                        leds_on = False
                    else:
                        leds.clear()
                        leds.set_flashlight(False)
                        leds.set_rocket(0,0)
                        leds.set_rocket(1,0)
                        leds.set_rocket(2,0)
                        flashlight_on = False
                        leds_on = False

                if highlighted == WATCH:
                    screen = WATCH
                    highlighted = VOID

                if highlighted == DATE:
                    screen = DATE
                    highlighted = VOID

                if highlighted == BATTERY:
                    screen = BATTERY
                    highlighted = VOID

                if highlighted == WEATHER:
                    screen = WEATHER
                    highlighted = VOID

                last_interaction_ms = millis

            # timeout from selection
            if ( (millis > last_interaction_ms + 5000) and (highlighted != VOID)):
                p_screen = VOID # force refresh
                highlighted = VOID
                vibra_tock = 6

        elif screen == WEATHER:
            if button_right_pressed == True:
                if weather_detail == 'pressure':
                    weather_detail = 'temperature'
                elif weather_detail == 'temperature':
                    weather_detail = 'humidity'
                elif weather_detail == 'humidity':
                    weather_detail = 'gas_resistance'
                elif weather_detail == 'gas_resistance':
                    weather_detail = 'pressure'
                last_interaction_ms = millis

            if button_left_pressed == True:
                if weather_detail == 'pressure':
                    weather_detail = 'humidity'
                elif weather_detail == 'humidity':
                    weather_detail = 'temperature'
                elif weather_detail == 'temperature':
                    weather_detail = 'gas_resistance'
                elif weather_detail == 'gas_resistance':
                    weather_detail = 'pressure'
                last_interaction_ms = millis

            if button_select_pressed:
                button_select_pressed = False
                disp.clear()
                screen = MAIN
                last_interaction_ms = millis

        elif screen == BATTERY:

            if (button_right_pressed == True) or (button_left_pressed == True):
                if battery_screen == 'log':
                    battery_screen = 'live'
                else:
                    battery_screen = 'log'
                last_interaction_ms = millis

            if button_select_pressed == True:
                button_select_pressed = False
                disp.clear()
                screen = MAIN
                last_interaction_ms = millis

        elif screen == DATE:
            if button_right_pressed == True:
                calendar_month += 1
                if calendar_month > 12:
                    calendar_month = 1
                    calendar_year += 1
                show_calendar()

            if button_left_pressed == True:
                calendar_month -= 1
                if calendar_month < 1:
                    calendar_month = 12
                    calendar_year -= 1
                show_calendar()

            if button_select_pressed == True:
                button_select_pressed = False
                disp.clear()
                screen = MAIN
                last_interaction_ms = millis

        elif screen == ALARM_SCREEN:
            if button_right_pressed or button_left_pressed or button_select_pressed:
                screen = MAIN
                last_interaction_ms = millis
                s['alarm']['active'] = False
                save_settings()


        elif screen == WATCH and p_screen == WATCH:

            if watch_state == EXIT:  # exit -> return to main
                if button_left_pressed:
                    watch_highlight = (watch_highlight - 1) % 4
                if button_right_pressed:
                    watch_highlight = (watch_highlight + 1) % 4

                if button_select_pressed:
                    if watch_highlight == EXIT:
                        button_select_pressed = False
                        disp.clear()
                        screen = MAIN
                        last_interaction_ms = millis

                    if watch_highlight == SET_TIME:
                        watch_state = SET_TIME_HH
                        button_select_pressed = False

                    if watch_highlight == SET_DATE:
                        watch_state = SET_DATE_YEAR
                        button_select_pressed = False

                    if watch_highlight == SET_ALARM:
                        watch_state = SET_ALARM_ACTIVE
                        button_select_pressed = False

            if watch_state == SET_TIME_HH:  # set hour
                if button_left_pressed:
                    set_hour = (set_hour - 1) % 24

                if button_right_pressed:
                    set_hour = (set_hour + 1) % 24

                if button_select_pressed:
                    watch_state = SET_TIME_MM
                    button_select_pressed = False

            if watch_state == SET_TIME_MM:  # set minute
                if button_left_pressed:
                    set_minute = (set_minute - 1) % 60

                if button_right_pressed:
                    set_minute = (set_minute + 1) % 60

                if button_select_pressed:  # save new time, return to exit
                    minute = set_minute
                    hour = set_hour
                    utime.set_time(
                        utime.mktime(
                            (
                                time_all[0],
                                time_all[1],
                                time_all[2],
                                set_hour,
                                set_minute,
                                0,
                                time_all[6],
                                time_all[7],
                            )
                        )
                    )

                    watch_highlight = SET_DATE
                    watch_state = EXIT
                    button_select_pressed = False

            if watch_state == SET_DATE_YEAR:  # set year
                if button_left_pressed:
                    set_year -= 1

                if button_right_pressed:
                    set_year += 1
                set_year = constrain(set_year, 2019, 3000)

                if button_select_pressed:
                    watch_state = SET_DATE_MONTH
                    button_select_pressed = False

            if watch_state == SET_DATE_MONTH:  # set month
                if button_left_pressed:
                    set_month -= 1

                if button_right_pressed:
                    set_month += 1

                set_month = ((set_month - 1) % 12) + 1

                if button_select_pressed:
                    watch_state = SET_DATE_DAY
                    button_select_pressed = False

            if watch_state == SET_DATE_DAY:  # set day

                max_feb = 28
                if set_year % 4 == 0:
                    max_feb = 29
                max_day = (31, max_feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

                if button_left_pressed:
                    set_day -= 1

                if button_right_pressed:
                    set_day += 1

                set_day = ((set_day - 1) % max_day[set_month - 1]) + 1

                if button_select_pressed:  # save new date, return to exit

                    utime.set_time(
                        utime.mktime(
                            (
                                set_year,
                                set_month,
                                set_day,
                                time_all[3],
                                time_all[4],
                                time_all[5],
                                time_all[6],
                                time_all[7],
                            )
                        )
                    )

                    watch_highlight = EXIT
                    watch_state = EXIT
                    button_select_pressed = False

            if watch_state == SET_ALARM_ACTIVE:  # alarm on / off
                last_interaction_ms = millis

                if button_left_pressed or button_right_pressed:
                    s['alarm']['active'] = s['alarm']['active'] == False

                if button_select_pressed:
                    if s['alarm']['active'] == False:
                        watch_state = EXIT
                        watch_highlight = EXIT
                        save_settings()
                        button_select_pressed = False
                    else:
                        watch_state = SET_ALARM_HOUR
                        button_select_pressed = False

            if watch_state == SET_ALARM_HOUR:  # alarm hours

                if button_left_pressed:
                    s['alarm']['hour'] -= 1
                if button_right_pressed:
                    s['alarm']['hour'] += 1
                s['alarm']['hour'] = s['alarm']['hour'] % 24

                if button_select_pressed:
                    watch_state = SET_ALARM_MINUTE
                    button_select_pressed = False

            if watch_state == SET_ALARM_MINUTE:  # alarm minutes

                if button_left_pressed:
                    s['alarm']['minute'] -= 1
                if button_right_pressed:
                    s['alarm']['minute'] += 1
                s['alarm']['minute'] = s['alarm']['minute'] % 60

                if button_select_pressed:
                    watch_state = EXIT
                    watch_highlight = EXIT
                    save_settings()
                    button_select_pressed = False

        if screen == MAIN:
            read_bhi()
            show_compass()
            if plugged_in and ((averaged_orientation_y - current_orientation_y) > s['gesture_threshold']):
            # this gesture check is for debugging only
                leds.set_rocket(1,11)
                screen = MAIN
                disp.backlight(display_brightness).update()
                last_interaction_ms = millis
                utime.sleep_ms(222)
                leds.set_rocket(1,0)
            if (p_minute != minute) or (p_screen != screen):
                show_time()
                show_date()
                show_weather()
            if (int(p_charge) != int(charge)) or (p_plugged_in != plugged_in) or (p_screen != screen):
                last_interaction_ms = millis
                show_battery()

        elif screen == WATCH:
            show_set_watch_screen()

        elif screen == ALARM_SCREEN:
            show_alarm_screen()

        elif screen == DATE:
            if p_screen != DATE:
                calendar_day = time_all[2]
                calendar_month = time_all[1]
                calendar_year = time_all[0]
                show_calendar()

        elif screen == BATTERY:
            show_battery_log()

        elif screen == WEATHER:
            show_weather_log()

        if s['alarm']['active'] == True:
            for o in (0,1,2,3):
                leds.prep(11+o, (140-(abs((((millis+o*980)//28)%140)-70)), 80-(abs((((millis+o*980)//28)%140)-70)), 140-(abs((((millis+o*980)//28)%140)-70))))
        else:
            leds.prep(11, (0, 0, 0))
            leds.prep(12, (0, 0, 0))
            leds.prep(13, (0, 0, 0))
            leds.prep(14, (0, 0, 0))

        leds.update()

        if vibra_tock > 0:
            vibra.vibrate(vibra_tock)

        p_screen = screen
        p_minute = minute

