Files
pig-house-controller/main.py
2025-09-25 20:14:48 +08:00

317 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
猪舍主控系统主程序入口
"""
import machine
import time
import struct
import client_pb
# 初始化RS485串口
# 使用UART2连接到ESP32的GPIO16(RX)和GPIO17(TX)
rs485_uart = machine.UART(2, baudrate=9600, bits=8, parity=None, stop=1, rx=16, tx=17)
rs485_uart.init()
# RS485收发控制引脚
rs485_re_de_pin = machine.Pin(5, machine.Pin.OUT)
rs485_re_de_pin.value(0) # 默认接收模式
# ESP32设备地址应该唯一标识这个ESP32设备
ESP32_ADDRESS = 1
# LoRaWAN模块地址
LORA_MODULE_ADDRESS = 254
def receive_lora_message():
"""
接收来自LoRaWAN模块的消息
返回: 字节数据
"""
# 在共享的RS485总线上监听来自LoRaWAN模块的消息
# 需要检查消息是否是发给本设备的
buffer = bytearray()
# 持续监听,不设置超时
while rs485_uart.any():
data = rs485_uart.read()
if data:
buffer.extend(data)
# 简单的帧检测逻辑
if len(buffer) >= 3 and buffer[0] == 0xAA and buffer[-1] == 0x55:
# 检查地址是否匹配
if len(buffer) >= 3 and buffer[2] == ESP32_ADDRESS:
# 提取有效数据(去掉帧头、地址、校验和帧尾)
return buffer[3:-2]
else:
break # 没有更多数据可读
return None
def send_lora_message(data):
"""
通过LoRaWAN模块发送消息
参数:
data: 要发送的字节数据
"""
# 切换到发送模式
rs485_re_de_pin.value(1)
time.sleep_ms(10)
try:
# 构造发送给LoRaWAN模块的RS485帧
frame = bytearray()
frame.append(0xAA) # 帧头
frame.append(LORA_MODULE_ADDRESS & 0xFF) # LoRaWAN模块地址
frame.append(ESP32_ADDRESS & 0xFF) # 本设备地址(作为源地址)
# 添加数据
frame.extend(data)
# 计算校验和
checksum = sum(frame[1:]) & 0xFF
frame.append(checksum)
frame.append(0x55) # 帧尾
# 发送命令
rs485_uart.write(frame)
print(f"通过LoRa发送数据: {frame.hex()}")
finally:
# 切换回接收模式
time.sleep_ms(10)
rs485_re_de_pin.value(0)
def send_rs485_command(bus_number, bus_address, command, channel=None):
"""
发送命令到RS485总线上的设备
参数:
bus_number: 总线号
bus_address: 设备地址
command: 命令内容
channel: 通道号(可选)
"""
# 切换到发送模式
rs485_re_de_pin.value(1)
time.sleep_ms(10)
try:
# 构造RS485命令帧
frame = bytearray()
frame.append(0xAA) # 帧头
frame.append(bus_number & 0xFF) # 总线号
frame.append(bus_address & 0xFF) # 设备地址
# 添加命令数据
if isinstance(command, str):
frame.extend(command.encode('utf-8'))
elif isinstance(command, bytes):
frame.extend(command)
elif isinstance(command, int):
frame.append(command & 0xFF)
# 如果有通道号,则添加
if channel is not None:
frame.append(channel & 0xFF)
# 计算校验和
checksum = sum(frame[1:]) & 0xFF
frame.append(checksum)
frame.append(0x55) # 帧尾
# 发送命令
rs485_uart.write(frame)
print(f"已发送RS485命令: {frame.hex()}")
finally:
# 切换回接收模式
time.sleep_ms(10)
rs485_re_de_pin.value(0)
def collect_sensor_data(bus_number, bus_address):
"""
从传感器收集数据
参数:
bus_number: 总线号
bus_address: 传感器地址
返回:
传感器数据
"""
# 切换到发送模式
rs485_re_de_pin.value(1)
time.sleep_ms(10)
try:
# 构造读取传感器数据的命令
frame = bytearray([0xAA, bus_number & 0xFF, bus_address & 0xFF, 0x01, 0x00, 0x55])
rs485_uart.write(frame)
print(f"已发送传感器读取命令: {frame.hex()}")
# 等待响应
time.sleep_ms(50) # 短暂等待响应
# 读取传感器返回的数据
buffer = bytearray()
while rs485_uart.any():
data = rs485_uart.read()
if data:
buffer.extend(data)
# 简单的帧检测逻辑
if len(buffer) >= 3 and buffer[0] == 0xAA and buffer[-1] == 0x55:
# 检查地址是否匹配
if len(buffer) >= 3 and buffer[2] == ESP32_ADDRESS:
# 提取有效数据
if len(buffer) >= 5:
# 模拟一个浮点数值
value = float(buffer[3] + (buffer[4] << 8)) / 100.0
return value
else:
break
if len(buffer) > 0:
print(f"传感器响应不完整: {buffer.hex()}")
else:
print("传感器无响应")
return None
finally:
# 切换回接收模式
time.sleep_ms(10)
rs485_re_de_pin.value(0)
def parse_instruction(data):
"""
解析来自LoRaWAN的指令
参数:
data: protobuf编码的指令数据
返回:
解析后的指令对象失败时返回None
"""
try:
instruction = client_pb.decode_instruction(data)
return instruction
except Exception as e:
print(f"解析指令失败: {e}")
return None
def handle_switch_instruction(switch_msg):
"""
处理开关指令
参数:
switch_msg: Switch消息字典
"""
action = switch_msg.get('device_action', '')
bus_number = switch_msg.get('bus_number', 0)
bus_address = switch_msg.get('bus_address', 0)
channel = switch_msg.get('relay_channel', 0)
print(f"处理开关指令: 动作={action}, 总线={bus_number}, 地址={bus_address}, 通道={channel}")
if action.upper() == "ON":
# 发送开启设备命令
send_rs485_command(bus_number, bus_address, 0x01, channel)
elif action.upper() == "OFF":
# 发送关闭设备命令
send_rs485_command(bus_number, bus_address, 0x00, channel)
else:
# 其他自定义命令
send_rs485_command(bus_number, bus_address, action, channel)
def handle_collect_instruction(collect_msg):
"""
处理采集指令
参数:
collect_msg: Collect消息字典
"""
bus_number = collect_msg.get('bus_number', 0)
bus_address = collect_msg.get('bus_address', 0)
print(f"处理采集指令: 总线={bus_number}, 地址={bus_address}")
# 从传感器采集数据
value = collect_sensor_data(bus_number, bus_address)
if value is not None:
# 构造Collect响应消息
collect_data = client_pb.encode_collect(bus_number, bus_address, value)
# 构造Instruction消息包装Collect数据
instruction_data = client_pb.encode_instruction(client_pb.METHOD_TYPE_COLLECT, collect_data)
# 发送回上位机
send_lora_message(instruction_data)
else:
print("采集数据失败")
def process_instruction(instruction):
"""
处理解析后的指令
参数:
instruction: 解析后的指令字典
"""
method = instruction.get('method', -1)
if method == client_pb.METHOD_TYPE_SWITCH:
# 处理开关指令
if 'data' in instruction:
switch_msg = client_pb.decode_switch(instruction['data'])
handle_switch_instruction(switch_msg)
else:
print("开关指令缺少data字段")
elif method == client_pb.METHOD_TYPE_COLLECT:
# 处理采集指令
if 'data' in instruction:
collect_msg = client_pb.decode_collect(instruction['data'])
handle_collect_instruction(collect_msg)
else:
print("采集指令缺少data字段")
else:
print(f"不支持的指令类型: {method}")
def main_loop():
"""
主循环
"""
print("猪舍控制系统启动...")
print(f"设备地址: {ESP32_ADDRESS}")
while True:
# 接收LoRaWAN消息
lora_data = receive_lora_message()
if lora_data:
print(f"收到LoRaWAN消息: {lora_data.hex()}")
# 解析指令
instruction = parse_instruction(lora_data)
if instruction:
# 处理指令
process_instruction(instruction)
else:
print("无效的指令数据")
# 其他周期性任务可以放在这里
# 例如定时采集传感器数据等
time.sleep(0.01) # 短暂休眠避免过度占用CPU
# 程序入口
if __name__ == "__main__":
try:
main_loop()
except KeyboardInterrupt:
print("程序被中断")
except Exception as e:
print(f"程序异常: {e}")