Step 1: Managing Credentials Off-Chip

As with previous builds, authentication credentials remain isolated from the primary firmware logic in a local secrets.py file. Copy and paste the code below into a new code window, enter your own username and key, and save it as secrets.py on the MicroPython device.

# secrets.py
ADAFRUIT_IO_USERNAME = "your_username_here"
ADAFRUIT_IO_KEY      = "your_aio_key_here"

This keeps personal API tokens off version control while allowing the main script to reference dynamic feed paths.

Step 2: The Firmware Breakdown

Let's dissect the implementation details and hardware considerations behind this autonomous edge controller.

1. Imports and Setpoint Configuration

import gc
import network
import time
from machine import I2C, SPI, Pin
import ahtx0
from umqtt.simple import MQTTClient

from secrets import ADAFRUIT_IO_KEY, ADAFRUIT_IO_USERNAME

# MQTT Broker Details
MQTT_HOST = "io.adafruit.com"
MQTT_PORT = 1883
TEMP_TOPIC = ADAFRUIT_IO_USERNAME + "/feeds/temperature"
HUMIDITY_TOPIC = ADAFRUIT_IO_USERNAME + "/feeds/humidity"
FAN_TOPIC = ADAFRUIT_IO_USERNAME + "/feeds/fan"
PUBLISH_INTERVAL_MS = 30000

# Temperature thresholds for fan control (Celsius)
TEMP_FAN_ON = 25.0
TEMP_FAN_OFF = 24.0   # Hysteresis band

Quick Note:
Make sure you have the ahtx0 and MQTT client libraries installed. The MQTT client library can be installed by navigating to Tools -> Manage Packages and searching for micropython-umqtt.simple.
The ahtx0 library can be installed using the mip module. Once your board is connected to the internet (via your Ethernet setup), open the REPL and run:

import mip
mip.install("github:targetblank/micropython_ahtx0/blob/master/ahtx0.py")

 2. Robust W5500 Bring-Up with Hardware Reset

# Configure SPI 2 for the internal W5500 controller spi = SPI(2, baudrate=8_000_000, polarity=0, phase=0) cs = Pin("PB12", Pin.OUT) rst = Pin("PD9", Pin.OUT) # Explicit hardware reset toggle rst.value(0) time.sleep_ms(10) rst.value(1) time.sleep_ms(200) nic = network.WIZNET5K(spi, cs, rst) nic.active(True) nic.ifconfig("dhcp") print("Waiting for DHCP...") deadline = time.ticks_add(time.ticks_ms(), 30000) while nic.ifconfig()[0] == "0.0.0.0": if time.ticks_diff(deadline, time.ticks_ms()) <= 0: raise RuntimeError("DHCP timeout") time.sleep_ms(500) # Verify physical link state timeout = 10 while not nic.isconnected() and timeout > 0: time.sleep(1) timeout -= 1 if nic.isconnected(): print("→ Ethernet Link UP!") print(" IP Address:", nic.ifconfig()[0]) else: print("→ Failed to detect physical link. Check cable and switch.")

 

3. Peripherals: I2C Sensor and High-Power Actuator

# Initialize I2C bus and AHT20 sensor i2c = I2C(1) print("I2C devices:", i2c.scan()) sensor = ahtx0.AHT20(i2c) # Configure fan actuator output fan = Pin("PD13", Pin.OUT) # Connected to MOSFET TRIG/PWM pin fan.off()

 4. MQTT Connection

client_id = "w55mh32-mqtt"
client = MQTTClient(
    client_id,
    MQTT_HOST,
    MQTT_PORT,
    ADAFRUIT_IO_USERNAME,
    ADAFRUIT_IO_KEY,
)
client.connect()
print("Connected to MQTT")

Unlike our earlier project, this client operates in an uplink-first telemetry mode. It does not subscribe to incoming actuator toggles; the device evaluates its environment locally and transmits status updates upstream.

5. Autonomous Control with Hysteresis

def update_fan_and_sensors():
    temperature = round(sensor.temperature, 1)
    humidity = round(sensor.relative_humidity, 1)

    print("Temperature:", temperature, "C")
    print("Humidity:", humidity, "%")

    # Hysteresis fan logic
    if temperature >= TEMP_FAN_ON and fan.value() == 0:
        fan.on()
        print(f"→ Fan turned ON (Temp >= {TEMP_FAN_ON}°C)")
        try:
            client.publish(FAN_TOPIC, "ON")
        except Exception:
            pass
    elif temperature < TEMP_FAN_OFF and fan.value() == 1:
        fan.off()
        print(f"→ Fan turned OFF (Temp < {TEMP_FAN_OFF}°C)")
        try:
            client.publish(FAN_TOPIC, "OFF")
        except Exception:
            pass

    # Publish environment feeds
    client.publish(TEMP_TOPIC, str(temperature))
    client.publish(HUMIDITY_TOPIC, str(humidity))
    print("Sensor data published")

6. Periodic Execution Loop

last_publish = time.ticks_ms()

try:
    while True:
        now = time.ticks_ms()
        if time.ticks_diff(now, last_publish) >= PUBLISH_INTERVAL_MS:
            last_publish = now
            update_fan_and_sensors()
            gc.collect()

        time.sleep_ms(100)

except KeyboardInterrupt:
    print("Disconnecting...")
    fan.off()
    client.disconnect()

The Result

Connecting the W55MH32L to an external MOSFET and configuring an Adafruit IO dashboard turns this into a fully functioning system. Below is a demonstration video.

Improvements 

Something we can immediately notice from the video is that it takes quite a long time for the sensor to publish it's data before the fan turns on. 

The code uses a 30-second repeating countdown timer. The problem was that instead of starting the timer after doing the first check, the program started the countdown timer first. Because the function was placed entirely inside that timer, the system was forced to wait 30 seconds before taking its very first reading. 

One solution to fix this is to run it once immediately before entering the loop.

update_fan_and_sensors() 
last_publish = time.ticks_ms()