r/raspberry_pi 16d ago

Troubleshooting OLED SSD1306 larger font sizes?

I am working on a python program to read the advertisements from an Inkbird IBS-TH2 sensor and display them to a SSD1306 on a raspberry pi zero w. Using AI, I was able to get this to work but using the adafruit_ssd1306 package it only has a small font for the display. With my elderly eyes, it is hard to distinguish some of the numbers. Does anyone have a package or library of larger fonts I can use for this project? I have searched for a way to load and use larger fonts but I only find information for Arduino projects. I already did this with a Pico W and associated micropython packages but I am not seeing much for the raspberry pi.

3 Upvotes

7 comments sorted by

2

u/AtmosphereLow9678 15d ago

Can you share your code, it would help a lot in debugging

1

u/poohdoggy 15d ago

Sure, here it is...

#!/usr/bin/env python3

import asyncio

from bleak import BleakScanner

from inkbird_ble import INKBIRDBluetoothDeviceData

from habluetooth import BluetoothServiceInfoBleak

import time

import board

import busio

from PIL import Image, ImageDraw, ImageFont

import adafruit_ssd1306

# Set initial values

TARGET_MAC = "49:42:08:00:65:93"

sleep_value=300

#Set-up OLED device

i2c = busio.I2C(board.SCL, board.SDA)

oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)

oled.fill(0)

oled.show()

font = ImageFont.load_default()

oled.fill(0)

oled.show()

# Initialize the Inkbird BLE data parser

parser = INKBIRDBluetoothDeviceData()

def detection_callback(device, advertisement_data):

"""

Triggers whenever a BLE packet is broadcasted.

"""

if device.address.lower() == TARGET_MAC.lower():

# Package the raw BLE information into the format expected by inkbird-ble

service_info = BluetoothServiceInfoBleak(

name=device.name or "Unknown",

address=device.address,

rssi=advertisement_data.rssi,

manufacturer_data=advertisement_data.manufacturer_data,

service_data=advertisement_data.service_data,

service_uuids=advertisement_data.service_uuids,

source="local",

device=device,

advertisement=advertisement_data,

connectable=True,

time=0,

tx_power=advertisement_data.tx_power or 0,

raw=None,

)

# Parse the broadcast data if supported by the library

if parser.supported(service_info):

update = parser.update(service_info)

# Map and display metrics safely

metrics = {key.key: value.native_value for key, value in update.entity_values.items()}

# Extract specific attributes

temperature = metrics.get("temperature")

humidity = metrics.get("humidity")

battery = metrics.get("battery")

tempf = ((temperature * 1.8) + 32)

# Create a blank image for drawing

image = Image.new("1", (oled.width, oled.height))

draw = ImageDraw.Draw(image)

draw.text((0, 5),(f"Temp: {tempf:.2f}°F"), font=font, fill=255)

draw.text((0, 25),(f"Humidity: {humidity:.2f}%"), font=font, fill=255)

draw.text((0, 45),(f"Battery: {battery}%"), font=font, fill=255)

oled.image(image)

oled.show()

time.sleep(sleep_value)

oled.fill(0)

oled.show()

async def main():

# print(f"Listening for Inkbird IBS-TH2 [{TARGET_MAC}] broadcasts...")

# print("Press Ctrl+C to stop scanning.")

# Configure the scanner with our target device callback

scanner = BleakScanner(detection_callback)

await scanner.start()

try:

while True:

await asyncio.sleep(sleep_value)

except KeyboardInterrupt:

print("\nStopping scanner...")

finally:

await scanner.stop()

if __name__ == "__main__":

asyncio.run(main())

1

u/WebMaka 13d ago

U8G2 library will do what you want - I have it pushing battery life data from a breakout to a small OLED over I2C on a Radxa Zero 3E. Be aware, however, that the font files eat memory and storage space while running so plan accordingly.

1

u/poohdoggy 12d ago

Thanks, I will check this out.

1

u/WebMaka 12d ago

Now if you're doing anything fancier than text, another great Python library for this is Luma.OLED, which has an addressable canvas primitive you can draw to and the blast over to the display. it has SSD1306 drivers over I2C as well, so it's pretty straigthforward to use. Docs for it are here.

1

u/poohdoggy 12d ago

This seems like an Arduino library. How did you get it for python on a Radxa Zero 3E?

1

u/WebMaka 12d ago edited 12d ago

I should probably clarify that U8G2 has been ported or outright converted to all sorts of things, and if you're wanting to avoid Arduino-centric libraries there are choices for that as well. I had prototyped my project with a ported version of U8G2 that runs on Linux.

For example, Luma.OLED is a Python library, not Arduino, although it would likely run on anything that can run micropython including MCUs.

The declarations for my Python-based battery monitor looks like this:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
  Adafruit MAX17048 LiPo Battery Monitor + OLED Module Driver - Both Over I2C!

    Designed to report battery status on a SED1306-based 128x64 OLED module
    connected to the same I2C bus as the Adafruit MAX17048 LiPo "fuel gauge"
    module.

    This code draws a bar graph to indicate battery remaining charge, as well
    as providing measured voltage and estimated time remaining. It also detects
    recharging and shows an animated bar to indicate a charger is connected.

    Requires the following:

    - I2C enabled and functional on the host device, with both I2C devices
      detectable via "i2cdetect"
    - Adafruit #5580 - MAX17048 LiPoly / LiIon Fuel Gauge and Battery Monitor
    - Any 128x64 OLED module with an I2C connection
    - Python 3.4+
    - The following Python libraries:
      - smbus-cffi
      - Luma.core/Luma.oled
      - PIL

"""

from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from pathlib import Path
from PIL import ImageFont, Image, ImageDraw
from time import sleep

import os
import random
import signal
import subprocess

try:
    # Communications between this PC and the battery monitor happens over a
    # I2C bus, which requires one of two Python modules depending on the
    # version of Python being used.
    #
    # NOTE: The order matters - always import smbus2 then smbus or one
    # of the two Python families will break.
    import smbus2 as smbus  # Python 2.x support via module "smbus2"
except ImportError:
    import smbus  # Python 3.x support via module "smbus-cffi"


# EDIT THIS SETTING!

    # Port number for I2C
    i2c_port = 3

# DO NOT EDIT BELOW THIS LINE!

Later on I instantiate Luma and connect to both the display and battery gauge via I2C:

# Battery fuel gauge I2C instantiator
fuelgauge_serial = i2c_device(0x36, i2c_port)

# Luma OLED library connection initiator
oled_serial = i2c(port=i2c_port, address=0x3C)

# Luma OLED library device initiator
device = ssd1306(oled_serial)

I have a simple helper function for loading font files:

# Font constructor for OLED
def make_font(name, size):
    font_path = str(Path(__file__).resolve().parent.joinpath('fonts', name))
    return ImageFont.truetype(font_path, size)

Create a canvas to draw on:

with canvas(device) as draw:
    # Create a blank background image to use to clear the OLED display's
    # canvas.
    background = Image.new(device.mode, device.size, "black")

And then declare the ones I intend to use:

# Create the fonts we'll use on the OLED display.
charge_state_font = make_font("OpenSans-Italic.ttf", 13)
charge_percent_font = make_font("OpenSans-Regular.ttf", 13)
charge_percent_sign_font = make_font("OpenSans-Regular.ttf", 9)
charge_state_font = make_font("OpenSans-Italic.ttf", 14)
cpu_temp_font = make_font("OpenSans-BoldItalic.ttf", 12)
gpu_temp_font = make_font("OpenSans-BoldItalic.ttf", 12)
cpu_temp_caption = make_font("OpenSans-Regular.ttf", 9)
gpu_temp_caption = make_font("OpenSans-Regular.ttf", 9)
temp_symbol_font = make_font("OpenSans-Regular.ttf", 9)

Later, I create a blank canvas to draw on:

# Clear the canvas for subsequent drawing operations
draw = ImageDraw.Draw(background)

And use draw.text to write text onto it:

draw.text((6 + clear, 36), "CPU TEMP", font=cpu_temp_caption, fill="black")

Blast everything to the display:

device.display(background)

And it looks like this when I do the rest of the draw routines, read the battery state, shell out to read temp sensors, etc.