Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
| ||||||
![]() |
|
Overview
Read moreA temperature and humidity sensor and rain sensor connected to M5Stamp pico were installed outdoors.
The data obtained is transmitted to M5Stack BASIC installed indoors using ESP-NOW, a communication protocol dedicated to ESP32.
When it rains, an alarm and rain animation are displayed on the display, and an alarm sound is emitted.
MovieOutdoor temperature humidity and rain sensor --- YouTube LINK
Unique Features of This Project- The design and outdoor operating equipment were selected based on one year of operation of the prototype that was created earlier.
- Communication uses ESP-NOW, and the receiving M5Stack BASIC does not need to be connected to a WiFi access point.
- The design and installation were optimized for long-term outdoor use rather than an indoor experimental system.
- In addition to temperature and humidity, the system is capable of detecting rainfall.
- To prevent the M5Stamp pico from malfunctioning due to rain or humidity, it was placed in a plastic container and further protected with a PVC cover that is resistant to long-term weathering.
- The rain detection sensor uses a commercially available rain sensor [RS-1].
- The conventional detection method, which detects the conduction of weak currents between electrodes, is not suitable for long-term use due to rapid electrode corrosion.
- Therefore, the rain detection uses the touch sensor function of the M5Stamp pico's GPIO to detect capacitance.
- Since the rain sensor does not require current conduction, it was treated with color spray to prevent rusting.
- The transmitting M5Stamp pico synchronizes with an NTP server to include the date and time in the transmitted data, and the receiving M5Stack BASIC displays that date and time, allowing confirmation of when the data was sent.
- When it starts raining, an alarm buzzer sounds and an alarm light flashes, providing both visual and auditory alerts.
- While rain is detected, an animation of rain falling is displayed to indicate the weather conditions.
# 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()
# 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")
Comments