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