1+ # PyPortal TOTP Authenticator
12import time
23
3- import board
4- import busio
5- from digitalio import DigitalInOut
6-
74import adafruit_hashlib as hashlib
5+ import adafruit_imageload
86import adafruit_touchscreen
7+ import board
8+ import busio
99import displayio
1010import neopixel
1111import terminalio
1818from adafruit_esp32spi import adafruit_esp32spi
1919from adafruit_ntp import NTP
2020from adafruit_pyportal import PyPortal
21+ from digitalio import DigitalInOut
2122
2223# Background/Images
2324BACKGROUND = 0x059ACE
3334 print ("WiFi secrets are kept in secrets.py, please add them there!" )
3435 raise
3536
36-
3737# Initialize PyPortal Display
3838display = board .DISPLAY
3939
4747 ),
4848 size = (WIDTH , HEIGHT ))
4949
50-
5150# Create a SHA1 Object
5251SHA1 = hashlib .sha1
5352
6059spi = busio .SPI (board .SCK , board .MOSI , board .MISO )
6160esp = adafruit_esp32spi .ESP_SPIcontrol (spi , esp32_cs , esp32_ready , esp32_reset )
6261
63-
6462# HMAC implementation, as hashlib/hmac wouldn't fit
6563# From https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
6664def HMAC (k , m ):
@@ -116,7 +114,6 @@ def int_to_bytestring(i, padding=8):
116114
117115# HMAC -> OTP generator, pretty much same as
118116# https://github.com/pyotp/pyotp/blob/master/src/pyotp/otp.py
119-
120117def generate_otp (int_input , secret_key , digits = 6 ):
121118 if int_input < 0 :
122119 raise ValueError ('input must be positive integer' )
@@ -132,7 +129,6 @@ def generate_otp(int_input, secret_key, digits=6):
132129 str_code = str (code % 10 ** digits )
133130 while len (str_code ) < digits :
134131 str_code = '0' + str_code
135-
136132 return str_code
137133
138134
@@ -163,43 +159,33 @@ def generate_otp(int_input, secret_key, digits=6):
163159
164160splash .append (background )
165161
166-
167162key_group = displayio .Group (scale = 5 )
168163# We'll use a default text placeholder for this label
169164label_key = Label (font , text = "000 000" )
170165label_key .x = (display .width // 2 ) // 13
171- label_key .y = 15
166+ label_key .y = 17
172167key_group .append (label_key )
173168
174169label_title = Label (font , max_glyphs = 14 )
175- label_title .x = (display .width // 2 ) // 10
170+ label_title .text = "loading.."
171+ label_title .x = (display .width // 2 ) // 13
176172label_title .y = 5
177173key_group .append (label_title )
178174
179175splash .append (key_group )
180176
181- # Create a label to monitor the status
182- label_status = Label (font , max_glyphs = 45 )
183- label_status .x = (display .width // 2 ) - 50
184- label_status .y = 120
185- splash .append (label_status )
186-
187177# Show the group
188178display .show (splash )
189179
190-
191180print ("Connecting to AP..." )
192- label_status .text = "Connecting to AP..."
193181while not esp .is_connected :
194182 try :
195183 esp .connect_AP (secrets ['ssid' ], secrets ['password' ])
196184 except RuntimeError as e :
197185 print ("could not connect to AP, retrying: " , e )
198- label_status .text ("Retrying..." )
199186 continue
200187
201188print ("Connected to SSID: " , secrets ['ssid' ])
202- label_status .text = "Connected! Fetching NTP..."
203189
204190# Initialize the NTP object
205191ntp = NTP (esp )
@@ -220,57 +206,46 @@ def generate_otp(int_input, secret_key, digits=6):
220206mono_time = int (time .monotonic ())
221207print ("Monotonic time" , mono_time )
222208
223- # Clear the status label
224- label_status .text = ""
225209
226- # Add buttons to the interface
227- # TODO: Generate them like in https://learn.adafruit.com/pyportal-philips-hue-lighting-controller/code-walkthrough
228- # TODO: Make these dynamically based on what is store in secrets.py
229- # TODO: Add icons to buttons instead of text
210+ def display_otp (unix_time , name , secret ):
211+ """Updates text objects to display the OTP and name.
230212
231- assert len (secrets ['totp_keys' ]) < 8 , "This code can only render 8 keys at a time"
213+ :param int unix_time: Current unix time
214+ :param str name: OTP name
215+ :param str secret: OTP Secret
216+ """
217+ otp = generate_otp (unix_time // 30 , secret )
218+ print (name + " OTP output: " , otp )
219+ # display the key's name
220+ label_title .text = name
221+ # format and display the OTP
222+ label_key .text = "{} {}" .format (str (otp )[0 :3 ],str (otp )[3 :6 ])
232223
233- buttons = []
224+ def get_unix_time ():
225+ """Calculate current time based on NTP + monotonic
234226
235- # generate buttons
236- btn_x = 20
237- for i in secrets ['totp_keys' ]:
238- print (i )
239- button = Button (name = i [0 ], x = btn_x , y = 130 ,
240- width = 60 , height = 60 ,
241- label = i [0 ], label_font = font , label_color = 0x0 ,
242- fill_color = None , outline_color = None )
243- buttons .append (button )
244- btn_x += 60
245-
246- # append buttons to splash group
247- for b in buttons :
248- splash .append (b .group )
227+ """
228+ unix_time = t - mono_time + int (time .monotonic ())
229+ return unix_time
249230
231+ # how long to stay on if not in always_on mode
232+ countdown = ON_SECONDS
233+ cur_otp = 0
234+ max_otp = len (secrets ['totp_keys' ])
250235
236+ display_otp (get_unix_time (), secrets ['totp_keys' ][cur_otp ][0 ],
237+ secrets ['totp_keys' ][cur_otp ][1 ])
251238
252- countdown = ON_SECONDS # how long to stay on if not in always_on mode
253239while ALWAYS_ON or (countdown > 0 ):
254- # Calculate current time based on NTP + monotonic
255- unix_time = t - mono_time + int (time .monotonic ())
256240 p = ts .touch_point
257241 if p :
258- for i , b in enumerate (buttons ):
259- if b .contains (p ):
260- b .selected = True
261- for name , secret in secrets ['totp_keys' ]:
262- if b .name == name :
263- # generate OTP
264- #print('Background color: ', background_color)
265- otp = generate_otp (unix_time // 30 , secret )
266- print ('{} selected: ' .format (name ))
267- print (name + " OTP output: " , otp )
268- # display the key's name
269- label_title .text = name
270- # format and display the OTP
271- label_key .text = "{} {}" .format (str (otp )[0 :3 ],str (otp )[3 :6 ])
272- else :
273- b .selected = False
242+ if cur_otp < max_otp :
243+ display_otp (get_unix_time (), secrets ['totp_keys' ][cur_otp ][0 ],
244+ secrets ['totp_keys' ][cur_otp ][1 ])
245+ cur_otp += 1
246+ else :
247+ # reset the current otp display
248+ cur_otp = 0
274249 # We'll update every 1/4 second, we can hash very fast so its no biggie!
275250 countdown -= 0.25
276251 time .sleep (0.25 )
0 commit comments