From 96c62ced127bd5d84a9edb9fb87215e6f63a2d8b Mon Sep 17 00:00:00 2001 From: milk <53408947@qq.com> Date: Fri, 14 Nov 2025 16:58:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96flask=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=88=97=E8=A1=A8=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/AnchorModel.cpython-312.pyc | Bin 1114 -> 0 bytes .../__pycache__/DeviceModel.cpython-312.pyc | Bin 1158 -> 0 bytes Entity/__pycache__/ResultData.cpython-312.pyc | Bin 1031 -> 0 bytes Entity/__pycache__/Variables.cpython-312.pyc | Bin 3227 -> 0 bytes Module/DeviceInfo.py | 78 ++++++++++++++---- Module/FlaskService.py | 39 ++++++++- Module/__pycache__/DeviceInfo.cpython-312.pyc | Bin 46348 -> 0 bytes .../__pycache__/FlaskService.cpython-312.pyc | Bin 43148 -> 0 bytes .../FlaskSubprocessManager.cpython-312.pyc | Bin 17299 -> 0 bytes Module/__pycache__/Main.cpython-312.pyc | Bin 3700 -> 0 bytes Utils/__pycache__/AiUtils.cpython-312.pyc | Bin 49997 -> 0 bytes .../__pycache__/ControlUtils.cpython-312.pyc | Bin 14344 -> 0 bytes .../DevDiskImageDeployer.cpython-312.pyc | Bin 7157 -> 0 bytes Utils/__pycache__/JsonUtils.cpython-312.pyc | Bin 11684 -> 0 bytes Utils/__pycache__/LogManager.cpython-312.pyc | Bin 14674 -> 0 bytes Utils/__pycache__/Requester.cpython-312.pyc | Bin 6260 -> 0 bytes .../__pycache__/SubprocessKit.cpython-312.pyc | Bin 1214 -> 0 bytes .../__pycache__/ThreadManager.cpython-312.pyc | Bin 11838 -> 0 bytes .../__pycache__/ScriptManager.cpython-312.pyc | Bin 71013 -> 0 bytes 19 files changed, 99 insertions(+), 18 deletions(-) delete mode 100644 Entity/__pycache__/AnchorModel.cpython-312.pyc delete mode 100644 Entity/__pycache__/DeviceModel.cpython-312.pyc delete mode 100644 Entity/__pycache__/ResultData.cpython-312.pyc delete mode 100644 Entity/__pycache__/Variables.cpython-312.pyc delete mode 100644 Module/__pycache__/DeviceInfo.cpython-312.pyc delete mode 100644 Module/__pycache__/FlaskService.cpython-312.pyc delete mode 100644 Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc delete mode 100644 Module/__pycache__/Main.cpython-312.pyc delete mode 100644 Utils/__pycache__/AiUtils.cpython-312.pyc delete mode 100644 Utils/__pycache__/ControlUtils.cpython-312.pyc delete mode 100644 Utils/__pycache__/DevDiskImageDeployer.cpython-312.pyc delete mode 100644 Utils/__pycache__/JsonUtils.cpython-312.pyc delete mode 100644 Utils/__pycache__/LogManager.cpython-312.pyc delete mode 100644 Utils/__pycache__/Requester.cpython-312.pyc delete mode 100644 Utils/__pycache__/SubprocessKit.cpython-312.pyc delete mode 100644 Utils/__pycache__/ThreadManager.cpython-312.pyc delete mode 100644 script/__pycache__/ScriptManager.cpython-312.pyc diff --git a/Entity/__pycache__/AnchorModel.cpython-312.pyc b/Entity/__pycache__/AnchorModel.cpython-312.pyc deleted file mode 100644 index 18285a94171454a16e6694f29eaa5e6dee0dee1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1114 zcmZ`&OKTHR6h8OPOd6Y}FB_WF*NCeTDOg;I2nADd;V87L5E#PT(Tt>t&P;?UDRhxV z7Ioz!EePGVbfYVOgd3%sGOL3AfYvO8t~}>XCY=f%$j5npbI*5gzUA}dfOaD~s^8%N zKPgB|9Xf*|I(^{q6a;iZutV%5t3b#N#g2m*hZPt+z_<>kVo4N&!OR05Wowe_ znTW>hR=XK^p`k>o?=)U{8SM4O5Pag)b^ZKq`C-+@uU21%LA}+ix-V9j-D;PJanu3w}*s`kht2;ms1m9Kr!e zR(VEwT@pwS|2{$&23*x{olZh#<_x{UKtb=6ykU-MD@-QOIJq$!&6@^`2}>AUeB?a) dC_d$5ZvhMROnnmTRAG$$$;`49_8SQ0$Y0l>-Ch6y diff --git a/Entity/__pycache__/DeviceModel.cpython-312.pyc b/Entity/__pycache__/DeviceModel.cpython-312.pyc deleted file mode 100644 index a45103065bfc380f9b45dd56540b298e0fe01f8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1158 zcmaJ=L2J}d5T2J!(rmV&wQS3}wxx$6M8RIfgNP`!Y7f>zZ(-rFCeLo#bho~w>Xtq9 zu;3nAum@QyLeHf?#gph^2_6>w1C^~(dU9T}Np~+k$jtj@-pu=E=H;tl=zwb`{9fT1 zfL{V6OX+WVMclMOf~Vj>6CA?e6wiufk-B7N>=?vS>Vw`qZr)=Y!&44eQVU)Yhcq=S z<48@gT_Yi21 z2GBl1fQE!fPQn@)L`h-LkP)e=PeGL4-cS)~iO&%^vqLCqoUqivlvbXYxlr+Uwiy?F zgixc@t-1lFq2@%zEjyv^+nllLYK;e>^44{NN~l(tyIw))+hvc1a!}u9p~6|&sf$35 z1#WfS)I!;3-YYK1alwfTDqJw&SUT`g6k(Q9+bjD%rM@^b7q7j`TTAy#Hnz63S`R9< zYRP@E_Sh{gRRcGumj*h2XS>ctGj{WX#-tA6@t>i%*n^*%vA_B0?l;Z+q22Bl=34T; z(JjuS8tYCLQR&^OX;ircqHjbjCy0bh<;i@gX^B80jK8DH{Ma}VRC_^=S)|^uB1v TS%wgDuHPW~hns%@RTT0MOn>RJ diff --git a/Entity/__pycache__/ResultData.cpython-312.pyc b/Entity/__pycache__/ResultData.cpython-312.pyc deleted file mode 100644 index 9b905794662181daba87ae6c46522e2c175e9a48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmZ8f&ubGw6n?Wm(oLGErNt`6rihB`!GlK;QLsuqM6sThz_9La?S@TKcXq*qJ@im8 zN^9GL#)IHl>fL`q@FaSW(UVfKw@O+mJ@w6QcCC3ZZ{C~v=HtEZ&Bs!y0BEQ9=lLH5 z;HOAZQ~N>hGzML;!6qIUAi*uLr6sUshiu3R#*kV>SGGh_v67?2_wS*14)Z6-;($SH zFeF>|=djQVZkT~-DTn9;n^?X3=w6U61leUErC|gs z!vGuolt4rxplKZtNtk7vk&Ic|C$vB(6+OpQ7A`oxFHl^g)NQx{rChO^w!;-0i{e9d zhcWY^qpMtDjyLCPi1qWr2|an9Qp+fV{X?pCK8 zfg7}{|2n?7(DH}yQG4{zSz3i)V`tIrEy8!Lv|M|2=96~*i+17b*ols^oKLs$$&Rv8 z>>&^=oNHz|jA8|aa;RT+SIhLvun<8lQ|iA{!7^Zo6&ehfOAxXJGmk$uN~h7+~QXUU6k0r diff --git a/Entity/__pycache__/Variables.cpython-312.pyc b/Entity/__pycache__/Variables.cpython-312.pyc deleted file mode 100644 index 229192638d5bc646e593091fde7e3a91f3add928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3227 zcma)8?Qa}M8K1qqJD=_I%Uu%3ZkjTVDaj%CP>QOQs$iMgEpF2^s!gLDM%H?F?r!b9 zonv-3*ET{a72v z+Ke-Swdt5L3Cd$_qg42}(s!zSyism*k8jDv z8uMkjxYa=P6KJRmEJ^2nZ~9$+&?L) zmf*^LUXNLq7_}s|{I)h=9wSE^-0E}i0PB7mj`+3N&i60WEqOAa>=N_oCX?`$x&f?J zBC2Ypd{ho37W8%1l^UN@zUjF&OS;?-_7wF^(>vfn6AmaGmM<~U*Qyj$1!G|c(Oo$S z-~?|#C8Mt{zP?H=gwbxpQOK9B4cxUgh{s8ULrI~QEnVp6K3#LS*ZB544LJ5I;IIS^ z9X$A5=S>acCPSJqMMj8Jn%w)~x1H19VxGt6sfuI^+bnOJTAMtS5ClG<-O^qKVYA7F zTnL|n#WB~)z3%%B#5-PNqD)`t3(}rj)CPtN;SUvuOLsnc?ba5a#iHv(i6*S__^MSiIqQHo;2V`H?@15PZ2)_~!O4xKz2 zl1Ef1D;?@_01V-ugNo$=^+Y4&lsjh=Y*f1Y+XJQ|xJ@nryz&}iq?+03-3KE30~@&SU!>`4wDUM5U9^EwHA6sgxT)w$5A1$(qo8jiD9< zSbp*Q@JF!h^Sy2H&_lS?Q34UJf60|P(bepc^9k7ngFFx6Z&@Nf6I@xR4UW31xm6j^ zO)CIms4JZor~#a5@Y-hO@VNw|c`avOWfRql(DqsOL$L?C4ra$ckQ%5~U{GgXQ->8k z589#5tR%D4v8s-XGQFT(g=t8igXc0bMx9bE?GRh&ytJq9Ksp>q_&qZ}BR3)vWg69)B%#qV1Fj^^t zT!bV=dDFZ|TE@r5xsn-;@84l(xxFov8BtcS28y+0h#aPY5>6kUT7GWbhS}@qaprP= z-Fecv+LXRMTD|{xvFbYj6>8JCG!-TF1b5=^gb~% zvv+gFJGq%#xtVKE{xwG*m>@k+biMd6P*%J>^(FInnXe2*S&d!rzrp(uI}d_)nJkg- z&J%LQkP2Y^ZKadZet+3WV7!u&3J3jd4JV`h{&FhWX^@|m(_~mZh5k?2g?f0EG6JtI zB2+M=Q3o%PHLl@`@yBC=`A*3c6KaEr4Ux$=w*qB9rBsY5@?pzyG}9U%K==rjzK%b5 z2~CW!nVI|K(9EYKH9Ysu3wMjh?-Wb7ilrNKw~J-W^HX>7$8Y72U$g&~KXE^UO%F_P zJRoT9tL9;#jD7p*MbrGFnOV#aMQO*vwK$l*YOK>&#p)_j@`}+W+SiXsDJ7;rjq<2# zkJmz`QXx<&nz&McD@zc$#?QxF#A8@q#vcr!>5@+l9sR-CT?|R-STro_C_x9+8D(X= ziC!q(LIpJ{_EWOl$E1&{#(DgLV=XqD-wb`nV=GQHhIienjbp$+kDEA}!1Tq?$2)*) zLO&E~54J`V8yl97w?qF~vau1Rb+PCesuyN7ipM|$$G7q<8d^NRvhm9D#i-zM+wu~Z z^jbdN80cA{Uws#F#H_L)faWM8`8M;T9BwwtmoPIM2289tx^yBFH6&DsH9kT;sKqhW zsEl}+dmgnBdh8pbjHwDqa0AVbX&A;`GWiek_}|Hwce12-x;u`paHKodD^7JMdh|qh z=7g%ozaNG6Nj zeDClSCcWH9H`5!1#{Z552$gAA+Iw bool: try: from tidevice import Usbmux, ConnectionType @@ -629,7 +653,7 @@ class DeviceInfo: self._iproxy_fail_count[udid] = 0 print(f"[Manager] 准备发送设备数据到前端 {udid}") - self._manager_send(model) + self._manager_send() print(datetime.datetime.now().strftime("%H:%M:%S")) print(f"[Add] 设备添加成功 {udid}, port={port}, {w}x{h}@{s}") @@ -669,7 +693,7 @@ class DeviceInfo: # 通知上层 try: - self._manager_send(model) + self._manager_send() except Exception as e: print(f"[Remove] 通知上层异常 {udid}: {e}") @@ -832,22 +856,44 @@ class DeviceInfo: except Exception as e: print(f"[Proc] 结束进程异常: {e}") - def _manager_send(self, model: DeviceModel): + def _manager_send(self): + # try: + # if self._manager.send(model.toDict()): + # print(f"[Manager] 已发送前端数据 {model.deviceId}") + # return + # except Exception as e: + # print(f"[Manager] 首次发送异常: {e}") + # + # # 自愈:拉起一次并重试一次(不要用 and 连接) + # try: + # self._manager.start() # 不关心返回值 + # if self._manager.send(model.toDict()): + # print(f"[Manager] 重试发送成功 {model.deviceId}") + # return + # except Exception as e: + # print(f"[Manager] 重试发送异常: {e}") + """对外统一的“通知 Flask 有设备变动”的入口(无参数)。 + 作用:把当前所有设备的全量快照发给 Flask。 + """ + # 第 1 次:直接发快照 try: - if self._manager.send(model.toDict()): - print(f"[Manager] 已发送前端数据 {model.deviceId}") - return + self._send_snapshot_to_flask() + return except Exception as e: - print(f"[Manager] 首次发送异常: {e}") + print(f"[Manager] 首次发送快照异常: {e}") - # 自愈:拉起一次并重试一次(不要用 and 连接) + # 自愈:尝试拉起 Flask 子进程 try: - self._manager.start() # 不关心返回值 - if self._manager.send(model.toDict()): - print(f"[Manager] 重试发送成功 {model.deviceId}") - return + self._manager.start() except Exception as e: - print(f"[Manager] 重试发送异常: {e}") + print(f"[Manager] 拉起 Flask 子进程异常: {e}") + + # 第 2 次:再发快照 + try: + self._send_snapshot_to_flask() + print(f"[Manager] 重试发送快照成功") + except Exception as e: + print(f"[Manager] 重试发送快照仍失败: {e}") def _find_iproxy(self) -> str: env_path = os.getenv("IPROXY_PATH") diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 6ba5eb2..9626208 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -6,7 +6,7 @@ import threading import time from pathlib import Path from queue import Queue -from typing import Any, Dict +from typing import Any, Dict, List from Entity import Variables from Utils.AiUtils import AiUtils from Utils.IOSAIStorage import IOSAIStorage @@ -85,6 +85,29 @@ def _normalize_type(v) -> int: def _is_online(d: Dict[str, Any]) -> bool: return _normalize_type(d.get("type", 1)) == 1 +def _apply_device_snapshot(devices: List[Dict[str, Any]]): + """接收 DeviceInfo 送来的全量设备列表,直接覆盖 listData""" + global listData + try: + normed = [] + for d in devices: + # 拷贝一份,避免引用共享 + d = dict(d) + d["type"] = _normalize_type(d.get("type", 1)) # 规范成 0/1 + normed.append(d) + + with listLock: + before = len(listData) + listData[:] = normed # 全量覆盖 + + _log_device_changes("SNAPSHOT") + try: + LogManager.info(f"[DEVICE][SNAPSHOT] size={len(normed)} (was={before})") + except Exception: + print(f"[DEVICE][SNAPSHOT] size={len(normed)} (was={before})") + except Exception as e: + LogManager.error(f"[DEVICE][SNAPSHOT][ERROR] {e}") + def _apply_device_event(obj: Dict[str, Any]): """把单条设备上线/下线事件落到 listData,并打印关键日志""" try: @@ -140,13 +163,25 @@ def _handle_conn(conn: socket.socket, addr): LogManager.warning(f"[SOCKET][WARN] 非法 JSON 丢弃: {line[:120]} err={e}") continue + # === 新增:如果是全量快照(包含 devices 字段) === + if "devices" in obj: + devs = obj.get("devices") or [] + LogManager.info(f"[SOCKET][RECV][SNAPSHOT] size={len(devs)} keys={list(obj.keys())}") + try: + _apply_device_snapshot(devs) + LogManager.info(f"[SOCKET][APPLY][SNAPSHOT] size={len(devs)}") + except Exception as e: + LogManager.error(f"[DEVICE][APPLY_SNAPSHOT][ERROR] {e}") + continue # 处理完这一条,继续下一条 JSON + + # === 否则按原来的单条设备事件处理(兼容旧逻辑) === dev_id = obj.get("deviceId") typ = _normalize_type(obj.get("type", 1)) obj["type"] = typ # 规范 1/0 LogManager.info(f"[SOCKET][RECV] deviceId={dev_id} type={typ} keys={list(obj.keys())}") try: - _apply_device_event(obj) # ← 保持你的原设备增删逻辑 + _apply_device_event(obj) # 保持你原来的增删逻辑 LogManager.info(f"[SOCKET][APPLY] deviceId={dev_id} type={typ}") except Exception as e: # 单条业务异常不让线程死 diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc deleted file mode 100644 index cc33d61f55eb4b5855acef1f64ae68306afdf1e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46348 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