"""
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()