diff --git a/main.py b/main.py index f33c85c..d1fc277 100644 --- a/main.py +++ b/main.py @@ -21,24 +21,25 @@ from version import VERSION # from logger import init_logging, get_logger, stop_logging from logger_manager import logger_manager from time_sync import sync_system_time_from_4g -from power import init_ina226, get_bus_voltage, voltage_to_percent +from power import init_ina226 from laser_manager import laser_manager -from vision import detect_circle_v3, estimate_distance, enqueue_save_shot, start_save_shot_worker +from vision import start_save_shot_worker from network import network_manager from ota_manager import ota_manager from hardware import hardware_manager from camera_manager import camera_manager +from shoot_manager import process_shot, preload_triangle_calib def laser_calibration_worker(): """后台线程:持续检测是否需要执行激光校准""" from laser_manager import laser_manager from ota_manager import ota_manager - + logger = logger_manager.logger if logger: logger.info("[LASER] 激光校准线程启动") - + while True: try: try: @@ -55,7 +56,7 @@ def laser_calibration_worker(): if laser_manager.calibration_active: # 调用校准方法,所有逻辑都在 LaserManager 中 result = laser_manager.calibrate_laser_position(timeout_ms=8000, check_sharpness=True) - + # 如果超时仍未成功,稍微休息一下 if laser_manager.calibration_active: time.sleep_ms(300) @@ -78,37 +79,38 @@ def cmd_str(): """主程序入口""" # ==================== 第一阶段:硬件初始化 ==================== # 按照 main104.py 的顺序,先完成所有硬件初始化 - + # 1. 引脚功能映射 for pin, func in config.PIN_MAPPINGS.items(): try: pinmap.set_pin_function(pin, func) except: pass - + # 2. 初始化硬件对象(UART、I2C、ADC) hardware_manager.init_uart4g() hardware_manager.init_bus() hardware_manager.init_adc() hardware_manager.init_at_client() - + # 3. 初始化激光模块(串口 + 开机关闭激光防误触发) laser_manager.init() - + # 3. 初始化 INA226 电量监测芯片 init_ina226() # 4. 初始化显示和相机 camera_manager.init_camera(640, 480) + # camera_manager.init_camera(1280,720) camera_manager.init_display() - + # ==================== 第二阶段:软件初始化 ==================== - + # 1. 初始化日志系统 import logging logger_manager.init_logging(log_level=logging.DEBUG) logger = logger_manager.logger - + # 补充:因为初始化的时候,激光会亮,先关了它 # laser_manager.turn_off_laser() @@ -126,25 +128,31 @@ def cmd_str(): # 2.5. 启动存图 worker 线程(队列 + worker,避免主循环阻塞) start_save_shot_worker() - + + # 2.6 预加载三角形标定/坐标文件(避免首次射箭卡顿) + try: + preload_triangle_calib() + except Exception: + pass + # 3. 启动时检查:是否需要恢复备份 pending_path = f"{config.APP_DIR}/ota_pending.json" if os.path.exists(pending_path): try: with open(pending_path, 'r', encoding='utf-8') as f: pending_obj = json.load(f) - + restart_count = pending_obj.get('restart_count', 0) max_restarts = pending_obj.get('max_restarts', 3) backup_dir = pending_obj.get('backup_dir') - + if logger: logger.info(f"检测到 ota_pending.json,重启计数: {restart_count}/{max_restarts}") - + if restart_count >= max_restarts: if logger: logger.error(f"[STARTUP] 重启次数 ({restart_count}) 超过阈值 ({max_restarts}),执行恢复...") - + if backup_dir and os.path.exists(backup_dir): if ota_manager.restore_from_backup(backup_dir): if logger: @@ -160,7 +168,7 @@ def cmd_str(): else: if logger: logger.error(f"[STARTUP] 恢复备份失败") - + try: os.remove(pending_path) if logger: @@ -168,7 +176,7 @@ def cmd_str(): except Exception as e: if logger: logger.error(f"[STARTUP] 删除 pending 文件失败: {e}") - + if logger: logger.info(f"[STARTUP] 恢复完成,准备重启系统...") time.sleep_ms(2000) @@ -199,10 +207,10 @@ def cmd_str(): return except: pass - + # 4. 初始化设备ID(network_manager 内部会自动设置 device_id 和 password) network_manager.read_device_id() - + # 5. 创建照片存储目录(如果启用图像保存) if config.SAVE_IMAGE_ENABLED: photo_dir = config.PHOTO_DIR @@ -281,7 +289,7 @@ def cmd_str(): while not app.need_exit(): try: current_time = time.ticks_ms() - + # OTA 期间暂停相机预览 try: if ota_manager.ota_in_progress: @@ -346,146 +354,9 @@ def cmd_str(): last_adc_trigger = current_time # 触发前先把缓存刷出来,避免波形被长耗时处理截断 _flush_pressure_buf("before_trigger") - + try: - frame = camera_manager.read_frame() - - laser_point_method = None # 记录激光点选择方法 - if config.HARDCODE_LASER_POINT: - # 硬编码模式:使用硬编码值 - laser_point = laser_manager.laser_point - laser_point_method = "hardcode" - elif laser_manager.has_calibrated_point(): - # 假如校准过,并且有保存值,使用校准值 - laser_point = laser_manager.laser_point - laser_point_method = "calibrated" - logger_manager.logger.info(f"[算法] 使用校准值: {laser_manager.laser_point}") - elif distance_m and distance_m > 0: - # 动态计算模式:根据距离计算激光点 - # 先检测靶心以获取距离(用于计算激光点) - # 第一次检测不使用激光点,仅用于获取距离 - result_img_temp, center_temp, radius_temp, method_temp, best_radius1_temp, ellipse_params_temp = detect_circle_v3(frame, None) - # 计算距离 - distance_m = estimate_distance(best_radius1_temp) if best_radius1_temp else None - laser_point = laser_manager.calculate_laser_point_from_distance(distance_m) - laser_point_method = "dynamic" - if laser_point is None: - logger = logger_manager.logger - if logger: - logger.warning("[MAIN] 激光点未初始化,跳过本次检测") - time.sleep_ms(100) - continue - - x, y = laser_point - - # 检测靶心 - result_img, center, radius, method, best_radius1, ellipse_params = detect_circle_v3(frame, laser_point) - - if config.SHOW_CAMERA_PHOTO_WHILE_SHOOTING: - camera_manager.show(result_img) - - # 计算偏移与距离(如果检测到靶心) - if center and radius: - dx, dy = laser_manager.compute_laser_position(center, (x, y), radius, method) - distance_m = estimate_distance(best_radius1) - else: - # 未检测到靶心 - dx, dy = None, None - distance_m = None - if logger: - logger.warning("[MAIN] 未检测到靶心,但会保存图像") - - # 快速激光测距(激光一闪而过,约500-600ms) - laser_distance_m = None - laser_signal_quality = 0 - # try: - # result = laser_manager.quick_measure_distance() - # if isinstance(result, tuple) and len(result) == 2: - # laser_distance_m, laser_signal_quality = result - # else: - # # 向后兼容:如果返回的是单个值 - # laser_distance_m = result if isinstance(result, (int, float)) else 0.0 - # laser_signal_quality = 0 - # if logger: - # if laser_distance_m > 0: - # logger.info(f"[MAIN] 激光测距成功: {laser_distance_m:.3f} m, 信号质量: {laser_signal_quality}") - # else: - # logger.warning("[MAIN] 激光测距失败或返回0") - # except Exception as e: - # if logger: - # logger.error(f"[MAIN] 激光测距异常: {e}") - - # 读取电量 - voltage = get_bus_voltage() - battery_percent = voltage_to_percent(voltage) - - # 生成射箭ID - from shot_id_generator import shot_id_generator - shot_id = shot_id_generator.generate_id() # 不需要使用device_id - - - - # 构造上报数据 - inner_data = { - "shot_id": shot_id, # 射箭ID,用于关联图片和服务端日志 - "x": float(dx) if dx is not None else 200.0, - "y": float(dy) if dy is not None else 200.0, - "r": 90.0, - "d": round((distance_m or 0.0) * 100), # 视觉测距值(厘米) - "d_laser": round((laser_distance_m or 0.0) * 100), # 激光测距值(厘米) - "d_laser_quality": laser_signal_quality, # 激光测距信号质量 - "m": method if method else "no_target", - "adc": adc_val, - # 新增字段:激光点选择方法 - "laser_method": laser_point_method, # 激光点选择方法:hardcode/calibrated/dynamic/default - # 激光点坐标(像素) - "target_x": float(x), # 激光点 X 坐标(像素) - "target_y": float(y), # 激光点 Y 坐标(像素) - } - - # 添加椭圆参数(如果存在) - if ellipse_params: - (ell_center, (width, height), angle) = ellipse_params - inner_data["ellipse_major_axis"] = float(max(width, height)) # 长轴(像素) - inner_data["ellipse_minor_axis"] = float(min(width, height)) # 短轴(像素) - inner_data["ellipse_angle"] = float(angle) # 椭圆角度(度) - inner_data["ellipse_center_x"] = float(ell_center[0]) # 椭圆中心 X 坐标(像素) - inner_data["ellipse_center_y"] = float(ell_center[1]) # 椭圆中心 Y 坐标(像素) - else: - inner_data["ellipse_major_axis"] = None - inner_data["ellipse_minor_axis"] = None - inner_data["ellipse_angle"] = None - inner_data["ellipse_center_x"] = None - inner_data["ellipse_center_y"] = None - - report_data = {"cmd": 1, "data": inner_data} - network_manager.safe_enqueue(report_data, msg_type=2, high=True) - # 闪一下激光(射箭反馈) - if config.FLASH_LASER_WHILE_SHOOTING: - laser_manager.flash_laser(config.FLASH_LASER_DURATION_MS) - - # 保存图像(无论是否检测到靶心都保存):放入队列由 worker 异步保存,不阻塞主循环 - enqueue_save_shot( - result_img, - center, - radius, - method, - ellipse_params, - (x, y), - distance_m, - shot_id=shot_id, - photo_dir=config.PHOTO_DIR if config.SAVE_IMAGE_ENABLED else None, - ) - - - if center and radius: - logger.info(f"射箭事件已加入发送队列(已检测到靶心),ID: {shot_id}") - else: - logger.info(f"射箭事件已加入发送队列(未检测到靶心,已保存图像),ID: {shot_id}") - - - - time.sleep_ms(100) + process_shot(adc_val) except Exception as e: logger = logger_manager.logger if logger: @@ -537,13 +408,13 @@ if __name__ == "__main__": # 用于测试图片清晰度 # 方式1: 测试单张图片 # test_sharpness("/root/phot/image.bmp") - + # 方式2: 测试目录下所有图片 # test_sharpness("/root/phot") - + # 方式3: 使用默认路径(config.PHOTO_DIR) # test_sharpness("/root/phot/") - + # 用于测试激光点清晰度 # 方式1: 测试单张图片 # test_laser_point_sharpness("/root/phot/image.bmp")