Beispiel #1
0
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
Beispiel #2
0
 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
Beispiel #3
0
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:
Beispiel #7
0
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))
Beispiel #8
0
# 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: