Posted in

Mini weather station with temp display

I had for a long time a little project on my desk. A temperature display reading last outside temperature from the server and display it on the dual seven segment display I have build some time ago.

My type of display.

But now that summer is closing I realized that the thermometer I have on my desk is not working any more. I searched in the parts pile and I found a DHT22 that I keep as a spare for Station 1. I also had in stock a TM1637 display from previous project. So I build the thing.

Reads the temperature and humidity from DHT22 and posts them to the server (Station 3) and gets the last temperature posted from Station 1. So, it displays for 20 seconds the local (room) temperature and for 20 seconds the outside temperature. That is that the LEDs indicate.

The final product.

The real reason I used the TM1637 display is that for some reason my dual ssd stopped working and couldn’t find why. Maybe the reason that the first TM1637 I connected was fried also. Also managed to destroy a DHT22 and 2 LEDs too. Destructive day. Anyways all good at the end, fortunately I had spare parts.

The prototype in action

Indoors mini weather station and display

The finished device in it’s final location.

For anyone interested the source.

import board, time
import adafruit_dht
import digitalio
import os, sys
import adafruit_connection_manager
import wifi
import adafruit_requests
import gc
import asyncio
import math
import TM1637

##############################################################################################
class NoSsidAvailable(Exception):
    pass

##############################################################################################
class HttpClient:
    
    def __init__(self,my_wifi_ssids, password):
        self.my_wifi_ssids = my_wifi_ssids        
        self.password = password
        self.ssid = self.findSsidToConnect()
        self.requests = None
        #self.requests = self.setupRequests()
        
    def findSsidToConnect(self):
        print(" | Available WiFi networks:")
        networks = []
        for network in wifi.radio.start_scanning_networks():
            #print(network.ssid, network.ssid in self.my_wifi_ssids)
            if network.ssid in self.my_wifi_ssids:
                networks.append(network)
            #print(network.ssid, network.rssi, network.channel) # Debug print
        wifi.radio.stop_scanning_networks()
        networks = sorted(networks, key=lambda net: net.rssi, reverse=True)

        for network in networks:
            print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(network.ssid, "utf-8"), network.rssi, network.channel))
        if len(networks)>0:
            return str(networks[0].ssid, "utf-8");
        else:
            raise NoSsidAvailable(f"None of your SSID(s) {self.my_wifi_ssids} is available",300)
        
    def connectWifi(self):
        retries = 0
        while retries < 5:
            if wifi.radio.connected:
                print(f"Connected to {self.ssid}")
                if self.requests==None:
                    self.requests = self.setupRequests()
                return True  # Already connected
            print(f"\n | Connecting to {self.ssid}... (attempt {retries+1}/5)")
            show_in_display(f"c{retries+1}")
            retries += 1
            try:
                wifi.radio.connect(self.ssid, self.password)
                if self.requests==None:
                    self.requests = self.setupRequests()
                print("✅ Wifi!")
                show_in_display("co")
                return True
            except OSError as e:
                print(f"❌ OSError: {e}")
                show_in_display("ce")
                time.sleep(2)  # Add delay between retries
        return False  # Failed to connect


    def setupRequests(self):
        # Initalize Wifi, Socket Pool, Request Session
        pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
        ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
        requests = adafruit_requests.Session(pool, ssl_context)
        return requests

    def postData(self,url, json_data):
        out = False
        retries = 0
        while retries<3:
            try:
                # Check if connected first, reconnect if needed
                if self.connectWifi():
                    print(f" | POST To: {url} Payload: {json_data}")
                    show_in_display(f"p{retries+1}")
                    with self.requests.post(url, json=json_data, timeout=10) as response:
                        json_resp = response.json()
                        print(f" | API Response: {json_resp['message']}")
                        if json_resp['message']=="YES":
                            out = True
                            break
                        else:
                            print("Post failed, trying again")
                else:
                    print("Not connected to wifi, will not post")
                    show_in_display(f"pc")
                    break
            except Exception as error:
                print(f" | HttpClient.postData => Exception {error}")
                show_in_display(f"pe")
            retries += 1
            gc.collect()
            time.sleep(2)  # Wait before retry
            print("Trying again to post...")
        return out

    def getData(self, url):
        out = None
        gc.collect()
        retries = 0
        while retries<3:
            print(f"in getData {gc.mem_free()=}")
            show_in_display(f"g{retries+1}")
            try:
                if self.connectWifi(): # in case wifi is disconnected, connect again
                    print(f" | GET: {url}")
                    with self.requests.get(url, timeout=10) as response:
                        json_resp = response.json()
                        if json_resp['message']=='YES':
                            out = json_resp
                            break
            except Exception as error:
                 print(f" | HttpClient.getData => Exception [{error}]")
                 show_in_display("ge")
            retries += 1
            time.sleep(2)  # Wait before retry
            if retries<3:
                print("Trying again to get...")
            else:
                print("error")
                show_in_display("gf")
            gc.collect()
            
        return out
    
def getTempFromServer():
    success = True
    last_temp = STATION_STATE['current_out']
    data = client.getData(STATION_SETTINGS['json_get_data_url'])
    if data!=None:
        temp = data['temp']
        if temp!=None:
            last_temp = int(round( float(temp),0))
        
    else:
        success = False
        
    if success:
        STATION_STATE['current_out'] = last_temp

######################################################################################
class Thermometer:
    def __init__(self, pinNum):
        self.dhtDevice = adafruit_dht.DHT22(pinNum)
        self.getReadings()
        
    def getReadings(self):
        retries = 0
        while True:
            retries = retries+1
            if retries>10:
                break
            try:
                temperature_c = self.dhtDevice.temperature
                humidity = self.dhtDevice.humidity
                if temperature_c is not None and humidity is not None:
                    return {"t": temperature_c,"h": humidity}
            except RuntimeError as error:
                # Errors happen fairly often, DHT's are hard to read, just keep going
                print(f" | Thermometer.getReadings Runtime error: {error}")
                time.sleep(0.3)
                continue
            except Exception as error:
                print(f" | Thermometer.getReadings Exception {error}")
                time.sleep(0.3)
                continue
        return None
    
######################################################################################
class IndicatorLed:
    def __init__(self, pin):
        self.ledPin = digitalio.DigitalInOut(pin)
        self.ledPin.direction = digitalio.Direction.OUTPUT

    def on(self):
        self.ledPin.value = True
    def off(self):
        self.ledPin.value = False

##############################################################################################
def displayValueToSSD(v):
    temp = int(round( float(v),0))
    s = "{:02d}C".format(temp)
    width = 4
    s = " " * (width - len(s)) + s
    show_in_display(s)

##############################################################################################
def show_in_display(txt):
    display.write([0, 0, 0, 0])
    display.show(txt)

##############################################################################################
async def getRemote():
    while True:
        getTempFromServer()
        print("Get Remote")
        gc.collect()
        await asyncio.sleep(STATION_SETTINGS['station_sleep_time'])
        
##############################################################################################
async def postLocal():
    while True:
        print("Post local")
        json_data = {
            "stid": STATION_SETTINGS['station_id'],
        }
        readings = STATION_STATE['dht'].getReadings()
        if readings is not None:
            json_data["ta"] = readings["t"]
            json_data["ha"] = readings["h"]
            STATION_STATE['current_in'] = readings['t']
            
        resp = True
        if 'ta' in json_data:
            resp = client.postData(STATION_SETTINGS['json_post_url'], json_data)
        if not resp:
            print("Failed to post data")
        print("Loop end")
        gc.collect()
        await asyncio.sleep(STATION_SETTINGS['station_sleep_time'])
##############################################################################################
async def displayValue():
    while True:
        print("#################################")
        if STATION_STATE['show']=='in':
            print(f"Local {STATION_STATE['current_in']}")
            displayValueToSSD(STATION_STATE['current_in'])
            STATION_STATE['show']='out'
            STATION_STATE['indLed1'].on()
            STATION_STATE['indLed2'].off()
        else:
            print(f"Remote {STATION_STATE['current_out']}")
            displayValueToSSD(STATION_STATE['current_out'])
            STATION_STATE['show']='in'
            STATION_STATE['indLed1'].off()
            STATION_STATE['indLed2'].on()
        print("#################################")
        await asyncio.sleep(20)
##############################################################################################
async def main():
    tasks = []
    tasks.append(asyncio.create_task(postLocal()))
    tasks.append(asyncio.create_task(getRemote()))
    tasks.append(asyncio.create_task(displayValue()))
    await asyncio.gather(*tasks)

##############################################################################################
STATION_SETTINGS = {
    'wifi_ssids' : os.getenv("WIFI_SSID").split(','),
    'password' : os.getenv("NETWORK_PASSWORD") ,
    'json_post_url': "https://homeweather.salix.me/v1/measurements/log",
    'json_get_data_url' : "https://homeweather.salix.me/v1/appdata/locationLastTemp/loc_1",
    'station_id' : os.getenv("STATION_ID"),
    'station_sleep_time' : os.getenv("STATION_SLEEP_TIME"),
    'board_type': 'C3'
}
#########################################################################
STATION_STATE = {
    'dht': None,
    'indLed1' : None,
    'indLed2' : None,
    'current_in' : 0,
    'current_out' : 0,
    'show': 'in', 
}
board_type_name = os.uname().machine
print("Board: ", board_type_name)
print(STATION_SETTINGS)


display = TM1637.TM1637(board.IO1, board.IO2)
display.write([0, 0, 0, 0])
display.show('boot')
time.sleep(1)
display.write([0, 0, 0, 0])
time.sleep(1)
display.show('boot')

STATION_STATE['dht'] = Thermometer(board.IO21)
STATION_STATE['indLed1'] = IndicatorLed(board.IO20)
STATION_STATE['indLed2'] = IndicatorLed(board.IO10)
STATION_STATE['indLed1'].on()
STATION_STATE['indLed2'].on()

try:
    client = HttpClient(STATION_SETTINGS['wifi_ssids'],STATION_SETTINGS['password'])
    client.connectWifi()
except Exception as e:
    print(f"Caught an error: {e}")
    display.show("er")
    sys.exit()
gc.enable()

readings = STATION_STATE['dht'].getReadings()
print(readings)
if readings is not None:
    STATION_STATE['current_in'] = readings['t']


asyncio.run(main())

A tech note. Esp32 C3 supermini has less resources compared to the ESP32 DevKitc V4 board I have, that code without garbage collection is not working. On DevKitc V4 runs smoothly. On C3 supermini HTTP requests fail, with strange timeout error message. If free memory is less than 50k there is trouble.

UPDATE: The solution after all was CircuitPython. My DevKitc V4 has installed 9.2 and Station 1 too. So I downgraded CircuitPython from 10.1 to 9.2 and in combination with garbage collector, runs without issues.