def play(filename_or_url, volume=None, loop=False, sync_beat=None, start_at_next=None, on_finished=None): if volume is None: try: volume = NVS('system').get_i32('volume') except: volume = 255 _start_audio_if_needed() channel_id = _add_channel(filename_or_url, on_finished) if channel_id is None or channel_id < 0: print('Failed to start audio channel') return channel_id sndmixer.volume(channel_id, volume) if loop: sndmixer.loop(channel_id, True) if sync_beat is not None and start_at_next is not None: sndmixer.start_beat_sync(sync_beat) sndmixer.start_at_next(channel_id, start_at_next) else: sndmixer.play(channel_id) return channel_id
def __init__(self, display_obj, notifier_obj, lid_sensor_obj, font_obj, nvs_namespace='LBOX_CFG'): self.__namespace = NVS(nvs_namespace) # non-volatile storage self.__display = display_obj self.__render_font = font_obj self.__notifier = notifier_obj self.__lid_sensor = lid_sensor_obj self.__wifi_manager = __LetterBoxWiFiManager( display_obj=self.__display, font_obj=self.__render_font, ap_ssid="LetterBox", ap_password='******') self.__requester = ConnectedRequester(self.__wifi_manager) self.serial_number = None self.private_key = None
class LetterBox: def __init__(self, display_obj, notifier_obj, lid_sensor_obj, font_obj, nvs_namespace='LBOX_CFG'): self.__namespace = NVS(nvs_namespace) # non-volatile storage self.__display = display_obj self.__render_font = font_obj self.__notifier = notifier_obj self.__lid_sensor = lid_sensor_obj self.__wifi_manager = __LetterBoxWiFiManager( display_obj=self.__display, font_obj=self.__render_font, ap_ssid="LetterBox", ap_password='******') self.__requester = ConnectedRequester(self.__wifi_manager) self.serial_number = None self.private_key = None def initialize(self): initial_config_done = None try: self.__store_keys() # write any new keys if given initial_config_done = self.__namespace.get_i32( "config_done") # check if this var exists in NVS except OSError: #means config hasn't been done print("Initializing device...") self.__onboard_device( ) # onboard the device (register the public key) self.__display_no_letter_text( ) # tell user to go register on front-end self.__namespace.set_i32("config_done", 1) # don't come back here again self.__namespace.commit() print("Device initialized.") finally: initial_config_done = self.__namespace.get_i32( "config_done") #should be done now if initial_config_done == 1: self.serial_number = bytearray( 12) # store the serial number for easy access self.__namespace.get_blob("serial_number", self.serial_number) self.serial_number = self.serial_number.decode('utf-8') print("Serial number is {0}.".format(self.serial_number)) self.private_key = bytearray( self.__namespace.get_i32( "pri_key_len")) # and store the private key self.__namespace.get_blob("pri_key", self.private_key) self.private_key = self.private_key.decode('utf-8') print('Private key loaded.') return True return False # shouldn't happen def __onboard_device(self): print("Onboarding device...") # Onboarding consists of registering your serial number and public key with the server status_code = 0 attempt_serial = unique_id() # first serial to try is the MAC address pub_key = bytearray(self.__namespace.get_i32("pub_key_len")) self.__namespace.get_blob("pub_key", pub_key) pub_key_b64 = b2a_base64(pub_key).strip().decode('utf-8') while status_code != 200: # will keep generating new serials until one works self.serial_number = hexlify(attempt_serial).strip().decode( 'utf-8').upper() r = self.__requester.request( 'POST', 'https://letterbox.mayursaxena.com/.netlify/functions/onboard', json={ 'id': self.serial_number, 'pk': pub_key_b64 }) status_code = r.status_code print('{0}: {1}'.format(r.status_code, r.reason)) attempt_serial = urandom(6) r.close() self.__namespace.set_blob( 'serial_number', self.serial_number) # store the serial that worked in NVS self.__namespace.commit() print("Device onboarded with serial {0}.".format(self.serial_number)) async def poll_async(self): while True: try: gc.collect() # something about memory gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) print("Polling for new image...") url = "https://letterbox.mayursaxena.com/.netlify/functions/waiting" r = self.__requester.request('POST', url, json={ "id": self.serial_number, "content": 1 }) if r.status_code != 200: print('{0} {1}: {2}'.format(r.status_code, r.reason, r.text)) else: json_resp = r.json() if 'content' in json_resp: img_bytes = self.__decrypt_img( loads( a2b_base64( json_resp["content"]).strip().decode( 'utf-8'))) notify_task = uasyncio.create_task( self.__start_notifier()) # start spinning! print('Waiting for lid to open...') await self.__wait_for_lid('open' ) # "blocks" until lid opens notify_task.cancel() # stop spinning self.__display_image( img_bytes) # put the image on the screen print('Waiting for lid to close...') await self.__wait_for_lid( 'close') # "blocks" until lid closes self.__acknowledge_image( json_resp["sha"]) # delete image off the server self.__display.clear( ) # clear the display to avoid burn-in (necessary?) r.close() await uasyncio.sleep(30) # wait 30 seconds between each poll except Exception as e: print("Encountered error in poll: {0}".format(e)) async def __wait_for_lid(self, state): state = state.lower() if state not in ('open', 'close'): return False p_last = 0 # need to have two consecutive readings in order to trigger LOWER_BOUND = 500 # any reading above this on the photoresistor we're assuming means the lid is off while True: p_now = self.__lid_sensor.read() if LOWER_BOUND <= p_last and LOWER_BOUND <= p_now and state == 'open': return True elif p_last < LOWER_BOUND and p_now < LOWER_BOUND and state == 'close': return True p_last = p_now await uasyncio.sleep(1) # wait one second between each reading async def __start_notifier(self): # Servo motors have frequencies of 50Hz, meaning they update every 20ms # Duty ranges from 20 - 120 ==> 0 deg to 180 deg self.__notifier.duty(70) try: curr_duty = self.__notifier.duty() # make an array from 70...20...120...70 duties = [i for i in range(curr_duty, 20, -1)] + [ i for i in range(20, 120) ] + [i for i in range(120, curr_duty, -1)] while True: for i in duties: self.__notifier.duty(i) await uasyncio.sleep(0.005) # sleep for 5ms await uasyncio.sleep(3) # 3 seconds between each sweep except uasyncio.CancelledError: print('Cancelling notifier...') raise finally: self.__notifier.duty(70) # reset the position to 90 degrees print('Notifier cancelled.') def __display_image(self, img_bytes): self.__display.clear() # clear the image and put out a new image self.__display.draw_image(bytes=img_bytes) def __acknowledge_image(self, file_hash): print('Acknowledging image...') #send a message of form hash;timestamp (probably doesn't need to be b64) to_send = { 'id': self.serial_number, 'message': b2a_base64('{0};{1}'.format(file_hash, timestamp())).strip().decode('utf-8') } signature = generate_rsa_signature(to_send['message'], self.private_key) to_send['signature'] = b2a_base64(signature).strip().decode('utf-8') # sign a message saying we've seen the image r = self.__requester.request( 'POST', 'https://letterbox.mayursaxena.com/.netlify/functions/received', json=to_send) print('{0} {1}: {2}'.format(r.status_code, r.reason, r.text)) r.close() def __decrypt_img(self, json_payload): # decrypt key and IV using our RSA private key, then decrypt the image using AES dec_key = a2b_base64( rsa_decrypt(a2b_base64(json_payload['k']), self.private_key)) dec_iv = a2b_base64( rsa_decrypt(a2b_base64(json_payload['i']), self.private_key)) decryptor = aes(dec_key, 2, dec_iv) dec = decryptor.decrypt(a2b_base64(json_payload['d'])) # server uses DEFLATE to keep sizes down return inflate(dec) def bootup(self): print('Device has been initialized... Beginning normal bootup.') # ensure LetterBox has WiFi, then set the time via NTP self.__wifi_manager.ensure_connection() for attempt in range(1, 4): try: settime() print('Time set.') break except: print('Error setting time (attempt #{0}).'.format(attempt)) def __display_no_letter_text(self): # Prompt the user to go register. self.__display.clear() self.__display.draw_text(0, 0, 'No letters yet!', self.__render_font, 65535) self.__display.draw_text(0, 36, 'Register this device to be', self.__render_font, 65535) self.__display.draw_text(0, 72, 'able to send letters.', self.__render_font, 65535) self.__display.draw_text(0, 108, 'Head on over to:', self.__render_font, 65535) self.__display.draw_text(0, 144, 'letterbox.mayursaxena.com', self.__render_font, 65535) self.__display.draw_text(0, 180, 'and register this serial #:', self.__render_font, 65535) self.__display.draw_text(0, 216, self.serial_number, self.__render_font, 65535) def __store_keys(self): try: with open('private.key', 'r') as f: print("Storing private key...") pri = f.read() self.__namespace.set_blob('pri_key', pri) self.__namespace.set_i32('pri_key_len', len(pri)) self.__namespace.commit() remove_file('private.key') print('Key stored.') except OSError: pass try: with open('public.crt', 'r') as f: print("Storing public key...") pub = f.read() self.__namespace.set_blob('pub_key', pub) self.__namespace.set_i32('pub_key_len', len(pub)) self.__namespace.commit() remove_file('public.crt') print('Key stored.') except OSError: pass
import woezel, interface, system, gc, time from esp32 import NVS nvs = NVS("uinstaller") nvs_launcher = NVS("launcher") def install(app_name): nvs.set_blob('to_install', app_name) nvs.commit() system.start('uinstaller') if system.__current_app__ == 'uinstaller': appname = bytearray(40) nvs.get_blob("to_install", appname) to_install = appname.decode("utf-8").rstrip('\x00') nvs.set_blob('to_install', "") nvs.commit() print('Installing %s' % to_install) if not to_install: system.reboot() ### For some reason normal uinterface.connect_wifi() doesn't ### work from dashboard.terminal.installer, so we have to do this ### odd workaround of connecting twice. uinterface.connect_wifi() uinterface.loading_text('Installing %s' % to_install) gc.collect() if woezel.install(to_install):
# Test the esp32 NVS class - access to esp-idf's Non-Volatile-Storage from esp32 import NVS nvs = NVS("mp-test") # test setting and gettin an integer kv nvs.set_i32("key1", 1234) print(nvs.get_i32("key1")) nvs.set_i32("key2", -503) print(nvs.get_i32("key2")) print(nvs.get_i32("key1")) # test setting and getting a blob kv using a bytearray blob1 = "testing a string as a blob" nvs.set_blob("blob1", blob1) buf1 = bytearray(len(blob1)) len1 = nvs.get_blob("blob1", buf1) print(buf1) print(len(blob1), len1) # test setting and getting a blob kv using a string blob2 = b"testing a bytearray" nvs.set_blob("blob2", blob2) buf2 = bytearray(len(blob2)) len2 = nvs.get_blob("blob2", buf2) print(buf2) print(len(blob2), len2) # test raising of error exceptions nvs.erase_key("key1")
import wifi, time, consts from esp32 import NVS nvs = NVS("system") def download_info(show=False): import urequests as req result = False try: data = req.get(consts.OTA_WEB_PROTOCOL + "://" + consts.OTA_WEB_SERVER + ":" + consts.OTA_WEB_PORT + consts.OTA_WEB_VERSION_PATH) except BaseException as e: print("Exception", e) return False try: result = data.json() except: data.close() return False data.close() return result def available(update=False, show=False): if update: if not wifi.status(): try: return nvs.get_i32('OTA.ready') == 1 except Exception:
import uos as os, time, machine, system import interface, woezel from esp32 import NVS nvs = NVS("launcher") app_bytes = bytearray(50) app_file_bytes = bytearray(50) nvs.get_blob("uninstall_name", app_bytes) nvs.get_blob("uninstall_file", app_file_bytes) app = app_bytes.decode("utf-8").rstrip('\x00') app_file = app_file_bytes.decode("utf-8").rstrip('\x00') if (not app) or not (app_file): system.home() agreed = interface.confirmation_dialog('Uninstall \'%s\'?' % app) if not agreed: system.home() interface.loading_text("Removing " + app + "...") install_path = woezel.get_install_path() print(app) print(app_file) print(install_path) for rm_file in os.listdir("%s/%s" % (install_path, app_file)): os.remove("%s/%s/%s" % (install_path, app_file, rm_file)) os.rmdir("%s/%s" % (install_path, app_file))
# log some info and check any heap constraints given in the config if sys.platform == "esp32": import esp32 log.warning("Boot partition: %s", esp32.Partition(esp32.Partition.RUNNING).info()[4]) # heap constraints, see docs https://github.com/tve/micropython/blob/esp32-set-heapsize/docs/esp32/quickref.rst#controlling-the-python-heap-size # FIXME: if machine.reset() gets called we end up in safeboot mode, which is not ideal # probably the whole thing needs to be moved out of main and be done later when the # watchdog can be used to reboot into the same mode min_idf_heap = getattr(board, "min_idf_heap", None) max_mp_heap = getattr(board, "max_mp_heap", None) if min_idf_heap or max_mp_heap: try: from esp32 import NVS, idf_heap_info nvs = NVS("micropython") reset=False # minimum IDF heap size if min_idf_heap: try: mih = nvs.get_i32("min_idf_heap") except: mih = None print("mih", mih, "min_idf_heap", min_idf_heap) if mih != min_idf_heap: nvs.set_i32("min_idf_heap", min_idf_heap) reset= True # maximum MP heap size if max_mp_heap: try: mmh = nvs.get_i32("max_mp_heap") except: mmh = None print("mmh", mmh, "max_mp_heap", max_mp_heap) if mmh != max_mp_heap: