13 Commits
Author SHA1 Message Date
linyimin c3f1cfdea0 feat: TCP协议支持protobuf序列化,保留JSON兼容
- 新增 tcp_messages_pb2.py protobuf Python绑定
- msg_handler.cpp: 新增 make_packet_pb/raw bytes打包, parse_packet_raw/raw解析
- archery_netcore.cpp: 注册 make_packet_pb/parse_packet_raw
- network.py: 新增 _use_proto 标志,_make_send_packet/_parse_recv 自动选择proto/JSON
- 登录version加+proto后缀标识proto模式
- protobuf序列化/反序列化失败时自动回退JSON
2026-09-16 09:30:26 +08:00
linyimin 8457bd29c6 fix: 2026-09-04 11:14:17 +08:00
linyimin 5bde549d91 update: version 2026-09-04 11:11:41 +08:00
linyimin d96ca4d031 fix: 调整气压值 2026-09-02 14:19:04 +08:00
linyimin ced66682ed fix: 去除3秒内只能射箭一次的限制 2026-09-02 14:10:46 +08:00
linyimin a09b45738a fix: camera flip 2026-09-02 13:43:16 +08:00
linyimin e4d8454947 fix: update version 2026-09-02 11:26:44 +08:00
linyimin c09189332d fix: 摄像头翻转 2026-09-02 11:25:33 +08:00
yrx a00baa1770 two ! 2026-09-02 11:22:01 +08:00
yrx 179b30a944 two 2026-09-01 11:07:46 +08:00
yrx 42026d43e5 模型调用 2026-08-17 15:49:01 +08:00
yrx 8a83deddd3 yolo模型 2026-08-14 16:32:17 +08:00
yrx 6a1d3fe2bd 整合yolo版本 2026-08-14 15:48:40 +08:00
41 changed files with 1873 additions and 149 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"cmake.sourceDirectory": "E:/code/code/code/new/new/new/new/new/nw/archery - 副本/cpp_ext"
}
+88 -24
View File
@@ -109,6 +109,7 @@
from maix import app, uart, pinmap, time
import hashlib
import hmac
import re
import ujson
# ========== 配置 ==========
@@ -130,53 +131,109 @@ def generate_token(device_id):
return "Arrow_" + hmac.new((SALT + device_id).encode(), SALT2.encode(), hashlib.sha256).hexdigest()
def send_cmd(cmd_str, timeout_ms=3000):
"""发送 AT 指令并等待 OK / ERROR"""
"""发送 AT 指令并返回完整响应;超时返回已收到的内容。"""
print("[AT] =>", cmd_str)
http_serial.write((cmd_str + "\r\n").encode())
buffer = b""
start = time.ticks_ms()
while time.ticks_ms() - start < timeout_ms:
while time.ticks_diff(time.ticks_ms(), start) < timeout_ms:
data = http_serial.read(128)
if data:
buffer += data
try:
decoded = buffer.decode()
print("<= ", decoded.strip())
if "OK" in decoded:
return True
if "+CME ERROR" in decoded or "ERROR" in decoded:
return False
decoded = buffer.decode("utf-8", "ignore")
if "OK" in decoded or "+CME ERROR" in decoded or "ERROR" in decoded:
print("[AT] <=", decoded.strip())
return decoded
except:
pass
time.sleep_ms(10)
decoded = buffer.decode("utf-8", "ignore")
print("[AT] !! timeout", timeout_ms, "ms, response:", decoded.strip() or "<empty>")
return decoded
def response_ok(response):
return "OK" in response and "ERROR" not in response
def wait_modem_ready():
"""等待模组响应,并确认 PDP 上下文已经获得 IP。"""
for attempt in range(15):
if response_ok(send_cmd("AT", 1000)):
break
print("[4G] 等待模组启动", attempt + 1, "/15")
time.sleep_ms(1000)
else:
print("[4G] UART2 无 AT 响应,请检查模组供电、A28/A29 接线和串口占用")
return False
send_cmd("ATE0", 1000)
cpin = send_cmd("AT+CPIN?", 3000)
if "READY" not in cpin:
print("[4G] SIM 卡未就绪:", cpin.strip())
return False
addr = send_cmd("AT+CGPADDR=1", 3000)
match = re.search(r'\+CGPADDR:\s*1,"([^\"]+)"', addr)
if match and match.group(1) != "0.0.0.0":
print("[4G] PDP ready, IP:", match.group(1))
return True
send_cmd("AT+MIPCALL=1,1", 15000)
for _ in range(20):
addr = send_cmd("AT+CGPADDR=1", 3000)
match = re.search(r'\+CGPADDR:\s*1,"([^\"]+)"', addr)
if match and match.group(1) != "0.0.0.0":
print("[4G] PDP ready, IP:", match.group(1))
return True
time.sleep_ms(1000)
print("[4G] PDP 未获得 IP,请检查 SIM 流量、信号和 APN")
return False
def clear_http_instances():
for instance_id in range(6):
send_cmd(f"AT+MHTTPDEL={instance_id}", 1200)
def create_http_instance(url):
cmd = f'AT+MHTTPCREATE="{url}"'
if send_cmd(cmd):
# 尝试提取 instance ID(如果模块返回)
# 注意:部分模块不会返回 ID,可忽略,直接用 0 或 1
return True
return False
response = send_cmd(cmd, 8000)
match = re.search(r"\+MHTTPCREATE:\s*(\d+)", response)
if not response_ok(response) or not match:
print("❌ 创建 HTTP 实例失败,模组响应:", response.strip() or "<empty>")
return None
return int(match.group(1))
def send_http_request(url, api_path, token, device_id, json_data):
# 1. 创建 HTTP 实例
if not create_http_instance(url):
print("❌ 创建 HTTP 实例失败")
instance_id = create_http_instance(url)
if instance_id is None:
return False
# 2. 设置 Headers(假设实例 ID 为 0,或根据模块默认)
instance_id = 0 # 大多数模块默认实例为 0;若支持多实例,需解析返回值
send_cmd(f'AT+MHTTPCFG="header",{instance_id},"Content-Type: application/json"')
send_cmd(f'AT+MHTTPCFG="header",{instance_id},"Authorization: {token}"')
send_cmd(f'AT+MHTTPCFG="header",{instance_id},"DeviceId: {device_id}"')
# 2. 设置 Headers
commands = (
f'AT+MHTTPCFG="header",{instance_id},"Content-Type: application/json"',
f'AT+MHTTPCFG="header",{instance_id},"Authorization: {token}"',
f'AT+MHTTPCFG="header",{instance_id},"DeviceId: {device_id}"',
)
for command in commands:
if not response_ok(send_cmd(command)):
print("❌ HTTP Header 配置失败")
send_cmd(f"AT+MHTTPDEL={instance_id}", 2000)
return False
# 3. 发送 Body
json_str = ujson.dumps(json_data)
send_cmd(f'AT+MHTTPCONTENT={instance_id},0,0,"{json_str}"')
at_json = json_str.replace("\\", "\\\\").replace('"', '\\"')
if not response_ok(send_cmd(f'AT+MHTTPCONTENT={instance_id},0,0,"{at_json}"', 8000)):
print("❌ HTTP Body 配置失败")
send_cmd(f"AT+MHTTPDEL={instance_id}", 2000)
return False
# 4. 发起 POST 请求
if send_cmd(f'AT+MHTTPREQUEST={instance_id},2,0,"{api_path}"'):
if response_ok(send_cmd(f'AT+MHTTPREQUEST={instance_id},2,0,"{api_path}"', 15000)):
print("✅ HTTP 请求已发送")
return True
else:
@@ -199,7 +256,7 @@ def read_response(timeout_ms=5000):
print("🚀 启动直接上传流程...")
token = generate_token(device_id)
print("🔑 Token:", token)
print("🔑 Token 已生成:", token[:12] + "...")
# 构造模拟数据
timestamp = int(time.time() * 1000)
@@ -216,7 +273,14 @@ json_data = {
}
# 执行上传
if send_http_request(url, api_path, token, device_id, json_data):
upload_ok = False
if not wait_modem_ready():
print("💥 4G 模组未就绪")
else:
clear_http_instances()
upload_ok = send_http_request(url, api_path, token, device_id, json_data)
if upload_ok:
read_response()
else:
print("💥 上传流程失败")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -1
View File
@@ -1,6 +1,6 @@
id: t11
name: t11
version: 2.16.4
version: 3.0.5
author: t11
icon: ''
desc: t11
@@ -18,11 +18,14 @@ files:
- laser_manager.py
- logger_manager.py
- main.py
- model_285484.cvimodel
- model_285484.mud
- network.py
- ota_curl.sh
- ota_manager.py
- power.py
- server.pem
- set_autostart.py
- shoot_manager.py
- shot_id_generator.py
- target_roi_yolo.py
+17 -1
View File
@@ -8,6 +8,15 @@ import threading
import config
from logger_manager import logger_manager
_USE_CV = False
try:
import cv2
import numpy as np
from maix import image as _maix_image
_USE_CV = True
except ImportError:
pass
class CameraManager:
"""相机管理器(单例)"""
@@ -57,6 +66,12 @@ class CameraManager:
with self._camera_lock:
if self._camera is None:
self._camera = camera.Camera(width, height)
v_flip = getattr(config, 'CAMERA_V_FLIP', False)
h_mirror = getattr(config, 'CAMERA_H_MIRROR', False)
if v_flip:
self._camera.vflip(1)
if h_mirror:
self._camera.hmirror(1)
return self._camera
@@ -101,7 +116,8 @@ class CameraManager:
with self._camera_lock:
if self._camera is None:
self.init_camera()
return self._camera.read()
frame = self._camera.read()
return frame
def show(self, image):
"""
+35
View File
@@ -15,6 +15,8 @@ LOCAL_FILENAME = APP_DIR + "/main_tmp.py"
# 相机初始化分辨率(CameraManager / main.py 使用)
CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480
CAMERA_V_FLIP = True # 摄像头垂直翻转(上下颠倒时设为 True)
CAMERA_H_MIRROR = True # 摄像头水平镜像(左右反了时设为 True)
# 三角形检测缩图比例:默认按相机最长边缩到 1/2(性能更稳;可按需调整)
# 取值范围建议 (0.25 ~ 1.0]1.0 表示不缩图
@@ -96,6 +98,11 @@ ADC_LASER_THRESHOLD = 3000
# ==================== 激光配置 ====================
MODULE_ADDR = 0x00
# 激光开关改由 A14 GPIO 控制:低电平开启,高电平关闭。
LASER_CONTROL_PIN = "A14"
LASER_CONTROL_GPIO = "GPIOA14"
LASER_CONTROL_ON_LEVEL = 0
LASER_CONTROL_OFF_LEVEL = 1
LASER_ON_CMD = bytes([0xAA, MODULE_ADDR, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x01, 0xC1])
LASER_OFF_CMD = bytes([0xAA, MODULE_ADDR, 0x01, 0xBE, 0x00, 0x01, 0x00, 0x00, 0xC0])
DISTANCE_QUERY_CMD = bytes([0xAA, MODULE_ADDR, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x21]) # 激光测距查询命令
@@ -262,6 +269,16 @@ TRIANGLE_SAMPLE_PATCH_HALF_PX = 2
# 开机阶段预加载 YOLO detectordetect 使用 dual_buff=False,避免返回上一帧结果。
TRIANGLE_YOLO_PRELOAD_ON_BOOT = False
# YOLO target size classification: class 0=20cm, class 1=40cm.
TARGET_CLASS_YOLO_ENABLE = True
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_285484.mud"
TARGET_CLASS_YOLO_LABELS = (20, 40)
TARGET_CLASS_YOLO_CONF_TH = 0.50
TARGET_CLASS_YOLO_IOU_TH = 0.45
TARGET_CLASS_YOLO_RETRY_ON_EMPTY = False
TARGET_CLASS_YOLO_RETRY_CONF_TH = 0.25
TARGET_CLASS_YOLO_PRELOAD_ON_BOOT = True
# ── 第二段 YOLO:仅在 Stage1 裁切出的靶环图上推理(与合成 stage2 训练数据一致)→ 子框内传统算法取直角点 ──
# Stage1 靶环裁切内如何找黑三角标记(对比耗时时可切换):
# "yolo" — 调 Stage2 黑三角模型得子框,再子框内传统提取(需 TRIANGLE_BLACK_YOLO_ENABLE=True)。
@@ -338,11 +355,29 @@ PIN_MAPPINGS = {
"A28": "UART2_TX",
"A15": "I2C5_SCL",
"A27": "I2C5_SDA",
"A14": "GPIOA14", # 激光开关:低开、高关
"A24": "GPIOA24", # 电源板关机控制
"A25": "GPIOA25", # 电源状态绿灯
"A23": "GPIOA23", # 电源状态红灯
}
# ==================== 电源配置 ====================
AUTO_POWER_OFF_IN_SECONDS = 10 * 60 # 自动关机时间(秒),0表示不自动关机
# 充电时自动关机暂时禁用;需要恢复时改为 True。
CHARGING_AUTO_POWER_OFF_ENABLED = False
# 一代电源控制:A24 由电源板负责按键/关机信号,软件关机时输出高电平。
# 电源状态指示灯
STATUS_LED_GREEN_GPIO = "GPIOA25"
STATUS_LED_RED_GPIO = "GPIOA23"
STATUS_LED_GREEN_ENABLED = True
STATUS_LED_RED_ENABLED = True
STATUS_LED_ACTIVE_LEVEL = 1
STATUS_LED_LOW_BATTERY_PERCENT = 10
STATUS_LED_FULL_BATTERY_PERCENT = 90
STATUS_LED_CHARGING_BLINK_MS = 500
STATUS_LED_POLL_MS = 1000
BATTERY_SOC_LPF_ALPHA = 0.5
BATTERY_SOC_AVG_WINDOW = 5
+7
View File
@@ -57,9 +57,16 @@ PYBIND11_MODULE(archery_netcore, m) {
"Pack TCP packet: header (len+type+checksum) + JSON body",
py::arg("msg_type"), py::arg("body_dict"));
m.def("make_packet_pb", &netcore::make_packet_pb,
"Pack TCP packet: header (len+type+checksum) + raw bytes body (for protobuf)",
py::arg("msg_type"), py::arg("body_bytes"));
m.def("parse_packet", &netcore::parse_packet,
"Parse TCP packet, return (msg_type, body_dict)");
m.def("parse_packet_raw", &netcore::parse_packet_raw,
"Parse TCP packet, return (msg_type, body_bytes) without JSON parsing");
m.def("get_config", &get_config, "Get system configuration");
m.def(
+59
View File
@@ -51,6 +51,43 @@ namespace netcore {
return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size());
}
// 打包 TCP 数据包 (raw bytes body, 用于 protobuf)
py::bytes make_packet_pb(int msg_type, py::bytes body_bytes) {
netcore::log_debug(std::string("make_packet_pb msg_type=") + std::to_string(msg_type));
py::buffer_info buf = py::buffer(body_bytes).request();
uint32_t body_len = buf.size;
uint32_t checksum = body_len + msg_type;
std::vector<uint8_t> packet;
packet.reserve(12 + body_len);
// body_len (big-endian, 4 bytes)
packet.push_back((body_len >> 24) & 0xFF);
packet.push_back((body_len >> 16) & 0xFF);
packet.push_back((body_len >> 8) & 0xFF);
packet.push_back(body_len & 0xFF);
// msg_type (big-endian, 4 bytes)
packet.push_back((msg_type >> 24) & 0xFF);
packet.push_back((msg_type >> 16) & 0xFF);
packet.push_back((msg_type >> 8) & 0xFF);
packet.push_back(msg_type & 0xFF);
// checksum (big-endian, 4 bytes)
packet.push_back((checksum >> 24) & 0xFF);
packet.push_back((checksum >> 16) & 0xFF);
packet.push_back((checksum >> 8) & 0xFF);
packet.push_back(checksum & 0xFF);
// 追加 body bytes
const uint8_t* body_ptr = static_cast<const uint8_t*>(buf.ptr);
packet.insert(packet.end(), body_ptr, body_ptr + body_len);
netcore::log_debug(std::string("make_packet_pb done bytes=") + std::to_string(packet.size()));
return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size());
}
// 解析 TCP 数据包
py::tuple parse_packet(py::bytes data) {
// 1) 转换为 bytes view
@@ -110,4 +147,26 @@ namespace netcore {
return py::make_tuple(py::int_(msg_type), raw_dict);
}
}
// 解析 TCP 数据包 -> (msg_type, body_bytes) 不做 JSON 解析
py::tuple parse_packet_raw(py::bytes data) {
py::buffer_info buf = py::buffer(data).request();
if (buf.size < 12) {
return py::make_tuple(py::none(), py::none());
}
const uint8_t* ptr = static_cast<const uint8_t*>(buf.ptr);
uint32_t body_len = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
uint32_t msg_type = (ptr[4] << 24) | (ptr[5] << 16) | (ptr[6] << 8) | ptr[7];
uint32_t expected_len = 12 + body_len;
if (buf.size < expected_len) {
return py::make_tuple(py::none(), py::none());
}
// 返回原始 body bytes(不做 JSON 解析)
py::bytes body_bytes(reinterpret_cast<const char*>(ptr + 12), body_len);
return py::make_tuple(py::int_(msg_type), body_bytes);
}
}
+9 -2
View File
@@ -7,8 +7,15 @@ namespace py = pybind11;
namespace netcore {
// 打包 TCP 数据包
// 打包 TCP 数据包 (JSON body)
py::bytes make_packet(int msg_type, py::dict body_dict);
// 解包 TCP 数据包
// 打包 TCP 数据包 (raw bytes body, 用于 protobuf)
py::bytes make_packet_pb(int msg_type, py::bytes body_bytes);
// 解包 TCP 数据包 -> (msg_type, body_dict)
py::tuple parse_packet(py::bytes data);
// 解包 TCP 数据包 -> (msg_type, body_bytes) 不做 JSON 解析
py::tuple parse_packet_raw(py::bytes data);
}
+79 -1
View File
@@ -5,6 +5,7 @@
提供硬件对象的统一管理和访问
"""
from maix import time
import _thread
import config
from at_client import ATClient
@@ -28,6 +29,7 @@ class HardwareManager:
self._bus = None # I2C总线
self._adc_obj = None # ADC对象
self._at_client = None # AT客户端
self._status_led_monitor_started = False
self._last_active_time = 0 # 用于记录用户的最后一次活跃的时间
self._stop_timer = False # 用于停止定时器的标志
@@ -104,11 +106,87 @@ class HardwareManager:
# 物理引脚是 A24,对应 GPIO 功能是 GPIOA24
# 注意:这里需要先在 config.PIN_MAPPINGS 中配置好 "A24": "GPIOA24"
from maix import gpio
# 输出高电平关闭
# 一代电源板关机信号为高电平
gpio.GPIO("GPIOA24", gpio.Mode.OUT).value(1)
except Exception as e:
print(f"关机失败: {e}")
def start_status_led_monitor(self):
"""后台更新状态灯:正常/充满绿常亮、充电绿闪烁、低电量红常亮。"""
if self._status_led_monitor_started:
return
self._status_led_monitor_started = True
_thread.start_new_thread(self._status_led_loop, ())
def _status_led_loop(self):
from maix import gpio
from power import get_bus_voltage, is_charging, voltage_to_percent
try:
green = None
if getattr(config, "STATUS_LED_GREEN_ENABLED", True):
green = gpio.GPIO(config.STATUS_LED_GREEN_GPIO, gpio.Mode.OUT)
red = None
if getattr(config, "STATUS_LED_RED_ENABLED", True):
red = gpio.GPIO(config.STATUS_LED_RED_GPIO, gpio.Mode.OUT)
active = int(config.STATUS_LED_ACTIVE_LEVEL)
inactive = 0 if active else 1
if green is not None:
green.value(inactive)
if red is not None:
red.value(inactive)
last_state = None
blink_on = False
blink_period = max(100, int(config.STATUS_LED_CHARGING_BLINK_MS))
poll_ms = max(100, int(config.STATUS_LED_POLL_MS))
tick_ms = min(blink_period, poll_ms)
sensor_elapsed = poll_ms
blink_elapsed = blink_period
state = "normal"
percent = None
charging = False
while self._status_led_monitor_started:
if sensor_elapsed >= poll_ms:
voltage = get_bus_voltage()
percent = voltage_to_percent(voltage) if voltage > 0 else None
charging = is_charging()
low = percent is not None and percent <= int(config.STATUS_LED_LOW_BATTERY_PERCENT)
full = percent is not None and percent >= int(config.STATUS_LED_FULL_BATTERY_PERCENT)
if charging:
state = "full" if full else "charging"
else:
state = "low" if low else "normal"
sensor_elapsed = 0
if state == "low":
if green is not None:
green.value(inactive)
if red is not None:
red.value(active)
elif state == "charging":
if blink_elapsed >= blink_period:
blink_on = not blink_on
blink_elapsed = 0
if green is not None:
green.value(active if blink_on else inactive)
if red is not None:
red.value(inactive)
else: # normal or full
if green is not None:
green.value(active)
if red is not None:
red.value(inactive)
if state != last_state:
print(f"[STATUS_LED] state={state} percent={percent} charging={charging}")
last_state = state
time.sleep_ms(tick_ms)
sensor_elapsed += tick_ms
blink_elapsed += tick_ms
except Exception as e:
self._status_led_monitor_started = False
print(f"[STATUS_LED] monitor failed: {e}")
def start_idle_timer(self):
self._stop_timer = False
self._last_active_time = time.time()
+42 -70
View File
@@ -31,6 +31,7 @@ class LaserManager:
# 私有状态
self._serial = None # 激光串口,由 laser_manager 自己持有
self._laser_gpio = None # A14 激光开关,低电平开启、高电平关闭
self._calibration_active = False
self._calibration_result = None
self._calibration_lock = threading.Lock()
@@ -69,10 +70,21 @@ class LaserManager:
# ==================== 初始化方法 ====================
def init_control_gpio(self):
"""尽早初始化 A14,并拉高确保激光关闭。"""
from maix import gpio, pinmap
pinmap.set_pin_function(config.LASER_CONTROL_PIN, config.LASER_CONTROL_GPIO)
if self._laser_gpio is None:
self._laser_gpio = gpio.GPIO(config.LASER_CONTROL_GPIO, gpio.Mode.OUT)
self._laser_gpio.value(config.LASER_CONTROL_OFF_LEVEL)
self._laser_turned_on = False
print(f"[LASER] {config.LASER_CONTROL_PIN}=HIGH,激光已关闭")
def init(self, serial_device=None, baudrate=None):
"""
初始化激光模块(包括串口)
初始化完成后主动发送关闭命令,防止 UART 初始化噪声误触发激光
初始化激光模块(A14 开关 + 测距串口)
初始化时先将 A14 拉高关闭激光,防止开机误触发
Args:
serial_device: 串口设备路径,默认使用 config.DISTANCE_SERIAL_DEVICE
@@ -82,23 +94,11 @@ class LaserManager:
device = serial_device or config.DISTANCE_SERIAL_DEVICE
baud = baudrate or config.DISTANCE_SERIAL_BAUDRATE
self.init_control_gpio()
self._serial = uart.UART(device, baud)
print(f"[LASER] 激光串口初始化完成: device={device}, baudrate={baud}")
# 等待串口稳定后主动关闭激光,防止初始化噪声误触发
time.sleep_ms(100)
try:
self._serial.read(-1) # 清空接收缓冲区
except Exception:
pass
self._serial.write(config.LASER_OFF_CMD)
time.sleep_ms(60)
try:
self._serial.read(-1) # 清空回包
except Exception:
pass
print("[LASER] 已发送关闭命令(防止开机误触发)")
# ==================== 业务方法 ====================
def load_laser_point(self):
@@ -147,66 +147,38 @@ class LaserManager:
return False
def turn_on_laser(self):
"""发送指令开启激光,并读取回包(部分模块支持)"""
if self._serial is None:
self.logger.error("[LASER] 激光串口未初始化,请先调用 init()")
return None
# 打印调试信息
self.logger.info(f"[LASER] 发送开启命令: {config.LASER_ON_CMD.hex()}")
# 清空接收缓冲区
"""A14 输出低电平,开启激光。"""
if self._laser_gpio is None:
if self.logger:
self.logger.error("[LASER] A14 GPIO 未初始化,请先调用 init()")
return False
try:
self._serial.read(-1) # 清空缓冲区
except:
pass
# 发送命令
written = self._serial.write(config.LASER_ON_CMD)
self.logger.info(f"[LASER] 写入字节数: {written}")
time.sleep_ms(60)
# 读取回包
resp = self._serial.read(len=20, timeout=10)
if resp:
self.logger.info(f"[LASER] 收到回包 ({len(resp)}字节): {resp.hex()}")
if resp == config.LASER_ON_CMD:
self.logger.info("✅ 激光开启指令已确认")
else:
self.logger.warning("🔇 无回包(可能正常或模块不支持回包)")
self._laser_gpio.value(config.LASER_CONTROL_ON_LEVEL)
self._laser_turned_on = True
return resp
if self.logger:
self.logger.info("[LASER] A14=LOW,激光开启")
return True
except Exception as e:
if self.logger:
self.logger.error(f"[LASER] A14 开启激光失败: {e}")
return False
def turn_off_laser(self):
"""发送指令关闭激光"""
if self._serial is None:
self.logger.error("[LASER] 激光串口未初始化,请先调用 init()")
return None
# 打印调试信息
self.logger.info(f"[LASER] 发送关闭命令: {config.LASER_OFF_CMD.hex()}")
# 清空接收缓冲区
"""A14 输出高电平,关闭激光"""
if self._laser_gpio is None:
if self.logger:
self.logger.error("[LASER] A14 GPIO 未初始化,请先调用 init()")
return False
try:
self._serial.read(-1)
except:
pass
# 发送命令
written = self._serial.write(config.LASER_OFF_CMD)
self.logger.info(f"[LASER] 写入字节数: {written}")
time.sleep_ms(60)
# 读取回包
resp = self._serial.read(20)
if resp:
self.logger.info(f"[LASER] 收到回包 ({len(resp)}字节): {resp.hex()}")
else:
self.logger.warning("🔇 无回包")
self._laser_gpio.value(config.LASER_CONTROL_OFF_LEVEL)
self._laser_turned_on = False
return resp
if self.logger:
self.logger.info("[LASER] A14=HIGH,激光关闭")
return True
except Exception as e:
if self.logger:
self.logger.error(f"[LASER] A14 关闭激光失败: {e}")
return False
def flash_laser(self, duration_ms=1000):
"""闪一下激光(非阻塞版本)"""
+22 -18
View File
@@ -76,12 +76,14 @@ def laser_calibration_worker():
import traceback
traceback.print_exc()
time.sleep_ms(1000) # 等待1秒后继续
def cmd_str():
"""主程序入口"""
# ==================== 第一阶段:硬件初始化 ====================
# 按照 main104.py 的顺序,先完成所有硬件初始化
# 开机第一步先拉高 A14 关闭激光,避免其他硬件初始化期间误亮。
laser_manager.init_control_gpio()
# 1. 引脚功能映射
for pin, func in config.PIN_MAPPINGS.items():
try:
@@ -103,6 +105,8 @@ def cmd_str():
print(f"[BOOT] init_ina226 开始 wall_s={_w_boot:.3f}")
init_ina226()
print(f"[BOOT] init_ina226 结束 wall +{int(round((wall_time.time() - _w_boot) * 1000))} ms")
# 启动 A25 绿灯和 A23 红灯状态指示。
hardware_manager.start_status_led_monitor()
# 4. 初始化显示和相机
_w_boot = wall_time.time()
@@ -132,6 +136,7 @@ def cmd_str():
sync_system_time_from_4g()
# 2.1 WiFi 热点配网兜底:仅当 STA 与 4G 均不可用时起 AP + HTTP;提交后删 /boot/wifi.ap、建 wifi.sta 并 reboot
_ota_pending_path = f"{config.APP_DIR}/ota_pending.json"
try:
from wifi_config_httpd import maybe_start_wifi_ap_fallback
@@ -162,7 +167,11 @@ def cmd_str():
and _loc_black == "yolo"
and bool(getattr(config, "TRIANGLE_BLACK_YOLO_PRELOAD_ON_BOOT", True))
)
_preload_yolo = _preload_yolo or _need_black_preload
_need_target_preload = (
bool(getattr(config, "TARGET_CLASS_YOLO_ENABLE", False))
and bool(getattr(config, "TARGET_CLASS_YOLO_PRELOAD_ON_BOOT", True))
)
_preload_yolo = _preload_yolo or _need_black_preload or _need_target_preload
if _preload_yolo:
preload_yolo_detector(logger)
except Exception as e:
@@ -278,12 +287,13 @@ def cmd_str():
logger.info("系统准备完成...")
last_adc_trigger = 0
trigger_adc_val = 0 # 触发时的气压值,气压需降回此值以下才能再次触发
# 读取一次ADC初始值,防止开机时传感器已有压力导致误触发
enable_check = True
try:
last_adc_val = hardware_manager.adc_obj.read()
except Exception:
last_adc_val = 0
peak_adc_val = 0 # 当前周期内的压力峰值
# 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样
PRESSURE_BATCH_SIZE = 100
@@ -373,22 +383,16 @@ def cmd_str():
pressure_max = adc_val
if len(pressure_buf) >= PRESSURE_BATCH_SIZE:
_flush_pressure_buf("batch")
# 峰值检测:压力从峰值下降时触发,确保捕获到最大冲击时刻
if adc_val > peak_adc_val:
peak_adc_val = adc_val # 更新峰值
if (peak_adc_val >= config.ADC_TRIGGER_THRESHOLD
and adc_val < peak_adc_val
and last_adc_val >= peak_adc_val):
# 封顶后下降沿触发:peak是最大值,当前值开始下降,且上次值还在peak位置
# 突变增量检测:压力增量大于300时触发
# 触发后需等气压降到触发值以下才重新检测增量
if adc_val < trigger_adc_val :
enable_check = True
if (adc_val - last_adc_val) > 200 and enable_check:
hardware_manager.start_idle_timer() # 重新计时
diff_ms = current_time - last_adc_trigger
if diff_ms < 3000:
peak_adc_val = 0 # 去抖期间重置峰值
time.sleep_ms(5)
continue
last_adc_trigger = current_time
peak_adc_val = 0 # 触发后重置峰
# 触发前先把缓存刷出来,避免波形被长耗时处理截断
trigger_adc_val = adc_val # 记录触发时的气压
last_adc_val = adc_val # 更新基准值,防止连续增量误触发
enable_check = False
_flush_pressure_buf("before_trigger")
try:
@@ -407,7 +411,7 @@ def cmd_str():
camera_manager.show(camera_manager.read_frame())
except Exception as e:
pass
time.sleep_ms(5)
time.sleep_ms(1)
last_adc_val = adc_val
except Exception as e:
-13
View File
@@ -1,13 +0,0 @@
[basic]
type = cvimodel
model = model_270139.cvimodel
[extra]
model_type = yolov5
input_type = rgb
mean = 0, 0, 0
scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098
anchors = 10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326
labels = 黑三角和圆环
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
[basic]
type = cvimodel
model = model_270820.cvimodel
model = model_285484.cvimodel
[extra]
model_type = yolov5
@@ -9,5 +9,5 @@ input_type = rgb
mean = 0, 0, 0
scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098
anchors = 10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326
labels = triangle
labels = 20, 40
+115 -8
View File
@@ -23,6 +23,14 @@ from logger_manager import logger_manager
from wifi import wifi_manager
import subprocess
# protobuf 支持
try:
import tcp_messages_pb2 as pb
_HAS_PROTO = True
except ImportError:
_HAS_PROTO = False
print("[NET] tcp_messages_pb2 not found, protobuf disabled")
def _wifi_tls_would_block(exc):
"""
@@ -72,6 +80,9 @@ class NetworkManager:
self._raw_line_data = []
self._manual_trigger_flag = False
# protobuf 协议支持
self._use_proto = _HAS_PROTO # 默认启用 proto(如果可用)
# 限制并发命令线程数
self._cmd_thread_lock = threading.Lock()
self._cmd_thread_count = 0
@@ -711,6 +722,103 @@ class NetworkManager:
"""线程安全地将消息加入队列(公共方法)"""
self._enqueue((msg_type, data_dict), high)
def _make_send_packet(self, msg_type, data_dict):
"""根据协议模式构造发送数据包"""
if self._use_proto and _HAS_PROTO:
return self._make_proto_packet(msg_type, data_dict)
return self._netcore.make_packet(msg_type, data_dict)
def _make_proto_packet(self, msg_type, data_dict):
"""使用 protobuf 序列化构造数据包"""
try:
if msg_type == 1:
# 登录消息
msg = pb.LoginRequest(
device_id=data_dict.get("deviceId", ""),
password=data_dict.get("password", ""),
if_admin=data_dict.get("ifAdmin", False),
version=data_dict.get("version", ""),
vol=data_dict.get("vol", 0),
vol_per=data_dict.get("vol_per", 0),
iccid=data_dict.get("iccid", ""),
)
elif msg_type == 4:
# 心跳消息
msg = pb.Heartbeat(
t=data_dict.get("t", 0),
vol=data_dict.get("vol", 0),
vol_per=data_dict.get("vol_per", 0),
)
elif msg_type == 2:
# 业务逻辑消息
cmd = data_dict.get("cmd", 0)
inner_data = {k: v for k, v in data_dict.items() if k != "cmd"}
data_bytes = json.dumps(inner_data).encode("utf-8") if inner_data else b""
msg = pb.LogicBody(cmd=cmd, data=data_bytes)
else:
# 其他消息类型,回退到 JSON
return self._netcore.make_packet(msg_type, data_dict)
body_bytes = msg.SerializeToString()
return self._netcore.make_packet_pb(msg_type, body_bytes)
except Exception as e:
self.logger.error(f"[NET] protobuf 序列化失败,回退到 JSON: {e}")
return self._netcore.make_packet(msg_type, data_dict)
def _parse_recv(self, payload):
"""解析接收的数据包,返回 (msg_type, body_dict)"""
if self._use_proto and _HAS_PROTO:
msg_type, body_bytes = self._netcore.parse_packet_raw(payload)
if msg_type is None:
return None, None
try:
body_dict = self._parse_proto_body(msg_type, body_bytes)
return msg_type, body_dict
except Exception as e:
self.logger.error(f"[NET] protobuf 反序列化失败: {e}")
# 回退到 JSON 解析
return self._netcore.parse_packet(payload)
else:
return self._netcore.parse_packet(payload)
def _parse_proto_body(self, msg_type, body_bytes):
"""将 protobuf body bytes 反序列化为 dict"""
if msg_type == 1:
msg = pb.LoginResponse()
msg.ParseFromString(body_bytes)
return {"cmd": msg.cmd, "data": msg.data}
elif msg_type == 4:
# 心跳 ACK 通常无 body
return {}
elif msg_type == 2:
msg = pb.LogicBody()
msg.ParseFromString(body_bytes)
result = {"cmd": msg.cmd}
if msg.data:
try:
result["data"] = json.loads(msg.data.decode("utf-8"))
except:
result["data"] = {"raw": msg.data.hex()}
return result
elif msg_type == 40:
msg = pb.OtaFragment()
msg.ParseFromString(body_bytes)
return {"l": msg.l, "d": msg.d, "t": msg.t, "v": msg.v}
elif msg_type == 100:
msg = pb.ImageUploadCommand()
msg.ParseFromString(body_bytes)
return {"uploadUrl": msg.upload_url, "token": msg.token, "shootId": msg.shoot_id, "outlink": msg.outlink}
elif msg_type == 101:
msg = pb.LogUploadCommand()
msg.ParseFromString(body_bytes)
return {"uploadUrl": msg.upload_url, "token": msg.token, "key": msg.key, "outlink": msg.outlink, "archive": msg.archive}
else:
# 未知类型,尝试 JSON 解析
try:
return json.loads(body_bytes.decode("utf-8"))
except:
return {"raw": body_bytes.hex()}
def connect_server(self):
"""
连接到服务器(自动选择WiFi或4G)
@@ -1842,14 +1950,13 @@ class NetworkManager:
login_data = {
"deviceId": self.device_id,
"password": self.password,
"version": config.APP_VERSION,
"version": config.APP_VERSION + ("+proto" if self._use_proto else ""),
"vol": vol_val,
"vol_per": voltage_to_percent(vol_val)
}
iccid_pending_marker = self._maybe_add_iccid_to_login(login_data)
print(f"login_data: {login_data}")
# if not self.tcp_send_raw(self.make_packet(1, login_data)):
if not self.tcp_send_raw(self._netcore.make_packet(1, login_data)):
if not self.tcp_send_raw(self._make_send_packet(1, login_data)):
self._tcp_connected = False
try:
self.disconnect_server()
@@ -1926,7 +2033,7 @@ class NetworkManager:
pass
# msg_type, body = self.parse_packet(payload)
msg_type, body = self._netcore.parse_packet(payload)
msg_type, body = self._parse_recv(payload)
# 处理登录响应
if not logged_in and msg_type == 1:
@@ -2146,7 +2253,7 @@ class NetworkManager:
}
self.safe_enqueue(battery_data, 2)
self.logger.info(f"电量上报: {battery_percent}% 充电: {is_charging()}")
if is_charging():
if getattr(config, "CHARGING_AUTO_POWER_OFF_ENABLED", False) and is_charging():
self.safe_enqueue(
{
"cmd": 700,
@@ -2310,7 +2417,7 @@ class NetworkManager:
if item:
msg_type, data_dict = item
pkt = self._netcore.make_packet(msg_type, data_dict)
pkt = self._make_send_packet(msg_type, data_dict)
if not self.tcp_send_raw(pkt):
# 发送失败:将消息放回队首(队列满则丢弃)
with self.get_queue_lock():
@@ -2339,8 +2446,8 @@ class NetworkManager:
current_time = time.ticks_ms()
if logged_in and current_time - last_heartbeat_send_time > config.HEARTBEAT_INTERVAL * 1000:
vol_val = get_bus_voltage()
if not self.tcp_send_raw(
self._netcore.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})):
heartbeat_pkt = self._make_send_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})
if not self.tcp_send_raw(heartbeat_pkt):
# if not self.tcp_send_raw(self.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})):
send_hartbeat_fail_count += 1
# 短暂波动可能导致一次发送失败:连续失败达到阈值才重连,避免重连风暴
+603
View File
@@ -0,0 +1,603 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
import sys
import logging
import os
import re
import os.path
import collections
import uuid
import argparse
import tarfile
import io
from struct import pack, unpack
PYTHON_MIN_VERSION = (3, 5, 2) # Ubuntu 16.04 LTS contains Python v3.5.2 by default
if sys.version_info < PYTHON_MIN_VERSION:
print("Python >= %r is required" % (PYTHON_MIN_VERSION,))
sys.exit(-1)
try:
import coloredlogs
except ImportError:
coloredlogs = None
try:
import argcomplete
except ImportError:
argcomplete = None
TOC_HEADER_NAME = 0xAA640001
FIP_MAX_SIZE = 0xA0000
FIP_ALIGN_SIZE = 2 * 1024
ENTRY_SIZE = 0x28
IV_ZERO = b"\0" * 16
class FIP_HEADER_FLAG:
BitRange = collections.namedtuple("BitRange", "shift, bits")
REE_SCS = BitRange(0, 2)
REE_ENCRYPTION = BitRange(2, 2)
@classmethod
def test(cls, value, flag):
v = value >> flag.shift
v &= (1 << flag.bits) - 1
return v
@classmethod
def value(cls, flag):
v = (1 << flag.bits) - 1
v <<= flag.shift
return v
class FIP_UUID:
# from arm-trusted-firmware/include/tools_share/firmware_image_package.h
uuid_c_define = """
/* ToC Entry UUIDs */
#define UUID_LICENSE_FILE \
{0x25360c62, 0x5151, 0x48ad, 0xb5, 0x91, {0x2d, 0x35, 0x67, 0x26, 0x85, 0xa5} }
#define UUID_TRUSTED_UPDATE_FIRMWARE_SCP_BL2U \
{0x03279265, 0x742f, 0x44e6, 0x8d, 0xff, {0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10} }
#define UUID_TRUSTED_UPDATE_FIRMWARE_BL2U \
{0x37ebb360, 0xe5c1, 0x41ea, 0x9d, 0xf3, {0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01} }
#define UUID_TRUSTED_UPDATE_FIRMWARE_NS_BL2U \
{0x111d514f, 0xe52b, 0x494e, 0xb4, 0xc5, {0x83, 0xc2, 0xf7, 0x15, 0x84, 0x0a} }
#define UUID_TRUSTED_FWU_CERT \
{0xb28a4071, 0xd618, 0x4c87, 0x8b, 0x2e, {0xc6, 0xdc, 0xcd, 0x50, 0xf0, 0x96} }
#define UUID_TRUSTED_BOOT_FIRMWARE_BL2 \
{0x0becf95f, 0x224d, 0x4d3e, 0xa5, 0x44, {0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a} }
#define UUID_BLD \
{0x3dfd6697, 0xbe89, 0x49e8, 0xae, 0x5d, {0x78, 0xa1, 0x40, 0x60, 0x82, 0x13} }
#define UUID_EL3_RUNTIME_FIRMWARE_BL31 \
{0x6d08d447, 0xfe4c, 0x4698, 0x9b, 0x95, {0x29, 0x50, 0xcb, 0xbd, 0x5a, 0x00} }
#define UUID_SECURE_PAYLOAD_BL32 \
{0x89e1d005, 0xdc53, 0x4713, 0x8d, 0x2b, {0x50, 0x0a, 0x4b, 0x7a, 0x3e, 0x38} }
#define UUID_NON_TRUSTED_FIRMWARE_BL33 \
{0xa7eed0d6, 0xeafc, 0x4bd5, 0x97, 0x82, {0x99, 0x34, 0xf2, 0x34, 0xb6, 0xe4} }
/* Key certificates */
#define UUID_ROT_KEY_CERT \
{0x721d2d86, 0x60f8, 0x11e4, 0x92, 0x0b, {0x8b, 0xe7, 0x62, 0x16, 0x0f, 0x24} }
#define UUID_BLD1_KEY_CERT \
{0x90e87e82, 0x60f8, 0x11e4, 0xa1, 0xb4, {0x77, 0x7a, 0x21, 0xb4, 0xf9, 0x4c} }
#define UUID_BLD2_KEY_CERT \
{0xa1214202, 0x60f8, 0x11e4, 0x8d, 0x9b, {0xf3, 0x3c, 0x0e, 0x15, 0xa0, 0x14} }
#define UUID_SOC_FW_KEY_CERT \
{0xccbeb88a, 0x60f9, 0x11e4, 0x9a, 0xd0, {0xeb, 0x48, 0x22, 0xd8, 0xdc, 0xf8} }
#define UUID_TRUSTED_OS_FW_KEY_CERT \
{0x03d67794, 0x60fb, 0x11e4, 0x85, 0xdd, {0xb7, 0x10, 0x5b, 0x8c, 0xee, 0x04} }
#define UUID_BL33_KEY_CERT \
{0x2a83d58a, 0x60fb, 0x11e4, 0x8a, 0xaf, {0xdf, 0x30, 0xbb, 0xc4, 0x98, 0x59} }
/* Content certificates */
#define UUID_TRUSTED_BOOT_FW_CERT \
{0xea69e2d6, 0x635d, 0x11e4, 0x8d, 0x8c, {0x9f, 0xba, 0xbe, 0x99, 0x56, 0xa5} }
#define UUID_BLD_CONTENT_CERT \
{0x046fbe44, 0x635e, 0x11e4, 0xb2, 0x8b, {0x73, 0xd8, 0xea, 0xae, 0x96, 0x56} }
#define UUID_SOC_FW_CONTENT_CERT \
{0x200cb2e2, 0x635e, 0x11e4, 0x9c, 0xe8, {0xab, 0xcc, 0xf9, 0x2b, 0xb6, 0x66} }
#define UUID_TRUSTED_OS_FW_CONTENT_CERT \
{0x11449fa4, 0x635e, 0x11e4, 0x87, 0x28, {0x3f, 0x05, 0x72, 0x2a, 0xf3, 0x3d} }
#define UUID_BL33_CONTENT_CERT \
{0xf3c1c48e, 0x635d, 0x11e4, 0xa7, 0xa9, {0x87, 0xee, 0x40, 0xb2, 0x3f, 0xa7} }
/* CV keys */
#define UUID_CV_TRUSTED_KEY_CERT \
{0x64fbfc49, 0x4b8c, 0x4ad3, 0xb9, 0x92, {0x93, 0x55, 0x89, 0xee, 0xf0, 0x12} }
#define UUID_CV_NON_TRUSTED_KEY_CERT \
{0xcb48bf0d, 0x7012, 0x4201, 0xbc, 0x35, {0x8a, 0x51, 0xc4, 0x90, 0x90, 0x94} }
/* DDR init*/
#define UUID_CV_DDRINIT_KEY_CERT \
{0xa61c53c9, 0x886c, 0x484f, 0x96, 0x5d, {0xd2, 0xda, 0xd7, 0xc3, 0xeb, 0x13} }
#define UUID_CV_DDRINIT_CONTENT_CERT \
{0x9dfaabd2, 0x7f1b, 0x47e6, 0xa8, 0xa6, {0x6a, 0xc3, 0x10, 0xcc, 0xac, 0x91} }
#define UUID_CV_DDRINIT \
{0x5888a5cd, 0x38fc, 0x4f66, 0xae, 0x3d, {0x2e, 0x18, 0x6d, 0x69, 0x41, 0xfb} }
/* Fast boot */
#define UUID_CV_FASTBOOT_KEY_CERT \
{0x285df54e, 0x7b50, 0x4309, 0x9b, 0x52, {0x4b, 0xc4, 0x92, 0x82, 0x60, 0xdd} }
#define UUID_CV_FASTBOOT_CONTENT_CERT \
{0x61f7595b, 0x8d77, 0x4e13, 0x91, 0x2a, {0x63, 0x6e, 0x58, 0xda, 0x5b, 0x69} }
#define UUID_CV_FASTBOOT \
{0x43766198, 0xc363, 0x48db, 0xa9, 0x97, {0xf1, 0x0e, 0x93, 0x80, 0x4f, 0xea} }
"""
@classmethod
def cls_init(cls):
txt = cls.uuid_c_define
txt = txt.replace("\r\n", "\n")
txt = txt.replace("\\\n", "\n")
rx = r"""
\#define\s+
(?P<name>\S+)\s+
{
\s*(?P<u0>0x\S+)\s*,\s*
\s*(?P<u1>0x\S+)\s*,\s*
\s*(?P<u2>0x\S+)\s*,\s*
\s*(?P<u3>0x\S+)\s*,\s*
\s*(?P<u4>0x\S+)\s*,\s*
{
\s*(?P<u5>0x\S+)\s*,\s*
\s*(?P<u6>0x\S+)\s*,\s*
\s*(?P<u7>0x\S+)\s*,\s*
\s*(?P<u8>0x\S+)\s*,\s*
\s*(?P<u9>0x\S+)\s*,\s*
\s*(?P<u10>0x\S+)\s*
}\s*,?\s*
}
"""
for m in re.finditer(rx, txt, flags=re.X):
name = m.group("name")
u = m.group(*["u%d" % i for i in range(11)])
u = [int(i, 0) for i in u]
u = pack("<IHHBBBBBBBB", *u)
u = uuid.UUID(bytes=u)
setattr(cls, name, u)
class Entry:
__slots__ = ["name", "loc", "uuid", "address", "flag", "content"]
def __init__(self):
self.loc = 0
self.uuid = uuid.UUID(int=0)
self.address = 0
self.flag = 0
self.content = b""
@classmethod
def make(cls, uuid, content):
entry = cls()
entry.uuid = uuid
entry.content = content
return entry
@classmethod
def from_fip(cls, name, loc, fip_bin):
data = fip_bin[loc : loc + ENTRY_SIZE]
uuid_bytes, address, size, flag = unpack("<16sQQQ", data)
content = fip_bin[address : address + size]
entry = cls()
entry.name = name
entry.loc = loc
entry.uuid = uuid.UUID(bytes=uuid_bytes)
entry.address = address
entry.flag = flag
entry.content = content
return entry
def to_bytes(self):
return pack("<16sQQQ", self.uuid.bytes, self.address, self.size, self.flag)
@property
def size(self):
return len(self.content)
@property
def end(self):
return self.address + self.size
def __str__(self):
return "<%-31s loc=0x%03x U=%s a=0x%05x,0x%05x,0x%05x f=0x%x>" % (
self.name,
self.loc,
self.uuid.hex[:8],
self.address,
self.end,
self.size,
self.flag,
)
class FIP:
ENTRY_NAMES = collections.OrderedDict(
[
("LICENSE_FILE", "UUID_LICENSE_FILE"),
("BL2", "UUID_TRUSTED_BOOT_FIRMWARE_BL2"),
("BLD", "UUID_BLD"),
("BL31", "UUID_EL3_RUNTIME_FIRMWARE_BL31"),
("BL32", "UUID_SECURE_PAYLOAD_BL32"),
("BL33", "UUID_NON_TRUSTED_FIRMWARE_BL33"),
("BLD1_KEY_CERT", "UUID_BLD1_KEY_CERT"),
("BLD2_KEY_CERT", "UUID_BLD2_KEY_CERT"),
("CV_TRUSTED_KEY_CERT", "UUID_CV_TRUSTED_KEY_CERT"),
("SOC_FW_KEY_CERT", "UUID_SOC_FW_KEY_CERT"),
("TRUSTED_OS_FW_KEY_CERT", "UUID_TRUSTED_OS_FW_KEY_CERT"),
("CV_NON_TRUSTED_KEY_CERT", "UUID_CV_NON_TRUSTED_KEY_CERT"),
("BL33_KEY_CERT", "UUID_BL33_KEY_CERT"),
("TRUSTED_BOOT_FW_CERT", "UUID_TRUSTED_BOOT_FW_CERT"),
("BLD_CONTENT_CERT", "UUID_BLD_CONTENT_CERT"),
("SOC_FW_CONTENT_CERT", "UUID_SOC_FW_CONTENT_CERT"),
("TRUSTED_OS_FW_CONTENT_CERT", "UUID_TRUSTED_OS_FW_CONTENT_CERT"),
("BL33_CONTENT_CERT", "UUID_BL33_CONTENT_CERT"),
("CV_DDRINIT", "UUID_CV_DDRINIT"),
("CV_FASTBOOT", "UUID_CV_FASTBOOT"),
]
)
TOC_Header = collections.namedtuple(
"TOC_Header", "name, serial, flag_res, flag_plat, flag_res2"
)
def __init__(self, path):
logging.info("FIP_BIN: %s", path)
self.path = path
def load(self):
with open(self.path, "rb") as fp:
self.binary = fp.read(FIP_MAX_SIZE)
logging.info("%s is %d bytes", self.path, len(self.binary))
self.header = self.TOC_Header(*unpack("<IIIHH", self.binary[0x00:0x10]))
if self.header.name != TOC_HEADER_NAME:
raise ValueError(
"FIP header is 0x%08x but should be 0x%08x"
% (self.header[0], TOC_HEADER_NAME)
)
logging.info("TOC header: flag_plat=0x%04x", self.header.flag_plat)
logging.info(
" REE_SCS: %r",
FIP_HEADER_FLAG.test(self.header.flag_plat, FIP_HEADER_FLAG.REE_SCS),
)
logging.info(
" REE_ENCRYPTION: %r",
FIP_HEADER_FLAG.test(self.header.flag_plat, FIP_HEADER_FLAG.REE_ENCRYPTION),
)
ents = []
for k, v in self.ENTRY_NAMES.items():
try:
ents.append((k, self.find_entry(v)))
except ValueError as err:
logging.warning("%s", err)
ents.sort(key=lambda x: x[1].address)
for n, (k, v) in enumerate(ents):
logging.debug("%s", v)
if n > 0:
pk, pv = ents[n - 1]
if v.loc != pv.loc + ENTRY_SIZE or v.address != pv.address + pv.size:
raise Exception("Invalid FIP")
rest = self.binary[ents[-1][1].end :]
loc = rest.find(b"APLB")
if loc < 0:
raise Exception("No BLD/DDRC")
self.blp_ddrc_binary = rest[loc:]
logging.debug("blp_ddrc: 0x%04x at 0x%08x", len(self.blp_ddrc_binary), loc)
self.ents = collections.OrderedDict(ents)
def make_fip(self, output_path=None):
logging.info("New TOC header: flag_plat=0x%04x", self.header.flag_plat)
header_bin = pack("<IIIHH", *self.header)
fip_bin = header_bin
# Sort self.ents by the order of FIP.ENTRY_NAMES
sorted_ents = collections.OrderedDict()
for name in self.ENTRY_NAMES:
try:
sorted_ents[name] = self.ents[name]
except KeyError:
pass
self.ents = sorted_ents
offset = (len(self.ents) + 1) * ENTRY_SIZE + 0x10
for k, v in self.ents.items():
v.address = offset
fip_bin += v.to_bytes()
offset += v.size
null_entry = Entry()
null_entry.address = offset
fip_bin += null_entry.to_bytes()
for k, v in self.ents.items():
fip_bin += v.content
if (len(fip_bin) % FIP_ALIGN_SIZE) > 0:
fip_bin += b"\x00" * (FIP_ALIGN_SIZE - len(fip_bin) % FIP_ALIGN_SIZE)
fip_bin += self.blp_ddrc_binary
if output_path:
path = output_path
else:
path = os.path.splitext(self.path)
path = path[0] + "_signed_encrypted" + path[1]
logging.info("Save new FIP image to %s", path)
with open(path, "wb") as fp:
fp.write(fip_bin)
def dump_uuids(self):
for k, v in vars(FIP_UUID).items():
if k.startswith("UUID_"):
print("%-38s" % k, v.hex)
def find_entry(self, name):
# UUID=0, offset=any, size=0, flags=0
nullm = re.search(rb"\0{16}.{8}\0{16}", self.binary, flags=re.DOTALL)
if nullm is None:
raise Exception("NULL TOC entry is not found")
max_toc_size = nullm.start(0)
uuid = getattr(FIP_UUID, name)
loc = self.binary.find(uuid.bytes, 0, max_toc_size)
if loc < 0:
raise ValueError("%s is not found" % name)
return Entry.from_fip(name, loc, self.binary)
def entry(args):
logging.debug("cmd_fip")
def init_logging(log_file=None, file_level="DEBUG", stdout_level="WARNING"):
root_logger = logging.getLogger()
root_logger.setLevel(logging.NOTSET)
fmt = "%(asctime)s %(levelname)8s:%(name)s:%(message)s"
if log_file is not None:
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setFormatter(logging.Formatter(fmt))
file_handler.setLevel(file_level)
root_logger.addHandler(file_handler)
if coloredlogs:
os.environ["COLOREDLOGS_DATE_FORMAT"] = "%H:%M:%S"
field_styles = {
"asctime": {"color": "green"},
"hostname": {"color": "magenta"},
"levelname": {"color": "black", "bold": True},
"name": {"color": "blue"},
"programname": {"color": "cyan"},
}
level_styles = coloredlogs.DEFAULT_LEVEL_STYLES
level_styles["debug"]["color"] = "cyan"
coloredlogs.install(
level=stdout_level,
fmt=fmt,
field_styles=field_styles,
level_styles=level_styles,
milliseconds=True,
)
def parse_fip(fip_path):
logging.debug("parse_fip: %s", fip_path)
fip = FIP(fip_path)
fip.load()
def unpack_fip(fip_path):
logging.debug("unpack_fip: %s", fip_path)
fip = FIP(fip_path)
fip.load()
def save(name, content):
fn = os.path.splitext(fip_path)
fn = "%s_%s%s" % (fn[0], name, fn[1])
logging.info("Save %s", fn)
with open(fn, "wb") as fp:
fp.write(content)
for k, v in fip.ents.items():
save(k, v.content)
save("BLP_DDRC", fip.blp_ddrc_binary)
def tar_bld(fip_path, output_path, multibin):
logging.debug("tar_bld: %s multibin=%r", fip_path, multibin)
fip = FIP(fip_path)
fip.load()
members = [
"BLD_CONTENT_CERT",
"BLD2_KEY_CERT",
"BLD1_KEY_CERT",
"CV_DDRINIT" if multibin else "BLD",
]
if not output_path:
output_path = os.path.join(os.path.dirname(fip_path), "bld.tar")
logging.info("bld_tar_path=%s", output_path)
with tarfile.open(output_path, "w") as tf:
for m in members:
logging.debug("Tar %s", m)
try:
fp = io.BytesIO(fip.ents[m].content)
except KeyError:
logging.warning("%s doesn't exist", m)
continue
info = tarfile.TarInfo(name=m + ".bin")
info.size = len(fp.getbuffer())
tf.addfile(tarinfo=info, fileobj=fp)
def merge_fip(fip_path, inputs, output_path):
logging.debug("merge_fip: %s", fip_path)
fip = FIP(fip_path)
fip.load()
for name in FIP.ENTRY_NAMES:
binary = inputs.get(name)
if not binary:
continue
logging.debug("merge %s", name)
ent = fip.ents.get(name)
if ent:
ent.content = binary
else:
ent = Entry.make(getattr(FIP_UUID, "UUID_" + name), binary)
fip.ents[name] = ent
binary = inputs.get("BLP_DDRC")
if binary:
fip.blp_ddrc_binary = binary
if not output_path:
fn = os.path.splitext(fip_path)
fn = "%s_%s%s" % (fn[0], "merged", fn[1])
output_path = fn
fip.make_fip(output_path)
def round_up(n, k):
return (n + k - 1) // k * k
def read_blp_and_ddrc(inputs, blp_path, ddrc_path):
logging.info("Open %s and %s", blp_path, ddrc_path)
with open(blp_path, "rb") as fp:
blp_bin = fp.read()
logging.info("Open %s", ddrc_path)
with open(ddrc_path, "rb") as fp:
ddrc_bin = fp.read()
blp_bin += b"\0" * (round_up(len(blp_bin), FIP_ALIGN_SIZE) - len(blp_bin))
ddrc_bin += b"\0" * (round_up(len(ddrc_bin), FIP_ALIGN_SIZE) - len(ddrc_bin))
inputs["BLP_DDRC"] = blp_bin + ddrc_bin
def read_bld_tar(inputs, bld_tar_path, multibin):
logging.info("Open %s multibin=%r", bld_tar_path, multibin)
members = [
"BLD_CONTENT_CERT.bin",
"BLD2_KEY_CERT.bin",
"BLD1_KEY_CERT.bin",
"CV_DDRINIT.bin" if multibin else "BLD.bin",
]
with tarfile.open(bld_tar_path, "r") as tf:
for member in members:
try:
fp = tf.extractfile(member)
inputs[os.path.splitext(member)[0]] = fp.read()
except KeyError:
logging.warning("%s does not exist", member)
def main():
parser = argparse.ArgumentParser(description="FIP packer")
for name in FIP.ENTRY_NAMES:
parser.add_argument(
"--add-%s" % name.lower(),
dest=name,
type=str,
help="Merge %s into FIP" % name,
)
parser.add_argument(
"--add-blp-ddrc", dest="BLP_DDRC", type=str, help="Merge BLP+DDRC into FIP"
)
parser.add_argument("--add-blp", dest="BLP", type=str, help="Merge BLP into FIP")
parser.add_argument("--add-ddrc", dest="DDRC", type=str, help="Merge DDRC into FIP")
parser.add_argument(
"--add-bld-tar", dest="BLD_TAR", type=str, help="Merge BLD.tar into FIP"
)
parser.add_argument("--multibin", action="store_true", help="Use multibin")
parser.add_argument("FIP_BIN", type=str, nargs=1, help="Input FIP binary")
parser.add_argument("--output", type=str, help="Output filename")
parser.add_argument(
"--version", action="store_true", help="Output version information and exit"
)
parser.add_argument(
"--verbose",
help="Increase output verbosity",
action="store_const",
const=logging.DEBUG,
default=logging.DEBUG,
)
parser.add_argument("--unpack", action="store_true", help="Unpack FIP.bin")
parser.add_argument("--parse", action="store_true", help="Parse FIP.bin")
parser.add_argument(
"--tar-bld", action="store_true", help="Extrace BLD.bin and tar"
)
if argcomplete:
argcomplete.autocomplete(parser)
args = parser.parse_args()
init_logging(stdout_level=args.verbose)
logging.debug("args=%r", args)
FIP_UUID.cls_init()
if args.parse:
parse_fip(args.FIP_BIN[0])
if args.unpack:
unpack_fip(args.FIP_BIN[0])
if args.tar_bld:
tar_bld(args.FIP_BIN[0], args.output, args.multibin)
inputs = collections.OrderedDict()
for name in list(FIP.ENTRY_NAMES) + ["BLP_DDRC"]:
fn = getattr(args, name)
if not fn:
continue
logging.info("Open %s", fn)
with open(fn, "rb") as fp:
inputs[name] = fp.read()
if args.BLP or args.DDRC:
read_blp_and_ddrc(inputs, args.BLP, args.DDRC)
if args.BLD_TAR:
read_bld_tar(inputs, args.BLD_TAR, args.multibin)
if len(inputs):
merge_fip(args.FIP_BIN[0], inputs, args.output)
if __name__ == "__main__":
main()
+10
View File
@@ -7,10 +7,12 @@
import config
import os
import subprocess
import _thread
from logger_manager import logger_manager
from maix import time as maix_time
_INA226_PRESENT = None
_INA226_LOCK = _thread.allocate_lock()
def _ina226_ready() -> bool:
@@ -32,7 +34,11 @@ def write_register(reg, value):
data = [(value >> 8) & 0xFF, value & 0xFF]
# 某些底层驱动在失败时只打印 “write failed” 并返回 -1,而不是抛异常;
# 为避免误判“初始化成功”导致后续 readfrom_mem SIGSEGV,这里把失败显式转成异常。
_INA226_LOCK.acquire()
try:
ret = hardware_manager.bus.writeto_mem(config.INA226_ADDR, reg, bytes(data))
finally:
_INA226_LOCK.release()
if isinstance(ret, int) and ret < 0:
if logger:
logger.error(f"[INA226] writeto_mem 失败: addr=0x{config.INA226_ADDR:02X} reg=0x{reg:02X} ret={ret}")
@@ -42,7 +48,11 @@ def write_register(reg, value):
def read_register(reg):
"""读取INA226寄存器"""
from hardware import hardware_manager
_INA226_LOCK.acquire()
try:
data = hardware_manager.bus.readfrom_mem(config.INA226_ADDR, reg, 2)
finally:
_INA226_LOCK.release()
return (data[0] << 8) | data[1]
+31
View File
@@ -325,6 +325,18 @@ def process_shot(adc_val):
# 网络事件移到拍照之后,避免阻塞拍照
network_manager.safe_enqueue({"shoot_event": "start"}, msg_type=2, high=True)
# Classify only the current shot frame; never reuse a previous result.
target_class_result = None
try:
from target_roi_yolo import try_get_target_class_from_yolo
target_class_result = try_get_target_class_from_yolo(frame, logger=logger)
if logger:
logger.info(f"[YOLO-TARGET] 当前箭业务结果: {target_class_result}")
except Exception as exc:
if logger:
logger.warning(f"[YOLO-TARGET] 当前箭分类失败,按未知处理: {exc}")
# 调用算法分析
analysis_result = analyze_shot(frame)
@@ -384,11 +396,25 @@ def process_shot(adc_val):
srv_y = round(float(dy), 4) if dy is not None else 200.0
# 构造上报数据
target_label = (
target_class_result.get("label")
if isinstance(target_class_result, dict)
else None
)
target_confidence = (
target_class_result.get("confidence")
if isinstance(target_class_result, dict)
else None
)
inner_data = {
"shot_id": shot_id,
"x": srv_x,
"y": srv_y,
"r": 20.0, # 保留字段(服务端当前忽略,物理外环半径 cm)
"target_class": target_label,
"target_class_confidence": (
float(target_confidence) if target_confidence is not None else None
),
"d": round((distance_m or 0.0) * 100),
"d_laser": round((laser_distance_m or 0.0) * 100),
"d_laser_quality": laser_signal_quality,
@@ -416,6 +442,11 @@ def process_shot(adc_val):
inner_data["ellipse_center_y"] = None
report_data = {"cmd": 1, "data": inner_data}
if logger:
logger.info(
f"[REPORT-TARGET] enqueue shot_id={shot_id}, "
f"target_class={target_label}, confidence={target_confidence}"
)
network_manager.safe_enqueue(report_data, msg_type=2, high=True)
# 数据上报后再画标注,不干扰检测阶段的原始画面
+143 -1
View File
@@ -89,6 +89,29 @@ def _stage2_roi_crop_save_worker(
_detector_by_path = {}
def _resolve_model_path(model_path: str):
"""Resolve a model in either the installed app or MaixVision run directory."""
model_path = (model_path or "").strip()
if model_path and os.path.isfile(model_path):
return model_path
if not model_path:
return ""
name = os.path.basename(model_path)
module_dir = os.path.dirname(os.path.abspath(__file__))
candidates = (
os.path.join(module_dir, name),
os.path.join(module_dir, "test", name),
os.path.join("/tmp/maixpy_run", name),
os.path.join("/tmp/maixpy_run", "test", name),
os.path.join(os.getcwd(), name),
os.path.join(os.getcwd(), "test", name),
)
for candidate in candidates:
if os.path.isfile(candidate):
return candidate
return model_path
def reset_yolo_detector_cache():
"""切换模型路径时可调用(通常不必)。"""
global _detector_by_path
@@ -175,6 +198,23 @@ def preload_yolo_detector(logger=None):
% (_loc_black,)
)
if bool(getattr(cfg, "TARGET_CLASS_YOLO_ENABLE", False)) and bool(
getattr(cfg, "TARGET_CLASS_YOLO_PRELOAD_ON_BOOT", True)
):
class_model_path = _resolve_model_path(
getattr(cfg, "TARGET_CLASS_YOLO_MODEL_PATH", "") or ""
)
class_detector = _get_detector(class_model_path)
if class_detector is None:
if logger:
logger.warning(
f"[YOLO-TARGET] 预加载失败:无法加载模型 {class_model_path}"
)
else:
ok = True
if logger:
logger.info(f"[YOLO-TARGET] 靶规格模型已预加载: {class_model_path}")
return ok
@@ -206,8 +246,10 @@ def _det_obj_class_id(o):
if v is None:
continue
try:
if callable(v):
v = v()
return int(float(v))
except (TypeError, ValueError):
except (TypeError, ValueError, AttributeError):
continue
return None
@@ -242,6 +284,106 @@ def _normalize_objs(objs):
return out
def _det_obj_score(o):
"""Return confidence across supported Maix YOLO result formats."""
for key in ("score", "confidence", "conf", "prob"):
if hasattr(o, key):
try:
value = getattr(o, key)
if callable(value):
value = value()
value = float(value)
if value == value:
return value
except (TypeError, ValueError, AttributeError):
pass
return 0.0
def try_get_target_class_from_yolo(maix_frame, logger=None):
"""Classify the current target as 20cm or 40cm; return None if unknown."""
try:
import config as cfg
except Exception:
return None
if not bool(getattr(cfg, "TARGET_CLASS_YOLO_ENABLE", False)):
return None
model_path = _resolve_model_path(
getattr(cfg, "TARGET_CLASS_YOLO_MODEL_PATH", "") or ""
)
if not os.path.isfile(model_path):
if logger:
logger.warning(f"[YOLO-TARGET] 模型文件不存在: {model_path}")
return None
detector = _get_detector(model_path)
if detector is None:
if logger:
logger.warning("[YOLO-TARGET] 无法加载 nn.YOLOv5")
return None
conf_th = float(getattr(cfg, "TARGET_CLASS_YOLO_CONF_TH", 0.5))
iou_th = float(getattr(cfg, "TARGET_CLASS_YOLO_IOU_TH", 0.45))
labels = getattr(cfg, "TARGET_CLASS_YOLO_LABELS", (20, 40))
if isinstance(labels, str):
labels = tuple(x.strip() for x in labels.split(",") if x.strip())
labels = tuple(labels)
def _detect(threshold):
try:
raw = detector.detect(maix_frame, conf_th=threshold, iou_th=iou_th)
except Exception as exc:
if logger:
logger.warning(f"[YOLO-TARGET] detect 异常: {exc}")
return []
return _normalize_objs(raw if raw is not None else [])
def _candidates(objs):
found = []
for obj in objs:
class_id = _det_obj_class_id(obj)
if class_id is None or class_id < 0 or class_id >= len(labels):
continue
try:
label = int(float(labels[class_id]))
except (TypeError, ValueError):
continue
if label in (20, 40):
found.append((label, class_id, _det_obj_score(obj)))
return found
objects = _detect(conf_th)
candidates = _candidates(objects)
if logger and objects:
logger.info(
"[YOLO-TARGET] 原始框=%d, 解析类别=%s"
% (
len(objects),
[(_det_obj_class_id(o), _det_obj_score(o)) for o in objects[:8]],
)
)
if not candidates and bool(
getattr(cfg, "TARGET_CLASS_YOLO_RETRY_ON_EMPTY", False)
):
retry_th = float(getattr(cfg, "TARGET_CLASS_YOLO_RETRY_CONF_TH", conf_th))
if 0 < retry_th < conf_th:
candidates = _candidates(_detect(retry_th))
if not candidates:
if logger:
logger.warning("[YOLO-TARGET] 当前帧未识别到 20/40,按未知处理")
return None
label, class_id, confidence = max(candidates, key=lambda item: item[2])
result = {"label": label, "class_id": class_id, "confidence": confidence}
if logger:
logger.info(
f"[YOLO-TARGET] 当前帧分类={label}, class_id={class_id}, "
f"conf={confidence:.3f}"
)
return result
def _det_to_src_xyxy(o, coord_mode: str, src_w: int, src_h: int, net_w: int, net_h: int):
"""把单个检测框转为全图坐标系下的 xyxy(半开区间语义与后续 clip 一致)。"""
x, y, w, h = float(o.x), float(o.y), float(o.w), float(o.h)
+295
View File
@@ -0,0 +1,295 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: tcp_messages.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12tcp_messages.proto\x12\x03tcp\"\x83\x01\n\x0cLoginRequest\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x10\n\x08if_admin\x18\x03 \x01(\x08\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x0b\n\x03vol\x18\x05 \x01(\x01\x12\x0f\n\x07vol_per\x18\x06 \x01(\x01\x12\r\n\x05iccid\x18\x07 \x01(\t\"*\n\rLoginResponse\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\"4\n\tHeartbeat\x12\t\n\x01t\x18\x01 \x01(\x03\x12\x0b\n\x03vol\x18\x02 \x01(\x01\x12\x0f\n\x07vol_per\x18\x03 \x01(\x01\"&\n\tLogicBody\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\")\n\x0cResponseBody\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"@\n\x0bMonitorBody\x12\x11\n\tdevice_id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x10\n\x08msg_type\x18\x03 \x01(\r\",\n\x16MonitorDevicesResponse\x12\x12\n\ndevice_ids\x18\x01 \x03(\t\"9\n\x0bOtaFragment\x12\t\n\x01l\x18\x01 \x01(\x05\x12\t\n\x01\x64\x18\x02 \x01(\t\x12\t\n\x01t\x18\x03 \x01(\x05\x12\t\n\x01v\x18\x04 \x01(\t\"\xab\x02\n\tShootData\x12\x0f\n\x07shot_id\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\x12\t\n\x01r\x18\x04 \x01(\x01\x12\t\n\x01\x64\x18\x05 \x01(\x01\x12\x0b\n\x03\x61\x64\x63\x18\x06 \x01(\x01\x12\x14\n\x0ctarget_class\x18\x07 \x01(\t\x12\x1f\n\x17target_class_confidence\x18\x08 \x01(\x01\x12\x0f\n\x07\x64_laser\x18\t \x01(\x01\x12\x17\n\x0f\x64_laser_quality\x18\n \x01(\x01\x12\t\n\x01m\x18\x0b \x01(\t\x12\x14\n\x0claser_method\x18\x0c \x01(\t\x12\x10\n\x08target_x\x18\r \x01(\x01\x12\x10\n\x08target_y\x18\x0e \x01(\x01\x12\x15\n\roffset_method\x18\x0f \x01(\t\x12\x17\n\x0f\x64istance_method\x18\x10 \x01(\t\"!\n\nShootEvent\x12\x13\n\x0bshoot_event\x18\x01 \x01(\t\"C\n\rBatteryReport\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\x01\x12\x0f\n\x07voltage\x18\x02 \x01(\x01\x12\x10\n\x08net_type\x18\x03 \x01(\t\"9\n\x11\x43\x65nterPointResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\"3\n\x0e\x43\x65nterPointSet\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\"(\n\tOtaResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\"\x1f\n\rGenericResult\x12\x0e\n\x06result\x18\x01 \x01(\t\"&\n\x08IpReport\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\n\n\x02ip\x18\x02 \x01(\t\"Z\n\x12ImageUploadCommand\x12\x12\n\nupload_url\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x10\n\x08shoot_id\x18\x03 \x01(\t\x12\x0f\n\x07outlink\x18\x04 \x01(\t\"d\n\x10LogUploadCommand\x12\x12\n\nupload_url\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0f\n\x07outlink\x18\x04 \x01(\t\x12\x0f\n\x07\x61rchive\x18\x05 \x01(\t\"K\n\x0eOtaRequestData\x12\x0c\n\x04ssid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x0b\n\x03url\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\"1\n\x0fWifiConnectData\x12\x0c\n\x04ssid\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"_\n\x11ImageUploadResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x10\n\x08shoot_id\x18\x02 \x01(\t\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06reason\x18\x04 \x01(\t\x12\x0b\n\x03via\x18\x05 \x01(\t\"v\n\x0fLogUploadResult\x12\x0e\n\x06result\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x10\n\x08\x66ilename\x18\x03 \x01(\t\x12\x13\n\x0bstatus_code\x18\x04 \x01(\x05\x12\x0c\n\x04ssid\x18\x05 \x01(\t\x12\x0e\n\x06\x64\x65tail\x18\x06 \x01(\t\"J\n\x14\x42\x61tteryQueryResponse\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\x01\x12\x0f\n\x07voltage\x18\x02 \x01(\x01\x12\x10\n\x08net_type\x18\x03 \x01(\t\"\'\n\x0fOta4gSubCodeReq\x12\t\n\x01l\x18\x01 \x01(\x05\x12\t\n\x01v\x18\x02 \x01(\t\"5\n\x10Ota4gSubCodeResp\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\x12\t\n\x01l\x18\x02 \x01(\x05\x12\t\n\x01v\x18\x03 \x01(\t\"\x1e\n\x0fShutdownCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\r\" \n\x0c\x41utoShutdown\x12\x10\n\x08poweroff\x18\x01 \x01(\tBB\n\rcom.shoot.tcpZ1git.shelingxingqiu.com/shoot-tcp-server/proto;tcpb\x06proto3')
_LOGINREQUEST = DESCRIPTOR.message_types_by_name['LoginRequest']
_LOGINRESPONSE = DESCRIPTOR.message_types_by_name['LoginResponse']
_HEARTBEAT = DESCRIPTOR.message_types_by_name['Heartbeat']
_LOGICBODY = DESCRIPTOR.message_types_by_name['LogicBody']
_RESPONSEBODY = DESCRIPTOR.message_types_by_name['ResponseBody']
_MONITORBODY = DESCRIPTOR.message_types_by_name['MonitorBody']
_MONITORDEVICESRESPONSE = DESCRIPTOR.message_types_by_name['MonitorDevicesResponse']
_OTAFRAGMENT = DESCRIPTOR.message_types_by_name['OtaFragment']
_SHOOTDATA = DESCRIPTOR.message_types_by_name['ShootData']
_SHOOTEVENT = DESCRIPTOR.message_types_by_name['ShootEvent']
_BATTERYREPORT = DESCRIPTOR.message_types_by_name['BatteryReport']
_CENTERPOINTRESULT = DESCRIPTOR.message_types_by_name['CenterPointResult']
_CENTERPOINTSET = DESCRIPTOR.message_types_by_name['CenterPointSet']
_OTARESULT = DESCRIPTOR.message_types_by_name['OtaResult']
_GENERICRESULT = DESCRIPTOR.message_types_by_name['GenericResult']
_IPREPORT = DESCRIPTOR.message_types_by_name['IpReport']
_IMAGEUPLOADCOMMAND = DESCRIPTOR.message_types_by_name['ImageUploadCommand']
_LOGUPLOADCOMMAND = DESCRIPTOR.message_types_by_name['LogUploadCommand']
_OTAREQUESTDATA = DESCRIPTOR.message_types_by_name['OtaRequestData']
_WIFICONNECTDATA = DESCRIPTOR.message_types_by_name['WifiConnectData']
_IMAGEUPLOADRESULT = DESCRIPTOR.message_types_by_name['ImageUploadResult']
_LOGUPLOADRESULT = DESCRIPTOR.message_types_by_name['LogUploadResult']
_BATTERYQUERYRESPONSE = DESCRIPTOR.message_types_by_name['BatteryQueryResponse']
_OTA4GSUBCODEREQ = DESCRIPTOR.message_types_by_name['Ota4gSubCodeReq']
_OTA4GSUBCODERESP = DESCRIPTOR.message_types_by_name['Ota4gSubCodeResp']
_SHUTDOWNCOMMAND = DESCRIPTOR.message_types_by_name['ShutdownCommand']
_AUTOSHUTDOWN = DESCRIPTOR.message_types_by_name['AutoShutdown']
LoginRequest = _reflection.GeneratedProtocolMessageType('LoginRequest', (_message.Message,), {
'DESCRIPTOR' : _LOGINREQUEST,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LoginRequest)
})
_sym_db.RegisterMessage(LoginRequest)
LoginResponse = _reflection.GeneratedProtocolMessageType('LoginResponse', (_message.Message,), {
'DESCRIPTOR' : _LOGINRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LoginResponse)
})
_sym_db.RegisterMessage(LoginResponse)
Heartbeat = _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _HEARTBEAT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Heartbeat)
})
_sym_db.RegisterMessage(Heartbeat)
LogicBody = _reflection.GeneratedProtocolMessageType('LogicBody', (_message.Message,), {
'DESCRIPTOR' : _LOGICBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogicBody)
})
_sym_db.RegisterMessage(LogicBody)
ResponseBody = _reflection.GeneratedProtocolMessageType('ResponseBody', (_message.Message,), {
'DESCRIPTOR' : _RESPONSEBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ResponseBody)
})
_sym_db.RegisterMessage(ResponseBody)
MonitorBody = _reflection.GeneratedProtocolMessageType('MonitorBody', (_message.Message,), {
'DESCRIPTOR' : _MONITORBODY,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.MonitorBody)
})
_sym_db.RegisterMessage(MonitorBody)
MonitorDevicesResponse = _reflection.GeneratedProtocolMessageType('MonitorDevicesResponse', (_message.Message,), {
'DESCRIPTOR' : _MONITORDEVICESRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.MonitorDevicesResponse)
})
_sym_db.RegisterMessage(MonitorDevicesResponse)
OtaFragment = _reflection.GeneratedProtocolMessageType('OtaFragment', (_message.Message,), {
'DESCRIPTOR' : _OTAFRAGMENT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaFragment)
})
_sym_db.RegisterMessage(OtaFragment)
ShootData = _reflection.GeneratedProtocolMessageType('ShootData', (_message.Message,), {
'DESCRIPTOR' : _SHOOTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShootData)
})
_sym_db.RegisterMessage(ShootData)
ShootEvent = _reflection.GeneratedProtocolMessageType('ShootEvent', (_message.Message,), {
'DESCRIPTOR' : _SHOOTEVENT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShootEvent)
})
_sym_db.RegisterMessage(ShootEvent)
BatteryReport = _reflection.GeneratedProtocolMessageType('BatteryReport', (_message.Message,), {
'DESCRIPTOR' : _BATTERYREPORT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.BatteryReport)
})
_sym_db.RegisterMessage(BatteryReport)
CenterPointResult = _reflection.GeneratedProtocolMessageType('CenterPointResult', (_message.Message,), {
'DESCRIPTOR' : _CENTERPOINTRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.CenterPointResult)
})
_sym_db.RegisterMessage(CenterPointResult)
CenterPointSet = _reflection.GeneratedProtocolMessageType('CenterPointSet', (_message.Message,), {
'DESCRIPTOR' : _CENTERPOINTSET,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.CenterPointSet)
})
_sym_db.RegisterMessage(CenterPointSet)
OtaResult = _reflection.GeneratedProtocolMessageType('OtaResult', (_message.Message,), {
'DESCRIPTOR' : _OTARESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaResult)
})
_sym_db.RegisterMessage(OtaResult)
GenericResult = _reflection.GeneratedProtocolMessageType('GenericResult', (_message.Message,), {
'DESCRIPTOR' : _GENERICRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.GenericResult)
})
_sym_db.RegisterMessage(GenericResult)
IpReport = _reflection.GeneratedProtocolMessageType('IpReport', (_message.Message,), {
'DESCRIPTOR' : _IPREPORT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.IpReport)
})
_sym_db.RegisterMessage(IpReport)
ImageUploadCommand = _reflection.GeneratedProtocolMessageType('ImageUploadCommand', (_message.Message,), {
'DESCRIPTOR' : _IMAGEUPLOADCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ImageUploadCommand)
})
_sym_db.RegisterMessage(ImageUploadCommand)
LogUploadCommand = _reflection.GeneratedProtocolMessageType('LogUploadCommand', (_message.Message,), {
'DESCRIPTOR' : _LOGUPLOADCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogUploadCommand)
})
_sym_db.RegisterMessage(LogUploadCommand)
OtaRequestData = _reflection.GeneratedProtocolMessageType('OtaRequestData', (_message.Message,), {
'DESCRIPTOR' : _OTAREQUESTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.OtaRequestData)
})
_sym_db.RegisterMessage(OtaRequestData)
WifiConnectData = _reflection.GeneratedProtocolMessageType('WifiConnectData', (_message.Message,), {
'DESCRIPTOR' : _WIFICONNECTDATA,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.WifiConnectData)
})
_sym_db.RegisterMessage(WifiConnectData)
ImageUploadResult = _reflection.GeneratedProtocolMessageType('ImageUploadResult', (_message.Message,), {
'DESCRIPTOR' : _IMAGEUPLOADRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ImageUploadResult)
})
_sym_db.RegisterMessage(ImageUploadResult)
LogUploadResult = _reflection.GeneratedProtocolMessageType('LogUploadResult', (_message.Message,), {
'DESCRIPTOR' : _LOGUPLOADRESULT,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.LogUploadResult)
})
_sym_db.RegisterMessage(LogUploadResult)
BatteryQueryResponse = _reflection.GeneratedProtocolMessageType('BatteryQueryResponse', (_message.Message,), {
'DESCRIPTOR' : _BATTERYQUERYRESPONSE,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.BatteryQueryResponse)
})
_sym_db.RegisterMessage(BatteryQueryResponse)
Ota4gSubCodeReq = _reflection.GeneratedProtocolMessageType('Ota4gSubCodeReq', (_message.Message,), {
'DESCRIPTOR' : _OTA4GSUBCODEREQ,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Ota4gSubCodeReq)
})
_sym_db.RegisterMessage(Ota4gSubCodeReq)
Ota4gSubCodeResp = _reflection.GeneratedProtocolMessageType('Ota4gSubCodeResp', (_message.Message,), {
'DESCRIPTOR' : _OTA4GSUBCODERESP,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.Ota4gSubCodeResp)
})
_sym_db.RegisterMessage(Ota4gSubCodeResp)
ShutdownCommand = _reflection.GeneratedProtocolMessageType('ShutdownCommand', (_message.Message,), {
'DESCRIPTOR' : _SHUTDOWNCOMMAND,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.ShutdownCommand)
})
_sym_db.RegisterMessage(ShutdownCommand)
AutoShutdown = _reflection.GeneratedProtocolMessageType('AutoShutdown', (_message.Message,), {
'DESCRIPTOR' : _AUTOSHUTDOWN,
'__module__' : 'tcp_messages_pb2'
# @@protoc_insertion_point(class_scope:tcp.AutoShutdown)
})
_sym_db.RegisterMessage(AutoShutdown)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\rcom.shoot.tcpZ1git.shelingxingqiu.com/shoot-tcp-server/proto;tcp'
_LOGINREQUEST._serialized_start=28
_LOGINREQUEST._serialized_end=159
_LOGINRESPONSE._serialized_start=161
_LOGINRESPONSE._serialized_end=203
_HEARTBEAT._serialized_start=205
_HEARTBEAT._serialized_end=257
_LOGICBODY._serialized_start=259
_LOGICBODY._serialized_end=297
_RESPONSEBODY._serialized_start=299
_RESPONSEBODY._serialized_end=340
_MONITORBODY._serialized_start=342
_MONITORBODY._serialized_end=406
_MONITORDEVICESRESPONSE._serialized_start=408
_MONITORDEVICESRESPONSE._serialized_end=452
_OTAFRAGMENT._serialized_start=454
_OTAFRAGMENT._serialized_end=511
_SHOOTDATA._serialized_start=514
_SHOOTDATA._serialized_end=813
_SHOOTEVENT._serialized_start=815
_SHOOTEVENT._serialized_end=848
_BATTERYREPORT._serialized_start=850
_BATTERYREPORT._serialized_end=917
_CENTERPOINTRESULT._serialized_start=919
_CENTERPOINTRESULT._serialized_end=976
_CENTERPOINTSET._serialized_start=978
_CENTERPOINTSET._serialized_end=1029
_OTARESULT._serialized_start=1031
_OTARESULT._serialized_end=1071
_GENERICRESULT._serialized_start=1073
_GENERICRESULT._serialized_end=1104
_IPREPORT._serialized_start=1106
_IPREPORT._serialized_end=1144
_IMAGEUPLOADCOMMAND._serialized_start=1146
_IMAGEUPLOADCOMMAND._serialized_end=1236
_LOGUPLOADCOMMAND._serialized_start=1238
_LOGUPLOADCOMMAND._serialized_end=1338
_OTAREQUESTDATA._serialized_start=1340
_OTAREQUESTDATA._serialized_end=1415
_WIFICONNECTDATA._serialized_start=1417
_WIFICONNECTDATA._serialized_end=1466
_IMAGEUPLOADRESULT._serialized_start=1468
_IMAGEUPLOADRESULT._serialized_end=1563
_LOGUPLOADRESULT._serialized_start=1565
_LOGUPLOADRESULT._serialized_end=1683
_BATTERYQUERYRESPONSE._serialized_start=1685
_BATTERYQUERYRESPONSE._serialized_end=1759
_OTA4GSUBCODEREQ._serialized_start=1761
_OTA4GSUBCODEREQ._serialized_end=1800
_OTA4GSUBCODERESP._serialized_start=1802
_OTA4GSUBCODERESP._serialized_end=1855
_SHUTDOWNCOMMAND._serialized_start=1857
_SHUTDOWNCOMMAND._serialized_end=1887
_AUTOSHUTDOWN._serialized_start=1889
_AUTOSHUTDOWN._serialized_end=1921
# @@protoc_insertion_point(module_scope)
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
"""Run independently and keep A24 at a high logic level."""
from maix import app, gpio, pinmap, time
PIN = "A17"
GPIO_NAME = "GPIOA17"
def main():
pinmap.set_pin_function(PIN, GPIO_NAME)
output = gpio.GPIO(GPIO_NAME, gpio.Mode.OUT)
output.value(1)
print(f"{PIN} is HIGH. Stop the script to set it LOW.")
try:
while not app.need_exit():
# Refresh the output in case another component changes its state.
output.value(1)
time.sleep_ms(100)
finally:
output.value(0)
print(f"{PIN} is LOW.")
if __name__ == "__main__":
main()
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Interactive GPIO test for physical pin A14."""
import sys
from maix import gpio, pinmap
PIN = "A14"
GPIO_NAME = "GPIOA14"
def set_level(output, command):
if command == "1":
output.value(1)
print("A14 = HIGH, laser OFF")
return True
if command == "0":
output.value(0)
print("A14 = LOW, laser ON")
return True
return False
def main():
pinmap.set_pin_function(PIN, GPIO_NAME)
output = gpio.GPIO(GPIO_NAME, gpio.Mode.OUT)
# One-shot mode for SSH/serial shells: python3 test_gpio_a14.py 1|0
if len(sys.argv) > 1:
command = sys.argv[1].strip()
if not set_level(output, command):
print("Invalid argument. Use 1 or 0.")
return
return
output.value(1)
print("A14 laser test: input 0 for ON, 1 for OFF, q to quit.")
try:
while True:
try:
command = input("A14> ").strip().lower()
except EOFError:
print("This runner has no stdin. Run from an SSH/serial shell with argument 1 or 0.")
return
if set_level(output, command):
continue
elif command in ("q", "quit", "exit"):
break
elif command:
print("Invalid input. Use 1, 0, or q.")
except KeyboardInterrupt:
print()
finally:
output.value(1)
print("A14 = HIGH, laser OFF, test stopped.")
if __name__ == "__main__":
main()
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Standalone WiFi/GPIO/INA226 isolation test for the official MaixPy tool.
This file intentionally does not import project modules or start project
threads. Select TEST_MODE below, then run the file directly.
"""
import time
from maix import gpio, i2c, network, pinmap
# Change only this value before each run.
# wifi WiFi only
# a25 A25 only
# a23 A23 only
# gpio A23/A26 only
# ina INA226 only
# gpio_ina GPIOs, then INA226
# a25_wifi A25, then WiFi
# a23_wifi A23, then WiFi
# all GPIOs, INA226, then WiFi
TEST_MODE = "a25_wifi"
WIFI_SSID = "sheling4b02-5G"
WIFI_PASSWORD = "Aa12345678"
WIFI_TIMEOUT_S = 20
I2C_BUS_NUM = 5
INA226_ADDR = 0x40
def init_leds():
return init_selected_leds(True, True)
def init_selected_leds(use_a26, use_a23):
outputs = []
if use_a26:
print("Initializing A25 -> GPIOA25")
pinmap.set_pin_function("A25", "GPIOA25")
green = gpio.GPIO("GPIOA25", gpio.Mode.OUT)
green.value(0)
outputs.append(("GPIOA25", green))
print("GPIOA25 initialized LOW")
if use_a23:
print("Initializing A23 -> GPIOA23")
pinmap.set_pin_function("A23", "GPIOA23")
red = gpio.GPIO("GPIOA23", gpio.Mode.OUT)
red.value(0)
outputs.append(("GPIOA23", red))
print("GPIOA23 initialized LOW")
return outputs
def test_ina226():
# Match the board mapping used by the application before opening I2C5.
pinmap.set_pin_function("A15", "I2C5_SCL")
pinmap.set_pin_function("A27", "I2C5_SDA")
print("A15/A27 configured for I2C5")
print("Initializing I2C bus", I2C_BUS_NUM)
bus = i2c.I2C(I2C_BUS_NUM, i2c.Mode.MASTER)
print("Reading INA226 at 0x%02X" % INA226_ADDR)
config = bus.readfrom_mem(INA226_ADDR, 0x00, 2)
voltage_raw = bus.readfrom_mem(INA226_ADDR, 0x02, 2)
voltage = ((voltage_raw[0] << 8) | voltage_raw[1]) * 1.25 / 1000
print("INA226 config=0x%02X%02X voltage=%.3fV" % (config[0], config[1], voltage))
return bus
def test_wifi():
print("Starting MaixPy WiFi connection...")
wifi = network.wifi.Wifi()
result = wifi.connect(WIFI_SSID, WIFI_PASSWORD, wait=True, timeout=WIFI_TIMEOUT_S)
print("WiFi connect result:", result)
print("WiFi connected:", wifi.is_connected())
try:
print("WiFi IP:", wifi.get_ip())
except Exception as exc:
print("WiFi status query failed:", exc)
def main():
valid = ("wifi", "a25", "a23", "gpio", "ina", "gpio_ina", "a25_wifi", "a23_wifi", "all")
mode = TEST_MODE.lower()
if mode not in valid:
print("TEST_MODE must be one of:", ", ".join(valid))
return 1
leds = []
try:
print("=== Standalone WiFi/GPIO/INA226 isolation ===")
print("mode:", mode)
if mode in ("a25", "a25_wifi"):
leds = init_selected_leds(True, False)
time.sleep(1)
elif mode in ("a23", "a23_wifi"):
leds = init_selected_leds(False, True)
time.sleep(1)
elif mode in ("gpio", "gpio_ina", "all"):
leds = init_leds()
time.sleep(1)
if mode in ("ina", "gpio_ina", "all"):
test_ina226()
time.sleep(1)
if mode in ("wifi", "a25_wifi", "a23_wifi", "all"):
test_wifi()
print("TEST COMPLETE")
return 0
except Exception as exc:
print("TEST FAILED:", repr(exc))
return 1
finally:
for name, led in leds:
try:
led.value(0)
print(name, "LOW")
except Exception as exc:
print(name, "cleanup failed:", exc)
main()
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Standalone WiFi/GPIO isolation test.
This script intentionally does not import any project module. It only tests
MaixPy WiFi startup with optional A23/A26 GPIO initialization.
"""
import time
from maix import gpio, network, pinmap
GREEN_PIN = "A26"
GREEN_GPIO = "GPIOA26"
RED_PIN = "A23"
RED_GPIO = "GPIOA23"
# Run this file directly from the official MaixPy tool.
# Change only TEST_MODE between runs: none -> a26 -> a23 -> both.
TEST_MODE = "none"
WIFI_SSID = "sheling4b02-5G"
WIFI_PASSWORD = "Aa12345678"
WIFI_TIMEOUT_S = 20
def init_gpio(mode):
outputs = []
if mode in ("a26", "both"):
pinmap.set_pin_function(GREEN_PIN, GREEN_GPIO)
green = gpio.GPIO(GREEN_GPIO, gpio.Mode.OUT)
green.value(1)
outputs.append((GREEN_GPIO, green))
print("GPIOA26 initialized HIGH")
if mode in ("a23", "both"):
pinmap.set_pin_function(RED_PIN, RED_GPIO)
red = gpio.GPIO(RED_GPIO, gpio.Mode.OUT)
red.value(1)
outputs.append((RED_GPIO, red))
print("GPIOA23 initialized HIGH")
return outputs
def connect_wifi(ssid, password, timeout_s):
print("Starting MaixPy WiFi connection...")
wifi = network.wifi.Wifi()
result = wifi.connect(ssid, password, wait=True, timeout=timeout_s)
print("WiFi connect result:", result)
try:
print("WiFi connected:", wifi.is_connected())
print("WiFi IP:", wifi.get_ip())
except Exception as exc:
print("WiFi status query failed:", exc)
return result
def main():
mode = TEST_MODE.lower()
if mode not in ("none", "a26", "a23", "both"):
print("TEST_MODE must be none, a26, a23, or both")
return 1
ssid = WIFI_SSID
password = WIFI_PASSWORD
timeout_s = WIFI_TIMEOUT_S
print("=== Standalone WiFi/GPIO isolation ===")
print("mode:", mode)
print("ssid:", ssid)
outputs = []
try:
outputs = init_gpio(mode)
time.sleep(1)
connect_wifi(ssid, password, timeout_s)
return 0
except Exception as exc:
print("TEST FAILED:", repr(exc))
return 1
finally:
for gpio_name, output in outputs:
try:
output.value(0)
print(gpio_name, "LOW")
except Exception as exc:
print(gpio_name, "cleanup failed:", exc)
if __name__ == "__main__":
raise SystemExit(main())
+2
View File
@@ -29,3 +29,5 @@
# 2.15.16 修复wifi连接问题
# 2.15.17 修复wifi连接问题
# 2.15.18 wifi连接成功重新登录
# 2.16.4 优化射箭延迟
# 2.17.0 yolo标靶类别识别
+1 -1
View File
@@ -4,6 +4,6 @@
应用版本号
每次 OTA 更新时,只需要更新这个文件中的版本号
"""
VERSION = '2.16.4'
VERSION = '3.0.5'