Pi Pico W won't auto run main.py or boot.py

There seems to be a weird interaction between using the network library and the I2C on my Pico W's. I am trying to set up any sort of display using I2C and connecting to the internet. The program runs fine when run via Thonny, but when naming the programs either main.py or boot.py they crash before the display starts to function. I added a few break points to blink the onboard LED to see where the program aborts and it is in the section where it connects to wlan. If I take out the wlan code or the I2C code it will run, but it won't if I try to do both.
I've tried this with 2 different.Pico W's and 2 different I2C displays. One display is based on SSD1315, the other using a ST7789.

Any ideas what is happening?

Since this doesn't seem to be related to Node-RED, It would seem you would be better off asking this on the Raspberry Pi forum

Its difficult to do any testing on the Node-Red side when your device can't connect to the internet. The folks over at the Pi Forum have not been very helpful. The folks over here are a lot more engaged and inciteful.

I appreciate this reply will not further you along, but I recently spent ages trying to debug why I couldn't get the network stuff working before realising I had plugged in a Pico, not a Pico W... :man_facepalming:

4 Likes

Can you share what you have in your boot.py and main.py files?

Personally, I find it easier to have a folder for the project I'm working on as I can keep all the versions I've made in there. So for example, this is what the filing structure would like like on my Pico-W.

folder named... lib containing all your libraries
folder named... egg_hatching_project containing your various versions egg_hatch_v01.py, egg_hatch_v02.py

Then in boot.py I would just have.... import main
And in main.py I would have something like...

import sys
sys.path.append('/egg_hatching_project')
import egg_hatch_v02  #Change this as you create more versions

Hope this helps?

I'm not using both boot and main. I just tried them both. I'm not understanding what you are saying though.

This project is supposed to connect to the internet/node-red and get a periodic time stamp sent back to the Pico W to display the time. Things work fine when I run it from Thonny, but when I try to get it to auto run it dies when trying to connect to the internet.

I found a few other posts and even a youtube that shows this issue. They fixed it by adding delays, but that isn't working here. There seems to be something different when Thonny runs the program vs. using main.py or boot.py.

The way Micro-Python, on a RPi Pico-W, is supposed to operate is...

When the device is powered-up it will first of all run boot.py (if it exists) and then main.py (if it exists).
The idea is you put all the common material in boot.py and your project-application in main.py

As I'm using my ESP32-S2-Mini or Pico-W to develop my project as well as run it, I find it very tiresome to keep having to copy main.py to another file in order to have a back-up copy that I can revert to later.

So I developed the structure outlined in my previous post, so I could have many versions of my project-code in a project folder and just edit the import statement in main.py.

If you can share what you have in boot.py and main.py then that will help me advise you further.

Maybe before trying to run a complicated Python script, it might be an idea to have simple script that toggles an LED on a pin as your main.py and see if that crashes the Pico-W (it shouldn't)?

Try this in your boot.py file

import main

And this in your main.py file

import machine
import utime

# Define the onboard LED pin
led_pin = machine.Pin("LED", machine.Pin.OUT)

# Define the frequency of blinking (in milliseconds)
blink_frequency_ms = 500

while True:
    # Turn on the LED
    led_pin.value(1)
    # Wait for the specified time
    utime.sleep_ms(blink_frequency_ms)
    # Turn off the LED
    led_pin.value(0)
    # Wait for the specified time
    utime.sleep_ms(blink_frequency_ms)

This is my starting point. It connects to the internet, then to the NTC to get the current time. It then sets the RTC on the Pico and starts displaying it on the Oled. It works fine if I run it from Thonny, but not if I just plug in a usb with the program named main.py or boot.py. main runs, but aborts when attempting to connect to the internet. The sleep(1.5) are because of a youtube recommendation here:
https://www.youtube.com/watch?v=mCYB9tjsF0I.

# Test the I2C connections
import network
from machine import RTC
import rp2
import sys
import utime as time
import usocket as socket
import ustruct as struct
import time
from machine import Pin, I2C, ADC
import ssd1306
import rp2
import sys
import utime as time
import usocket as socket
import ustruct as struct
from time import sleep
from ssd1306 import SSD1306_I2C
ssid="XXXXXXX"
password="XXXXXXXX"

WIDTH  = 128         # oled display width
HEIGHT = 64          # oled display height
sleep(1.5)
sda=machine.Pin(6)   # Data pin
scl=machine.Pin(7)   # Clock pin
i2c=machine.I2C(1,sda=sda, scl=scl, freq=400000) # Connection controller 0
sleep(1.5)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)         # Initialise ssd1306 display
# wintertime / Summerzeit
#GMT_OFFSET = 3600 * -5 # 3600 = 1 h (wintertime)
GMT_OFFSET = 3600 * -4 # 3600 = 1 h (summertime)

# NTP-Host
NTP_HOST = 'pool.ntp.org'

# Funktion: get time from NTP Server
def getTimeNTP():
    NTP_DELTA = 2208988800
    NTP_QUERY = bytearray(48)
    NTP_QUERY[0] = 0x1B
    addr = socket.getaddrinfo(NTP_HOST, 123)[0][-1]
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.settimeout(1)
        res = s.sendto(NTP_QUERY, addr)
        msg = s.recv(48)
    finally:
        s.close()
    ntp_time = struct.unpack("!I", msg[40:44])[0]
    return time.gmtime(ntp_time - NTP_DELTA + GMT_OFFSET)

# Funktion: copy time to PI pico´s RTC
def setTimeRTC():
    tm = getTimeNTP()
    rtc.datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))

  
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
sleep(1.5)
max_wait = 10
#print('Waiting for connection')
while max_wait > 10:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1    
    sleep(1)
status = None
if wlan.status() != 3:
    raise RuntimeError('Connections failed')
else:
    status = wlan.ifconfig()
#    print('connection to', ssid,'succesfull established!', sep=' ')
#    print('IP-adress: ' + status[0])
ipAddress = status[0]

rtc = RTC()  

setTimeRTC()
# ======== ssd1306 set up for I2C ========

# ====== Universal code starts here ======
def centre(msg, yy, cc):          # Procedure to centre text on a line
    space = WIDTH-(len(msg) * 8)  # Characters are 8 pixels wide
    gap = int(space/2)
    #    text(string, x, y, colour)
    oled.text(msg,gap,yy,cc)      # Write to buffer
    
delay = 1
while True:
    tm=rtc.datetime()
    A=(str(tm[4])+":"+str(tm[5])+":"+str(tm[6]))
# Waiting time in seconds

    oled.fill(0) # Fill with Black
#        ( text string,   x,   y, colour)
    oled.text(A, 30, 30, 1) # White at (16,10)

    oled.show()          # Send buffer to display
    time.sleep(delay)
    oled.fill(0)



This is weird. I extended the sleep just before the connection to the internet to 4 seconds, now it works. Go figure.

Just had a quick look, first thing that sticks-out is the while loop looks incorrect. I think it should be...

while max_wait > 0:

I'll take a more detailed look on Thursday.

EDIT:
This is what ChatGPT reports...
The script appears to have a few issues and areas for improvement:

  1. Redundant Imports: You have multiple redundant imports. For instance, import time and import utime as time are both present. Choose one and stick to it. The same applies to other redundant imports like import rp2 and import sys.
  2. Infinite Loop: In the while loop while max_wait > 10, the condition seems incorrect. It should probably be while max_wait > 0 to create an actual countdown loop.
  3. Unused Variable: The variable ipAddress is assigned the value of status[0], but it is never used. If it's not needed, remove it to make the code cleaner.
  4. Inconsistent Commenting: There are inconsistent comments throughout the code. It's better to maintain a consistent commenting style for readability.
  5. Misuse of sleep: In the while loop, you are using sleep(1.5) before setting up the I2C connection, and again after setting the RTC time. Ensure that these delays are necessary and properly timed.
  6. Redundant Code: The line # ======== ssd1306 set up for I2C ======== seems to be a placeholder comment and can be removed.
  7. Potential Issue with RTC Setup: Ensure that the RTC module (RTC()) is properly set up on your microcontroller. If not set up correctly, it might lead to errors.
  8. Indentation: Some lines might have inconsistent indentation. Ensure all lines are properly indented for clarity and correct execution.

Thanks. I didn't really clean this up much. It was cobbled together from several examples. At least it works now.

Really pleased your program is working now.

I'll try to explain why it didn't work and why, after inserting the 4-second delay, it did work.

The key-part of the code is the while loop. Its role (if it was written correctly) is to check if the connection to the WiFi has successfully been made. It performs this check every second and will do it up to ten times. As soon as a successful connection is found the break statement will exit the while loop (so the construct doesn't spend the whole of the 10-seconds checking - if a connection was made early).

In your version, max_wait was set equal to 10 just before the while loop. As soon as the while loop performs the test max_wait > 10 the result returned will be false (as max_wait is not greater than 10). This means the while loop will not be performed and the program will go immediately to the statement status = None As this has happened immediately there is zero chance a WiFi connection has been established - hence the errors you encountered.

By introducing a delay of 4 seconds after the statement wlan.connect(ssid, password) you are giving Pico a short amount of time to make the WiFi connection. It was probably pure luck that the 4-sec delay was sufficient. Some networks could have needed a longer delay, some a short delay. Note: The while loop will immediately exit as the test-condition hasn't been met, so that part of the code is not executed.

By changing the while statement to... while max_wait > 0: then the while loop will perform correctly and there should be no need for the sleep(1.5) or sleep(4) statements in your code.

Not the best piece of code I've seen as you need to ask yourself the question... "what happens if at the end of the 10 iterations around the while loop a connection hasn't been made?" In your piece of code it assumes a connection will always be made within that time. In practice that might not happen and your program should take care of that situation. It's called 'robust programming'.

Hope this helps explain why your program was failing?

@dynamicdave:
Good analysis. I am still at the "Monkey See Monkey Do" stage, but you are helping me a lot.
My power and internet have good going up & down like a yo-yo but it has shown me a lot of issues with how my program connects and handles interruptions.
When internet is lost the incubator program ends up choking on a publish mqtt message after a bit and crashes.

The clock program has a different problem, but does not crash when the internet goes away, but in the case where power goes away along with the internet, I have an issue. The power comes back long before the router is ready. Once the router is ready the program does not reconnect and reset the clock.

I'm just not that familiar with the protocol for connecting to the internet and waiting until things are good.

I tried this but while it doesn't crash, it doesn't work to reconnect after the router is ready.

wlan = network.WLAN(network.STA_IF)
while not wlan.isconnected():
    wlan.active(True)
    wlan.connect(ssid, password)
    print("waiting")
    sleep(5)
    pass

I still have no idea why it works when Thonny is connected and not when it isn't. Your fix to connecting did fix the autostarting when the internet is available, but not when its not. I have been unable to make it wait and just watch until it comes back. Hope that is clear enough.

Try this...

import network
from time import sleep

# Make sure to define your SSID and password
ssid = 'YourNetworkName'
password = 'YourNetworkPassword'

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
    print('Connecting to network...')
    wlan.connect(ssid, password)

    # Attempt to connect with a timeout
    for _ in range(10):  # Tries to connect for up to 50 seconds
        if wlan.isconnected():
            break
        print('Waiting for connection...')
        sleep(5)
else:
    print('Already connected.')

if wlan.isconnected():
    print('Network connected:', wlan.ifconfig())
else:
    print('Unable to connect to the network.')

There is a delay between when the lan is available and when internet is available. Also, internet goes down quite a bit with the lan still being in service. I can't seem to find a way to check if I'm connected to the internet and re-try until I can. I need to block sending MQTT publish commands until I can.

I was thinking maybe to ping the target site and re-try until I get a result?

OK, I got it. I went to circuit python and found some hints.

I use 2 try loops, one for connecting to wifi and one for connecting to the NTP server. If either fails I reset the microcontroller. The program is stored in code.py so it restarts over and over until things come back on line: Brute force, but at least it works.

import os
import time
import ssl
import wifi
import socketpool
import microcontroller
import adafruit_ntp
import os
import socketpool
import wifi
from time import sleep
import board
import rtc
import board, busio, displayio, os, terminalio, microcontroller, time
import adafruit_displayio_ssd1306
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
from adafruit_httpserver import Server, Request, Response


TZ_OFFSET = -4  # time zone offset in hours from UTC
#  connect to SSID
trying1=True
while trying1:
    try:
        wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
        trying1=False
        print("connected to wifi")
    except Exception as e:
        print("Error:\n", str(e))
        print("Resetting microcontroller in 10 seconds")
        time.sleep(10)
        microcontroller.reset()

pool = socketpool.SocketPool(wifi.radio)

trying2 = True
while trying2:
    try:
        ntp = adafruit_ntp.NTP(pool, tz_offset=TZ_OFFSET)
        rtc.RTC().datetime = ntp.datetime
        print(rtc.RTC().datetime)
        trying2=False
    # pylint: disable=broad-except
    except Exception as e:
        print("Error:\n", str(e))
        print("Resetting microcontroller in 10 seconds")
        time.sleep(10)
        microcontroller.reset()

Next I will work on sending messages to the display so I can see what is happening as Thonny doesn't auto-reconnect.

Can I suggest you consider joining the Pico Micro-Python group on Discord as that channel is very active whereas not many people use Micro-Python on this forum as it is intended for Node-RED related topics.

I'll give it a try, but I joined a discord mushroom group and could not figure out how to use it.

It is node-red related. I thought I'd explained that, My incubator project using the Node-Red dashboard crashes when the internet goes down. It dies after sending out a bunch of MQTT publish tries. I need a way to detect the loss of connectivity and stop sending the MQTT publish messages.

I made a program in 2019 with a sonoff module with an esp8266, to turn on and off a lamp from nodered.
It is with micropython, but I hope there is something that can be used.

from machine import Pin, PWM
import network
wlan = network.WLAN(network.STA_IF)
from simple import MQTTClient
import time

BTN = Pin(0, Pin.IN, Pin.PULL_UP)
tState = False # OFF
LED = Pin(2, Pin.OUT, value = not tState)
pLED = PWM(LED,freq=5, duty=512)

def BTN_cb(p):
	time.sleep_ms(20)
	global tState
	tState = not tState
	toggle(tState)

BTN.irq(trigger=Pin.IRQ_FALLING, handler=BTN_cb)

while (wlan.ifconfig()[0]=='0.0.0.0'):
    pass
serverId = "192.168.20.80"
#serverId = "192.168.11.21"
clientId = "/esp8266/" + wlan.ifconfig()[0] + '/'
c = MQTTClient(clientId, serverId)

print('serverId', serverId)
print('clientId', clientId)
print('network config:', wlan.ifconfig())

def toggle(State):
	global tState
	tState = State
	#pLED.duty(1009 ^ pLED.duty())	
	if(State): # ON
		pLED.duty(25)
		c.publish(clientId + "sta", 'ON')
	else: # OFF
		pLED.duty(1000)
		c.publish(clientId + "sta", 'OFF')

def sub_cb(topic, msg):
	#Warning: Comparison between bytes and str
	#if(topic == b'/esp8266/' + wlan.ifconfig()[0] + '/cmd'):
	if(topic == b'{}'.format(clientId + 'cmd')):
		print((topic, msg))
		if msg == b"ON":
			toggle(True)
		elif msg == b"OFF":
			toggle(False)

def reconnect():
	pLED = PWM(LED,freq=5, duty=512)
	while True:
		if not (wlan.ifconfig()[0]=='0.0.0.0'):
			try:
				c.connect() # error if not connected
				break
			except Exception as ex:
				print('error connect()', ex)
				time.sleep(5)

def main():
	CONNECT = const(0)
	CONNECTED = const(1)
	FSMa = CONNECT
	c.set_callback(sub_cb)

	while True:
		FSM = FSMa	
		if(FSM == CONNECT):
			reconnect()
			pLED = PWM(LED,freq=1, duty=1000)
			cnt = 0
			ttime = time.ticks_add(time.ticks_ms(), 5000)
			c.subscribe(clientId + 'cmd')
			print("subscribe to %s " % (clientId + 'cmd'))
			#c.publish(clientId + "sta", 'OFF')
			toggle(False)
			FSMa = CONNECTED

		if(FSM == CONNECTED):
			try:
				c.check_msg() # error if not connected
				if time.ticks_diff(time.ticks_ms(), ttime) > 0:
					ttime = time.ticks_add(time.ticks_ms(), 5000)
					c.publish(clientId + 'cnt', str(cnt))
					print('count is now ' + str(cnt))
					cnt += 1
			except Exception as ex:
				print('error check_msg()', ex)
				FSMa = CONNECT
		time.sleep_ms(10)

	c.disconnect()

main()