diff --git a/app.yaml b/app.yaml index de68447..e4105c1 100644 --- a/app.yaml +++ b/app.yaml @@ -1,6 +1,6 @@ id: t11 name: t11 -version: 1.2.1 +version: 1.2.3 author: t11 icon: '' desc: t11 diff --git a/config.py b/config.py index ae36722..b4f5bf2 100644 --- a/config.py +++ b/config.py @@ -58,9 +58,12 @@ REG_CURRENT = 0x04 # 电流寄存器 REG_CALIBRATION = 0x05 CALIBRATION_VALUE = 0x1400 +# ==================== 空气传感器配置 ==================== +ADC_TRIGGER_THRESHOLD = 50000 # TODO:只是用于测试,最终需要改为正常值 +AIR_PRESSURE_lOG = False # TODO: 在正式环境中关闭 + # ADC配置 ADC_CHANNEL = 0 -ADC_TRIGGER_THRESHOLD = 3000 ADC_LASER_THRESHOLD = 3000 # ==================== 激光配置 ==================== @@ -110,6 +113,8 @@ SAVE_IMAGE_ENABLED = True # 是否保存图像(True=保存,False=不保存 PHOTO_DIR = "/root/phot" # 照片存储目录 MAX_IMAGES = 1000 +SHOW_CAMERA_PHOTO_WHILE_SHOOTING = False # 是否在拍摄时显示摄像头图像(True=显示,False=不显示),建议在连着USB测试过程中打开 + # ==================== OTA配置 ==================== MAX_BACKUPS = 5 LOG_MAX_BYTES = 10 * 1024 * 1024 # 10MB diff --git a/main.py b/main.py index 34161e7..21950a5 100644 --- a/main.py +++ b/main.py @@ -212,6 +212,39 @@ def cmd_str(): logger.info("系统准备完成...") last_adc_trigger = 0 + # 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样 + PRESSURE_BATCH_SIZE = 100 + pressure_buf = [] + pressure_sum = 0 + pressure_min = 4095 + pressure_max = 0 + pressure_t0_ms = None + + 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 + if not pressure_buf: + return + t1_ms = time.ticks_ms() + n = len(pressure_buf) + avg = (pressure_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"values={','.join(map(str, pressure_buf))}" + ) + if logger: + logger.debug(line) + else: + print(line) + pressure_buf = [] + pressure_sum = 0 + pressure_min = 4095 + pressure_max = 0 + pressure_t0_ms = None # 主循环:检测扳机触发 → 拍照 → 分析 → 上报 while not app.need_exit(): @@ -245,12 +278,27 @@ def cmd_str(): logger.error(f"[MAIN] ADC读取异常: {e}") time.sleep_ms(100) continue - - if adc_val > config.ADC_TRIGGER_THRESHOLD: + + # ====== 气压采样缓存(每次循环都记录,批量输出日志)====== + if pressure_t0_ms is None: + pressure_t0_ms = current_time + pressure_buf.append(adc_val) + pressure_sum += adc_val + if adc_val < pressure_min: + pressure_min = adc_val + if adc_val > pressure_max: + pressure_max = adc_val + if len(pressure_buf) >= PRESSURE_BATCH_SIZE: + _flush_pressure_buf("batch") + # if adc_val >= 2000: + # print(f"adc :{adc_val}") + if adc_val >= config.ADC_TRIGGER_THRESHOLD: diff_ms = current_time - last_adc_trigger if diff_ms < 3000: continue last_adc_trigger = current_time + # 触发前先把缓存刷出来,避免波形被长耗时处理截断 + _flush_pressure_buf("before_trigger") try: frame = camera_manager.read_frame() @@ -397,13 +445,14 @@ def cmd_str(): time.sleep_ms(100) continue else: - try: - camera_manager.show(camera_manager.read_frame()) - except Exception as e: - logger = logger_manager.logger - if logger: - logger.error(f"[MAIN] 显示异常: {e}") - time.sleep_ms(50) + if config.SHOW_CAMERA_PHOTO_WHILE_SHOOTING: + try: + camera_manager.show(camera_manager.read_frame()) + except Exception as e: + logger = logger_manager.logger + if logger: + logger.error(f"[MAIN] 显示异常: {e}") + time.sleep_ms(1) except Exception as e: # 主循环的顶层异常捕获,防止程序静默退出 @@ -416,6 +465,11 @@ def cmd_str(): print(f"[MAIN] 主循环异常: {e}") import traceback traceback.print_exc() + # 异常发生时尽量把缓存刷盘,方便定位问题 + try: + _flush_pressure_buf("exception") + except: + pass time.sleep_ms(1000) # 等待1秒后继续 diff --git a/network.py b/network.py index ea46c61..7706518 100644 --- a/network.py +++ b/network.py @@ -466,11 +466,29 @@ class NetworkManager: if not self._wifi_socket: return False try: - # 尝试发送0字节来检测连接状态 - self._wifi_socket.send(b"", socket.MSG_DONTWAIT) + # send(b"") 在很多实现里是 no-op,无法可靠探测断线。 + # 用非阻塞 peek 来判断:若对端已关闭,recv 会返回 b""。 + data = self._wifi_socket.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT) + if data == b"": + raise OSError("wifi socket closed") return True - except: - # socket已断开 + except BlockingIOError: + # 无数据可读但连接仍在(EAGAIN) + return True + except OSError as e: + # 兼容不同平台的 EAGAIN / would block + err = getattr(e, "errno", None) + if err in (11, 35, 10035): # EAGAIN/EWOULDBLOCK on linux/mac/win + return True + # socket已断开或不可用,清理 + try: + self._wifi_socket.close() + except: + pass + self._wifi_socket = None + self._tcp_connected = False + return False + except Exception: try: self._wifi_socket.close() except: @@ -524,6 +542,9 @@ class NetworkManager: # 根据网络类型选择发送方式 if self._network_type == "wifi": + # 先快速校验 WiFi socket 是否仍有效,避免卡在半开连接上 + if not self._check_wifi_connection(): + return False return self._tcp_send_raw_via_wifi(data, max_retries) elif self._network_type == "4g": return self._tcp_send_raw_via_4g(data, max_retries) @@ -546,7 +567,7 @@ class NetworkManager: if sent == 0: # socket连接已断开 self.logger.warning(f"[WIFI-TCP] 发送失败,socket已断开(尝试 {attempt+1}/{max_retries})") - break + raise OSError("wifi socket closed (send returned 0)") total_sent += sent if total_sent == len(data): @@ -557,10 +578,23 @@ class NetworkManager: except OSError as e: self.logger.error(f"[WIFI-TCP] 发送异常: {e}(尝试 {attempt+1}/{max_retries})") - time.sleep_ms(50) + # 发送异常通常意味着连接已不可用,主动关闭以触发重连 + try: + self._wifi_socket.close() + except: + pass + self._wifi_socket = None + self._tcp_connected = False + return False except Exception as e: self.logger.error(f"[WIFI-TCP] 未知错误: {e}(尝试 {attempt+1}/{max_retries})") - time.sleep_ms(50) + try: + self._wifi_socket.close() + except: + pass + self._wifi_socket = None + self._tcp_connected = False + return False return False @@ -743,6 +777,9 @@ class NetworkManager: temp_dir = "/tmp" temp_file_path = os.path.join(temp_dir, new_filename) + # 先 sync,确保所有日志都已写入磁盘 + os.system("sync") + # 复制日志文件到临时位置 shutil.copy2(log_file_path, temp_file_path) self.logger.info(f"[LOG_UPLOAD] 日志文件已复制到: {temp_file_path}") @@ -853,6 +890,10 @@ class NetworkManager: # 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)): self._tcp_connected = False + try: + self.disconnect_server() + except: + pass time.sleep_ms(2000) continue @@ -863,6 +904,10 @@ class NetworkManager: last_heartbeat_send_time = time.ticks_ms() while True: + # 如果底层连接已断开,尽快跳出内层循环触发重连/重选网络 + if not self._tcp_connected: + break + # OTA 期间暂停 TCP 活动 try: from ota_manager import ota_manager @@ -1090,6 +1135,8 @@ class NetworkManager: except: pass time.sleep_ms(2000) + os.system("sync") # 刷新文件系统缓存到磁盘,防止数据丢失 + time.sleep_ms(500) os.system("poweroff") return elif inner_cmd == 43: # 上传日志命令 @@ -1111,23 +1158,33 @@ class NetworkManager: time.sleep_ms(5) # 发送队列中的业务数据 - if logged_in and (self._high_send_queue or self._normal_send_queue): - msg_type = None - data_dict = None - if self.get_queue_lock().acquire(blocking=False): - try: - if self._high_send_queue: - msg_type, data_dict = self._high_send_queue.pop(0) - elif self._normal_send_queue: - msg_type, data_dict = self._normal_send_queue.pop(0) - finally: - self.get_queue_lock().release() + if logged_in: + item = None + item_is_high = False + # 出队:发送失败时会把 item 放回队首,避免丢数据 + with self.get_queue_lock(): + if self._high_send_queue: + item = self._high_send_queue.pop(0) + item_is_high = True + elif self._normal_send_queue: + item = self._normal_send_queue.pop(0) + item_is_high = False - if msg_type is not None and data_dict is not None: + if item: + msg_type, data_dict = item pkt = self._netcore.make_packet(msg_type, data_dict) - # pkt = self.make_packet(msg_type, data_dict) if not self.tcp_send_raw(pkt): + # 发送失败:将消息放回队首,触发重连(避免丢消息) + with self.get_queue_lock(): + if item_is_high: + self._high_send_queue.insert(0, item) + else: + self._normal_send_queue.insert(0, item) self._tcp_connected = False + try: + self.disconnect_server() + except: + pass break # 发送激光校准结果 @@ -1144,15 +1201,15 @@ class NetworkManager: 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)})): # if not self.tcp_send_raw(self.make_packet(4, {"vol": vol_val, "vol_per": voltage_to_percent(vol_val)})): - self.logger.error("心跳发送失败") - time.sleep_ms(3000) + # 心跳失败说明链路不可用:立刻触发重连,避免长时间卡住 + self.logger.error("心跳发送失败,准备重连") send_hartbeat_fail_count += 1 - if send_hartbeat_fail_count >= 3: - send_hartbeat_fail_count = 0 - self.logger.error("连续3次发送心跳失败,重连") - break - else: - continue + self._tcp_connected = False + try: + self.disconnect_server() + except: + pass + break else: send_hartbeat_fail_count = 0 last_heartbeat_send_time = current_time diff --git a/version.py b/version.py index 8defd66..37ceea1 100644 --- a/version.py +++ b/version.py @@ -4,14 +4,14 @@ 应用版本号 每次 OTA 更新时,只需要更新这个文件中的版本号 """ -VERSION = '1.2.3' +VERSION = '1.2.4' # 1.2.0 开始使用C++编译成.so,替换部分代码 # 1.2.1 ota使用加密包 # 1.2.2 支持wifi ota,并且设定时区,并使用单独线程保存图片 # 1.2.3 修改ADC_TRIGGER_THRESHOLD 为2300,支持上传日志到服务器 - - +# 1.2.4 修改ADC_TRIGGER_THRESHOLD 为3000,并默认关闭摄像头的显示,并把ADC的采样间隔从50ms降低到10ms +# 1.2.5 支持空气传感器采样,并默认关闭日志。优化断网时的发送队列丢消息问题,解决 WiFi 断线检测不可靠问题。