import machine
import time
import dht
import onewire
import ds18x20
import random
import network
import ujson
from umqtt.simple import MQTTClient
from machine import Pin, ADC
# ================= 配置区域 =================
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASS = ""
BROKER = "broker.hivemq.com"
CLIENT_ID = "qin08_esp32_client" # 确保你的电脑端 Client ID 与这里不同
TOPIC_PREFIX = "assessment/qin08/qin225"
TOPIC_TELEMETRY = f"{TOPIC_PREFIX}/telemetry"
TOPIC_STATUS = f"{TOPIC_PREFIX}/status"
TOPIC_COMMAND = f"{TOPIC_PREFIX}/command"
TOPIC_STATE = f"{TOPIC_PREFIX}/state"
DEVICE_ID = "qin08_wokwi_esp32"
# ⚠️ 测试阶段用 10000(10秒),最终提交/验收前请改回 300000(5分钟)
REPORT_INTERVAL = 10000
HEARTBEAT_INTERVAL = 30000
# ============================================
# ================= 硬件初始化 =================
ow = onewire.OneWire(Pin(4))
ds = ds18x20.DS18X20(ow)
devices = ds.scan()
d = dht.DHT22(machine.Pin(15))
ldr = ADC(Pin(34))
ldr.atten(ADC.ATTN_11DB)
pot = ADC(Pin(32))
pot.atten(ADC.ATTN_11DB)
# 继电器初始化 (根据实际接线,假设高电平触发)
relay = Pin(5, Pin.OUT)
relay.value(0) # 初始化为关闭状态
state_data = {"soil_temp": 23.0, "soil_moisture": 50.0, "air_temp": 25.0, "air_hum": 60.0, "illuminance": 400.0}
def clamp(val, min_v, max_v): return max(min_v, min(val, max_v))
def smooth_random(prev, step, min_v, max_v, target=None, pull=0.1):
if target is not None:
change = (target - prev) * pull + random.uniform(-step, step)
else:
change = random.uniform(-step, step)
change = clamp(change, -step, step)
return clamp(prev + change, min_v, max_v)
# ================= MQTT 回调 =================
def sub_cb(topic, msg):
if topic.decode() == TOPIC_COMMAND:
try:
data = ujson.loads(msg)
cmd_id = data.get("command_id")
target_state = data.get("pump_on")
# 安全校验:必须是 0 或 1 才执行
if target_state in (0, 1):
relay.value(target_state)
print(f"收到指令 {cmd_id}: 设置水泵为 {target_state}")
# 回传实际执行的状态 (必须是 relay.value())
ack = ujson.dumps({"command_id": cmd_id, "pump_on": relay.value()})
mqtt_client.publish(TOPIC_STATE, ack)
else:
print(f"无效的指令状态: {target_state}")
except Exception as e:
print("指令解析失败:", e)
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
print("正在连接 Wi-Fi...")
while not wlan.isconnected():
time.sleep(0.5)
print("Wi-Fi 连接成功!")
def connect_mqtt():
print("连接 MQTT...")
try:
# 增加遗嘱消息 (Last Will) - 掉线时自动通知电脑端
will_msg = ujson.dumps({"device_id": DEVICE_ID, "status": "offline"})
client = MQTTClient(
CLIENT_ID, BROKER, keepalive=60,
will_topic=TOPIC_STATUS.encode(),
will_msg=will_msg
)
client.set_callback(sub_cb)
client.connect()
client.publish(TOPIC_STATUS, ujson.dumps({"device_id": DEVICE_ID, "status": "online"}))
client.subscribe(TOPIC_COMMAND)
print("MQTT 连接成功并订阅了 command 主题!")
return client
except Exception as e:
print("MQTT 连接失败,错误:", e)
return None
# ================= 主流程 =================
connect_wifi()
mqtt_client = None
while mqtt_client is None:
mqtt_client = connect_mqtt()
if mqtt_client is None:
print("3秒后重试...")
time.sleep(3)
last_report = time.ticks_ms()
last_heartbeat = time.ticks_ms()
while True:
current_ms = time.ticks_ms()
# 1. 检查 MQTT 消息 (非阻塞,接收指令必加)
try:
mqtt_client.check_msg()
except Exception as e:
print("接收指令异常,尝试重连", e)
mqtt_client = None
while mqtt_client is None:
mqtt_client = connect_mqtt()
time.sleep(3)
# 2. 发送心跳和在线状态 (30秒一次)
if time.ticks_diff(current_ms, last_heartbeat) >= HEARTBEAT_INTERVAL:
try:
mqtt_client.publish(TOPIC_STATUS, ujson.dumps({"device_id": DEVICE_ID, "status": "online"}))
last_heartbeat = current_ms
except Exception:
pass # 断线交由 check_msg 捕获
# 3. 采集数据并上报 (定时)
if time.ticks_diff(current_ms, last_report) >= REPORT_INTERVAL:
try:
# 采集空气温湿度
d.measure()
base_air_temp = d.temperature()
base_air_hum = d.humidity()
except OSError:
base_air_temp, base_air_hum = state_data["air_temp"], state_data["air_hum"]
# 采集土壤湿度(电位器)和光照
base_moisture = (pot.read() / 4095.0) * 100
base_illuminance = 50 + (ldr.read() / 4095.0) * 1150
# 采集土壤温度(DS18B20)
base_soil_temp = state_data["soil_temp"]
if devices:
ds.convert_temp()
time.sleep_ms(750) # 等待转换
base_soil_temp = ds.read_temp(devices[0])
# 平滑随机变化
# ⚠️ 注意:这里以 10 秒(测试) 为基础步长,5分钟上报时步长要调小(如除以30)
state_data["soil_temp"] = smooth_random(state_data["soil_temp"], 0.01, 18.0, 30.0, target=base_soil_temp)
state_data["soil_moisture"] = smooth_random(state_data["soil_moisture"], 0.1, 25.0, 80.0, target=base_moisture)
state_data["air_temp"] = smooth_random(state_data["air_temp"], 0.02, 18.0, 35.0, target=base_air_temp)
state_data["air_hum"] = smooth_random(state_data["air_hum"], 0.1, 35.0, 85.0, target=base_air_hum)
state_data["illuminance"] = smooth_random(state_data["illuminance"], 5, 50, 1200, target=base_illuminance)
# 构造上报数据
payload = {
"device_id": DEVICE_ID, "source": "wokwi_simulated",
"soil_temperature_c": round(state_data["soil_temp"], 2),
"soil_moisture_pct": round(state_data["soil_moisture"], 2),
"air_temperature_c": round(state_data["air_temp"], 2),
"air_humidity_pct": round(state_data["air_hum"], 2),
"illuminance_lx": round(state_data["illuminance"], 1),
"pump_on": relay.value()
}
try:
mqtt_client.publish(TOPIC_TELEMETRY, ujson.dumps(payload))
print("已发送数据:", payload)
last_report = current_ms
except Exception:
print("发送遥测数据失败...")
# 必须要有小休眠,防止死循环卡死 CPU
time.sleep(0.1)Loading
esp32-devkit-c-v4
esp32-devkit-c-v4