From 2a6b0bce3d4dc6ef160d1584692b7704683b4b3b Mon Sep 17 00:00:00 2001 From: chuyiwen Date: Sat, 26 Jul 2025 02:55:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=8A=B6=E6=80=81=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/api_server.cpython-313.pyc | Bin 28078 -> 28501 bytes .../__pycache__/cache_manager.cpython-313.pyc | Bin 13003 -> 15008 bytes .../__pycache__/snap7_client.cpython-313.pyc | Bin 5444 -> 6613 bytes gateway/api_server.py | 32 +++++-- gateway/cache_manager.py | 83 +++++++++++++++--- gateway/snap7_client.py | 30 +++++-- 6 files changed, 119 insertions(+), 26 deletions(-) diff --git a/gateway/__pycache__/api_server.cpython-313.pyc b/gateway/__pycache__/api_server.cpython-313.pyc index b99853a74fed72df5abd8a23170dc8a9c05b16ab..ca99335cdd00a270f493c410fa62ddefb9910987 100644 GIT binary patch delta 3124 zcmZWrdu&tJ89&Fq*ROk>ke7pPl9(ipV<41JG6oBeP)H$$5aO6uL>Q<=&6;12>N6V&l-6mD7wm*_)EZL>3tkSeqV#0(~+ke~n&NWV; zSJuzp`5x!{&i8%i@w1!w#$R#SeYe|%*!R&VXGZ%{=gVSs)`+vmMO*>vqt&ZomMHFEmhh?em_3R&9z-Z%g`BI7z0Hg2BeQ%~Z|g91 zQOw@PN)WYf{C-hgVCrH_jEWGr>WDdFHZ~bZS0c1%8eV3rZBVBFZC*yZq}ug%X5|1Y z_V0xMsB5)wu#$5GSn{6h2uUb__WarWkKtw%mgp-|T|0r;jdE3aMj`QJR#laxt_*(x zwuU^M)=DhHrs)Yir_t+@zitzVrA$K89Q z2fbGD1U^Fx6+2EHKq$wOHohY&sy3E!8{~?WzDPywr3P@Qw!QTwzLWEra@3{TqLOOg zyQCzOOENc;l`)wm%BUEQmdAuL6a~!+@Kc74@&PX{6I8`)469@IqEd~i6!kprZgaD+ z@SZ~tN*=8(a=u;SEHydIjn||G!JL0F%oaVJu918jEUa1ly@@7~{;hQ&uU2(i%ywxj z6*Ygo=0hvqL{EDI`^iRTKj|@>rH%L1g5qR(FEm=9WJx+fyQ--DP`}CB7j++<_8yV8 zGF{$ilVLjM^U)9LMS9Rzj=S=szJJ=@a=Q_!gj5dT(eJn$E0(%!g z4?qMYvKc`<$+}fDq~b_dCUkz(m~9v^ztf}vE_}4pR7I?xn1kOwDbJQEB=| zxt8^pr6T*v<{9sXxlku?*IkxwhdODh>Ff5$&o>ni?xJruH7spFtsRB6sG{bQYkEoJ z1>xQvjG7-W^yfcs>bBqr4YwS@v-GarkxGzn0~i8erw`KDMnJ`NA7-S{Mt?gLphrS(Je5Bdx*IyYa5&rf++-Bggrizk zHN@fE*tlkvV?j?aY(>{urb_k$1Q=)n1D5HcyJIw<1nFP)O86H2``#bmefj75`tY_1 zrexU16irK{lrPS)_f$UiZVH+Vpe+S?NfD-_x00Im&Xk6ZF#T=Z49LQSY8KqdQkMnB| zp0R8?$@H#T_(5q-G07NwM&^Q-o%cGlxnawWjIb-1-N}lY(nm?077jQ0N^)`@OfLY$ z0Z!7kK_7mZCx2{E!gup024^uoN%tM`oH_>@3%W}5apXWMbkCIP82Pd(X~ZodAKpb)>yHy|26u@ zp$1%;zccih{IS9y~YIO>Q_{*vF#A7fm2~TK{RDJ z&hGZaT6Wu!x9P=^=7ByiI}C6PfbXwSpw0rk3~&*E=anz#Ef|6~ja^4*h}W+O<}unp z%Tj*aPgkUxst9)O#`mqr^;p5e>4KUcN)@blDcyI>=XsyaV3aS)xSk;Tdivb4+$t}) z;RhESYy=*${HaKO4OBgVk3nx3Pgc@k=4pxF5)HtApI*ppZLAcR&9_>_C-J<)Cc5UA zNTPS%S1Z=euT9#;0sLi8IsJF0{nRhPg1=s1yBp4toSM|L*^EY)Zm3`gf!b~u+&Hj|Ip5YAN%PFS``j6Xw>8z^!UExCy#_Pd4rw@~X%RQ4a?SqJXM L^T@^j`E36OzqRt5 delta 2538 zcmZt|YiwIr`JC%}uV2@8+9Y;t$EoK!IPNQCY3V~*mvm{?Hfy~Dy+1P|ub)|G4)7lOuDiG^H8WanuVWQf^>j$rmY)W;<*!G8?G);y0Q3-LrYsV2C z=f`)y^S#ga(tZE$^o8ib4Lls#bM=$ zhk2sp`qNjam?C2y7-$ad;XIlm1v?tRqYC`ysNwu(<2UEpZK`Dr8}q~H6D=`S+(m07 zE~cz8r$TGkSUZZr6~W{1)oJh-p#q0(of1QNDB1cvimn-zxil+R3{8jM+8%}<+rqFx zoTDe;8)Bz&%b_egg6}$l?>WM-C?+=k%^z9zC;sYBTyrn^cRnwkcf&u$vw=2lkOetN z+E^6v8Uel~JxDvMZ%Z2}ja8TAUsD=@mmLwg&i-5v0WyLGpil2i%R z_*cfSYTqGFY_uq1g5QrlL5Q0~$+72a-xQ6Lqyx4q5sxYBM#iYJw5i_c)LO1Yo5m(!N{fj<&JO5(BU0coFv|JMNm2eR83ugK?!aDw7o}|^6O`cGmhtMFEklB&F;-^ProiuPbRR5-qP?av zJ*(%+rIMC2wCVZgTJg634^gQTE#>*o!S#qAwD3V0E6DobXW?EamSlcsU$+4lTiW1e zs1@F66|0f+C@$S*B z$*@Wut(~V?3?+x5FTO<{;i&!~6ylk9Abjc2#WUB!OM#uY5_>Nzw`v`6ZU0hm@2y1N zW#!|+=>|qr8 z5&}MgM8^eGf5SLO&FwG{k3v(IhrU&9P2Nr%SYHcv%QnlRnE1F}E}7DFr8uY8lDRG? znsUuGdz_21Q5eYv=nQ;i^t1F&aAEWn@hB(5_mlqW;Mi_Tb@;j(a`R!X#m?s7sv4$6 z_?5a%oVR!F{aAN>Jr8h?vs;p6f1VGQF_Pq1R!g7WV54E(%WP1$YKfHx-&&4uOGS9xhA<=>mULC;WNR zO&6=?yS-HIt-i3l*=Uji{2u{P9bcmj{Jl;&(C8lMjKdNp%zDyU;>wMElIm#DXCOXH;CugU%X?70P)&r34 z3t-&=j}3>n=0`-#T8OSwjPJs9hI_JFi!aLiqO$^f&eFBQlP_{*bDLGKn`wpKuKpqI z6zFMqKNI_6Bbv*r)0V7g}Y*%BY_QchzR(03yM_7Ywjm3Un=9ZY+&qUnZi+L@+T z3Wm-L!MjUFNzPOjs&@(`BCX+=UFKIE!{Q@f zpOsg!4oWs-f!{rUKwVN}3TWitqHLadQw3y7UXfK{j^&w9vJyuJqC&4*<_M@>Jk zkDnfb_wt*Y3)Fd(-n9`|_ljWQE7_`RcEv_F!M#Grzlid_fiE0ZHFi>CJ9wR#k$|nm zN_POQVxQE;W)osT1UC>^`v_OUNRrOC?%2@{e=KI**8as9*dpvL^~ai|U8`|HdX%m@ zWXZMKR;>X7mOhrRt`xsCn?g5_xY b2gLg!34chs{w)?9bcn9<|1kbxom}p}b=^~$ diff --git a/gateway/__pycache__/cache_manager.cpython-313.pyc b/gateway/__pycache__/cache_manager.cpython-313.pyc index 0378d6a470a0dd1c5b23fd4b90b5f0894d56e5c7..f4dff97eaf9fe2bcfce158fcfd9ad4dbbf85c530 100644 GIT binary patch delta 5895 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 LzYi$Ng~azS5FqMA delta 977 zcmZvaUuaWT9LLY;{g-?5=iWA?w>PQD?Vph*DP3(EOVeW4q-!aNjNTS?7Bp?HTFKIJ z5@lGnmknWTbdw)aCgPLgpa_*dSlmF;c6*r*Ejn8RLy$T3X;2;hd-0rL=Y#&>e(v}8 z_ue1p{25=3dY-u53ZfRe^7XObJZC&#B|E8i7L6l(QUPm$J9?^p8A^ zJ`+$)V9uB`UOg2d;r*zumyRg;Tq!>)H3U;KSL&0gUY3=VE-iaGru7Q_N;<$TH@Bv1 zxfKmly;3Wr1B)fES%rUs`@a1`HdD+bAz3@Cx6`2!*E_qxz1pU5nakPvH> 5: + status = "disconnected" + else: + status = area["status"] summary[plc_name][area_name] = { "status": status, @@ -90,7 +100,7 @@ class APIServer: th { background-color: #f2f2f2; } .status-connected { color: green; font-weight: bold; } .status-disconnected { color: red; } - .status-error { color: orange; } + .status-never-connected { color: orange; } .api-section { margin-top: 30px; } .api-endpoint { background-color: #f9f9f9; padding: 10px; margin: 5px 0; border-radius: 4px; } .config-link { margin-top: 20px; padding: 10px; background-color: #e9f7fe; border-radius: 4px; } @@ -117,19 +127,25 @@ class APIServer: for area_name, area in areas.items(): status_class = "" + status_text = area["status"] + if area["status"] == "connected": status_class = "status-connected" + elif area["status"] == "never_connected": + status_class = "status-never-connected" + status_text = "Never connected" elif area["status"] == "disconnected": status_class = "status-disconnected" + status_text = "Disconnected" else: - status_class = "status-error" + status_class = "status-disconnected" html += f""" {area_name} {area['type']} {area['size']} - {area['status']} + {status_text} {area['last_update']} """ diff --git a/gateway/cache_manager.py b/gateway/cache_manager.py index 32f8fff..9815846 100644 --- a/gateway/cache_manager.py +++ b/gateway/cache_manager.py @@ -23,6 +23,7 @@ class CacheManager: self.lock = threading.Lock() self.thread = None self.last_update = {} + self.plc_last_connected = {} # 跟踪PLC最后连接时间 self.logger = logging.getLogger("CacheManager") self.init_cache() @@ -32,6 +33,7 @@ class CacheManager: plc_name = plc["name"] self.cache[plc_name] = {} self.last_update[plc_name] = {} + self.plc_last_connected[plc_name] = 0 # 初始化为0(未连接) for area in plc["areas"]: name = area["name"] @@ -55,19 +57,37 @@ class CacheManager: plc_name = plc["name"] client = self.plc_manager.get_plc(plc_name) + # 检查PLC连接状态 + plc_connected = client.connected + for area in plc["areas"]: if area["type"] in ["read", "read_write"]: name = area["name"] try: data = client.read_db(area["db_number"], area["offset"], area["size"]) - with self.lock: - self.cache[plc_name][name]["data"] = bytearray(data) - self.cache[plc_name][name]["status"] = "connected" - self.last_update[plc_name][name] = time.time() + # 验证数据有效性 + if data and len(data) == area["size"]: + with self.lock: + self.cache[plc_name][name]["data"] = bytearray(data) + self.cache[plc_name][name]["status"] = "connected" + self.last_update[plc_name][name] = time.time() + # 更新PLC连接时间 + self.plc_last_connected[plc_name] = time.time() + else: + with self.lock: + self.cache[plc_name][name]["status"] = "disconnected" + self.logger.warning(f"PLC {plc_name} area {name} returned invalid data") except Exception as e: with self.lock: self.cache[plc_name][name]["status"] = "disconnected" self.logger.warning(f"PLC {plc_name} area {name} disconnected: {e}") + + # 更新所有区域的PLC连接状态 + if not plc_connected: + with self.lock: + for area in plc["areas"]: + name = area["name"] + self.cache[plc_name][name]["status"] = "disconnected" time.sleep(self.refresh_interval) except Exception as e: @@ -110,8 +130,19 @@ class CacheManager: summary[plc_name] = {} for area_name, area in areas.items(): last_update = self.last_update[plc_name][area_name] + plc_last_connected = self.plc_last_connected[plc_name] + + # 如果PLC从未连接过,显示特殊状态 + if plc_last_connected == 0: + status = "never_connected" + # 如果PLC断开连接超过5秒,标记为断开 + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + summary[plc_name][area_name] = { - "status": area["status"], + "status": status, "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_update)) if last_update > 0 else "Never", "size": area["size"], "type": area["type"] @@ -124,8 +155,18 @@ class CacheManager: area = self.cache.get(plc_name, {}).get(area_name) if not area: return {"status": "not_found", "message": "PLC or area not found"} + + # 检查PLC连接状态 + plc_last_connected = self.plc_last_connected.get(plc_name, 0) + if plc_last_connected == 0: + status = "never_connected" + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + return { - "status": area["status"], + "status": status, "last_update": self.last_update[plc_name][area_name], "size": area["size"], "type": area["type"] @@ -144,12 +185,18 @@ class CacheManager: client = self.plc_manager.get_plc(plc_name) try: data = client.read_db(area["db_number"], area["offset"] + offset, length) - # 更新缓存中的这部分数据 - for i in range(length): - area["data"][offset + i] = data[i] - self.last_update[plc_name][area_name] = time.time() - area["status"] = "connected" - return data, None + # 验证数据有效性 + if data and len(data) == length: + # 更新缓存中的这部分数据 + for i in range(length): + area["data"][offset + i] = data[i] + self.last_update[plc_name][area_name] = time.time() + self.plc_last_connected[plc_name] = time.time() + area["status"] = "connected" + return data, None + else: + area["status"] = "disconnected" + return None, "Invalid data returned" except Exception as e: area["status"] = "disconnected" self.logger.error(f"Read failed for {plc_name}/{area_name}: {e}") @@ -176,6 +223,7 @@ class CacheManager: for i in range(len(data)): area["data"][offset + i] = data[i] self.last_update[plc_name][area_name] = time.time() + self.plc_last_connected[plc_name] = time.time() area["status"] = "connected (last write)" return True, None else: @@ -265,13 +313,22 @@ class CacheManager: if not area: return {"error": "Area not found"} + # 检查PLC连接状态 + plc_last_connected = self.plc_last_connected.get(plc_name, 0) + if plc_last_connected == 0: + status = "never_connected" + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + structure = area.get("structure", []) if structure: return parse_data(area["data"], structure) else: return { "raw_data": list(area["data"]), - "status": area["status"], + "status": status, "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.last_update[plc_name][area_name])) if self.last_update[plc_name][area_name] > 0 else "Never" } \ No newline at end of file diff --git a/gateway/snap7_client.py b/gateway/snap7_client.py index 94c5218..3bc94dc 100644 --- a/gateway/snap7_client.py +++ b/gateway/snap7_client.py @@ -29,8 +29,18 @@ class Snap7Client: self.retry_count = 0 self.logger = logging.getLogger(f"Snap7Client[{ip}]") + def is_valid_connection(self): + """检查连接是否真正有效""" + try: + # 尝试读取PLC的运行状态 + cpu_state = self.client.get_cpu_state() + return cpu_state in [snap7.snap7types.cpu_statuses['S7CpuStatusRun'], + snap7.snap7types.cpu_statuses['S7CpuStatusStop']] + except: + return False + def connect(self): - """建立与PLC的连接""" + """建立与PLC的连接并验证""" current_time = time.time() # 指数退避重试 if self.retry_count > 0: @@ -41,14 +51,19 @@ class Snap7Client: self.last_connect_attempt = current_time try: self.client.connect(self.ip, self.rack, self.slot) - if self.client.get_connected(): + # 验证连接是否真正有效 + if self.client.get_connected() and self.is_valid_connection(): self.connected = True self.retry_count = 0 # 重置重试计数 - self.logger.info(f"Connected to PLC {self.ip}") + self.logger.info(f"Successfully connected to PLC {self.ip}") return True else: self.connected = False - self.logger.warning(f"Connection to {self.ip} established but not verified") + self.logger.warning(f"Connection to {self.ip} established but PLC is not responding") + try: + self.client.disconnect() + except: + pass return False except Exception as e: self.retry_count = min(self.retry_count + 1, self.max_retries) @@ -75,6 +90,11 @@ class Snap7Client: try: with self.lock: data = self.client.db_read(db_number, offset, size) + # 验证返回数据的有效性 + if data is None or len(data) != size: + self.connected = False + self.logger.error(f"Read DB{db_number} returned invalid data size (expected {size}, got {len(data) if data else 0})") + return b'\x00' * size return data except Exception as e: self.logger.error(f"Read DB{db_number} error: {e}") @@ -88,7 +108,7 @@ class Snap7Client: Args: db_number: DB编号 offset: 起始偏移量 - data: 要写入的数据 + 要写入的数据 Returns: bool: 是否写入成功