Compare commits
	
		
			2 Commits
		
	
	
		
			b80a04bfc1
			...
			c117759ac3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c117759ac3 | |||
| 5519d43253 | 
							
								
								
									
										173
									
								
								DEVELOPMENT.md
									
									
									
									
									
								
							
							
						
						
									
										173
									
								
								DEVELOPMENT.md
									
									
									
									
									
								
							| @@ -1,173 +0,0 @@ | |||||||
| # 开发环境搭建指南 |  | ||||||
|  |  | ||||||
| ## 系统要求 |  | ||||||
|  |  | ||||||
| - Python 3.7+ |  | ||||||
| - Raspberry Pi 或其他兼容的嵌入式Linux系统 |  | ||||||
| - LoRa通信模块(如SX1278) |  | ||||||
| - RS485通信接口 |  | ||||||
|  |  | ||||||
| ## 协议栈架构 |  | ||||||
|  |  | ||||||
| 本系统采用标准物联网协议栈: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| 应用层: LwM2M |  | ||||||
| 传输层: CoAP (Constrained Application Protocol) |  | ||||||
| 网络层: LoRaWAN |  | ||||||
| 数据链路层: LoRa |  | ||||||
| 物理层: LoRa |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## 安装步骤 |  | ||||||
|  |  | ||||||
| ### 1. 克隆项目 |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| git clone <项目地址> |  | ||||||
| cd pig-house-controller |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 2. 创建虚拟环境 |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| python3 -m venv venv |  | ||||||
| source venv/bin/activate  # Linux/Mac |  | ||||||
| # 或 |  | ||||||
| venv\Scripts\activate     # Windows |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 3. 安装依赖 |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| pip install -r requirements.txt |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 4. LwM2M支持的特殊处理 |  | ||||||
|  |  | ||||||
| 由于 `lwm2m-client` 包在 PyPI 上不可用,我们需要手动安装或使用替代方案: |  | ||||||
|  |  | ||||||
| #### 方案一:使用 wakaama-python (推荐) |  | ||||||
| ```bash |  | ||||||
| # 克隆 wakaama-python 仓库 |  | ||||||
| git clone https://github.com/djarek/wakaama-python.git |  | ||||||
| cd wakaama-python |  | ||||||
| # 按照项目说明进行安装 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| #### 方案二:使用 Eclipse Wakaama |  | ||||||
| Eclipse Wakaama 是一个成熟的 LwM2M 实现,可以作为 C 扩展集成到 Python 项目中。 |  | ||||||
|  |  | ||||||
| #### 方案三:自行实现 LwM2M 客户端 |  | ||||||
| 根据 LwM2M 规范自行实现必要的功能。 |  | ||||||
|  |  | ||||||
| ### 5. 配置系统 |  | ||||||
|  |  | ||||||
| 复制示例配置文件并根据实际环境进行修改: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| cp config.json.example config.json |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| 修改 [config.json](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/config.json) 文件中的参数,包括: |  | ||||||
| - LoRa通信参数 |  | ||||||
| - 总线配置 |  | ||||||
| - 设备配置 |  | ||||||
| - 日志配置 |  | ||||||
|  |  | ||||||
| ### 6. 硬件连接 |  | ||||||
|  |  | ||||||
| 1. 连接LoRa模块到树莓派的SPI接口 |  | ||||||
| 2. 连接传感器总线RS485模块到树莓派的UART接口 |  | ||||||
| 3. 连接执行器总线RS485模块到树莓派的另一个UART接口 |  | ||||||
|  |  | ||||||
| ### 7. 运行系统 |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| python main.py |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## 开发工具推荐 |  | ||||||
|  |  | ||||||
| - **IDE**: PyCharm 或 VS Code |  | ||||||
| - **版本控制**: Git |  | ||||||
| - **调试工具**: Python内置pdb或IDE调试器 |  | ||||||
| - **日志查看**: tail命令或专业日志查看工具 |  | ||||||
|  |  | ||||||
| ## 测试环境 |  | ||||||
|  |  | ||||||
| 建议在实际部署前进行充分测试: |  | ||||||
|  |  | ||||||
| 1. 单元测试: 验证各模块功能 |  | ||||||
| 2. 集成测试: 验证模块间协作 |  | ||||||
| 3. 系统测试: 验证完整功能流程 |  | ||||||
| 4. 硬件测试: 验证实际硬件连接和通信 |  | ||||||
|  |  | ||||||
| ## 协议栈实现说明 |  | ||||||
|  |  | ||||||
| ### LoRaWAN层 |  | ||||||
| 使用SX1278等LoRa芯片,通过SPI接口与树莓派通信。实现基本的LoRaWAN功能: |  | ||||||
| - OTAA/ABP入网 |  | ||||||
| - 数据加解密 |  | ||||||
| - 数据包重传机制 |  | ||||||
|  |  | ||||||
| ### CoAP层 |  | ||||||
| 实现CoAP协议栈,支持: |  | ||||||
| - GET/POST/PUT/DELETE方法 |  | ||||||
| - 资源发现 |  | ||||||
| - 块传输 |  | ||||||
| - 观察者模式 |  | ||||||
|  |  | ||||||
| ### LwM2M层 |  | ||||||
| 实现LwM2M客户端功能: |  | ||||||
| - 设备注册与管理 |  | ||||||
| - 固件更新 |  | ||||||
| - 参数配置 |  | ||||||
| - 数据上报 |  | ||||||
|  |  | ||||||
| 注意:由于 `lwm2m-client` 包不可用,需要使用替代方案实现 LwM2M 功能。 |  | ||||||
|  |  | ||||||
| ### SenML数据格式 |  | ||||||
| 所有传感器数据使用SenML格式进行编码和传输,确保数据标准化和互操作性。 |  | ||||||
|  |  | ||||||
| ## 抽象接口开发说明 |  | ||||||
|  |  | ||||||
| 系统定义了以下抽象接口,开发者在实现具体功能时需要继承这些基类: |  | ||||||
|  |  | ||||||
| ### 通信接口 (BaseComm) |  | ||||||
| 位于 [comms/base_comm.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/comms/base_comm.py),定义了通信模块的基本操作: |  | ||||||
| - `connect()`: 建立通信连接 |  | ||||||
| - `disconnect()`: 断开通信连接 |  | ||||||
| - `send()`: 发送数据 |  | ||||||
| - `receive()`: 接收数据 |  | ||||||
| - `is_connected()`: 检查连接状态 |  | ||||||
|  |  | ||||||
| ### 设备接口 (BaseDevice) |  | ||||||
| 位于 [devices/base_device.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/devices/base_device.py),定义了设备的基本操作: |  | ||||||
| - `connect()`: 连接设备 |  | ||||||
| - `disconnect()`: 断开设备连接 |  | ||||||
| - `read_data()`: 读取设备数据 |  | ||||||
| - `write_data()`: 向设备写入数据 |  | ||||||
| - `get_status()`: 获取设备状态 |  | ||||||
|  |  | ||||||
| ### 存储接口 (BaseStorage) |  | ||||||
| 位于 [storage/base_storage.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/storage/base_storage.py),定义了存储模块的基本操作: |  | ||||||
| - `save()`: 保存数据 |  | ||||||
| - `load()`: 加载数据 |  | ||||||
| - `delete()`: 删除数据 |  | ||||||
| - `exists()`: 检查键是否存在 |  | ||||||
| - `list_keys()`: 列出所有键 |  | ||||||
|  |  | ||||||
| ### 命令处理器接口 (BaseHandler) |  | ||||||
| 位于 [core/base_handler.py](file:///C:/Users/divano/Desktop/work/AA-Pig/pig-house-controller/core/base_handler.py),定义了命令处理的基本操作: |  | ||||||
| - `handle_command()`: 处理命令 |  | ||||||
| - `register_command()`: 注册命令处理函数 |  | ||||||
| - `unregister_command()`: 注销命令处理函数 |  | ||||||
|  |  | ||||||
| ## 注意事项 |  | ||||||
|  |  | ||||||
| 1. 确保硬件连接正确,特别是UART接口不要接反 |  | ||||||
| 2. 根据实际硬件调整配置文件中的串口设备路径 |  | ||||||
| 3. 注意LoRa频段的合法性,遵守当地无线电管理规定 |  | ||||||
| 4. 建议在开发阶段使用DEBUG日志级别,生产环境使用INFO或更高 |  | ||||||
| 5. LwM2M 功能需要特殊处理,因为标准包不可用 |  | ||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | swag: | ||||||
|  | 	python -m grpc_tools.protoc -I./proto --python_out=./proto ./proto/client.proto | ||||||
							
								
								
									
										306
									
								
								client_pb.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								client_pb.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,306 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | 根据client.proto生成的解析代码 | ||||||
|  | 适用于ESP32 MicroPython环境 | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import struct | ||||||
|  |  | ||||||
|  | # MethodType枚举 | ||||||
|  | METHOD_TYPE_SWITCH = 0 | ||||||
|  | METHOD_TYPE_COLLECT = 1 | ||||||
|  |  | ||||||
|  | def encode_varint(value): | ||||||
|  |     """编码varint值""" | ||||||
|  |     buf = bytearray() | ||||||
|  |     while value >= 0x80: | ||||||
|  |         buf.append((value & 0x7F) | 0x80) | ||||||
|  |         value >>= 7 | ||||||
|  |     buf.append(value & 0x7F) | ||||||
|  |     return buf | ||||||
|  |  | ||||||
|  | def decode_varint(buf, pos=0): | ||||||
|  |     """解码varint值""" | ||||||
|  |     result = 0 | ||||||
|  |     shift = 0 | ||||||
|  |     while pos < len(buf): | ||||||
|  |         byte = buf[pos] | ||||||
|  |         pos += 1 | ||||||
|  |         result |= (byte & 0x7F) << shift | ||||||
|  |         if not (byte & 0x80): | ||||||
|  |             break | ||||||
|  |         shift += 7 | ||||||
|  |     return result, pos | ||||||
|  |  | ||||||
|  | def encode_string(value): | ||||||
|  |     """编码字符串""" | ||||||
|  |     value_bytes = value.encode('utf-8') | ||||||
|  |     length = encode_varint(len(value_bytes)) | ||||||
|  |     return length + value_bytes | ||||||
|  |  | ||||||
|  | def decode_string(buf, pos=0): | ||||||
|  |     """解码字符串""" | ||||||
|  |     length, pos = decode_varint(buf, pos) | ||||||
|  |     value = buf[pos:pos+length].decode('utf-8') | ||||||
|  |     pos += length | ||||||
|  |     return value, pos | ||||||
|  |  | ||||||
|  | def encode_instruction(method, data): | ||||||
|  |     """ | ||||||
|  |     编码Instruction消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         method: 方法类型 (int) | ||||||
|  |         data: 数据 (bytes) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         bytearray: 编码后的数据 | ||||||
|  |     """ | ||||||
|  |     result = bytearray() | ||||||
|  |      | ||||||
|  |     # 编码method字段 (field_number=1, wire_type=0) | ||||||
|  |     result.extend(encode_varint((1 << 3) | 0))  # tag | ||||||
|  |     result.extend(encode_varint(method))        # value | ||||||
|  |      | ||||||
|  |     # 编码data字段 (field_number=2, wire_type=2) | ||||||
|  |     result.extend(encode_varint((2 << 3) | 2))  # tag | ||||||
|  |     result.extend(encode_varint(len(data)))     # length | ||||||
|  |     result.extend(data)                         # value | ||||||
|  |      | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def decode_instruction(buf): | ||||||
|  |     """ | ||||||
|  |     解码Instruction消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         buf: 编码后的数据 (bytes) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         dict: 解码后的消息 | ||||||
|  |     """ | ||||||
|  |     result = {} | ||||||
|  |     pos = 0 | ||||||
|  |      | ||||||
|  |     while pos < len(buf): | ||||||
|  |         # 读取标签 | ||||||
|  |         tag, pos = decode_varint(buf, pos) | ||||||
|  |         field_number = tag >> 3 | ||||||
|  |         wire_type = tag & 0x07 | ||||||
|  |          | ||||||
|  |         if field_number == 1:  # method字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['method'] = value | ||||||
|  |         elif field_number == 2:  # data字段 | ||||||
|  |             if wire_type == 2:  # 长度分隔类型 | ||||||
|  |                 length, pos = decode_varint(buf, pos) | ||||||
|  |                 value = buf[pos:pos+length] | ||||||
|  |                 pos += length | ||||||
|  |                 result['data'] = value | ||||||
|  |         else: | ||||||
|  |             # 跳过未知字段 | ||||||
|  |             if wire_type == 0:  # varint | ||||||
|  |                 _, pos = decode_varint(buf, pos) | ||||||
|  |             elif wire_type == 2:  # 长度分隔 | ||||||
|  |                 length, pos = decode_varint(buf, pos) | ||||||
|  |                 pos += length | ||||||
|  |             else: | ||||||
|  |                 pos += 1 | ||||||
|  |                  | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def encode_switch(device_action, bus_number, bus_address, relay_channel): | ||||||
|  |     """ | ||||||
|  |     编码Switch消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         device_action: 设备动作指令 (str) | ||||||
|  |         bus_number: 总线号 (int) | ||||||
|  |         bus_address: 总线地址 (int) | ||||||
|  |         relay_channel: 继电器通道号 (int) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         bytearray: 编码后的数据 | ||||||
|  |     """ | ||||||
|  |     result = bytearray() | ||||||
|  |      | ||||||
|  |     # 编码device_action字段 (field_number=1, wire_type=2) | ||||||
|  |     result.extend(encode_varint((1 << 3) | 2))      # tag | ||||||
|  |     action_bytes = encode_string(device_action)     # value (length + string) | ||||||
|  |     result.extend(action_bytes) | ||||||
|  |      | ||||||
|  |     # 编码bus_number字段 (field_number=2, wire_type=0) | ||||||
|  |     result.extend(encode_varint((2 << 3) | 0))      # tag | ||||||
|  |     result.extend(encode_varint(bus_number))        # value | ||||||
|  |      | ||||||
|  |     # 编码bus_address字段 (field_number=3, wire_type=0) | ||||||
|  |     result.extend(encode_varint((3 << 3) | 0))      # tag | ||||||
|  |     result.extend(encode_varint(bus_address))       # value | ||||||
|  |      | ||||||
|  |     # 编码relay_channel字段 (field_number=4, wire_type=0) | ||||||
|  |     result.extend(encode_varint((4 << 3) | 0))      # tag | ||||||
|  |     result.extend(encode_varint(relay_channel))     # value | ||||||
|  |      | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def decode_switch(buf): | ||||||
|  |     """ | ||||||
|  |     解码Switch消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         buf: 编码后的数据 (bytes) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         dict: 解码后的消息 | ||||||
|  |     """ | ||||||
|  |     result = {} | ||||||
|  |     pos = 0 | ||||||
|  |      | ||||||
|  |     while pos < len(buf): | ||||||
|  |         # 读取标签 | ||||||
|  |         tag, pos = decode_varint(buf, pos) | ||||||
|  |         field_number = tag >> 3 | ||||||
|  |         wire_type = tag & 0x07 | ||||||
|  |          | ||||||
|  |         if field_number == 1:  # device_action字段 | ||||||
|  |             if wire_type == 2:  # 字符串类型 | ||||||
|  |                 value, pos = decode_string(buf, pos) | ||||||
|  |                 result['device_action'] = value | ||||||
|  |         elif field_number == 2:  # bus_number字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['bus_number'] = value | ||||||
|  |         elif field_number == 3:  # bus_address字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['bus_address'] = value | ||||||
|  |         elif field_number == 4:  # relay_channel字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['relay_channel'] = value | ||||||
|  |         else: | ||||||
|  |             # 跳过未知字段 | ||||||
|  |             if wire_type == 0:  # varint | ||||||
|  |                 _, pos = decode_varint(buf, pos) | ||||||
|  |             elif wire_type == 2:  # 长度分隔 | ||||||
|  |                 length, pos = decode_varint(buf, pos) | ||||||
|  |                 pos += length | ||||||
|  |             else: | ||||||
|  |                 pos += 1 | ||||||
|  |                  | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def encode_collect(bus_number, bus_address, value): | ||||||
|  |     """ | ||||||
|  |     编码Collect消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         bus_number: 总线号 (int) | ||||||
|  |         bus_address: 总线地址 (int) | ||||||
|  |         value: 采集值 (float) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         bytearray: 编码后的数据 | ||||||
|  |     """ | ||||||
|  |     result = bytearray() | ||||||
|  |      | ||||||
|  |     # 编码bus_number字段 (field_number=1, wire_type=0) | ||||||
|  |     result.extend(encode_varint((1 << 3) | 0))      # tag | ||||||
|  |     result.extend(encode_varint(bus_number))        # value | ||||||
|  |      | ||||||
|  |     # 编码bus_address字段 (field_number=2, wire_type=0) | ||||||
|  |     result.extend(encode_varint((2 << 3) | 0))      # tag | ||||||
|  |     result.extend(encode_varint(bus_address))       # value | ||||||
|  |      | ||||||
|  |     # 编码value字段 (field_number=3, wire_type=5) | ||||||
|  |     result.extend(encode_varint((3 << 3) | 5))      # tag | ||||||
|  |     # 将float转换为little-endian的4字节 | ||||||
|  |     result.extend(struct.pack('<f', value))         # value | ||||||
|  |      | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | def decode_collect(buf): | ||||||
|  |     """ | ||||||
|  |     解码Collect消息 | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         buf: 编码后的数据 (bytes) | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         dict: 解码后的消息 | ||||||
|  |     """ | ||||||
|  |     result = {} | ||||||
|  |     pos = 0 | ||||||
|  |      | ||||||
|  |     while pos < len(buf): | ||||||
|  |         # 读取标签 | ||||||
|  |         tag, pos = decode_varint(buf, pos) | ||||||
|  |         field_number = tag >> 3 | ||||||
|  |         wire_type = tag & 0x07 | ||||||
|  |          | ||||||
|  |         if field_number == 1:  # bus_number字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['bus_number'] = value | ||||||
|  |         elif field_number == 2:  # bus_address字段 | ||||||
|  |             if wire_type == 0:  # varint类型 | ||||||
|  |                 value, pos = decode_varint(buf, pos) | ||||||
|  |                 result['bus_address'] = value | ||||||
|  |         elif field_number == 3:  # value字段 | ||||||
|  |             if wire_type == 5:  # 32位浮点类型 | ||||||
|  |                 # 从little-endian的4字节解析float | ||||||
|  |                 value = struct.unpack('<f', buf[pos:pos+4])[0] | ||||||
|  |                 pos += 4 | ||||||
|  |                 result['value'] = value | ||||||
|  |         else: | ||||||
|  |             # 跳过未知字段 | ||||||
|  |             if wire_type == 0:  # varint | ||||||
|  |                 _, pos = decode_varint(buf, pos) | ||||||
|  |             elif wire_type == 5:  # 32位固定长度 | ||||||
|  |                 pos += 4 | ||||||
|  |             elif wire_type == 2:  # 长度分隔 | ||||||
|  |                 length, pos = decode_varint(buf, pos) | ||||||
|  |                 pos += length | ||||||
|  |             else: | ||||||
|  |                 pos += 1 | ||||||
|  |                  | ||||||
|  |     return result | ||||||
|  |  | ||||||
|  | # 使用示例 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     # 创建一个Switch消息 | ||||||
|  |     switch_data = encode_switch("ON", 1, 10, 2) | ||||||
|  |     print(f"编码后的Switch消息: {switch_data.hex()}") | ||||||
|  |      | ||||||
|  |     # 创建一个Instruction消息,包含Switch数据 | ||||||
|  |     instruction_data = encode_instruction(METHOD_TYPE_SWITCH, switch_data) | ||||||
|  |     print(f"编码后的Instruction消息: {instruction_data.hex()}") | ||||||
|  |      | ||||||
|  |     # 解码Instruction消息 | ||||||
|  |     decoded_instruction = decode_instruction(instruction_data) | ||||||
|  |     print(f"解码后的Instruction消息: {decoded_instruction}") | ||||||
|  |      | ||||||
|  |     # 解码Switch消息 | ||||||
|  |     if 'data' in decoded_instruction: | ||||||
|  |         decoded_switch = decode_switch(decoded_instruction['data']) | ||||||
|  |         print(f"解码后的Switch消息: {decoded_switch}") | ||||||
|  |      | ||||||
|  |     # 创建一个Collect消息 | ||||||
|  |     collect_data = encode_collect(1, 20, 25.6) | ||||||
|  |     print(f"编码后的Collect消息: {collect_data.hex()}") | ||||||
|  |      | ||||||
|  |     # 创建一个Instruction消息,包含Collect数据 | ||||||
|  |     instruction_data2 = encode_instruction(METHOD_TYPE_COLLECT, collect_data) | ||||||
|  |     print(f"编码后的Instruction消息(Collect): {instruction_data2.hex()}") | ||||||
|  |      | ||||||
|  |     # 解码Instruction消息 | ||||||
|  |     decoded_instruction2 = decode_instruction(instruction_data2) | ||||||
|  |     print(f"解码后的Instruction消息: {decoded_instruction2}") | ||||||
|  |      | ||||||
|  |     # 解码Collect消息 | ||||||
|  |     if 'data' in decoded_instruction2: | ||||||
|  |         decoded_collect = decode_collect(decoded_instruction2['data']) | ||||||
|  |         print(f"解码后的Collect消息: {decoded_collect}") | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 通信层 |  | ||||||
| @@ -1,72 +0,0 @@ | |||||||
| from abc import ABC, abstractmethod |  | ||||||
| from typing import Any, Callable, Optional |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseComm(ABC): |  | ||||||
|     """ |  | ||||||
|     通信接口抽象基类 |  | ||||||
|     定义所有通信模块需要实现的基本方法 |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def connect(self) -> bool: |  | ||||||
|         """ |  | ||||||
|         建立通信连接 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             bool: 连接是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def disconnect(self) -> None: |  | ||||||
|         """ |  | ||||||
|         断开通信连接 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def send(self, data: bytes, address: Optional[str] = None) -> bool: |  | ||||||
|         """ |  | ||||||
|         发送数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             data: 要发送的数据 |  | ||||||
|             address: 目标地址(可选) |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 发送是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def receive(self, timeout: Optional[float] = None) -> Optional[bytes]: |  | ||||||
|         """ |  | ||||||
|         接收数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             timeout: 超时时间(秒) |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bytes: 接收到的数据,如果没有数据则返回None |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def is_connected(self) -> bool: |  | ||||||
|         """ |  | ||||||
|         检查通信连接状态 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             bool: 是否已连接 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def set_callback(self, callback: Callable[[bytes], None]) -> None: |  | ||||||
|         """ |  | ||||||
|         设置数据接收回调函数 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             callback: 接收数据时调用的回调函数 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # lora实现 |  | ||||||
| @@ -1,73 +0,0 @@ | |||||||
| { |  | ||||||
|     "lora": { |  | ||||||
|         "address": "0x1234", |  | ||||||
|         "frequency": 433, |  | ||||||
|         "bandwidth": 125, |  | ||||||
|         "spreading_factor": 7, |  | ||||||
|         "coding_rate": 5, |  | ||||||
|         "encryption_key": "your_encryption_key_here" |  | ||||||
|     }, |  | ||||||
|     "master": { |  | ||||||
|         "lora_address": "0xABCD", |  | ||||||
|         "protocol": "modbus" |  | ||||||
|     }, |  | ||||||
|     "bus": { |  | ||||||
|         "sensor": { |  | ||||||
|             "port": "/dev/ttyUSB0", |  | ||||||
|             "baudrate": 9600, |  | ||||||
|             "devices": [] |  | ||||||
|         }, |  | ||||||
|         "actuator": { |  | ||||||
|             "port": "/dev/ttyUSB1",  |  | ||||||
|             "baudrate": 9600, |  | ||||||
|             "devices": [] |  | ||||||
|         } |  | ||||||
|     }, |  | ||||||
|     "log": { |  | ||||||
|         "level": "INFO", |  | ||||||
|         "file_path": "logs/pig_house.log", |  | ||||||
|         "max_size": "1MB", |  | ||||||
|         "backup_count": 1, |  | ||||||
|         "report_errors": true, |  | ||||||
|         "terminate_on_report_failure": true |  | ||||||
|     }, |  | ||||||
|     "system": { |  | ||||||
|         "heartbeat_interval": 60, |  | ||||||
|         "data_collection_interval": 30, |  | ||||||
|         "command_timeout": 10, |  | ||||||
|         "retry_count": 3, |  | ||||||
|         "error_handling": "retry" |  | ||||||
|     }, |  | ||||||
|     "devices": [ |  | ||||||
|         { |  | ||||||
|             "id": "temp_01", |  | ||||||
|             "type": "temperature", |  | ||||||
|             "address": "0x01", |  | ||||||
|             "bus": "sensor", |  | ||||||
|             "unit": "celsius", |  | ||||||
|             "location": "main_hall" |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "id": "humidity_01", |  | ||||||
|             "type": "humidity", |  | ||||||
|             "address": "0x02",  |  | ||||||
|             "bus": "sensor", |  | ||||||
|             "unit": "%", |  | ||||||
|             "location": "main_hall" |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "id": "feed_01", |  | ||||||
|             "type": "feed_port", |  | ||||||
|             "address": "0x10", |  | ||||||
|             "bus": "actuator", |  | ||||||
|             "location": "feeding_area_1" |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             "id": "water_01", |  | ||||||
|             "type": "water_valve", |  | ||||||
|             "address": "0x11", |  | ||||||
|             "bus": "actuator",  |  | ||||||
|             "location": "watering_area_1" |  | ||||||
|         } |  | ||||||
|     ] |  | ||||||
| } |  | ||||||
							
								
								
									
										165
									
								
								config.py
									
									
									
									
									
								
							
							
						
						
									
										165
									
								
								config.py
									
									
									
									
									
								
							| @@ -1,165 +0,0 @@ | |||||||
| import json |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| class Config: |  | ||||||
|     def __init__(self, config_file="config.json"): |  | ||||||
|         self.config_file = config_file |  | ||||||
|         self.default_config = { |  | ||||||
|             # LoRa通信配置 |  | ||||||
|             "lora": { |  | ||||||
|                 "address": "0x1234", |  | ||||||
|                 "frequency": 433, |  | ||||||
|                 "bandwidth": 125, |  | ||||||
|                 "spreading_factor": 7, |  | ||||||
|                 "coding_rate": 5, |  | ||||||
|                 "encryption_key": "default_key" |  | ||||||
|             }, |  | ||||||
|              |  | ||||||
|             # 上位机配置 |  | ||||||
|             "master": { |  | ||||||
|                 "lora_address": "0x5678", |  | ||||||
|                 "protocol": "modbus" |  | ||||||
|             }, |  | ||||||
|              |  | ||||||
|             # 总线配置 |  | ||||||
|             "bus": { |  | ||||||
|                 "sensor": { |  | ||||||
|                     "port": "/dev/ttyUSB0", |  | ||||||
|                     "baudrate": 9600, |  | ||||||
|                     "devices": [] |  | ||||||
|                 }, |  | ||||||
|                 "actuator": { |  | ||||||
|                     "port": "/dev/ttyUSB1", |  | ||||||
|                     "baudrate": 9600, |  | ||||||
|                     "devices": [] |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|              |  | ||||||
|             # 日志配置 |  | ||||||
|             "log": { |  | ||||||
|                 "level": "INFO", |  | ||||||
|                 "file_path": "logs/pig_house.log", |  | ||||||
|                 "max_size": "1KB",  # 减小日志文件大小 |  | ||||||
|                 "backup_count": 1,  # 只保留一个备份文件 |  | ||||||
|                 "report_errors": True,  # 是否上报错误 |  | ||||||
|                 "terminate_on_report_failure": True  # 上报失败时是否终止程序 |  | ||||||
|             }, |  | ||||||
|              |  | ||||||
|             # 系统参数 |  | ||||||
|             "system": { |  | ||||||
|                 "heartbeat_interval": 60,  # 心跳包间隔(秒) |  | ||||||
|                 "data_collection_interval": 300,  # 数据采集间隔(秒) |  | ||||||
|                 "command_timeout": 10,  # 命令超时时间(秒) |  | ||||||
|                 "retry_count": 3,  # 重试次数 |  | ||||||
|                 "error_handling": "retry" |  | ||||||
|             }, |  | ||||||
|              |  | ||||||
|             # 设备配置 |  | ||||||
|             "devices": [ |  | ||||||
|                 { |  | ||||||
|                     "id": "temp_01", |  | ||||||
|                     "type": "temperature", |  | ||||||
|                     "address": "0x01", |  | ||||||
|                     "bus": "sensor", |  | ||||||
|                     "unit": "celsius" |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     "id": "humidity_01",  |  | ||||||
|                     "type": "humidity", |  | ||||||
|                     "address": "0x02", |  | ||||||
|                     "bus": "sensor", |  | ||||||
|                     "unit": "%" |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     "id": "feed_01", |  | ||||||
|                     "type": "feed_port", |  | ||||||
|                     "address": "0x10", |  | ||||||
|                     "bus": "actuator" |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     "id": "water_01", |  | ||||||
|                     "type": "water_valve", |  | ||||||
|                     "address": "0x11",  |  | ||||||
|                     "bus": "actuator" |  | ||||||
|                 } |  | ||||||
|             ] |  | ||||||
|         } |  | ||||||
|         self.config = {} |  | ||||||
|         self.load_config() |  | ||||||
|  |  | ||||||
|     def load_config(self): |  | ||||||
|         """加载配置文件""" |  | ||||||
|         if os.path.exists(self.config_file): |  | ||||||
|             try: |  | ||||||
|                 with open(self.config_file, "r", encoding="utf-8") as file: |  | ||||||
|                     config_data = json.load(file) |  | ||||||
|                     # 合并默认配置和文件配置 |  | ||||||
|                     self.config = self._merge_dict(self.default_config, config_data) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print(f"加载配置文件出错: {e},使用默认配置") |  | ||||||
|                 self.config = self.default_config |  | ||||||
|         else: |  | ||||||
|             print("配置文件不存在,使用默认配置") |  | ||||||
|             self.config = self.default_config |  | ||||||
|             self.save_config() |  | ||||||
|  |  | ||||||
|     def save_config(self): |  | ||||||
|         """保存配置到文件""" |  | ||||||
|         try: |  | ||||||
|             # 确保目录存在 |  | ||||||
|             os.makedirs(os.path.dirname(self.config_file), exist_ok=True) |  | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|              |  | ||||||
|         with open(self.config_file, "w", encoding="utf-8") as file: |  | ||||||
|             json.dump(self.config, file, indent=4, ensure_ascii=False) |  | ||||||
|  |  | ||||||
|     def get(self, key_path, default=None): |  | ||||||
|         """根据路径获取配置项,例如: get('lora.address')""" |  | ||||||
|         keys = key_path.split('.') |  | ||||||
|         value = self.config |  | ||||||
|         try: |  | ||||||
|             for key in keys: |  | ||||||
|                 value = value[key] |  | ||||||
|             return value |  | ||||||
|         except (KeyError, TypeError): |  | ||||||
|             return default |  | ||||||
|  |  | ||||||
|     def set(self, key_path, value): |  | ||||||
|         """根据路径设置配置项""" |  | ||||||
|         keys = key_path.split('.') |  | ||||||
|         config_ref = self.config |  | ||||||
|         for key in keys[:-1]: |  | ||||||
|             if key not in config_ref: |  | ||||||
|                 config_ref[key] = {} |  | ||||||
|             config_ref = config_ref[key] |  | ||||||
|         config_ref[keys[-1]] = value |  | ||||||
|  |  | ||||||
|     def _merge_dict(self, default, override): |  | ||||||
|         """合并两个字典,保留默认值""" |  | ||||||
|         merged = default.copy() |  | ||||||
|         for key, value in override.items(): |  | ||||||
|             if key in merged and isinstance(merged[key], dict) and isinstance(value, dict): |  | ||||||
|                 merged[key] = self._merge_dict(merged[key], value) |  | ||||||
|             else: |  | ||||||
|                 merged[key] = value |  | ||||||
|         return merged |  | ||||||
|  |  | ||||||
|     # 便捷访问方法 |  | ||||||
|     def lora_config(self): |  | ||||||
|         return self.config.get('lora', {}) |  | ||||||
|  |  | ||||||
|     def master_config(self): |  | ||||||
|         return self.config.get('master', {}) |  | ||||||
|  |  | ||||||
|     def bus_config(self): |  | ||||||
|         return self.config.get('bus', {}) |  | ||||||
|  |  | ||||||
|     def log_config(self): |  | ||||||
|         return self.config.get('log', {}) |  | ||||||
|  |  | ||||||
|     def system_config(self): |  | ||||||
|         return self.config.get('system', {}) |  | ||||||
|  |  | ||||||
|     def devices_config(self): |  | ||||||
|         return self.config.get('devices', []) |  | ||||||
| @@ -1,50 +0,0 @@ | |||||||
| from abc import ABC, abstractmethod |  | ||||||
| from typing import Any, Dict, Optional |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseHandler(ABC): |  | ||||||
|     """ |  | ||||||
|     命令处理器抽象基类 |  | ||||||
|     定义所有命令处理器需要实现的基本方法 |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def handle_command(self, command: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: |  | ||||||
|         """ |  | ||||||
|         处理命令 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             command: 命令类型 |  | ||||||
|             data: 命令数据 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             Dict[str, Any]: 处理结果 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def register_command(self, command: str, handler_func) -> bool: |  | ||||||
|         """ |  | ||||||
|         注册命令处理函数 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             command: 命令类型 |  | ||||||
|             handler_func: 处理函数 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 注册是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def unregister_command(self, command: str) -> bool: |  | ||||||
|         """ |  | ||||||
|         注销命令处理函数 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             command: 命令类型 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 注销是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 处理接收的指令 |  | ||||||
| @@ -1,43 +0,0 @@ | |||||||
| from enum import Enum |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LogLevel(Enum): |  | ||||||
|     """日志等级枚举""" |  | ||||||
|     DEBUG = "DEBUG" |  | ||||||
|     INFO = "INFO" |  | ||||||
|     WARNING = "WARNING" |  | ||||||
|     ERROR = "ERROR" |  | ||||||
|     CRITICAL = "CRITICAL" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeviceType(Enum): |  | ||||||
|     """设备类型枚举""" |  | ||||||
|     # 传感器类型 |  | ||||||
|     TEMPERATURE = "temperature" |  | ||||||
|     HUMIDITY = "humidity" |  | ||||||
|     PRESSURE = "pressure" |  | ||||||
|     LIGHT = "light" |  | ||||||
|     CO2 = "co2" |  | ||||||
|     NH3 = "nh3"  # 氨气 |  | ||||||
|     H2S = "h2s"  # 硫化氢 |  | ||||||
|      |  | ||||||
|     # 执行器类型 |  | ||||||
|     FEED_PORT = "feed_port" |  | ||||||
|     WATER_VALVE = "water_valve" |  | ||||||
|     FAN = "fan" |  | ||||||
|     HEATER = "heater" |  | ||||||
|     COOLER = "cooler" |  | ||||||
|     LIGHT_CONTROLLER = "light_controller" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BusType(Enum): |  | ||||||
|     """总线类型枚举""" |  | ||||||
|     SENSOR = "sensor" |  | ||||||
|     ACTUATOR = "actuator" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ErrorHandlingStrategy(Enum): |  | ||||||
|     """错误处理策略枚举""" |  | ||||||
|     RETRY = "retry" |  | ||||||
|     SKIP = "skip" |  | ||||||
|     ALERT = "alert" |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 心跳包 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 定时任务 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 设备驱动 |  | ||||||
| @@ -1,100 +0,0 @@ | |||||||
| from abc import ABC, abstractmethod |  | ||||||
| from typing import Any, Dict, Optional |  | ||||||
| from enum import Enum |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeviceStatus(Enum): |  | ||||||
|     """设备状态枚举""" |  | ||||||
|     UNKNOWN = "unknown" |  | ||||||
|     ONLINE = "online" |  | ||||||
|     OFFLINE = "offline" |  | ||||||
|     ERROR = "error" |  | ||||||
|     BUSY = "busy" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseDevice(ABC): |  | ||||||
|     """ |  | ||||||
|     设备接口抽象基类 |  | ||||||
|     定义所有设备需要实现的基本方法 |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, device_id: str, device_type: str, address: str, bus: str): |  | ||||||
|         """ |  | ||||||
|         初始化设备 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             device_id: 设备唯一标识 |  | ||||||
|             device_type: 设备类型 |  | ||||||
|             address: 设备地址 |  | ||||||
|             bus: 所在总线 |  | ||||||
|         """ |  | ||||||
|         self.device_id = device_id |  | ||||||
|         self.device_type = device_type |  | ||||||
|         self.address = address |  | ||||||
|         self.bus = bus |  | ||||||
|         self.status = DeviceStatus.UNKNOWN |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def connect(self) -> bool: |  | ||||||
|         """ |  | ||||||
|         连接设备 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             bool: 连接是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def disconnect(self) -> None: |  | ||||||
|         """ |  | ||||||
|         断开设备连接 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def read_data(self) -> Optional[Dict[str, Any]]: |  | ||||||
|         """ |  | ||||||
|         读取设备数据 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             dict: 设备数据,格式为 {数据名: 数据值},失败时返回None |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def write_data(self, data: Dict[str, Any]) -> bool: |  | ||||||
|         """ |  | ||||||
|         向设备写入数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             data: 要写入的数据,格式为 {数据名: 数据值} |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 写入是否成功 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def get_status(self) -> DeviceStatus: |  | ||||||
|         """ |  | ||||||
|         获取设备状态 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             DeviceStatus: 设备状态 |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def get_info(self) -> Dict[str, Any]: |  | ||||||
|         """ |  | ||||||
|         获取设备信息 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             dict: 设备信息 |  | ||||||
|         """ |  | ||||||
|         return { |  | ||||||
|             "device_id": self.device_id, |  | ||||||
|             "device_type": self.device_type, |  | ||||||
|             "address": self.address, |  | ||||||
|             "bus": self.bus, |  | ||||||
|             "status": self.status.value |  | ||||||
|         } |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 下料口 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 温度传感器 |  | ||||||
							
								
								
									
										311
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										311
									
								
								main.py
									
									
									
									
									
								
							| @@ -4,3 +4,314 @@ | |||||||
| """ | """ | ||||||
| 猪舍主控系统主程序入口 | 猪舍主控系统主程序入口 | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | 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}") | ||||||
							
								
								
									
										32
									
								
								proto/client.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								proto/client.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | syntax = "proto3"; | ||||||
|  |  | ||||||
|  | package device; | ||||||
|  |  | ||||||
|  | import "google/protobuf/any.proto"; | ||||||
|  |  | ||||||
|  | option go_package = "internal/app/service/device/proto"; | ||||||
|  |  | ||||||
|  | // 指令类型 | ||||||
|  | enum MethodType{ | ||||||
|  |   SWITCH = 0; // 启停 | ||||||
|  |   COLLECT = 1; // 采集 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 指令 | ||||||
|  | message Instruction{ | ||||||
|  |   MethodType method = 1; | ||||||
|  |   google.protobuf.Any data = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Switch{ | ||||||
|  |   string device_action = 1; // 指令 | ||||||
|  |   int32 bus_number = 2; // 总线号 | ||||||
|  |   int32 bus_address = 3; // 总线地址 | ||||||
|  |   int32 relay_channel = 4; // 继电器通道号 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message Collect{ | ||||||
|  |   int32 bus_number = 1; // 总线号 | ||||||
|  |   int32 bus_address = 2; // 总线地址 | ||||||
|  |   float value = 3; // 采集值 | ||||||
|  | } | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| # 项目依赖包 |  | ||||||
|  |  | ||||||
| # LoRa通信相关 |  | ||||||
| pyserial>=3.5 |  | ||||||
| paho-mqtt>=1.6.1 |  | ||||||
|  |  | ||||||
| # CoAP协议支持 |  | ||||||
| aiocoap>=0.4.5 |  | ||||||
|  |  | ||||||
| # LwM2M支持 (使用wakaama-python作为替代) |  | ||||||
| # 注意:lwm2m-client在PyPI上不可用,需要手动安装或使用其他实现 |  | ||||||
| # wakaama-python>=0.1.0 |  | ||||||
|  |  | ||||||
| # 数据格式(SenML) |  | ||||||
| senml>=1.0.0 |  | ||||||
|  |  | ||||||
| # 其他工具 |  | ||||||
| jsonschema>=4.0.0 |  | ||||||
|  |  | ||||||
| # 通用依赖 |  | ||||||
| typing-extensions>=3.10.0; python_version < "3.10" |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 持久化存储 |  | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| from abc import ABC, abstractmethod |  | ||||||
| from typing import Any, Dict, List, Optional |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseStorage(ABC): |  | ||||||
|     """ |  | ||||||
|     存储接口抽象基类 |  | ||||||
|     定义所有存储模块需要实现的基本方法 |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def save(self, key: str, value: Any) -> bool: |  | ||||||
|         """ |  | ||||||
|         保存数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             key: 键 |  | ||||||
|             value: 值 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 保存是否成功 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def load(self, key: str, default: Any = None) -> Any: |  | ||||||
|         """ |  | ||||||
|         加载数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             key: 键 |  | ||||||
|             default: 默认值 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             Any: 加载的数据 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def delete(self, key: str) -> bool: |  | ||||||
|         """ |  | ||||||
|         删除数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             key: 要删除的键 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 删除是否成功 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def exists(self, key: str) -> bool: |  | ||||||
|         """ |  | ||||||
|         检查键是否存在 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             key: 键 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 键是否存在 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def list_keys(self) -> List[str]: |  | ||||||
|         """ |  | ||||||
|         列出所有键 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             List[str]: 所有键的列表 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def clear(self) -> bool: |  | ||||||
|         """ |  | ||||||
|         清空所有数据 |  | ||||||
|          |  | ||||||
|         Returns: |  | ||||||
|             bool: 清空是否成功 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def save_batch(self, data: Dict[str, Any]) -> bool: |  | ||||||
|         """ |  | ||||||
|         批量保存数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             data: 要保存的数据字典 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             bool: 保存是否成功 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
|  |  | ||||||
|     @abstractmethod |  | ||||||
|     def load_batch(self, keys: List[str]) -> Dict[str, Any]: |  | ||||||
|         """ |  | ||||||
|         批量加载数据 |  | ||||||
|          |  | ||||||
|         Args: |  | ||||||
|             keys: 要加载的键列表 |  | ||||||
|              |  | ||||||
|         Returns: |  | ||||||
|             Dict[str, Any]: 加载的数据字典 |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
| @@ -1,40 +0,0 @@ | |||||||
| # json 文件实现 |  | ||||||
|  |  | ||||||
| import json |  | ||||||
|  |  | ||||||
| from storage.base_storage import BaseStorage |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class JSONStorage(BaseStorage): |  | ||||||
|     def __init__(self, filename="data.json"): |  | ||||||
|         self.filename = filename |  | ||||||
|         # 如果文件不存在,先创建空字典 |  | ||||||
|         try: |  | ||||||
|             with open(self.filename, "r") as f: |  | ||||||
|                 pass |  | ||||||
|         except OSError: |  | ||||||
|             with open(self.filename, "w") as f: |  | ||||||
|                 f.write("{}") |  | ||||||
|  |  | ||||||
|     def _read_all(self): |  | ||||||
|         with open(self.filename, "r") as f: |  | ||||||
|             return json.load(f) |  | ||||||
|  |  | ||||||
|     def _write_all(self, data): |  | ||||||
|         with open(self.filename, "w") as f: |  | ||||||
|             json.dump(data, f) |  | ||||||
|  |  | ||||||
|     def save(self, key, value): |  | ||||||
|         data = self._read_all() |  | ||||||
|         data[key] = value |  | ||||||
|         self._write_all(data) |  | ||||||
|  |  | ||||||
|     def load(self, key, default=None): |  | ||||||
|         data = self._read_all() |  | ||||||
|         return data.get(key, default) |  | ||||||
|  |  | ||||||
|     def delete(self, key): |  | ||||||
|         data = self._read_all() |  | ||||||
|         if key in data: |  | ||||||
|             del data[key] |  | ||||||
|             self._write_all(data) |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 内存实现 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 核心逻辑测试 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 校验工具 |  | ||||||
							
								
								
									
										57
									
								
								utils/fs.py
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								utils/fs.py
									
									
									
									
									
								
							| @@ -1,57 +0,0 @@ | |||||||
| # 兼容PC和MicroPython的文件操作 |  | ||||||
|  |  | ||||||
| # compat_fs.py |  | ||||||
| try: |  | ||||||
|     import uos as os  # MicroPython |  | ||||||
|     MICROPYTHON = True |  | ||||||
| except ImportError: |  | ||||||
|     import os         # CPython |  | ||||||
|     MICROPYTHON = False |  | ||||||
|  |  | ||||||
| def list_dir(path="."): |  | ||||||
|     """列出目录内容""" |  | ||||||
|     return os.listdir(path) |  | ||||||
|  |  | ||||||
| def make_dir(path): |  | ||||||
|     """创建目录""" |  | ||||||
|     if MICROPYTHON: |  | ||||||
|         os.mkdir(path) |  | ||||||
|     else: |  | ||||||
|         os.makedirs(path, exist_ok=True) |  | ||||||
|  |  | ||||||
| def remove_file(path): |  | ||||||
|     """删除文件""" |  | ||||||
|     os.remove(path) |  | ||||||
|  |  | ||||||
| def read_file(path, mode="r"): |  | ||||||
|     """读取文件内容""" |  | ||||||
|     with open(path, mode) as f: |  | ||||||
|         return f.read() |  | ||||||
|  |  | ||||||
| def write_file(path, data, mode="w"): |  | ||||||
|     """写入文件内容""" |  | ||||||
|     with open(path, mode) as f: |  | ||||||
|         f.write(data) |  | ||||||
|  |  | ||||||
| def is_file(path): |  | ||||||
|     """判断是否是文件""" |  | ||||||
|     try: |  | ||||||
|         st = os.stat(path) |  | ||||||
|         # MicroPython: stat()[0] >> 14 & 0xF == 8 表示普通文件 |  | ||||||
|         if MICROPYTHON: |  | ||||||
|             return (st[0] >> 14) & 0xF == 8 |  | ||||||
|         else: |  | ||||||
|             return os.path.isfile(path) |  | ||||||
|     except OSError: |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
| def is_dir(path): |  | ||||||
|     """判断是否是目录""" |  | ||||||
|     try: |  | ||||||
|         st = os.stat(path) |  | ||||||
|         if MICROPYTHON: |  | ||||||
|             return (st[0] >> 14) & 0xF == 2 |  | ||||||
|         else: |  | ||||||
|             return os.path.isdir(path) |  | ||||||
|     except OSError: |  | ||||||
|         return False |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 日志 |  | ||||||
| @@ -1 +0,0 @@ | |||||||
| # 数据解析工具 |  | ||||||
		Reference in New Issue
	
	Block a user