基于 Raspberry Pi Pico 2 W + MicroPython 的温湿度监控和格力空调红外控制项目。
支持温湿度采集、OLED 显示、定时点检上传、NG 邮件报警、服务器远程控制、终端智能温控,以及服务端自动控制断网后的本地接管。
| 项目 | 要求 |
|---|---|
| 开发板 | Raspberry Pi Pico 2 W |
| 运行环境 | MicroPython |
| 温湿度传感器 | SHT45 |
| OLED | SSD1309,128×64,I2C |
| 空调协议 | 当前支持的格力双帧红外协议 |
| 红外载波 | 38kHz |
| 系统 RTC | UTC |
| 业务时区 | 默认 UTC+8 |
本项目不是标准 CPython 应用。
代码使用 MicroPython 特有或常见接口:
machinenetworkframebuftime.ticks_ms()time.ticks_us()time.ticks_diff()time.ticks_add()ujsonusocketurandomurequests.py 是项目自带实现,不应随意替换成其他同名库。
SHT45 与 SSD1309 共用一个 I2C0 对象。
| 信号 | Pico GPIO |
|---|---|
| SDA | GP8 |
| SCL | GP9 |
| 设备 | 默认地址 |
|---|---|
| SHT45 | 0x44 |
| SSD1309 | 0x3C |
默认配置:
I2C_ID = 0
I2C_SDA_PIN = 8
I2C_SCL_PIN = 9
I2C_FREQ = 100000
| 项目 | 配置 |
|---|---|
| 发射引脚 | GP7 |
| 载波频率 | 38kHz |
| 完整命令重复次数 | 3 次 |
| 完整命令间隔 | 200ms |
红外接收目前未使用。
| 用途 | 默认引脚 |
|---|---|
| 上传指示 | GP1 |
| 错误指示 | GP2 |
| 成功指示 | GP3 |
不希望初始化某个 LED 引脚时,将对应配置设置为 None。
项目使用两个独立维度。
ENABLE_AC_CONTROL = True
设置为 False 时:
"0"。总开关属于本地设备配置。关闭后不会获取任务,因此不能通过同一个任务 API 远程重新打开。
AUTO_CONTROL_DEFAULT_FLAG = "1"
必须使用字符串:
| 值 | 含义 |
|---|---|
"0" |
关闭自动控制,保留服务器 control 指令执行 |
"1" |
终端智能控制 |
"2" |
服务端自动控制,断网时临时由终端智能控制接管 |
每次重启采用本地配置的默认模式,不恢复上一次运行时修改的模式,也不使用服务器字段覆盖启动默认值。
以下以控制总开关开启为前提:
| 功能 | 模式 "0" |
模式 "1" |
模式 "2" 在线 |
模式 "2" 离线 |
|---|---|---|---|---|
| 温湿度采集和显示 | 是 | 是 | 是 | 是 |
| 点检上传和邮件 API | 在线时 | 在线时 | 是 | 否 |
获取并执行 control |
在线时 | 在线时 | 是 | 否 |
| 本地智能温控 | 否 | 是 | 否 | 是 |
独立 CheckTemperature |
否 | 否 | 是 | 否 |
| 服务器控制成功后创建本地暂停 | 否 | 是 | 否 | 不适用 |
设备不区分 control 是人工创建还是服务器规则创建。只要任务 API 返回合法任务,就按相同规则执行。
当前以:
wifi.is_connected()
判断 Wi-Fi 是否连接。
Wi-Fi 已连接但某个 HTTP API 失败,不直接等同于断网。原有任务 API 连续失败请求 Wi-Fi 重连的机制仍然有效。
选择模式 "2" 且 Wi-Fi 断开时:
AutoControlFlag 为 "2"。接管不意味着一定立即发送红外。温度处于安全范围、算法判断无需调整时,不发送命令。
"2"。模式 "1" 普通断网时,原本的人工暂停仍然有效,不会因为断网而清除。
项目采用扁平目录,共 25 个文件:
pico_ac_monitor/
├─ boot.py
├─ main.py
├─ config.py
│
├─ runtime_state.py
├─ time_service.py
│
├─ sensor_service.py
├─ display_service.py
├─ measurement_service.py
├─ server_temperature_service.py
├─ equipment_service.py
│
├─ auto_ac_controller.py
├─ auto_ac_service.py
├─ remote_command_service.py
│
├─ wifi_manager.py
├─ urequests.py
│
├─ api_common.py
├─ api_client.py
├─ equip_info_api.py
├─ iot_webapi.py
├─ alert_api.py
│
├─ ir_commands.py
├─ ir_snd.py
├─ gree_protocol.py
│
├─ sht45.py
└─ ssd1309.py
所有文件上传到 Pico 文件系统根目录。
| 文件 | 职责 |
|---|---|
boot.py |
启动时执行 GC 和打印标题,不进行业务初始化 |
main.py |
配置校验、硬件初始化、服务装配、连接状态通知、主循环调度和安全退出 |
config.py |
设备身份、网络、默认标准、控制策略、上传周期和硬件参数 |
main.py 内包含两个轻量辅助类:
StatusLEDs:LED 初始化及状态回调。NetworkDispatcher:轮转调度网络服务,每轮最多消耗一次网络操作预算。| 文件 | 职责 |
|---|---|
runtime_state.py |
当前标准、标准来源、测量快照、有效性、样本序号、eid、选择模式及模式同步版本 |
time_service.py |
UTC 转业务时间、时间有效性、日期格式化和固定时点字符串处理 |
配置默认值不作为运行时变量修改。
持续时间使用 ticks 计时,日期和业务时间使用 RTC,避免 NTP 校时干扰人工暂停和观察期。
| 文件 | 职责 |
|---|---|
sensor_service.py |
调用 SHT45、更新测量状态、错误指示和采样序号 |
display_service.py |
OLED 大字体、状态轮播、临时提示、时段开关和故障恢复 |
equipment_service.py |
获取并应用标准、取得温度记录 eid、同步当前自动控制模式 |
measurement_service.py |
固定时点/间隔点检、测量快照、温湿度上传、整体 NG 判断和邮件触发 |
server_temperature_service.py |
独立固定间隔提交最新温度,与点检和邮件完全分离 |
| 文件 | 职责 |
|---|---|
auto_ac_controller.py |
温控状态机、趋势预测、温度历史、观察期、模式切换和人工暂停 |
auto_ac_service.py |
三态模式管理、模式2离线接管、样本门控、终端自动执行记录队列 |
remote_command_service.py |
获取任务、处理两种任务类型、结果待回写及有限去重 |
ir_commands.py |
命令解析、校验、标准化、中文摘要、协议编码和发送适配 |
ir_snd.py |
显式创建 PWM、发送完整 raw timing、关闭载波和释放硬件 |
gree_protocol.py |
格力双帧状态编码、校验和和 raw timing 生成 |
| 文件 | 职责 |
|---|---|
wifi_manager.py |
静态 IP、分步连接、快速/低频重连、连接周期编号、NTP 和失败退避 |
urequests.py |
底层 HTTP 请求、响应接收、完整性验证、超时和大小限制 |
api_common.py |
请求头、JSON 请求、标准业务响应检查、URL 编码和 UUID v4 |
api_client.py |
远程任务 GET、结果 PUT、终端自动执行记录构建和 POST |
equip_info_api.py |
设备信息解析、标准验证、eid 获取和模式部分 PUT |
iot_webapi.py |
温度/湿度上传及三字段 CheckTemperature |
alert_api.py |
报警 HTML 构建和邮件接口调用 |
| 文件 | 职责 |
|---|---|
sht45.py |
SHT45 测量、CRC 校验和数值换算 |
ssd1309.py |
OLED 显存、绘图代理、I2C 分块写入及电源控制 |
gree_protocol.py、sht45.py、ssd1309.py 优先保持已经验证的算法和底层实现。
以下列出项目文件之间的直接导入,不包含全部 MicroPython 标准模块。
| 文件 | 直接项目依赖 |
|---|---|
boot.py |
无 |
config.py |
无 |
runtime_state.py |
无;配置通过构造参数传入 |
time_service.py |
无 |
sensor_service.py |
sht45.py |
display_service.py |
ssd1309.py、time_service.py |
measurement_service.py |
time_service.py、iot_webapi.py、alert_api.py |
server_temperature_service.py |
iot_webapi.py |
equipment_service.py |
equip_info_api.py |
auto_ac_controller.py |
runtime_state.py 中的数值验证函数 |
auto_ac_service.py |
auto_ac_controller.py、api_client.py、runtime_state.py |
remote_command_service.py |
api_client.py、ir_commands.py、runtime_state.py |
wifi_manager.py |
无;配置通过构造参数传入 |
urequests.py |
config.py |
api_common.py |
config.py;请求时导入 urequests.py |
api_client.py |
config.py、api_common.py |
equip_info_api.py |
config.py、api_common.py、runtime_state.py |
iot_webapi.py |
config.py、api_common.py、runtime_state.py |
alert_api.py |
config.py、api_common.py |
ir_commands.py |
gree_protocol.py;正式发送时导入 ir_snd.py |
ir_snd.py |
无;配置通过 init(config) 传入 |
gree_protocol.py |
无 |
sht45.py |
无 |
ssd1309.py |
无 |
main.py 直接导入:
config
runtime_state
time_service
sensor_service
display_service
equipment_service
measurement_service
server_temperature_service
wifi_manager
仅在控制总开关开启时导入:
ir_snd
ir_commands
auto_ac_service
remote_command_service
main.py
│
├─ 配置、运行状态、业务时间
│
├─ 采集/显示/点检/设备/温度检查服务
│ ├─ 传感器与 OLED 驱动
│ └─ 对应 API 文件
│ └─ api_common
│ └─ urequests
│ └─ socket
│
└─ 条件加载空调控制服务
├─ auto_ac_controller
├─ api_client
└─ ir_commands
├─ gree_protocol
└─ ir_snd
└─ machine.PWM
部分依赖通过参数传递,而不是互相导入。
WiFiManager.is_time_synced
→ TimeService.bind_validity
AutoACService.update_temperature_limits
→ EquipmentService 温度范围回调
DisplayService.set_notice
→ 传感器、点检、自动控制、远程任务提示回调
execute_ir_command
→ AutoACService / RemoteCommandService 执行回调
TimeService.datetime_text
→ AutoACController 业务时间回调
AutoACService._on_execution
→ AutoACController 执行报告回调
控制器回调只创建并入队执行记录,不在温控决策里执行 HTTP。
RuntimeState 保存的共享事实| 状态 | 所属模块 |
|---|---|
| OLED 电源、页面、提示有效期 | display_service.py |
| 点检阶段、固定时点去重、上次整体 NG | measurement_service.py |
| 独立规则检查间隔 | server_temperature_service.py |
| 设备信息获取与模式同步重试 | equipment_service.py |
| 温度历史、推定空调状态、观察期、人工暂停 | auto_ac_controller.py |
| 临时离线接管状态、执行记录队列 | auto_ac_service.py |
| 待回写任务和去重缓存 | remote_command_service.py |
| Wi-Fi 连接阶段、NTP 状态 | wifi_manager.py |
模式2的“临时本地接管”属于执行状态,不改变 RuntimeState.auto_control_flag。
配置校验
↓
创建 RuntimeState、TimeService 和 LED
↓
创建共享 I2C
↓
初始化 OLED 和 SHT45
↓
首次采集和显示
↓
创建 WiFiManager
↓
按总开关初始化红外及控制服务
↓
创建设备、点检和独立温度检查服务
↓
注册网络调度任务
↓
进入主循环
首次显示不等待 Wi-Fi、NTP 或设备信息 API 完成。
到期采样
↓
显示维护
↓
推进 Wi-Fi 状态
↓
通知连接变化和离线接管状态
↓
在线:轮转执行至多一个网络服务
离线:结束未完成点检,不调用 API
↓
网络返回后重新检查是否需要采样
↓
再次同步网络/模式状态
↓
本地智能控制
↓
显示更新
↓
周期 GC
若本轮已获取并执行服务器控制任务,本地控制前已更新人工暂停与推定状态。
对于尚未 GET 到的服务器任务,设备无法提前感知。
配置:
CHECK_MODE = "fixed_time"
或:
CHECK_MODE = "interval"
UPLOAD_INTERVAL_MS = 10 * 60 * 1000
固定时点配置:
CHECK_TIMES = tuple(
"%02d:%02d" % (hour, minute)
for hour in range(24)
for minute in range(0, 60, 10)
)
点检顺序:
固定测量与标准快照
↓
POST 温度
↓
POST 湿度
↓
整体 NG 判断与必要的邮件
特点:
独立配置:
SERVER_TEMP_CHECK_INTERVAL_MS = 60 * 1000
仅在以下条件满足时执行:
"2"。请求不依赖温湿度点检,也不要求先上传 TemperatureApi。
进入在线模式2或网络恢复后允许先检查一次,之后按固定间隔检查。间隔从请求结束计算,实际频率还受网络调度和请求耗时影响。
不补传历史检查,不在失败后立即重复请求。
配置:
ENABLE_MEASUREMENT_ALERT = True
ALERT_MODE = "on_ng_transition"
支持:
| 模式 | 行为 |
|---|---|
every_ng |
每次点检整体 NG 都尝试发送 |
on_ng_transition |
整体状态从 OK 变成 NG 时尝试发送,持续 NG 不重复 |
整体 NG:
温度 NG 或湿度 NG
注意:
CheckTemperature 不触发邮件判断。| 参数 | 默认值 |
|---|---|
| 控制判断周期 | 1 分钟 |
| 普通调整观察期 | 5 分钟 |
| 模式切换等待 | 5 分钟 |
| 趋势历史窗口 | 10 分钟 |
| 预测时间 | 未来 10 分钟 |
| 普通调整步长 | 1℃ |
| 预测越界调整步长 | 2℃ |
| 服务器控制后的本地暂停 | 30 分钟 |
| 温度历史容量 | 128 个点 |
历史按新采样更新,不按主循环频率重复保存同一个读数。
环境标准为 22~28℃ 时:
| 模式 | 自动设定范围 |
|---|---|
| 制冷 | 16~28℃ |
| 制热 | 22~30℃ |
小数边界:
服务器 control 命令仍按协议 16~30℃ 校验,不受环境标准限制。
红外是单向通信,状态只能是推定状态:
unknown
off
cooling
heating
switch_wait
unknown 表示设备不知道真实状态,不等于确认空调关机。
独立遥控器操作、空调断电或红外未接收,都可能使推定状态与现场不一致。
所有路由都相对于:
API_BASE_URL
Pico 使用 IoT 网络可访问的代理地址。不要直接替换为仅办公网可访问的真实服务器地址。
| 方法 | 路由 | 用途 |
|---|---|---|
| GET | /api/RemoteCommandApi/GetSensorOneTask?raspip=... |
获取一个控制任务 |
| PUT | /api/RemoteCommandApi/UpdateRemoteCommand/{remoteid} |
回写执行结果 |
| POST | /api/IotRemoteCommandApi |
创建终端自动执行记录 |
| GET | /api/EquipInfoApi/GetEquipInfoByIP?IPADDR=... |
获取标准和 eid |
| PUT | /api/EquipInfoApi/{eid} |
部分更新当前模式 |
| POST | /api/TemperatureApi |
上传温度或湿度 |
| POST | /api/RemoteCommandApi/CheckTemperature |
服务端规则检查 |
| POST | /api/MessageSendApi/SendEmail |
NG 邮件报警 |
{
"success": true,
"code": "200",
"msg": "操作成功",
"data": {}
}
普通写入接口要求:
success 为真正的 JSON true。响应第一层字段兼容大小写。
获取任务保留已有接口约定:HTTP 成功且 success=false 视为无任务。后端若改变该语义,需同步修改 get_one_task()。
从 itemId="1" 的温度记录取得 eid:
PUT /api/EquipInfoApi/{eid}
{
"Eid": "设备Eid",
"AutoControlFlag": "2",
"MUser": "IoTSystemUser"
}
只提交需要更新的字段,不提交完整设备对象。
设备信息中的 eid 不等于温湿度上传的 SENSOR_EQUIP_ID1。
POST /api/RemoteCommandApi/CheckTemperature
严格只提交三个字段:
{
"IpAddr": "172.28.100.23",
"ItemId": "1",
"ItemValue": 26.4
}
ItemValue 为 JSON 数字。
检查成功不代表服务器一定生成控制任务。设备仍通过原任务 GET 接口取得并执行后续指令。
温度、湿度都使用:
POST /api/TemperatureApi
区别:
ItemId="1"、ItemType="TEMPERATURE"。ItemId="2"、ItemType="HUMIDITY"。该接口的 ItemValue 和标准边界保留字符串数值格式,例如 "26.4",与 CheckTemperature 的数值类型不同。
{
"remoteid": "任务GUID",
"ipaddr": "172.28.100.23",
"commandtype": "control",
"commandparams": "{\"brand\":\"gree\",\"power\":true,\"mode\":\"cool\",\"temperature\":26,\"fan\":\"auto\",\"swing_vertical\":true,\"swing_horizontal\":true}"
}
解码后的参数:
{
"brand": "gree",
"power": true,
"mode": "cool",
"temperature": 26,
"fan": "auto",
"swing_vertical": true,
"swing_horizontal": true
}
参数要求:
| 参数 | 支持值 |
|---|---|
brand |
gree |
power |
JSON 布尔值 |
mode |
cool、heat |
temperature |
转换后为 16~30 的整数 |
fan |
auto、1、2、3 |
swing_vertical |
JSON 布尔值 |
swing_horizontal |
JSON 布尔值 |
当前实测协议限制:开启任一扫风时,fan 必须为 auto。
温度保留原接口的 int() 转换兼容性:
"26" 转为 26。26.9 转为 26。设备会校验目标 IP。空目标 IP 保留为“不限制目标”的已有语义。
关闭自动控制:
{
"commandtype": "autocontrolmode",
"commandparams": "{\"AutoControlFlag\":\"0\"}"
}
终端智能控制:
{
"commandtype": "autocontrolmode",
"commandparams": "{\"AutoControlFlag\":\"1\"}"
}
服务端自动控制:
{
"commandtype": "autocontrolmode",
"commandparams": "{\"AutoControlFlag\":\"2\"}"
}
也支持 commandparams 直接为对象:
{
"commandtype": "autocontrolmode",
"commandparams": {
"AutoControlFlag": "2"
}
}
严格只接受字符串 "0"、"1"、"2":
0/1/2:拒绝。模式切换不直接发送空调关机,不视为人工空调指令,不创建人工暂停。
统一自动操作用户:
IOT_MUSER = "IoTSystemUser"
设备主动创建的空调执行记录:
DEVICE_USER_CODE = "RASPICO-100023"
对应:
{
"UserCode": "RASPICO-100023",
"MUser": "IoTSystemUser"
}
规则:
IoTSystemUser。IoTSystemUser。IoTSystemUser。UserCode。ADMIN 等用户字段推测任务来源。CheckTemperature 和邮件请求保持已确认 DTO,不擅自追加字段。若后端内部创建任务或审计记录,应由后端设置自动用户。
温湿度 DTO 未确认支持 UserCode,当前不擅自提交该字段。
第一协议帧
↓
约 40ms 间隔
↓
第二协议帧
一条完整 raw timing 固定为 279 项。
不能把同一协议帧重复两次代替格力所需的两个不同帧。
完整双帧命令
↓ 200ms
完整双帧命令
↓ 200ms
完整双帧命令
重复由 ir_snd.py 实现,协议层不额外重复。
命令为完整状态指令,不是 toggle;重复不会反转开关状态。
import ir_snd
ir_snd.init(config)
ir_snd.send_raw(raw_code)
ir_snd.deinit()
导入 ir_snd.py 不创建 PWM。
deinit() 关闭载波并释放 PWM,不向空调发送关机命令。
connection_revision。service_ntp_sync() 返回是否尝试网络,不是是否校时成功。
支持:
Content-Length。明确不做:
截断、超时和非法响应不会再通过字符串兜底判定成功。
当前 HTTPS 包装未配置证书验证,不应把它当作已完成可信服务器认证。
默认配置:
REMOTE_RESULT_QUEUE_LIMIT = 8
AUTO_RECORD_QUEUE_LIMIT = 16
REMOTE_TASK_CACHE_LIMIT = 32
部署新设备优先检查:
DEVICE_NAME
PICO_IP
DEVICE_USER_CODE
SENSOR_EQUIP_ID1
ENABLE_AC_CONTROL
AUTO_CONTROL_DEFAULT_FLAG
CHECKGROUPCODE
TEMP_STANDARD_MIN
TEMP_STANDARD_MAX
HUMI_STANDARD_MIN
HUMI_STANDARD_MAX
WIFI_SSID
WIFI_PASSWORD
WIFI_NETMASK
WIFI_GATEWAY
WIFI_DNS
NTP_HOST
API_BASE_URL
业务周期:
SAMPLE_INTERVAL_MS
CHECK_MODE
CHECK_TIMES
UPLOAD_INTERVAL_MS
SERVER_TEMP_CHECK_INTERVAL_MS
API_POLL_INTERVAL_MS
默认 OLED 时段:
OLED_ON_PERIODS = (
("07:50", "11:30"),
("13:00", "17:00"),
)
默认业务时区:
TIMEZONE_OFFSET_HOURS = 8
不要将包含真实 Wi-Fi 密码、API Token 的配置提交到公开仓库。共享源码前应脱敏。
main.py。不要在旧程序仍运行时混用新旧模块。
保留完整备份目录,例如:
backups/
└─ verified_previous_version/
回退时恢复整个兼容版本,不只恢复 main.py。
CheckTemperature。control。CheckTemperature。"2"。先检查:
接管只是把决策交给本地算法,不代表无条件发送。
检查:
API_BASE_URL 是否为 IoT 网络可访问地址。success 是否为真正的布尔 true。检查:
可能原因:
on_ng_transition 下持续 NG。正常。NTP 日志显示 UTC,业务显示默认使用 UTC+8。
completed 表示设备完成了红外发送流程,不代表空调确认接收。
检查发射方向、距离、供电、红外模块和空调机型兼容性。
main.py。config.py 常量当作动态状态修改。CheckTemperature 重新绑定到温湿度点检。本 README 对应已加入以下变更的版本:
AutoControlFlag 始终保留 "2"。CheckTemperature 已从点检流程拆出。SERVER_TEMP_CHECK_INTERVAL_MS 配置,默认 60 秒。上述两项新增行为已完成用户现场测试。其他故障路径、长时间稳定性及不同设备部署仍应按测试清单验证。