# 2025/1/2 17:00
# 不會上網對時,也不會發佈MQTT, 單機種程式
import network
import time
import ujson
from machine import Pin, RTC
import tm1637 # 引入 tm1637 驅動程式
# Wi-Fi 設定
SSID = "Bili-Net"
PASSWORD = "0932388283"
HOSTNAME = "MotionTimer-01"
# 台北時區 (UTC+8)
TIMEZONE_OFFSET = 8 * 60 * 60 # 秒
# 定義 HC-SR501 的輸出腳位、LED 顯示模組的 CLK 和 DIO 腳位,以及重置按鈕
PIR_PIN = Pin(4, Pin.IN) # ESP32 的 D4 腳位對應 GPIO4
LED_PIN = Pin(2, Pin.OUT) # GPIO2 用來控制 LED
RESET_BUTTON = Pin(12, Pin.IN, Pin.PULL_UP) # GPIO12 按鈕用來重置計時
tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17))
rtc = RTC()
# 設定相關變數
detect_flag = False # 是否偵測到物體
start_time = 0 # 計時開始時間
timeout = 45 # 偵測間隔為 45 秒
interval = 1 # 偵測間隔時間,單位:秒
no_motion_start_time = None # 用於記錄無移動時的開始時間
max_time = 1800 # 計時最大值為 30 分鐘 (1800 秒)
# 格式化時間
def format_datetime():
"""格式化當前日期和時間為 'YYYY-MM-DD HH:MM:SS'"""
now = time.localtime(time.time() + TIMEZONE_OFFSET) # 調整為台北時區
return "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(now[0], now[1], now[2], now[3], now[4], now[5])
# 顯示時間格式
def display_waiting():
"""等待偵測時顯示中間的冒號"""
tm.set_segments([0x00, 0x80, 0x00, 0x00]) # 中間顯示冒號
def display_time(minutes, seconds):
"""顯示時間格式 MM:SS"""
minute_tens = minutes // 10
minute_ones = minutes % 10
second_tens = seconds // 10
second_ones = seconds % 10
segments = [
tm.encode_digit(minute_tens),
tm.encode_digit(minute_ones) | 0x80, # 中間的冒號
tm.encode_digit(second_tens),
tm.encode_digit(second_ones)
]
tm.set_segments(segments)
def display_seconds(seconds):
"""只顯示秒數格式 :SS"""
second_tens = seconds // 10
second_ones = seconds % 10
segments = [0x00, 0x80, tm.encode_digit(second_tens), tm.encode_digit(second_ones)]
tm.set_segments(segments)
def display_upup():
"""顯示 'UPUP' 字樣"""
segments = [0x3e, 0x73, 0x3e, 0x73] # 'UPUP'
tm.set_segments(segments)
def reset_timer():
"""重置計時,並返回等待狀態"""
global detect_flag, start_time, no_motion_start_time
detect_flag = False
start_time = 0
no_motion_start_time = None
LED_PIN.off() # GPIO2 熄滅
display_waiting() # 回到等待模式
print("計時已重置,回到等待模式")
# 主程式
try:
print("PIR 模組初始化中...")
time.sleep(2) # 等待模組穩定
print("準備偵測中(按 Ctrl+C 結束)")
display_waiting() # 開始時顯示等待狀態
while True:
current_state = PIR_PIN.value()
if RESET_BUTTON.value() == 0:
reset_timer()
time.sleep(0.5) # 防止按鈕彈跳
if current_state:
if not detect_flag:
detect_flag = True
start_time = time.time()
no_motion_start_time = None
LED_PIN.on() # GPIO2 亮起
print("\n偵測到物體移動! 開始計時...", end="\r")
else:
elapsed_time = time.time() - start_time
if elapsed_time >= max_time:
print("計時達到60分鐘,顯示UPUP...")
while PIR_PIN.value():
if RESET_BUTTON.value() == 0:
reset_timer()
time.sleep(0.5)
break
display_upup()
time.sleep(1)
tm.clear()
time.sleep(1)
reset_timer()
else:
minutes = int(elapsed_time // 60)
seconds = int(elapsed_time % 60)
display_time(minutes, seconds)
print(f"持續偵測到物體移動! 計時... {minutes:02}:{seconds:02}", end="\r")
no_motion_start_time = None
else:
if detect_flag and no_motion_start_time is None:
no_motion_start_time = time.time()
print("\n未偵測到移動,開始計算倒數...", end="\r")
elif no_motion_start_time is not None:
no_motion_elapsed_time = time.time() - no_motion_start_time
if no_motion_elapsed_time >= timeout:
elapsed_time = time.time() - start_time
seconds = int(elapsed_time % 60)
display_seconds(seconds)
print(f"\n未偵測到移動,停止計時... 偵測停止")
reset_timer()
else:
remaining_time = int(timeout - no_motion_elapsed_time)
display_seconds(remaining_time)
print(f"\n倒數中,還剩:{remaining_time} 秒", end="\r")
time.sleep(interval)
except KeyboardInterrupt:
print("\n程式結束")