otojun
Published © GPL3+

Outdoor temperature, humidity and rain sensor

I installed temperature, humidity, and rain sensors outdoors and created a system that displays animations and plays sounds when it rains.

BeginnerFull instructions provided24 hours92
Outdoor temperature, humidity and rain sensor

Things used in this project

Story

Read more

Schematics

M5Stamp Circuit

Code

M5Stamp pico main.py

MicroPython
Sensor Data Transmission Program
# M5Stamp pico main.py
import sys
import dht
import time, utime
import machine
import neopixel
import network
import espnow
import json
import ntptime
from machine import RTC

# Save the file containing the WiFi access point's SSID and password as "secret.py".
# Write "ssid = 'myssid'" and then write "pswd = 'mypassword'" on the next line.
import secret

SSID = secret.ssid  # WiFi access point SSID
PSWD = secret.pswd  # WiFi access point password
PEER = b'\x98\xf4\xab\x6b\x98\x30'  # MAC address of peer's wifi interface

JST_OFFSET = 9 * 60 * 60 # JST (Japan standard time) = UTC + 9H (32400 sec)
INTERVAL = 5        # execution interval in seconds
IND_LIGHT_ON = 0.05 # indicator lighting time in seconds
THRESHOLD = 110     # touch sensor threshold for rain detection

# DHT22 connected to GPIO25 with pull-up resistor
dht_pin = machine.Pin(25, mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
dt = dht.DHT22(dht_pin)

# connecting the touchpad to GPIO32
tp = machine.TouchPad(machine.Pin(32))

# neopixel LED connected to GPOI27
np = neopixel.NeoPixel(machine.Pin(27), 1)

# Keep the Wi-Fi STA interface global for channel configuration
sta = network.WLAN(network.WLAN.IF_STA)

#Connect WiFi and synchronization using NTP
def connect_WiFi_and_sync_NTP():
    global sta # Use global variable sta
    sta.active(True)
    print("Connecting to WiFi for NTP sync...")
    sta.connect(SSID, PSWD)
    while(not(sta.isconnected())):
        print('.', end = "")
        time.sleep(1)
    print("\nConnected to WiFi.")
    # Time synchronization using NTP
    ntptime.settime() # Synchronization using NTP
    (year, month, day, hour, min, sec, wd, yd) = utime.localtime()  # Get time, print in UTC
    print(f"{year:4d}-{month:02d}-{day:02d} {hour:02d}:{min:02d}:{sec:02d} (UTC)")
    (year, month, day, hour, min, sec, wd, yd) = utime.localtime(utime.time() + JST_OFFSET)  # Get time, print in JST
    print(f"{year:4d}-{month:02d}-{day:02d} {hour:02d}:{min:02d}:{sec:02d} (JST)")

# Disconnect WiFi and set ESP-NOW channel
def disconnect_WiFi_and_set_channel(target_channel):
    global sta # Use global variable sta
    if sta.isconnected():
        print("Disconnecting from WiFi AP...")
        sta.disconnect()
        time.sleep(0.5) # Wait a bit for disconnection to complete
        print("Disconnected.")
    # Explicitly set STA mode channel
    print(f"Setting STA mode channel to {target_channel} for ESP-NOW...")
    sta.config(channel=target_channel)
    time.sleep(0.5) # Wait a bit for channel setting to apply
    print(f"Channel set to {target_channel}.")

# Activate ESP-NOW for sending/receiving
def connect_ESPNOW():
    en = espnow.ESPNow()
    en.active(True)
    en.add_peer(PEER)
    return en

# Blink red indicates
def neopixel_blink_RED():
    np[0] = (255, 0, 0)
    np.write()
    time.sleep(IND_LIGHT_ON)
    np[0] = (0, 0, 0)
    np.write()

# Turns blue indicates
def neopixel_turn_BLUE(TorF):
    if(TorF):
        np[0] = (0, 0, 255)
        np.write()
    else:
        np[0] = (0, 0, 0)
        np.write()

# main execution block
try:
    # 1. Connect to WiFi and sync time with NTP
    connect_WiFi_and_sync_NTP()
    
    # 2. Disconnect from WiFi AP and set ESP-NOW transmission channel to 6
    # Explicitly set the target channel to 6 here
    disconnect_WiFi_and_set_channel(6) 
    
    # 3. Initialize ESP-NOW
    en = connect_ESPNOW()
    
    while(True):
        neopixel_blink_RED()  # Blink red indicates it is running
        dt.measure()  # Measured with DHT22
        temp = dt.temperature() 
        temp = int(temp * 10.0) / 10.0  # Round to the first decimal place
        humd = dt.humidity()
        humd = int(humd * 10.0) / 10.0  # Round to the first decimal place
        touch = tp.read()  # Touch sensor returns a smaller integer when touched
        (year, month, day, hour, min, sec, wd, yd) = utime.localtime(utime.time() + JST_OFFSET)
        date = f'{month:02d}-{day:02d} {hour:02d}:{min:02d}'
        js = json.dumps({"TEMP": temp, "HUMD": humd, "TOUCH": touch , "DATE": date})
        print(js)
        en.send(PEER, js, False)  # 'False' means not waiting for a response
        neopixel_turn_BLUE(touch < THRESHOLD)  # Turns blue when it detects rain (touch < THRESHOLD)
        time.sleep(INTERVAL - IND_LIGHT_ON)  # Wait for the loop interval
except Exception as e:
    sys.print_exception(e)
    time.sleep(5)
    machine.reset()

M5Stack BASIC UIFlow generated

MicroPython
Sensor Data Receiving Program
# Micropython code generated by M5Stack BASIC UIFlow
import os, sys, io
import M5
from M5 import *
from m5espnow import M5ESPNow
import json
import time



TITLE = None
alartLAMP = None
rect0 = None
line0 = None
drop0 = None
rect1 = None
line1 = None
drop1 = None
drop2 = None
image1 = None
image2 = None
drop3 = None
temperature = None
humidity = None
label2 = None
label0 = None
label1 = None
label3 = None
espnow_0 = None


import random

draw = None
espnow_mac = None
espnow_data = None
timeStamp = None
temp = None
rainState = None
threshold = None
rainStart = None
count = None
touch = None
humd = None
beepCount = None
rainStateOld = None
raindrop0_y = None
raindrop1_y = None
interval = None
raindrop2_y = None
raindrop3_y = None
timeStamp_old = None

# Describe this function...
def rainBuzzer():
  global draw, espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, raindrop0_y, raindrop1_y, interval, raindrop2_y, raindrop3_y, timeStamp_old, TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0
  if (int(touch)) < threshold:
    rainState = True
  else:
    rainState = False
  if rainStateOld == False and rainState == True:
    rainStart = True
  if rainStart:
    beepCount = (beepCount if isinstance(beepCount, (int, float)) else 0) + 1
    Speaker.tone(2000, int(interval * 500))
    if beepCount % 2 == 0:
      alartLAMP.setVisible(True)
    else:
      alartLAMP.setVisible(False)
    if beepCount > 30:
      beepCount = 0
      rainStart = False
  rainStateOld = rainState

# Describe this function...
def rainDrop(draw):
  global espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, raindrop0_y, raindrop1_y, interval, raindrop2_y, raindrop3_y, timeStamp_old, TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0
  drop0.setVisible(False)
  drop1.setVisible(False)
  drop2.setVisible(False)
  drop3.setVisible(False)
  if draw:
    raindrop0_y = (raindrop0_y if isinstance(raindrop0_y, (int, float)) else 0) + (int(random.random() * 40))
    raindrop1_y = (raindrop1_y if isinstance(raindrop1_y, (int, float)) else 0) + (int(random.random() * 40))
    raindrop2_y = (raindrop2_y if isinstance(raindrop2_y, (int, float)) else 0) + (int(random.random() * 40))
    raindrop3_y = (raindrop3_y if isinstance(raindrop3_y, (int, float)) else 0) + (int(random.random() * 40))
    if raindrop0_y > 220:
      raindrop0_y = 49
    if raindrop1_y > 220:
      raindrop1_y = 45
    if raindrop2_y > 220:
      raindrop2_y = 49
    if raindrop3_y > 220:
      raindrop3_y = 45
    drop0.setCursor(x=214, y=raindrop0_y)
    drop1.setCursor(x=245, y=raindrop1_y)
    drop2.setCursor(x=274, y=raindrop2_y)
    drop3.setCursor(x=301, y=raindrop3_y)
    drop0.setVisible(True)
    drop1.setVisible(True)
    drop2.setVisible(True)
    drop3.setVisible(True)

# Describe this function...
def getTimeString():
  global draw, espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, raindrop0_y, raindrop1_y, interval, raindrop2_y, raindrop3_y, timeStamp_old, TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0
  timeStamp = (str((str(((time.localtime())[1])))) + str('/'))
  timeStamp = (str(((str(timeStamp) + str((str(((time.localtime())[2]))))))) + str('-'))
  timeStamp = (str(((str(timeStamp) + str((str(((time.localtime())[3]))))))) + str(':'))
  timeStamp = (str(timeStamp) + str((str(((time.localtime())[4])))))
  return timeStamp


def espnow_recv_callback(espnow_obj):
  global TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0, espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, draw, raindrop0_y, raindrop1_y, interval, raindrop2_y, random, raindrop3_y, timeStamp_old
  espnow_mac, espnow_data = espnow_obj.recv_data()
  print(espnow_data)
  temp = (json.loads(espnow_data))['TEMP']
  print(temp)
  humd = (json.loads(espnow_data))['HUMD']
  print(humd)
  touch = (json.loads(espnow_data))['TOUCH']
  touch = int(touch)
  print(touch)
  timeStamp = (json.loads(espnow_data))['DATE']
  print(timeStamp)
  espnow_0.deinit()
  espnow_0 = M5ESPNow(6)
  espnow_0.set_irq_callback(espnow_recv_callback)


def setup():
  global TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0, espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, draw, raindrop0_y, raindrop1_y, interval, raindrop2_y, random, raindrop3_y, timeStamp_old
  Widgets.fillScreen(0x93c1fb)
  TITLE = Widgets.Title("Weather", 3, 0xffffff, 0x0000FF, Widgets.FONTS.DejaVu24)
  alartLAMP = Widgets.Image("/flash/res/img/163.png", 206, 60, scale_x=1, scale_y=1)
  rect0 = Widgets.Rectangle(3, 25, 185, 104, 0xffffff, 0x93c1fb)
  line0 = Widgets.Line(67, 116, 175, 116, 0xffffff)
  drop0 = Widgets.Circle(214, 49, 9, 0x0000ff, 0x7676ff)
  rect1 = Widgets.Rectangle(3, 131, 185, 104, 0xffffff, 0x93c1fb)
  line1 = Widgets.Line(67, 217, 175, 217, 0xffffff)
  drop1 = Widgets.Circle(245, 45, 6, 0x0000ff, 0x7676ff)
  drop2 = Widgets.Circle(274, 49, 9, 0x0000ff, 0x7676ff)
  image1 = Widgets.Image("/flash/res/img/150.png", 6, 134, scale_x=0.4, scale_y=0.4)
  image2 = Widgets.Image("/flash/res/img/110.png", 5, 29, scale_x=0.4, scale_y=0.4)
  drop3 = Widgets.Circle(301, 45, 6, 0x0000ff, 0x7676ff)
  temperature = Widgets.Label(" 0.0", 56, 46, 1.0, 0x000000, 0x93c1fb, Widgets.FONTS.DejaVu56)
  humidity = Widgets.Label(" 0.0", 56, 150, 1.0, 0x000000, 0x93c1fb, Widgets.FONTS.DejaVu56)
  label2 = Widgets.Label("%", 18, 143, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
  label0 = Widgets.Label("'C", 19, 30, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
  label1 = Widgets.Label("temperature", 77, 106, 1.0, 0x00acff, 0xffffff, Widgets.FONTS.DejaVu12)
  label3 = Widgets.Label("humidity", 91, 207, 1.0, 0x00acff, 0xffffff, Widgets.FONTS.DejaVu12)


  Speaker.begin()
  alartLAMP.setVisible(False)
  count = 0
  humd = ' 0.0'
  temp = ' 0.0'
  threshold = 110
  interval = 0.7
  touch = 300
  rainStart = False
  beepCount = 0
  rainStateOld = False
  rainState = False
  raindrop0_y = 49
  raindrop1_y = 45
  raindrop2_y = 49
  raindrop3_y = 45
  drop0.setVisible(False)
  drop1.setVisible(False)
  drop2.setVisible(False)
  drop3.setVisible(False)
  timeStamp_old = '00-00 00:00'
  timeStamp = '00-00 00:00'
  TITLE.setText(((str('Weather ') + str(timeStamp))))
  espnow_0 = M5ESPNow(6)
  espnow_0.set_irq_callback(espnow_recv_callback)


def loop():
  global TITLE, alartLAMP, rect0, line0, drop0, rect1, line1, drop1, drop2, image1, image2, drop3, temperature, humidity, label2, label0, label1, label3, espnow_0, espnow_mac, espnow_data, timeStamp, temp, rainState, threshold, rainStart, count, touch, humd, beepCount, rainStateOld, draw, raindrop0_y, raindrop1_y, interval, raindrop2_y, random, raindrop3_y, timeStamp_old
  M5.update()
  print(touch)
  rainBuzzer()
  rainDrop(touch < threshold)
  count = (count if isinstance(count, (int, float)) else 0) + 1
  print(count)
  temperature.setText(str(temp))
  humidity.setText(str(humd))
  if timeStamp != timeStamp_old:
    TITLE.setText(((str('Weather ') + str(timeStamp))))
    timeStamp_old = timeStamp
  time.sleep(interval)


if __name__ == '__main__':
  try:
    setup()
    while True:
      loop()
  except (Exception, KeyboardInterrupt) as e:
    try:
      from utility import print_error_msg
      print_error_msg(e)
    except ImportError:
      print("please update to latest firmware")

Credits

otojun
1 project • 0 followers

Comments