diff --git a/app.yaml b/app.yaml index 75bfb64..d7dbbac 100644 --- a/app.yaml +++ b/app.yaml @@ -1,6 +1,6 @@ id: t11 name: t11 -version: 1.2.7 +version: 1.2.9 author: t11 icon: '' desc: t11 diff --git a/config.py b/config.py index 040f6a8..021b979 100644 --- a/config.py +++ b/config.py @@ -51,6 +51,7 @@ DISTANCE_SERIAL_BAUDRATE = 9600 # 无WiFi模块:I2C_BUS_NUM = 1,引脚:P18(I2C1_SCL), P21(I2C1_SDA) # 有WiFi模块:I2C_BUS_NUM = 5,引脚:A15(I2C5_SCL), A27(I2C5_SDA) I2C_BUS_NUM = 5 if HAS_WIFI_MODULE else 1 + INA226_ADDR = 0x40 REG_CONFIGURATION = 0x00 REG_BUS_VOLTAGE = 0x02 @@ -59,9 +60,9 @@ REG_CALIBRATION = 0x05 CALIBRATION_VALUE = 0x1400 # ==================== 空气传感器配置 ==================== -ADC_TRIGGER_THRESHOLD = 2500 # TODO:只是用于测试,最终需要改为正常值 +ADC_TRIGGER_THRESHOLD = 2700 # TODO:4096只是用于测试,因为最大值是4095,这个值是永远不会触发的,最终需要改为正常值 AIR_PRESSURE_lOG = False # TODO: 在正式环境中关闭 - +AIR_PRESSURE_HARDWARE_MAX = 40 # ADC配置 ADC_CHANNEL = 0 ADC_LASER_THRESHOLD = 3000 @@ -116,7 +117,7 @@ SAVE_IMAGE_ENABLED = True # 是否保存图像(True=保存,False=不保存 PHOTO_DIR = "/root/phot" # 照片存储目录 MAX_IMAGES = 1000 -SHOW_CAMERA_PHOTO_WHILE_SHOOTING = False # 是否在拍摄时显示摄像头图像(True=显示,False=不显示),建议在连着USB测试过程中打开 +SHOW_CAMERA_PHOTO_WHILE_SHOOTING = True # 是否在拍摄时显示摄像头图像(True=显示,False=不显示),建议在连着USB测试过程中打开 # ==================== OTA配置 ==================== MAX_BACKUPS = 5 @@ -142,7 +143,12 @@ PIN_MAPPINGS_WITH_WIFI = { "A28": "UART2_TX", "A15": "I2C5_SCL", "A27": "I2C5_SDA", + "A24": "GPIOA24", # 电源板的引脚 } # 根据WiFi模块开关选择引脚映射 PIN_MAPPINGS = PIN_MAPPINGS_WITH_WIFI if HAS_WIFI_MODULE else PIN_MAPPINGS_NO_WIFI + +# ==================== 电源配置 ==================== +AUTO_POWER_OFF_IN_SECONDS = 10 * 60 # 自动关机时间(秒),0表示不自动关机 + diff --git a/design_doc/solution_record.md b/design_doc/solution_record.md index 8dcbb82..0884276 100644 --- a/design_doc/solution_record.md +++ b/design_doc/solution_record.md @@ -74,3 +74,10 @@ * 快照 app.log* 到 /tmp staging * 打包成 tar.gz(默认)或 zip * 以 multipart/form-data 的 file 字段 POST 到 url + +8. 自动关机: + hardware中设定了开停表,然后再增加了获取idle的时间。 + 自动关机的时机: 超过配置的idle时长, + 禁止自动关机的情况:1.校准中,2.OTA中 + 重启计时的时机:1.校准完成,2.命令触发射箭,3.真实触发射箭,4.初始化完成 +9. \ No newline at end of file diff --git a/hardware.py b/hardware.py index e87da62..28af91c 100644 --- a/hardware.py +++ b/hardware.py @@ -4,7 +4,7 @@ 硬件管理器模块 提供硬件对象的统一管理和访问 """ -import threading +from maix import time import config from at_client import ATClient @@ -28,8 +28,13 @@ class HardwareManager: self._bus = None # I2C总线 self._adc_obj = None # ADC对象 self._at_client = None # AT客户端 - + + self._last_active_time = 0 # 用于记录用户的最后一次活跃的时间 + self._stop_timer = False # 用于停止定时器的标志 + self._initialized = True + + # ==================== 硬件访问(只读属性)==================== @@ -93,6 +98,34 @@ class HardwareManager: self._at_client.start() return self._at_client + def power_off(self): + """关闭电源板""" + try: + # 物理引脚是 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_idle_timer(self): + self._stop_timer = False + self._last_active_time = time.time() + + def stop_idle_timer(self): + self._stop_timer = True + + def get_idle_time_in_sec(self): + if self._stop_timer: + return 0 + diff = time.time() - self._last_active_time + if diff < 0: + # 时间可能被重置了,重新计时 + self._last_active_time = time.time() + return 0 + return diff + # 创建全局单例实例 hardware_manager = HardwareManager() diff --git a/main.py b/main.py index 21cbc5c..e5b5940 100644 --- a/main.py +++ b/main.py @@ -97,9 +97,7 @@ def cmd_str(): # 3. 初始化 INA226 电量监测芯片 init_ina226() - - # 4. 初始化显示和相机 camera_manager.init_camera(640, 480) camera_manager.init_display() @@ -213,33 +211,41 @@ def cmd_str(): # 7. 加载激光点配置 laser_manager.load_laser_point() + # 8. 启动空闲计时 + hardware_manager.start_idle_timer() + if logger: logger.info("系统准备完成...") last_adc_trigger = 0 # 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样 PRESSURE_BATCH_SIZE = 100 + pressure_buf = [] pressure_sum = 0 + pressure_abs_sum = 0 pressure_min = 4095 pressure_max = 0 pressure_t0_ms = None + last_avg_abs = 0 def _flush_pressure_buf(reason: str): if not config.AIR_PRESSURE_lOG: return - nonlocal pressure_buf, pressure_sum, pressure_min, pressure_max, pressure_t0_ms, logger + nonlocal pressure_buf, pressure_sum, pressure_min, pressure_max, pressure_t0_ms, logger, pressure_abs_sum, last_avg_abs if not pressure_buf: return t1_ms = time.ticks_ms() n = len(pressure_buf) avg = (pressure_sum / n) if n else 0 + avg_abs = (pressure_abs_sum / n) if n else 0 # 一行输出:方便后处理画曲线;同时带上统计信息便于快速看波峰 line = ( f"[气压批量] reason={reason} " f"t0={pressure_t0_ms} t1={t1_ms} n={n} " - f"min={pressure_min} max={pressure_max} avg={avg:.1f} " + f"min={pressure_min} max={pressure_max} avg={avg:.1f} avg_abs={avg_abs:.3f} " f"values={','.join(map(str, pressure_buf))}" + f" convert value (kpa): {(max(pressure_buf, key=lambda x: x[1])[1] - last_avg_abs) / (5 - 2.5) * config.AIR_PRESSURE_HARDWARE_MAX:.1f}" ) if logger: logger.debug(line) @@ -247,9 +253,11 @@ def cmd_str(): print(line) pressure_buf = [] pressure_sum = 0 + pressure_abs_sum = 0 pressure_min = 4095 pressure_max = 0 pressure_t0_ms = None + last_avg_abs = avg_abs # 主循环:检测扳机触发 → 拍照 → 分析 → 上报 while not app.need_exit(): @@ -267,16 +275,29 @@ def cmd_str(): logger.error(f"[MAIN] OTA检查异常: {e}") time.sleep_ms(250) continue - + + # 不在 OTA 状态下,检测是否空闲足够长,自动关机 + # print(f"[MAIN] 空闲时间: {hardware_manager.get_idle_time_in_sec() }秒") + # print(f"配置关机时间:{config.AUTO_POWER_OFF_IN_SECONDS} 秒") + if hardware_manager.get_idle_time_in_sec() > config.AUTO_POWER_OFF_IN_SECONDS: + logger.info("[MAIN] 超过设定时间未检测活动,自动关机") + network_manager.safe_enqueue({"poweroff": "超过设定时间未检测活动,自动关机"}, 2) + time.sleep_ms(100) + hardware_manager.power_off() + time.sleep_ms(2000) # 让关机指令生效 + break + # 读取ADC值(扳机检测) try: if network_manager.manual_trigger_flag: network_manager.clear_manual_trigger() adc_val = config.ADC_TRIGGER_THRESHOLD + 1 + adc_abs_val = 10 if logger: logger.info("[TEST] TCP命令触发射箭") else: adc_val = hardware_manager.adc_obj.read() + adc_abs_val = hardware_manager.adc_obj.read_vol() except Exception as e: logger = logger_manager.logger if logger: @@ -287,8 +308,9 @@ def cmd_str(): # ====== 气压采样缓存(每次循环都记录,批量输出日志)====== if pressure_t0_ms is None: pressure_t0_ms = current_time - pressure_buf.append(adc_val) + pressure_buf.append((adc_val, adc_abs_val)) pressure_sum += adc_val + pressure_abs_sum += adc_abs_val if adc_val < pressure_min: pressure_min = adc_val if adc_val > pressure_max: @@ -298,6 +320,7 @@ def cmd_str(): # if adc_val >= 2000: # print(f"adc :{adc_val}") if adc_val >= config.ADC_TRIGGER_THRESHOLD: + hardware_manager.start_idle_timer() # 重新计时 diff_ms = current_time - last_adc_trigger if diff_ms < 3000: continue @@ -460,7 +483,7 @@ def cmd_str(): logger = logger_manager.logger if logger: logger.error(f"[MAIN] 显示异常: {e}") - time.sleep_ms(10) + time.sleep_ms(5) except Exception as e: # 主循环的顶层异常捕获,防止程序静默退出 diff --git a/network.py b/network.py index cc351bc..b1c0ebc 100644 --- a/network.py +++ b/network.py @@ -1258,6 +1258,7 @@ class NetworkManager: if not laser_manager.calibration_active: laser_manager.turn_on_laser() time.sleep_ms(100) + hardware_manager.stop_idle_timer() # 停表 if not config.HARDCODE_LASER_POINT: laser_manager.start_calibration() self.safe_enqueue({"result": "calibrating"}, 2) @@ -1268,6 +1269,7 @@ class NetworkManager: from laser_manager import laser_manager laser_manager.turn_off_laser() laser_manager.stop_calibration() + hardware_manager.start_idle_timer() # 开表 self.safe_enqueue({"result": "laser_off"}, 2) elif inner_cmd == 4: # 上报电量 voltage = get_bus_voltage() @@ -1303,6 +1305,8 @@ class NetworkManager: mode = "4g" self.logger.info("ota auto-selected: 4g (WiFi not available or no credentials)") + hardware_manager.stop_idle_timer() # 停表,注意OTA停表之后,就没有再开表,因为OTA后面会重启,会重新开表 + if mode == "4g": ota_manager._set_ota_url(ota_url) # 记录 OTA URL,供命令7使用 ota_manager._start_update_thread() @@ -1348,6 +1352,7 @@ class NetworkManager: self.logger.info("[TEST] 收到TCP射箭触发命令") self._manual_trigger_flag = True self.safe_enqueue({"result": "trigger_ack"}, 2) + hardware_manager.start_idle_timer() # 重新计时 elif inner_cmd == 42: # 关机命令 self.logger.info("[SHUTDOWN] 收到TCP关机命令,准备关机...") self.safe_enqueue({"result": "shutdown_ack"}, 2) @@ -1362,7 +1367,8 @@ class NetworkManager: time.sleep_ms(2000) os.system("sync") # 刷新文件系统缓存到磁盘,防止数据丢失 time.sleep_ms(500) - os.system("poweroff") + # os.system("poweroff") + hardware_manager.power_off() return elif inner_cmd == 43: # 上传日志命令 # 格式: {"cmd":43, "data":{"ssid":"xxx","password":"xxx","url":"xxx", ...}} @@ -1373,7 +1379,9 @@ class NetworkManager: include_rotated = inner_data.get("include_rotated", True) max_files = inner_data.get("max_files") archive_format = inner_data.get("archive", "tgz") # tgz 或 zip - + + hardware_manager.start_idle_timer() # 重新计时 + if not upload_url: self.logger.error("[LOG_UPLOAD] 缺少 url 参数") self.safe_enqueue({"result": "log_upload_failed", "reason": "missing_url"}, 2) diff --git a/version.py b/version.py index 36f71de..4463f5d 100644 --- a/version.py +++ b/version.py @@ -4,7 +4,7 @@ 应用版本号 每次 OTA 更新时,只需要更新这个文件中的版本号 """ -VERSION = '1.2.7' +VERSION = '1.2.9' # 1.2.0 开始使用C++编译成.so,替换部分代码 # 1.2.1 ota使用加密包 @@ -15,6 +15,7 @@ VERSION = '1.2.7' # 1.2.6 在链接 wifi 前先判断 wifi 的可用性,假如不可用,则不落盘。增加日志批量压缩上传功能 # 1.2.7 修复OTA失败的bug, 空气压力传感器的阈值是2500 # 1.2.8 (1) 加快 wifi 下数据传输的速度。(2) 调整射箭时处理的逻辑,优先上报数据,再存照片之类的操作。(3)假如是用户打开激光的,射箭触发后不再关闭激光,因为是调瞄阶段 +# 1.2.9 增加电源板的控制和自动关机的功能