Esempio n. 1
0
def start():
    # Run the application from the MainLoop.
    try:
        # jotter.get_jotter().jot("start()", source_file=__name__)
        # Red and Green LEDs on during the startup wait period
        pyb.LED(1).on()
        pyb.LED(2).on()

        # 30 second delay at the start to connect via REPL and kill the program before it fires up the WDT.
        timeout_start = utime.time()
        while utime.time() < timeout_start + 30:
            utime.sleep_ms(100)

        # Red and Green LEDs off after the startup wait period
        pyb.LED(1).off()
        pyb.LED(2).off()

    except Exception as the_exception:
        jotter.get_jotter().jot_exception(the_exception)

        import sys
        sys.print_exception(the_exception)
        pass
        # Log to file

    # Get installed modules and versions
    installed_modules = None
    try:
        installed_modules = get_installed_module_versions()
        if installed_modules:
            print("Installed Modules")
            for (mod, version) in installed_modules.items():
                print(str(mod) + " : " + str(version))
    except Exception as the_exception:
        jotter.get_jotter().jot_exception(the_exception)

        import sys
        sys.print_exception(the_exception)
        pass
        # Log to file

    # Now run the mainloop
    try:
        import mainloop.main.mainloop as ml
        env_variables = {"installedModules": installed_modules}
        ml.set_environment_variables(env_variables)
        jotter.get_jotter().jot("start()::run_mainloop()",
                                source_file=__name__)
        ml.run_mainloop()
    except Exception as the_exception:
        jotter.get_jotter().jot_exception(the_exception)

        import sys
        sys.print_exception(the_exception)
        pass
        # Log to file

    pass
def run_mainloop():
    """Standard Interface for MainLoop. Never returns."""

    global _env_variables
    global _rtc_callback_flag
    global _rtc_callback_seconds
    global _nm3_callback_flag
    global _nm3_callback_seconds
    global _nm3_callback_millis
    global _nm3_callback_micros

    # Firstly Initialise the Watchdog machine.WDT. This cannot now be stopped and *must* be fed.
    wdt = machine.WDT(timeout=30000)  # 30 seconds timeout on the watchdog.

    # Now if anything causes us to crashout from here we will reboot automatically.

    # Last reset cause
    last_reset_cause = "PWRON_RESET"
    if machine.reset_cause() == machine.PWRON_RESET:
        last_reset_cause = "PWRON_RESET"
    elif machine.reset_cause() == machine.HARD_RESET:
        last_reset_cause = "HARD_RESET"
    elif machine.reset_cause() == machine.WDT_RESET:
        last_reset_cause = "WDT_RESET"
    elif machine.reset_cause() == machine.DEEPSLEEP_RESET:
        last_reset_cause = "DEEPSLEEP_RESET"
    elif machine.reset_cause() == machine.SOFT_RESET:
        last_reset_cause = "SOFT_RESET"
    else:
        last_reset_cause = "UNDEFINED_RESET"

    jotter.get_jotter().jot("Reset cause: " + last_reset_cause,
                            source_file=__name__)

    print("last_reset_cause=" + last_reset_cause)

    # Feed the watchdog
    wdt.feed()

    # Set RTC to wakeup at a set interval
    rtc = pyb.RTC()
    rtc.init(
    )  # reinitialise - there were bugs in firmware. This wipes the datetime.
    # A default wakeup to start with. To be overridden by network manager/sleep manager
    rtc.wakeup(2 * 1000, rtc_callback)  # milliseconds - # Every 2 seconds

    rtc_set_alarm_period_s(60 *
                           60)  # Every 60 minutes to do the sensors by default
    _rtc_callback_flag = True  # Set the flag so we do a status message on startup.

    pyb.LED(2).on()  # Green LED On

    jotter.get_jotter().jot("Powering off NM3", source_file=__name__)

    # Cycle the NM3 power supply on the powermodule
    powermodule = PowerModule()
    powermodule.disable_nm3()

    # Enable power supply to 232 driver and sensors and sdcard
    pyb.Pin.board.EN_3V3.on()
    pyb.Pin('Y5', pyb.Pin.OUT, value=0)  # enable Y5 Pin as output
    max3221e = MAX3221E(pyb.Pin.board.Y5)
    max3221e.tx_force_on()  # Enable Tx Driver

    # Set callback for nm3 pin change - line goes high on frame synchronisation
    # make sure it is clear first
    nm3_extint = pyb.ExtInt(pyb.Pin.board.Y3, pyb.ExtInt.IRQ_RISING,
                            pyb.Pin.PULL_DOWN, None)
    nm3_extint = pyb.ExtInt(pyb.Pin.board.Y3, pyb.ExtInt.IRQ_RISING,
                            pyb.Pin.PULL_DOWN, nm3_callback)

    # Serial Port/UART is opened with a 100ms timeout for reading - non-blocking.
    # UART is opened before powering up NM3 to ensure legal state of Tx pin.
    uart = machine.UART(1, 9600, bits=8, parity=None, stop=1, timeout=100)
    nm3_modem = Nm3(input_stream=uart, output_stream=uart)
    utime.sleep_ms(20)

    # Feed the watchdog
    wdt.feed()

    jotter.get_jotter().jot("Powering on NM3", source_file=__name__)

    utime.sleep_ms(10000)
    powermodule.enable_nm3()
    utime.sleep_ms(10000)  # Await end of bootloader

    # Feed the watchdog
    wdt.feed()

    jotter.get_jotter().jot("NM3 running", source_file=__name__)

    # nm3_network = Nm3NetworkSimple(nm3_modem)
    # gateway_address = 7

    # Grab address and voltage from the modem
    nm3_address = nm3_modem.get_address()
    utime.sleep_ms(20)
    nm3_voltage = nm3_modem.get_battery_voltage()
    utime.sleep_ms(20)
    print("NM3 Address {:03d} Voltage {:0.2f}V.".format(
        nm3_address, nm3_voltage))
    jotter.get_jotter().jot("NM3 Address {:03d} Voltage {:0.2f}V.".format(
        nm3_address, nm3_voltage),
                            source_file=__name__)

    # Sometimes (maybe from brownout) restarting the modem leaves it in a state where you can talk to it on the
    # UART fine, but there's no ability to receive incoming acoustic comms until the modem has been fired.
    # So here we will broadcast an I'm Alive message. Payload: U (for USMART), A (for Alive), Address, B, Battery
    send_usmart_alive_message(nm3_modem)

    # Feed the watchdog
    wdt.feed()

    # Delay for transmission of broadcast packet
    utime.sleep_ms(500)

    # sensor payload
    sensor = sensor_payload.get_sensor_payload_instance()
    # sensor.start_acquisition()
    # sensor_acquisition_start = utime.time()
    # while (not sensor.is_completed()) and (utime.time() < sensor_acquisition_start + 5):
    #    sensor.process_acquisition()

    # Feed the watchdog
    wdt.feed()

    nm3_network = NetProtocol()
    nm3_network.init_interfaces(
        nm3_modem, sensor,
        wdt)  # This function talks to the modem to get address and voltage
    network_can_go_to_sleep = False

    utime.sleep_ms(100)

    # Feed the watchdog
    wdt.feed()

    # Uptime
    uptime_start = utime.time()

    # Turn off the USB
    pyb.usb_mode(None)  # Except when in development

    # Operating Mode
    #
    # Note: Sensor data is only sent in response to a command from the gateway or relay.
    #
    # Sensor data is acquired when the RTC wakes us up. Default set RTC alarm to hourly.
    # In the absence of a packet with TTNF information we will at least continue to grab sensor values ready for when
    # we do next get a network packet.
    #
    # From Power up:
    #   WDT enabled at 30 seconds.
    #   NM3 powered. Sleep with wake on HW. Accepting unsolicited messages.
    #   Parse and feed to Localisation/Network submodules.
    #   RTC set to hourly to grab sensor data.
    #
    #   network.handle_packet() returns a stay awake flag and a time to next frame.
    #   If network says go to sleep with a time-to-next-frame
    #     Take off time for NM3 startup (7 seconds), set next RTC alarm for wakeup, power off NM3 and go to sleep.
    #     (Check TTNF > 30 seconds) - otherwise leave the NM3 powered.
    #
    #   If RTC alarm says wakeup
    #     power up NM3
    #     power up sensors and take a reading whilst NM3 is waking up.
    #
    #   General run state
    #     Go to sleep with NM3 powered. Await incoming commands and HW wakeup.
    #

    while True:
        try:

            # First entry in the while loop and also after a caught exception
            # pyb.LED(2).on()  # Awake

            # Feed the watchdog
            wdt.feed()

            # The order and flow below will change.
            # However sending packets onwards to the submodules will occur on receipt of incoming messages.
            # At the moment the RTC is timed to wakeup, take a sensor reading, and send it to the gateway
            # via Nm3 as simple unicast before returning to sleep. This will be expanded to accommodate the network
            # protocol with wakeup, resynchronise on beacon, time offset for transmission etc.

            # Start of the wake loop
            # 1. Incoming NM3 MessagePackets (HW Wakeup)
            # 2. Periodic Sensor Readings (RTC)

            # Enable power supply to 232 driver
            pyb.Pin.board.EN_3V3.on()

            if _rtc_callback_flag:
                _rtc_callback_flag = False  # Clear the flag
                print("RTC Flag. Powering up NM3 and getting sensor data." +
                      " time now=" + str(utime.time()))
                jotter.get_jotter().jot(
                    "RTC Flag. Powering up NM3 and getting sensor data.",
                    source_file=__name__)

                # Enable power supply to 232 driver and sensors and sdcard
                pyb.Pin.board.EN_3V3.on()
                max3221e.tx_force_on()  # Enable Tx Driver

                # Start Power up NM3
                utime.sleep_ms(100)
                network_can_go_to_sleep = False  # Make sure we keep NM3 powered until Network says otherwise
                powermodule.enable_nm3()
                nm3_startup_time = utime.time()

                # sensor payload - BME280 and LSM303AGR and VBatt
                utime.sleep_ms(
                    500
                )  # Allow sensors to startup before starting acquisition.
                sensor.start_acquisition()
                sensor_acquisition_start = utime.time()
                while (not sensor.is_completed()) and (
                        utime.time() < sensor_acquisition_start + 6):
                    sensor.process_acquisition()
                    # Feed the watchdog
                    wdt.feed()
                    utime.sleep_ms(100)  # yield

                # Wait for completion of NM3 bootup (if it wasn't already powered)
                while utime.time() < nm3_startup_time + 7:
                    utime.sleep_ms(100)  # yield
                    pass

            # If we're within 30 seconds of the last timestamped NM3 synch arrival then poll for messages.
            if _nm3_callback_flag or (utime.time() <
                                      _nm3_callback_seconds + 30):
                if _nm3_callback_flag:
                    print("Has received nm3 synch flag.")

                _nm3_callback_flag = False  # clear the flag

                # There may or may not be a message for us. And it could take up to 0.5s to arrive at the uart.

                nm3_modem.poll_receiver()
                nm3_modem.process_incoming_buffer()

                while nm3_modem.has_received_packet():
                    # print("Has received nm3 message.")
                    print("Has received nm3 message.")
                    jotter.get_jotter().jot("Has received nm3 message.",
                                            source_file=__name__)

                    message_packet = nm3_modem.get_received_packet()
                    # Copy the HW triggered timestamps over
                    message_packet.timestamp = utime.localtime(
                        _nm3_callback_seconds)
                    message_packet.timestamp_millis = _nm3_callback_millis
                    message_packet.timestamp_micros = _nm3_callback_micros

                    # Process special packets US
                    if message_packet.packet_payload and bytes(
                            message_packet.packet_payload) == b'USMRT':
                        # print("Reset message received.")
                        jotter.get_jotter().jot("Reset message received.",
                                                source_file=__name__)
                        # Reset the device
                        machine.reset()

                    if message_packet.packet_payload and bytes(
                            message_packet.packet_payload) == b'USOTA':
                        # print("OTA message received.")
                        jotter.get_jotter().jot("OTA message received.",
                                                source_file=__name__)
                        # Write a special flag file to tell us to OTA on reset
                        try:
                            with open('.USOTA', 'w') as otaflagfile:
                                # otaflagfile.write(latest_version)
                                otaflagfile.close()
                        except Exception as the_exception:
                            jotter.get_jotter().jot_exception(the_exception)

                            import sys
                            sys.print_exception(the_exception)
                            pass

                        # Reset the device
                        machine.reset()

                    if message_packet.packet_payload and bytes(
                            message_packet.packet_payload) == b'USPNG':
                        # print("PNG message received.")
                        jotter.get_jotter().jot("PNG message received.",
                                                source_file=__name__)
                        send_usmart_alive_message(nm3_modem)

                    if message_packet.packet_payload and bytes(
                            message_packet.packet_payload) == b'USMOD':
                        # print("MOD message received.")
                        jotter.get_jotter().jot("MOD message received.",
                                                source_file=__name__)
                        # Send the installed modules list as single packets with 1 second delay between each -
                        # Only want to be calling this after doing an OTA command and ideally not in the sea.

                        nm3_address = nm3_modem.get_address()

                        if _env_variables and "installedModules" in _env_variables:
                            installed_modules = _env_variables[
                                "installedModules"]
                            if installed_modules:
                                for (mod,
                                     version) in installed_modules.items():
                                    mod_string = "UM" + "{:03d}".format(nm3_address) + ":" + str(mod) + ":" \
                                                 + str(version if version else "None")
                                    nm3_modem.send_broadcast_message(
                                        mod_string.encode('utf-8'))

                                    # delay whilst sending
                                    utime.sleep_ms(1000)

                                    # Feed the watchdog
                                    wdt.feed()

                    if message_packet.packet_payload and bytes(
                            message_packet.packet_payload) == b'USCALDO':
                        # print("CAL message received.")
                        jotter.get_jotter().jot("CAL message received.",
                                                source_file=__name__)

                        nm3_address = nm3_modem.get_address()

                        # Reply with an acknowledgement then start the calibration
                        msg_string = "USCALMSG" + "{:03d}".format(
                            nm3_address) + ":Starting Calibration"
                        nm3_modem.send_broadcast_message(
                            msg_string.encode('utf-8'))
                        # delay whilst sending
                        utime.sleep_ms(1000)
                        # Feed the watchdog
                        wdt.feed()
                        # start calibration
                        (x_min, x_max, y_min, y_max, z_min,
                         z_max) = sensor.do_calibration(duration=20)
                        # Feed the watchdog
                        wdt.feed()
                        # magneto values are int16
                        caldata_string = "USCALDATA" + "{:03d}".format(nm3_address) + ":" \
                                         + "{:06d},{:06d},{:06d},{:06d},{:06d},{:06d}".format(x_min, x_max,
                                                                                              y_min, y_max,
                                                                                              z_min, z_max)
                        nm3_modem.send_broadcast_message(
                            caldata_string.encode('utf-8'))
                        # delay whilst sending
                        utime.sleep_ms(1000)

                    # Send on to submodules: Network/Localisation UN/UL
                    if message_packet.packet_payload and len(message_packet.packet_payload) > 2 and \
                            bytes(message_packet.packet_payload[:2]) == b'UN':
                        # Network Packet

                        # Wrap with garbage collection to tidy up memory usage.
                        import gc
                        gc.collect()
                        (sleepflag, ttnf, network_packet_ignored
                         ) = nm3_network.handle_packet(message_packet)
                        gc.collect()

                        if not network_packet_ignored:
                            network_can_go_to_sleep = sleepflag
                            time_till_next_req_ms = ttnf

                            print("network_can_go_to_sleep=" +
                                  str(network_can_go_to_sleep) +
                                  " time_till_next_req_ms=" +
                                  str(time_till_next_req_ms))
                            # Update the RTC alarm such that we power up the NM3 and take a sensor reading
                            # ahead of the next network frame.
                            if 60000 < time_till_next_req_ms:  # more than 60 seconds
                                # Next RTC wakeup = time_till_next_req_ms/1000 - 60
                                # to take into account the 10second resolution and NM3 powerup time
                                rtc_seconds_from_now = math.floor(
                                    (float(time_till_next_req_ms) - 60000.0) /
                                    1000.0)
                                rtc_set_next_alarm_time_s(rtc_seconds_from_now)
                                print(
                                    "Set RTC alarm with rtc_seconds_from_now="
                                    + str(rtc_seconds_from_now))
                                pass
                            else:
                                # RTC should default to hourly so leave alone.
                                print("Leaving RTC alarm as default (hourly).")
                                pass

                            # Check there's enough time to make it worth powering down the NM3
                            if network_can_go_to_sleep and time_till_next_req_ms < 30000:
                                # if not at least 30 seconds til next frame then we will not power off the modem
                                network_can_go_to_sleep = False

                        pass  # End of Network Packets

                    if message_packet.packet_payload and len(message_packet.packet_payload) > 2 and \
                            bytes(message_packet.packet_payload[:2]) == b'UL':
                        # Localisation Packet
                        pass  # End of Localisation Packets

            # If too long since last synch and not rtc callback and too long since
            if not _rtc_callback_flag and (not _nm3_callback_flag) and (
                    utime.time() > _nm3_callback_seconds + 30):

                # Double check the flags before powering things off
                if (not _rtc_callback_flag) and (not _nm3_callback_flag):
                    print("Going to sleep.")
                    jotter.get_jotter().jot("Going to sleep.",
                                            source_file=__name__)
                    if network_can_go_to_sleep:
                        print("NM3 powering down.")
                        powermodule.disable_nm3()  # power down the NM3
                        pass

                    # Disable the I2C pullups
                    pyb.Pin('PULL_SCL',
                            pyb.Pin.IN)  # disable 5.6kOhm X9/SCL pull-up
                    pyb.Pin('PULL_SDA',
                            pyb.Pin.IN)  # disable 5.6kOhm X10/SDA pull-up
                    # Disable power supply to 232 driver, sensors, and SDCard
                    max3221e.tx_force_off()  # Disable Tx Driver
                    pyb.Pin.board.EN_3V3.off()  # except in dev
                    pyb.LED(2).off()  # Asleep
                    utime.sleep_ms(10)

                while (not _rtc_callback_flag) and (not _nm3_callback_flag):
                    # Feed the watchdog
                    wdt.feed()
                    # Now wait
                    #utime.sleep_ms(100)
                    # pyb.wfi()  # wait-for-interrupt (can be ours or the system tick every 1ms or anything else)
                    machine.lightsleep(
                    )  # lightsleep - don't use the time as this then overrides the RTC

                # Wake-up
                # pyb.LED(2).on()  # Awake
                # Feed the watchdog
                wdt.feed()
                # Enable power supply to 232 driver, sensors, and SDCard
                pyb.Pin.board.EN_3V3.on()
                max3221e.tx_force_on()  # Enable Tx Driver
                # Enable the I2C pullups
                pyb.Pin('PULL_SCL', pyb.Pin.OUT,
                        value=1)  # enable 5.6kOhm X9/SCL pull-up
                pyb.Pin('PULL_SDA', pyb.Pin.OUT,
                        value=1)  # enable 5.6kOhm X10/SDA pull-up

            pass  # end of operating mode

        except Exception as the_exception:
            import sys
            sys.print_exception(the_exception)
            jotter.get_jotter().jot_exception(the_exception)
            pass
Esempio n. 3
0
def boot():
    # Check battery voltage and if below a set value power off all peripherals and permanently enter deepsleep.
    # Letting the battery run down and repeat brownout/POR damages the PYBD.

    # Cycle the NM3 power supply on the powermodule
    try:
        import pyb
        import machine
        from pybd_expansion.main.powermodule import PowerModule
        powermodule = PowerModule()

        # 2 second delay at the start to allow battery voltage at the ADC to stabilise.
        timeout_start = utime.time()
        while utime.time() < timeout_start + 2:
            utime.sleep_ms(100)

        vbatt_volts = powermodule.get_vbatt_reading()

        if vbatt_volts < 3.6:
            # Turn off the USB
            pyb.usb_mode(None)

            low_power_pins(disable_3v3=True, disable_leds=True)

            powermodule.disable_nm3(
            )  # Needs to be driven low in order to pull-down the external resistor.

            while 1:
                machine.lightsleep()  # Enter lightsleep
                utime.sleep_ms(100)

    except Exception as the_exception:
        import sys
        sys.print_exception(the_exception)
        pass

    # Check reason for reset - only update if power on reset.
    #try:
    #    if machine.reset_cause() == machine.PWRON_RESET:
    #        download_and_install_updates_if_available()  # commented out during development
    #except Exception as the_exception:
    #    jotter.get_jotter().jot_exception(the_exception)
    #    import sys
    #    sys.print_exception(the_exception)
    #    pass
    #    # Log to file

    # 2021-09-02
    # The update on POR has been disabled for now. Low-battery brownouts on long-term deployed units may well have
    # resulted in many many POR update events. As the MicroPython firmware pushes new driver firmware to the wifi SoC
    # everytime active(True) is called (on powerup of the wifi SoC) this may have detrimental effect.
    # See open issue: https://github.com/micropython/micropython/issues/7738

    # Manual OTA request
    try:
        # Check for the flag file .USOTA
        if '.USOTA' in os.listdir():
            # Then update is required
            # Remove the file first
            os.remove('.USOTA')
            download_and_install_updates_if_available()

    except Exception as the_exception:
        jotter.get_jotter().jot_exception(the_exception)

        import sys
        sys.print_exception(the_exception)
        pass
        # Log to file

    # Start the main application
    # try:
    start()
def boot():
    # Check battery voltage and if below a set value power off all peripherals and permanently enter deepsleep.
    # Letting the battery run down and repeat brownout/POR damages the PYBD.

    # Cycle the NM3 power supply on the powermodule
    try:
        import pyb
        import machine
        from pybd_expansion.main.powermodule import PowerModule
        powermodule = PowerModule()

        # 2 second delay at the start to allow battery voltage at the ADC to stabilise.
        timeout_start = utime.time()
        while utime.time() < timeout_start + 2:
            utime.sleep_ms(100)

        vbatt_volts = powermodule.get_vbatt_reading()

        if vbatt_volts < 3.7:
            # Turn off the USB
            pyb.usb_mode(None)

            low_power_pins(disable_3v3=True, disable_leds=True)

            powermodule.disable_nm3(
            )  # Needs to be driven low in order to pull-down the external resistor.

            while 1:
                machine.lightsleep()  # Enter lightsleep
                utime.sleep_ms(100)

    except Exception as the_exception:
        import sys
        sys.print_exception(the_exception)
        pass

    # Check reason for reset - only update if power on reset. For now we only want to do OTA if requested.
    # try:
    #     if machine.reset_cause() == machine.PWRON_RESET:
    #         download_and_install_updates_if_available()  # commented out during development
    # except Exception as the_exception:
    #     jotter.get_jotter().jot_exception(the_exception)
    #     import sys
    #     sys.print_exception(the_exception)
    #     pass
    #     # Log to file

    # Manual OTA request
    try:
        # Check for the flag file .USOTA
        if '.USOTA' in os.listdir():
            # Then update is required
            # Remove the file first
            os.remove('.USOTA')
            download_and_install_updates_if_available()

    except Exception as the_exception:
        jotter.get_jotter().jot_exception(the_exception)

        import sys
        sys.print_exception(the_exception)
        pass
        # Log to file

    # Start the main application
    # try:
    start()