WiFi#

Some microcontrollers come with built-in WiFi radios. Others use coprocessors for this purpose.

SSID and Passwords#

Create a file $IOT_PROJECTS/libs/secrets.py to store your SSID and password.

Sample secrets.py with wifi credentials and other “secrets”:

# secrets.py

# wifi
wifi_ssid = 'MY_SSID'
wifi_pwd  = 'MY_PASSWORD'

# timezone
tz_offset = -8*3600    # PST

# webrepl password, 4 .. 9 characters
webrepl_pwd = 'er93xa'

# https://openweathermap.org/
openweathermap_apiid = "1c336ac61add531d32b73af8f2b"

Now create or update a device description (don’t forget to update the uid to match your microcontroller). In addition to secrets.py it also includes code used elsewhere in this guide.

%%writefile $IOT_PROJECTS/devices/esp32.yaml
esp32:
    uid: 30:ae:a4:23:ab:d4
    path: internet
    include-patterns:
        - "./**/*.py"     
        - "./**/*.mpy"
        - "./**/"
        - "./**/*.key"     
        - "./**/*.crt"
    resources:
        - secrets.py:
            path: libs
        - code
Writing /home/iot/iot49.org/docs/projects/devices/esp32.yaml
%connect esp32
%rsync
Connected to esp32 @ serial:///dev/ttyUSB0
Directories match

Connecting#

The help command on the esp32 shows sample code for configuring the WiFi:

help()
Hide code cell output
Welcome to MicroPython on the ESP32!

For generic online docs please visit http://docs.micropython.org/

For access to the hardware use the 'machine' module:

import machine
pin12 = machine.Pin(12, machine.Pin.OUT)
pin12.value(1)
pin13 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
print(pin13.value())
i2c = machine.I2C(scl=machine.Pin(21), sda=machine.Pin(22))
i2c.scan()
i2c.writeto(addr, b'1234')
i2c.readfrom(addr, 4)

Basic WiFi configuration:

import network
sta_if = network.WLAN(network.STA_IF); sta_if.active(True)
sta_if.scan()                             # Scan for available access points
sta_if.connect("<AP_name>", "<password>") # Connect to an AP
sta_if.isconnected()                      # Check for successful connection

Control commands:
  CTRL-A        -- on a blank line, enter raw REPL mode
  CTRL-B        -- on a blank line, enter normal REPL mode
  CTRL-C        -- interrupt a running program
  CTRL-D        -- on a blank line, do a soft reset of the board
  CTRL-E        -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)
For a list of available modules, type help('modules')

Search available networks (optional):

import network, secrets

wlan = network.WLAN(network.STA_IF); 
wlan.active(True)
for net in wlan.scan():
    ssid, bssid, channel, RSSI, authmode, hidden = net
    ssid = ssid.decode()
    if len(ssid) == 0: ssid = "?"
    authmode = [ "open", "WEP", "WPA-PSK", "WPA2-PSK", "WPA/WPA2-PSK" ][authmode]
    bssid = ":".join("{:02x}".format(x) for x in bssid)
    hidden = [ "visible", "hidden" ][hidden]
    print(f"{ssid:12} {bssid} ch:{channel:2} {RSSI:4}dB {authmode:12} {hidden}")
TPA-Secure   d6:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
TPA-IoT      c6:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
TPA-Server   b4:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
TPA          b6:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
?            e6:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
?            f6:fb:e4:77:a3:f4 ch:11  -58dB WPA2-PSK     visible
ATT-Router   dc:7f:a4:c2:f2:d9 ch:11  -83dB WPA2-PSK     visible

Connect to wlan secrets.wifi_ssidsecrets.wifi_ssid:

import network, secrets

wlan = network.WLAN(network.STA_IF); 
wlan.active(True)
wlan.connect(secrets.wifi_ssid, secrets.wifi_pwd)
print("connected:", wlan.isconnected())
connected: True

Let’s check the IP address:

wlan = network.WLAN(network.STA_IF)
print(wlan.ifconfig()[0])
10.39.40.168

We can now ping the device:

!ping -c 3 10.39.40.168
PING 10.39.40.168 (10.39.40.168): 56 data bytes
64 bytes from 10.39.40.168: icmp_seq=0 ttl=255 time=217.643 ms
64 bytes from 10.39.40.168: icmp_seq=1 ttl=255 time=129.098 ms
64 bytes from 10.39.40.168: icmp_seq=2 ttl=255 time=49.519 ms
--- 10.39.40.168 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 49.519/132.087/217.643/68.669 ms

Internet time#

Now that we are connected, we can fetch the current time from the internet. Let’s check first what it is without initialization:

import time

print(time.gmtime())

Let’s fetch the current time from the internet.

import ntptime, time, machine, secrets

# fetch time from internet
tm = time.localtime(ntptime.time() + getattr(secrets, 'tz_offset', 0))
print("time", tm)

# set the time
machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))
time (2021, 8, 1, 20, 27, 19, 6, 213)

Formatted:

rtc = machine.RTC()
t = rtc.datetime()
print('{:04d}-{:02d}-{:02d} {:02d}:{:02d}'.format(t[0], t[1], t[2], t[4], t[5]))
2021-08-01 20:27

Webrepl#

Once the microcontroller is connected to the internet, we can program it wirelessly. Great for robot projects! Make sure that webrepl_pwd is defined in secrets.py, then create and upload webrepl_cfg.py.

!mkdir -p $IOT_PROJECTS/internet/code
%%writefile $IOT_PROJECTS/internet/code/webrepl_cfg.py
from secrets import webrepl_pwd

PASS = webrepl_pwd
Writing /home/iot/iot49.org/docs/projects/internet/code/webrepl_cfg.py
%%writefile $IOT_PROJECTS/devices/esp32.yaml
esp32:
    uid: 30:ae:a4:30:84:34
    path: internet
    resources:
        - secrets.py:
            path: libs
        - code
Writing /home/iot/iot49.org/docs/projects/devices/example.yaml
%rsync
Directories match

Start the webrepl daemon on the microcontroller:

import webrepl
webrepl.start()
WebREPL daemon started on ws://10.39.40.135:8266
Started webrepl in normal mode

Advertise the webrepl with regular broadcast messages so ide49 discovers it.

from socket import socket, AF_INET, SOCK_DGRAM
import _thread

def broadcaster(msg, port):
    so = socket(AF_INET, SOCK_DGRAM)
    try:
        # pre-allocate address (on heap)
        addr = ('255.255.255.255', port)
        while True:
            so.sendto(msg, addr)
            time.sleep(1)
    finally:
        so.close()

msg = "ws://{}:8266\n{}".format(
    network.WLAN(network.STA_IF).ifconfig()[0],
    ":".join("{:02x}".format(x) for x in machine.unique_id())
)
th = _thread.start_new_thread(broadcaster, (msg, getattr(secrets, 'broadcast_port', 50000)))

Connect wirelessly:

%discover
esp32  serial:///dev/ttyUSB0   
esp32  ws://10.39.40.135:8266  
%connect esp32 ws

print("connected to webrepl")
Connected to esp32 @ ws://10.39.40.135:8266
connected to webrepl

Unfortunately wireless connection via webrepl is slow. The primary reason is that webrepl lacks a feature to tell when it’s ready to receive code and when not. ide49 takes a conservative approach and sends code only in small chunks with pauses inbetween.

Note: if you rather not use the broadcaster code to advertise the webrepl, you can instead connect with the url:

%connect "ws://10.39.40.135:8266"
Connected to esp32 @ ws://10.39.40.135:8266

boot.py#

To automatically connect the ESP32 whenever it boots, place the instructions in a file boot.py stored in the root directory of the microcontroller. MicroPython executes this file whenever it starts.

%%writefile $IOT_PROJECTS/internet/code/boot.py
import micropython, machine, network, ntptime, time
import secrets

# handle errors in interrupts
micropython.alloc_emergency_exception_buf(100)

def connect():
    """connect to wifi, get ntp time, advertise hostname (if not None)"""
    wlan = network.WLAN(network.STA_IF)
    if wlan.isconnected(): return True
    wlan.active(True)
    # wlan.scan()
    print("Connecting to WLAN ... ", end="")
    wlan.connect(getattr(secrets, 'wifi_ssid', 'SSID'),
                 getattr(secrets, 'wifi_pwd', ''))
    # wait for connection to be established ...
    for _ in range(30):
        if wlan.isconnected(): break
        time.sleep_ms(100)
    if not wlan.isconnected():
        print("Unable to connect to WiFi!")
        wlan.disconnect()
        return False
    # set clock to local time
    tm = time.localtime(ntptime.time() + getattr(secrets, 'tz_offset', 0))
    print("time", tm)
    machine.RTC().datetime((tm[0], tm[1], tm[2], tm[6] + 1, tm[3], tm[4], tm[5], 0))
    return True

# connect to WiFi
conneced = connect()

if connected and hasattr(secrets, 'webrepl_pwd'):
    # start webrepl
    import webrepl
    webrepl.start()

    # advertise
    from socket import socket, AF_INET, SOCK_DGRAM
    import _thread

    def broadcaster(msg, port):
        so = socket(AF_INET, SOCK_DGRAM)
        try:
            # pre-allocate address (on heap)
            addr = ('255.255.255.255', port)
            while True:
                so.sendto(msg, addr)
                time.sleep(1)
        finally:
            so.close()

    msg = "ws://{}:8266\n{}".format(
        network.WLAN(network.STA_IF).ifconfig()[0],
        ":".join("{:02x}".format(x) for x in machine.unique_id())
    )
    th = _thread.start_new_thread(broadcaster, (msg, getattr(secrets, 'broadcast_port', 50000)))
Writing /home/iot/iot49.org/docs/projects/internet/code/boot.py
%rsync
%softreset
UPDATE  /boot.py

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!   softreset ...     !!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# switch back to serial connection
%connect esp32 serial
Connected to esp32 @ serial:///dev/ttyUSB0
# and again to wireless
%connect esp32 ws
Connected to esp32 @ ws://10.39.40.135:8266

Try operating the microcontroller from a battery or power-only USB cable to verify that connection is indeed wireless.