這是一個幾乎完整的程式,至少目前我時這麼認為,當然也有想要再改的,現在是逐一資料顯示,如果可以一次更新LCD,應該可以省下一些時間,但我覺得目前的程式易於閱讀,就不想改了。
之前的版整都是用 time.delay所以極耗資源,而且反應緩慢,後來改用 timer 如下的二行程式應該為核心程式
timer.init(period=1000, mode=Timer.PERIODIC, callback=timer_callback)
button_timer.init(period=50, mode=Timer.PERIODIC, callback=button_check)
用定時器在每一秒更新LCD螢幕上的資料,按鍵定時器隨時監測有沒有按鍵被按,在此範例中設定的按鍵有關閉LCD版光,跟時間跟網路資訊的切換。
功能說明:
1.開機時先連上網路,並進行對時。
2.第0行顯示日期、連網狀態、星期英文縮寫
3 .第1行顯示時間、溫度及溼度
4 .設定定時器為每秒顯示一次時間
5 .設定0 .1秒檢查按鈕有沒有按下,有時分辨執行GPIO14或是GPIO27內建程序
6 .主循環檢查於4:00進行時間校正,並且防止重複校正的情況發生
接腳說明:
DHT11 S訊號接 GPIO13
DHT11 +接GPIO12
GPIO14 為LCD背光按鈕
GPIO27為 顯示主機名稱及IP按鈕
1602 SDA GPIO21
1602 CSL GPIO22
# 2024/10/07-18:25
import machine
import network
import ntptime
import time
from machine import Pin, SoftI2C, RTC, Timer
from lcd_api import LcdApi
from i2c_lcd import I2cLcd
import dht # DHT11 感測器模組
from umqtt.simple import MQTTClient # 用來處理 MQTT 功能
import json # 用來處理 JSON 資料
# I2C 設定:LCD 顯示模組的 I2C 位址與尺寸
I2C_ADDR = 0x27
totalRows = 2
totalColumns = 16
# MQTT 設定
mqtt_server = '192.168.0.172'
mqtt_port = 11883
client_id = "Phobos-ESP32_client"
mqtt_topic = 'myTopic'
# Wi-Fi 設定
SSID = "Bili-Net"
PASSWORD = "0932388283"
CustomHostmane = "Phobos-ESP32" # 自訂設備的主機名稱
rtc = RTC() # RTC (實時時鐘) 模組,用來管理時間
# 初始化網路設置
wifi = network.WLAN(network.STA_IF) # 設定 Wi-Fi 為工作站模式
wifi.active(True) # 啟動 Wi-Fi 功能
wifi.config(dhcp_hostname=CustomHostmane) # 設定主機名稱
wifi_status = False # 初始化 Wi-Fi 狀態為未連線
dht_sensor = dht.DHT11(Pin(13)) # 初始化 DHT11 感測器 (接在 GPIO13)
dht_power = Pin(12, Pin.OUT) # DHT11 供電腳位設定為輸出
dht_power.value(1) # 拉高 GPIO12 提供電源給 DHT11
backlight_control = Pin(14, Pin.IN, Pin.PULL_UP) # 背光控制按鈕,使用 GPIO14
gpio27 = Pin(27, Pin.IN, Pin.PULL_UP) # GPIO27 針腳作為另一個按鈕的輸入
# 狀態變數
backlight_state = False # 背光狀態
gpio27_state = False # GPIO27 狀態
debounce_time_14 = 0 # 去彈跳時間控制 (按鈕)
debounce_time_27 = 0 # 去彈跳時間控制 (GPIO27)
pause_time_update = False # 暫停時間更新的控制變數
timer = Timer(0) # 主要時間計時器
button_timer = Timer(1) # 按鈕檢查計時器
# 新增狀態變數
previous_time = None # 上一次更新的時間記錄
previous_date = None # 上一次更新的日期記錄
previous_temp = None # 上一次更新的溫度
previous_hum = None # 上一次更新的濕度
previous_wifi_status = None # 上一次 Wi-Fi 狀態
previous_weekday_abbr = None # 上一次的星期縮寫
# 設定時區(台北,UTC+8)
def set_timezone():
offset = 8 * 3600 # UTC+8 時區的秒數偏移
now = time.time() + offset # 取得目前的 UNIX 時間並加上偏移
tm = time.localtime(now) # 轉換成本地時間
rtc.datetime((tm[0], tm[1], tm[2], 0, tm[3], tm[4], tm[5], 0)) # 設定 RTC 的時間
print('Timezone set to Taipei (UTC+8). Current RTC time:', rtc.datetime())
# 連接到 Wi-Fi
def connect_wifi():
global wifi_status
wifi_status = False # 初始化 Wi-Fi 狀態為未連線
wifi.connect(SSID, PASSWORD) # 使用指定的 SSID 和密碼連接 Wi-Fi
max_attempts = 10 # 最大連線嘗試次數
attempt_count = 0 # 當前嘗試次數
# 重複嘗試連接,直到成功或達到最大嘗試次數
while not wifi.isconnected() and attempt_count < max_attempts:
print("等待 Wi-Fi 連線... 嘗試次數:", attempt_count + 1)
time.sleep(1) # 每次嘗試間隔 1 秒
attempt_count += 1
if wifi.isconnected():
print("Wi-Fi 連線成功:", wifi.ifconfig()) # 連線成功後顯示 IP 設定
wifi_status = True
else:
print("Wi-Fi 連線失敗,已達最大嘗試次數")
# 時間同步
def sync_time():
try:
ntptime.settime() # 與 NTP 伺服器同步時間
set_timezone() # 設定時區
print("時間校對完成:", time.localtime())
except:
print("時間校對失敗")
# 初始化 I2C,連接 LCD 顯示器
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=10000)
lcd = I2cLcd(i2c, I2C_ADDR, totalRows, totalColumns) # LCD 設置
# 狀態變數,儲存按鈕的前一個狀態
backlight_prev_state = 1 # 背光控制按鈕的前一個狀態
gpio27_prev_state = 1 # GPIO27 按鈕的前一個狀態
# 初始化 MQTT 客戶端
client = MQTTClient(client_id, mqtt_server, port=mqtt_port)
def mqtt_connect():
try:
client.connect() # 連接到 MQTT 伺服器
print('Connected to MQTT Broker at {}:{}'.format(mqtt_server, mqtt_port))
except Exception as e:
print('Failed to connect to MQTT Broker. Error:', e)
def get_ip_address():
"""取得設備的 IP 地址"""
if wifi.isconnected():
return wifi.ifconfig()[0] # 回傳 IP 地址
return "0.0.0.0" # 若未連接 Wi-Fi,回傳預設值
def format_time_to_string():
"""手動格式化時間字串"""
now = time.localtime()
return "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(now[0], now[1], now[2], now[3], now[4], now[5])
def publish_to_mqtt(temp, hum):
try:
current_time = format_time_to_string() # 取得手動格式化的時間字串
ip_address = get_ip_address() # 取得 IP 地址
data = {
"timestamp": current_time,
"hostname": CustomHostmane,
"ip": ip_address,
"Temperature": temp,
"Humidity": hum
}
json_data = json.dumps(data)
client.publish(mqtt_topic, json_data)
print('Published to MQTT:', json_data)
except Exception as e:
print('Failed to publish to MQTT. Error:', e)
# 按鈕去彈跳控制並檢查狀態變化
def debounce_and_check_state(pin, prev_state, debounce_time, delay=100):
current_time = time.ticks_ms() # 取得當前時間 (毫秒)
current_state = pin.value() # 取得按鈕的當前狀態
if current_state == 0 and prev_state == 1 and (current_time - debounce_time > delay):
debounce_time = current_time
return True, debounce_time, current_state
return False, debounce_time, current_state
# 控制背光的開關
def control_backlight():
global backlight_state, debounce_time_14, backlight_prev_state
triggered, debounce_time_14, backlight_prev_state = debounce_and_check_state(backlight_control, backlight_prev_state, debounce_time_14)
if triggered: # 若按鈕被觸發
backlight_state = not backlight_state # 切換背光狀態
if backlight_state:
lcd.backlight_on() # 打開背光
else:
lcd.backlight_off() # 關閉背光
# 控制 GPIO27 的狀態
def control_gpio27():
global gpio27_state, debounce_time_27, gpio27_prev_state, pause_time_update
triggered, debounce_time_27, gpio27_prev_state = debounce_and_check_state(gpio27, gpio27_prev_state, debounce_time_27)
if triggered: # 若 GPIO27 按鈕被觸發
gpio27_state = not gpio27_state # 切換 GPIO27 狀態
pause_time_update = not pause_time_update # 切換時間更新暫停狀態
update_display() # 更新顯示
# 取得星期縮寫
def get_weekday_abbreviation(t):
weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
return weekdays[t[6]] # 根據時間中的星期索引回傳縮寫
# 恢復顯示日期、時間、Wi-Fi 狀態和溫濕度
def restore_display(date, time_str, wifi_status, temp, hum):
wifi_indicator = "W" if wifi_status else "X" # Wi-Fi 狀態指示
weekday_abbr = get_weekday_abbreviation(time.localtime()) # 取得當前星期縮寫
lcd.clear() # 清除 LCD 顯示器
lcd.move_to(0, 0) # 移到第一行的起點
lcd.putstr(date) # 顯示日期
lcd.move_to(totalColumns - 5, 0)
lcd.putstr(wifi_indicator) # 顯示 Wi-Fi 狀態
lcd.move_to(totalColumns - 3, 0)
lcd.putstr(weekday_abbr) # 顯示星期縮寫
lcd.move_to(0, 1) # 移到第二行的起點
lcd.putstr(time_str) # 顯示時間
if temp is not None and hum is not None:
lcd.putstr(" {:2d}C {:2d}%".format(temp, hum)) # 顯示溫度與濕度
else:
lcd.putstr(" NaC Na%") # 無法讀取資料時顯示 NaC Na%
# 更新顯示畫面(根據不同狀態)
def update_display():
global previous_time, previous_date, previous_temp, previous_hum, previous_wifi_status
now = time.localtime()
current_date = format_date(now)
current_time = format_time(now)
weekday_abbr = get_weekday_abbreviation(now)
wifi_indicator = "W" if wifi_status else "X"
if gpio27_state: # 若 GPIO27 狀態為 True,顯示主機名稱與 IP
hostname, ip = get_hostname_and_ip()
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(hostname) # 顯示主機名稱
lcd.move_to(0, 1)
lcd.putstr(ip) # 顯示 IP 位址
else: # 否則顯示日期與時間
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(current_date) # 顯示日期
lcd.move_to(totalColumns - 5, 0)
lcd.putstr(wifi_indicator) # 顯示 Wi-Fi 狀態
lcd.move_to(totalColumns - 3, 0)
lcd.putstr(weekday_abbr) # 顯示星期縮寫
lcd.move_to(0, 1)
lcd.putstr(current_time) # 顯示時間
lcd.move_to(len(current_time) + 1, 1)
lcd.putstr("{:2d}C {:2d}%".format(previous_temp, previous_hum)) # 顯示溫度與濕度
# 計時器回調函數
def timer_callback(timer):
global previous_time, previous_date, previous_temp, previous_hum, previous_wifi_status, previous_weekday_abbr
if not pause_time_update: # 如果未暫停更新
now = time.localtime()
current_time = format_time(now)
current_date = format_date(now)
temp, hum = read_dht11() # 讀取 DHT11 資料
current_weekday_abbr = get_weekday_abbreviation(now)
wifi_current_status = wifi_status
# 日期變更時更新顯示
if current_date != previous_date:
lcd.move_to(0, 0)
lcd.putstr(current_date)
previous_date = current_date
# 時間變更時更新顯示
if current_time != previous_time:
lcd.move_to(0, 1)
lcd.putstr(current_time)
previous_time = current_time
# Wi-Fi 狀態變更時更新顯示
if wifi_current_status != previous_wifi_status:
lcd.move_to(totalColumns - 5, 0)
wifi_indicator = "W" if wifi_current_status else "X"
lcd.putstr(wifi_indicator)
previous_wifi_status = wifi_current_status
# 星期變更時更新顯示
if previous_weekday_abbr != current_weekday_abbr:
lcd.move_to(totalColumns - 3, 0)
lcd.putstr(current_weekday_abbr)
previous_weekday_abbr = current_weekday_abbr
# 溫溼度變更時更新顯示並發佈到 MQTT
if temp != previous_temp or hum != previous_hum:
lcd.move_to(len(current_time) + 1, 1)
if temp is not None and hum is not None:
lcd.putstr("{:2d}C {:2d}%".format(temp, hum))
publish_to_mqtt(temp, hum) # 發佈到 MQTT
else:
lcd.putstr("NaC Na%")
previous_temp = temp
previous_hum = hum
# 檢查按鈕的回調函數
def button_check(timer):
control_backlight() # 控制背光
control_gpio27() # 控制 GPIO27
# 格式化時間
def format_time(t):
return "{:02}:{:02}:{:02}".format(t[3], t[4], t[5])
# 格式化日期
def format_date(t):
return "{:04}-{:02}-{:02}".format(t[0], t[1], t[2])
# 取得主機名稱與 IP
def get_hostname_and_ip():
if wifi.isconnected():
return wifi.config('dhcp_hostname'), wifi.ifconfig()[0]
return None, None
# 讀取 DHT11 感測器數據
def read_dht11():
try:
dht_sensor.measure() # 讀取溫溼度資料
return dht_sensor.temperature(), dht_sensor.humidity() # 回傳溫度與濕度
except:
return None, None # 讀取失敗時回傳 None
# 同步時間設定
sync_done = False
connect_wifi() # 連接 Wi-Fi
mqtt_connect() # 連接到 MQTT
sync_time() # 同步時間
# 初始化定期更新時間與顯示的計時器
timer.init(period=1000, mode=Timer.PERIODIC, callback=timer_callback)
button_timer.init(period=50, mode=Timer.PERIODIC, callback=button_check)
# 主循環,檢查同步時間
while True:
now = time.localtime()
if now[3] == 4 and now[4] == 0 and not sync_done:
sync_time() # 每天凌晨 4:00 同步時間
sync_done = True
if now[3] == 0 and now[4] == 0 and sync_done:
sync_done = False # 每天凌晨 0:00 重置同步標誌
time.sleep(0.1) # 小休眠
手動格式化時間字串:新增 format_time_to_string() 函數,用於手動將時間格式化成字串,類似於 strftime。
發佈到 MQTT:使用手動格式化的時間字串,發佈包含時間、IP、溫度和濕度的 JSON 資料到 MQTT。