Downloading Bitmap via WiFi with CircuitPython, Saving to Local File System, and Displaying on LCD using OnDiskBitmap.

In my previous post, I demonstrated basic exercises running on the Raspberry Pi Pico 2 W with CircuitPython 10.0.3, displaying graphics on a 1.28‑inch IPS module (GC9A01 + CST816S, 240×240 round, 4‑wire SPI).
In this post, I present exercises using CircuitPython 10.0.3 on the Raspberry Pi Pico 2 W to download a bitmap via WiFi, save it to the local file system, and display it on the GC9A01 LCD using OnDiskBitmap.


The BMP in my test was resized and converted from JPG to BMP(240*240 RGB888) using FFmpeg.


cpy_rpPicoW_gc9a01_OnDiskBitmap_wifi.py
"""
Raspberry Pi Pico 2 W/CircuitPython 10.0.3
with 1.28" 240x240 Round GC9A01 SPI IPS LCD
Download bmp to local file system and display it using OnDiskBitmap
~ Single bmp.

In this exercise, Pico 2 W download image from web server,
save it on CircuitPython local filesystem, named "image.bmp,
then display it on 1.28" 240x240 Round GC9A01 SPI IPS LCD using OnDiskBitmap.
https://coxxect.blogspot.com/2026/01/downloading-bitmap-via-wifi-with.html

Prepare Image Server
--------------------
The BMP in my test was resized and converted from JPG to BMP(240*240 RGB888) using FFmpeg.
https://coxxect.blogspot.com/2026/01/resize-and-convert-jpgmp4-to-bmpgif.html

A simple way to run a web server in Python is to switch to the folder containing the images and run:
> python -m http.server

Both the image server and the Pico 2 W must be connected to the same WiFi network.
You also need to provide the SSID and password in this exercise.

Prepare Libraries
-----------------
CircuitPython Libraries Bundle for Version 9.x needed:
(https://circuitpython.org/libraries)
- adafruit_gc9a01a.mpy
- adafruit_requests.mpy
- adafruit_connection_manager.mpy

To install libraries using circup, enter the command:
> circup install adafruit_gc9a01a adafruit_requests
(Install adafruit_requests will install adafruit_connection_manager also.)

For CircUp, read:
https://coxxect.blogspot.com/2024/12/install-and-using-circup-circuitpython.html

Make filesystem writable
------------------------
In order to save "image.bmp" on CircuitPython local filesystem,
have to make the filesystem writable.
Visit: https://coxxect.blogspot.com/2024/12/set-circuitpython-file-system-writable.html
Copy boot_writable.py, save it in CIRCUITPY's '/', name it boot.py.
and reset.

"""
import os, sys
import board
import time
import displayio
import busio
import adafruit_gc9a01a
from fourwire import FourWire

import wifi
import socketpool
import adafruit_requests

displayio.release_displays()

tft_blk = board.GP22
tft_cs  = board.GP17
tft_dc  = board.GP21
tft_res = board.GP20
tft_sda = board.GP19
tft_scl = board.GP18

# Release any resources currently in use for the displays
displayio.release_displays()

disp_spi = busio.SPI(clock=tft_scl, MOSI=tft_sda)

display_bus = FourWire(spi_bus=disp_spi,
                       command=tft_dc,
                       chip_select=tft_cs,
                       reset=tft_res)
display = adafruit_gc9a01a.GC9A01A(display_bus, width=240, height=240, backlight_pin=tft_blk)

#=======================================
info = os.uname()[4] + "\n" + \
       sys.implementation[0] + " " + os.uname()[3] + "\n" + \
       adafruit_gc9a01a.__name__ + " " + adafruit_gc9a01a.__version__ + "\n" + \
       displayio.OnDiskBitmap.__name__
print("=======================================")
print(info)
print("=======================================")
print()

# Create a dummy bitmap
dummy_bitmap = displayio.Bitmap(240, 240, 1)
dummy_palette = displayio.Palette(1)
dummy_palette[0] = 0x000000
bmpTileGrid = displayio.TileGrid(dummy_bitmap, pixel_shader=dummy_palette)

group = displayio.Group()
group.append(bmpTileGrid)
display.root_group = group

# Prepare WiFi
ssid = "ssid"
password="password"
print("Connecting", ssid)
wifi.radio.connect(ssid, password)
print("wifi.radio.connected:", wifi.radio.connected)
print("wifi.radio.ipv4_address", wifi.radio.ipv4_address) # Pico 2 W IP
print("wifi.radio.ipv4_gateway", wifi.radio.ipv4_gateway) # WiFi Station IP
server_ip = str(wifi.radio.ipv4_gateway)

pool = socketpool.SocketPool(wifi.radio)

requests = adafruit_requests.Session(pool, wifi)

# Assume web server is setup in the WiFi station, have the same IP.
# otherwise, fill in server_ip manually.
server_url = "http://" + server_ip + ":8000/"
image_file = "image_005.bmp"
url = server_url + image_file
print(url)

# This line sometimes fail in my early try,
# response = requests.get(url, stream=True)  # Enable streaming mode
# so I place it in try..except...block.
while True:
    try:
        print("requests.get()", end="")
        response = requests.get(url, stream=True)  # Enable streaming mode
        print(" - success")
        break
    except OSError as err:
        print(" - failed! retry in 1 second")
        print("OSError:", err)
        time.sleep(1)

save_image_file = "/image.bmp"

print("Save to", save_image_file)

start_time = time.time()

with open(save_image_file, "wb") as file:
    for chunk in response.iter_content(2048):  # Download in chunks of 1024 bytes
        file.write(chunk)

stop_time = time.time()
print("Downloaded and Saved in", stop_time-start_time, "s")

print("Saved")
# retrieve saved save_image_file info.
file_info = os.stat(save_image_file)
print(save_image_file, "File size:", file_info[6], "bytes")  # index 6 is file size

file.close()
response.close()

print("Display", save_image_file)
bitmap = displayio.OnDiskBitmap(save_image_file)
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
group = displayio.Group()
group.append(tile_grid)
display.root_group = group

print("~ finished ~")



cpy_rpPicoW_gc9a01_OnDiskBitmap_wifi_loop.py
"""
Raspberry Pi Pico 2 W/CircuitPython 10.0.3
with 1.28" 240x240 Round GC9A01 SPI IPS LCD
Download bmp to local file system and display it using OnDiskBitmap
~ Loop for multi bmp.

In this exercise, Pico 2 W download image from web server,
save it on CircuitPython local filesystem, named "image.bmp,
then display it on 1.28" 240x240 Round GC9A01 SPI IPS LCD using OnDiskBitmap.
https://coxxect.blogspot.com/2026/01/downloading-bitmap-via-wifi-with.html

Prepare Image Server
--------------------
The BMP in my test was resized and converted from JPG to BMP(240*240 RGB888) using FFmpeg.
https://coxxect.blogspot.com/2026/01/resize-and-convert-jpgmp4-to-bmpgif.html

A simple way to run a web server in Python is to switch to the folder containing the images and run:
> python -m http.server

Both the image server and the Pico 2 W must be connected to the same WiFi network.
You also need to provide the SSID and password in this exercise.

Prepare Libraries
-----------------
CircuitPython Libraries Bundle for Version 9.x needed:
(https://circuitpython.org/libraries)
- adafruit_gc9a01a.mpy
- adafruit_requests.mpy
- adafruit_connection_manager.mpy

To install libraries using circup, enter the command:
> circup install adafruit_gc9a01a adafruit_requests
(Install adafruit_requests will install adafruit_connection_manager also.)

For CircUp, read:
https://coxxect.blogspot.com/2024/12/install-and-using-circup-circuitpython.html

Make filesystem writable
------------------------
In order to save "image.bmp" on CircuitPython local filesystem,
have to make the filesystem writable.
Visit: https://coxxect.blogspot.com/2024/12/set-circuitpython-file-system-writable.html
Copy boot_writable.py, save it in CIRCUITPY's '/', name it boot.py.
and reset.

"""
import os, sys
import board
import time
import displayio
import busio
import adafruit_gc9a01a
from fourwire import FourWire

import wifi
import socketpool
import adafruit_requests

import gc

RED = "\033[31m"
RESET = "\033[0m"

displayio.release_displays()

tft_blk = board.GP22
tft_cs  = board.GP17
tft_dc  = board.GP21
tft_res = board.GP20
tft_sda = board.GP19
tft_scl = board.GP18

# Release any resources currently in use for the displays
displayio.release_displays()

disp_spi = busio.SPI(clock=tft_scl, MOSI=tft_sda)

display_bus = FourWire(spi_bus=disp_spi,
                       command=tft_dc,
                       chip_select=tft_cs,
                       reset=tft_res)
display = adafruit_gc9a01a.GC9A01A(display_bus, width=240, height=240, backlight_pin=tft_blk)

#=======================================
info = os.uname()[4] + "\n" + \
       sys.implementation[0] + " " + os.uname()[3] + "\n" + \
       adafruit_gc9a01a.__name__ + " " + adafruit_gc9a01a.__version__ + "\n" + \
       displayio.OnDiskBitmap.__name__
print("=======================================")
print(info)
print("=======================================")
print()

# Create a dummy bitmap
dummy_bitmap = displayio.Bitmap(240, 240, 1)
dummy_palette = displayio.Palette(1)
dummy_palette[0] = 0x000000
bmpTileGrid = displayio.TileGrid(dummy_bitmap, pixel_shader=dummy_palette)

group = displayio.Group()
group.append(bmpTileGrid)
display.root_group = group

# Prepare WiFi
ssid = "ssid"
password="password"
print("Connecting", ssid)
wifi.radio.connect(ssid, password)
print("wifi.radio.connected:", wifi.radio.connected)
print("wifi.radio.ipv4_address", wifi.radio.ipv4_address) # Pico 2 W IP
print("wifi.radio.ipv4_gateway", wifi.radio.ipv4_gateway) # WiFi Station IP
server_ip = str(wifi.radio.ipv4_gateway)

pool = socketpool.SocketPool(wifi.radio)

requests = adafruit_requests.Session(pool, wifi)

save_image_file = "/image.bmp"

# download_image(url):
# Download and save image.
# In order to prevent out of memory, enable streaming mode and download in chunks.

#chunk_size = 1024
chunk_size = 2048
#chunk_size = 8192

# This line sometimes fail in my early try,
# response = requests.get(url, stream=True)  # Enable streaming mode
# so I place it in try..except...block.
def download_image(url):
    print()
    print(url)

    while True:
        try:
            print("requests.get()", end="")
            response = requests.get(url, stream=True)  # Enable streaming mode
            print(" - success")
            break
        except OSError as err:
            print(" - failed! retry in 1 second")
            print("OSError:", err)
            time.sleep(1)
    print("Save to", save_image_file)

    start_time = time.time()
    with open(save_image_file, "wb") as file:
        for chunk in response.iter_content(chunk_size):  # Download in chunks
            if chunk:  # filter out keep-alive chunks
                file.write(chunk)

    stop_time = time.time()
    print("Downloaded and Saved in", stop_time-start_time, "s")
    
    # retrieve saved save_image_file info.
    file_info = os.stat(save_image_file)
    print(save_image_file, "File size:", file_info[6], "bytes")  # index 6 is file size

    file.close()
    response.close()
    
def show_image(filename):
    """Load and display an image file, replacing any previous one."""
    
    try:
        # Create new bitmap + tilegrid
        bitmap = displayio.OnDiskBitmap(save_image_file)
        print(bitmap.width, "x", bitmap.height)
        tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)

        # Replace old tilegrid if exists, else append
        if len(group) > 0:
            group[0] = tile_grid
        else:
            group.append(tile_grid)

        # Get memory free size, for reference only.
        print("Memory Free after bitmap updated:", gc.mem_free(), "bytes")
        # Force garbage collection to free old objects
        gc.collect()
        print("Memory Free after gc.collect():  ", gc.mem_free(), "bytes")
    except ValueError as e:
        # Catch invalid bmp or unsupported format, skip 
        print(RED, "* ValueError *", e, RESET) # print in Shell/REPL in RED
        
# Create one group globally
group = displayio.Group()
display.root_group = group

# Assume web server is setup in the WiFi station, have the same IP.
# otherwise, fill in server_ip manually.
server_url = "http://" + server_ip + ":8000/"

image_urls = ["image_000.bmp",
              "image_001.bmp",
              "invalid_image.bmp", # Invalid, no such bmp in server.
              "image_002.bmp",
              "image_003.bmp",
              "image_004.bmp",
              "image_005.bmp"
              ]

while True:
    for u in image_urls:
        download_image(server_url + u)
        show_image(save_image_file)



Comments

Popular posts from this blog

Drive 320x240 ILI9341 SPI TFT using ESP32-S3 (NodeMCU ESP-S3-12K-Kit) using TFT_eSPI library, in Arduino Framework.

480x320 TFT/ILI9488 SPI wih EP32C3 (arduino-esp32) using Arduino_GFX Library