From 6e74eaf206ea3d42c6931f0bfd43f270c2aa7fd5 Mon Sep 17 00:00:00 2001 From: fujinliang Date: Tue, 10 Feb 2026 10:18:17 +0800 Subject: [PATCH] =?UTF-8?q?0209=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11.jpg | Bin 87412 -> 0 bytes Fedding.py | 1153 ---------------- core/state.py | 109 -- core/system copy.py | 288 ---- core/system.py | 192 ++- core/system_state.py | 36 +- db/messages.db | Bin 12288 -> 20480 bytes db/three.db | Bin 151552 -> 262144 bytes db/three.db-shm | Bin 0 -> 32768 bytes db/three.db-wal | Bin 0 -> 4152 bytes feeding/__init__.py | 9 - feeding/__pycache__/__init__.cpython-39.pyc | Bin 365 -> 0 bytes feeding/__pycache__/controller.cpython-39.pyc | Bin 5591 -> 0 bytes feeding/__pycache__/process.cpython-39.pyc | Bin 6892 -> 0 bytes feeding/controller.py | 1195 ----------------- feeding/process copy.py | 167 --- feeding/process.py | 359 ----- feeding/process_bak.py | 450 ------- hardware/relay.py | 3 +- main.py | 183 --- main2.py | 133 -- opc/opcua_server.py | 171 ++- opc/opcua_server_bak.py | 154 +++ opc/opcua_server_test.py | 237 ---- test.py | 32 - test_angle.py | 193 --- test_dal_debug.py | 31 - test_door_close_startup.py | 69 - test_overflow_state_change.py | 59 - test_visiton.zip | Bin 6952 -> 0 bytes test_visiton/config/settings.py | 94 -- test_visiton/hardware/relay.py | 219 --- test_visiton/vision/visual_callback.py | 222 --- test_visual_callback.py | 56 - test_weight.py | 26 - tests/Investor485test.py | 259 ---- close_test.py => tests/close_all_relay.py | 0 test_led.py => tests/led-app.py | 0 {core => tests}/pd_system.py | 0 ...ntroller.py => relay_controller_pytest.py} | 0 tests/{test_rfid.py => rfid_app.py} | 0 tests/test_feeding_process.py | 54 - tests/test_inverter_controller.py | 161 --- tests/test_transmitter_controller.py | 165 --- tests/test_vision.py | 65 - upper_plc.py => tests/upper_plc.py | 0 tests/{485test.py => vf_control.py} | 0 test copy.py => tests/weight-app.py | 0 upper_to_lower.py | 159 --- view/main_window.py | 11 +- vision/.py | 137 -- vision/test_feed.py | 62 - vision/test_safe_close.py | 52 - vision/visual_callback copy 2.py | 277 ---- vision/visual_callback copy 3.py | 273 ---- vision/visual_callback copy 4.py | 452 ------- vision/visual_callback copy.py | 240 ---- vision/visual_callback.py | 2 - vision/visual_callback_1203.py | 480 ------- vision/visual_callback_dq.py | 81 +- ...k_dq copy.py => visual_callback_dq0209.py} | 550 ++++++-- vision/weight.txt | 654 --------- 62 files changed, 838 insertions(+), 9136 deletions(-) delete mode 100644 11.jpg delete mode 100644 Fedding.py delete mode 100644 core/state.py delete mode 100644 core/system copy.py create mode 100644 db/three.db-shm create mode 100644 db/three.db-wal delete mode 100644 feeding/__init__.py delete mode 100644 feeding/__pycache__/__init__.cpython-39.pyc delete mode 100644 feeding/__pycache__/controller.cpython-39.pyc delete mode 100644 feeding/__pycache__/process.cpython-39.pyc delete mode 100644 feeding/controller.py delete mode 100644 feeding/process copy.py delete mode 100644 feeding/process.py delete mode 100644 feeding/process_bak.py delete mode 100644 main.py delete mode 100644 main2.py create mode 100644 opc/opcua_server_bak.py delete mode 100644 opc/opcua_server_test.py delete mode 100644 test.py delete mode 100644 test_angle.py delete mode 100644 test_dal_debug.py delete mode 100644 test_door_close_startup.py delete mode 100644 test_overflow_state_change.py delete mode 100644 test_visiton.zip delete mode 100644 test_visiton/config/settings.py delete mode 100644 test_visiton/hardware/relay.py delete mode 100644 test_visiton/vision/visual_callback.py delete mode 100644 test_visual_callback.py delete mode 100644 test_weight.py delete mode 100644 tests/Investor485test.py rename close_test.py => tests/close_all_relay.py (100%) rename test_led.py => tests/led-app.py (100%) rename {core => tests}/pd_system.py (100%) rename tests/{test_relay_controller.py => relay_controller_pytest.py} (100%) rename tests/{test_rfid.py => rfid_app.py} (100%) delete mode 100644 tests/test_feeding_process.py delete mode 100644 tests/test_inverter_controller.py delete mode 100644 tests/test_transmitter_controller.py delete mode 100644 tests/test_vision.py rename upper_plc.py => tests/upper_plc.py (100%) rename tests/{485test.py => vf_control.py} (100%) rename test copy.py => tests/weight-app.py (100%) delete mode 100644 upper_to_lower.py delete mode 100644 vision/.py delete mode 100644 vision/test_feed.py delete mode 100644 vision/test_safe_close.py delete mode 100644 vision/visual_callback copy 2.py delete mode 100644 vision/visual_callback copy 3.py delete mode 100644 vision/visual_callback copy 4.py delete mode 100644 vision/visual_callback copy.py delete mode 100644 vision/visual_callback_1203.py rename vision/{visual_callback_dq copy.py => visual_callback_dq0209.py} (68%) delete mode 100644 vision/weight.txt diff --git a/11.jpg b/11.jpg deleted file mode 100644 index 51fe6d9c94b3c3cb35c245c58db44a8f067ec43d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87412 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
e8#%vgNT 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

7@aH^-n z91L6!%QcY(t7;gZHb2ia^Qv1Jq9%Cy`r~a(s>W+rn$x8^Z%LV2<=P9@gUudgvPTOK zL*=V~?A4**>!@Z|w-*H6C^9OPKRSRiLFvc!uO9K`+dqvpM~CjS#y=>Ke@dsKU)@=1 zDKt{brE-2(BeiC|msp9;y7**$D$!2GS}7w9{?lXiHDxWYq`+v^`05;E`PYH}0K!qL zD`jsk0Nh(1Vf?z+YvIou-$+p>xL~R=%bay5`qqos{{SgB#4fAuJumw<^ng5`Ki(hX zQm&nNflk1u>sWRkE0EbBH))e0`Kv9^DYa&fic+1r8JD^h#OV+=?j?prk~5BkAMoO| zqtkB;V3;3wf0ndb{_Wc);IaBsP36b@{5R)bQ_HZ8zG%1eI{m$CSu|1_W#4H306f*G zwCmE!p(7twpx@l*9&3JeEINeSRd=2{o=N;`YE*D^{?Fn}r&;wROKXdDCSwqP;TbSY5XhfLk85QdwDuELW8bFu2BR=Z_8i zLe}-45NQKc)S^Icn<4K#P!--z`q4N~4Vo+Z-@10;bmLZ1C|fJa`Oj^tMTledU;V%C>a z*<3JS2tH@~ueAPtmE!&!@qN~-q|2ph5DA=F;R8v`-*!K}=vkMKy}P~-8-5S*V_MAx z!$`zU^LcI5t3NpdxZE&0{G+&_S7)NfrQK;dCF)yU++STlFlU-qcP`7jt9pE=(DE@~ zI%}r$Qq|<2QJzbSmv$yePH?~hkU9f^2LN;x^(spNOIhvD%W-j&@3|VhXX7q}x>mhu zdu{-N_j8rUM*wH@$UmJ8Uvi@+j6Vv`ajEIeZ#0V~`c{xh3-KEn_!w&zuE~MsR;W%QfQuKJj(`0E+dQWRiJC znvyZLwmbz)@JQ~(aguxVz~a087D(*A9&5&F3~}5=G%`rXfTl12{7L<5%Cv^I8l|gT zvZbiIjocCUmw3qhvsF_;Q}@~EcIY)tFkL~_<0Z4(1cE<9{{ZUdxCfoI&34}Kj9kY$ zPI)Jg**ua@T-T=jEsh4!bh#UT%b8??;T^My6yyQN8Bz{E5y%8)d`|FmdTiGB7E&RK z;mQn(fS50%?$!itWp!^D5vd1cya#r1cpD5Dqd=-RG*B)g82?o3k777gdJyUzTL@ z+930liCcvj;MG~QfYz*%Lh4lh>ULfuKf{y#JJ!~xrZ%56XumWK z8p{h~ork7<@(p>FWz5dl=!@PNjpWqlmLrF@vz~B$_Us?4ie;9T`WA?SVf%}jqK^{s zz9{2n-;Rgpbn8=T7nd4ViKyyUu_L&a8+^zKRaPf}`L)RrEms}8R;V~TdB%&GNd@g!Gvws!K)!BHo;3$`c}z78|gKSR6gkF8_aC9U?IaPwOuAziX6j5$n=!GGHH_r*=% zJx9van$}Tr!W#*MpbTVJ2zI8a%)9)I5i{{X;+d7E=U^zC;U z{{UK?--|T{(rp*R7L&^G#Khdn*&pZ)SBrz3wAcvq0Ey$%pbU?mNl~XaZ7H<2}NH| zZflo?l%3p?Ye!6#n(g6s8wzx-5)&lw?X>p^acK)eqEN2JP&f;og!A9EbQbLu4u5|T4}nq#B$^TX*$4EfXA6K|rr}Cf4 zQ;(JrunsZCE8VoI{5pIuqMOKdE4DU~mSkyyq>cl+k1yq1G3)JKwi)wW;j1oGRMqXs z5B`&?%WW6>+iU&FGzUBeQchIyh8^+hD!l9;O_J6yRSJ@O=C!qpi)*`RWwF%XRXl(o|r^gY-A|!SMdBbdL$@*U^x(%Q1-w>zob0mPxMj!@p}? zI@bRA<6Frf%MG{+rvwk=ex&xm9$n#mD(6zy6HB-bmez7L#l%P00fY7VN9$e3#GP75 zZBtIReTHSrk=TFr>z1N&(B6fjx%(ZyxqL2KC*72h9s&OIZIKV<3I71Tg=E`mSFEXS ztiDpQz{lO^p!Ux{QfjAzZ6=?=me$uye$Y^~YH^6eXPKhUC!U$IONx!_`6woZdnn|&~brX$B6C)zMCThox;3!>QglC z?0eT|L(Y@Cx!!yh(i-PmzSJ0QTS&oH-Hoo>loQC$0RAGo-xbI%bxktQBVlng{{Zf; zSpNXuKy5t_&HO;|U*Rs7_CE+icPfRszjnA_RDuxjyphm_4S;`(p{zd@-tE#g-ApnU z-Tj{%D}q9vSd4H1`3T1!dxbdy+p;pdl&pC#i}f$<9cKFX{(4Gw{X0}z&LYwDt8uty zFFfR9?{ir9k_K(L`qlk@;cl;xMZ{6Yv7r9w0 z_u6t?+q@4WB9sY*=tt7L`{6d6v1nG;L~>j{e>DEJ-)q+Qca~D7pK@((_LhoGpn%vT zoP*er{yh()81ls)Wq0o!&Yz*Z&xY$MZye{SC-4KcU(#20wXEwb zGA6~8NBxEQiTquePy2$h^e-5ti`=fcV)nL>ARlcI`B64VUAW0AcN}1z&FNhPth#1| zj-*_yP;bmVCXoF3AMZCmjWI-n#QMB?290%g=wz5g&Z-VL0Ovo6QPZjC10;Fe5Y2ON z@eM)cH@5DpaI8RW=ytpIz>Q1p^^5_4aZ>n-?(RGU6CVqa=+ekjop~6mr>PqEn``6~-~s)2Crv)2i9(Qoz|>V%Y?WI` zkx(?{Km>D&$GW|EQRV9-lR~ZE0)S^tDn8(ZcRVGc2hSyM4ATZ-KvmNrv zibH0&R+y`Rf6q_xt5({hL~)#oxXQ#!Q?Zgl=v0zJ3}Bk$b)5xf-zA{KP~N{<+tXrLJT0Xoyk_JqrO{Y1RWh?Canm7(bL6_ z66wZ0^G}ak)R1#>8|xbX0EK2Ns!9iD>-p3Y&V14V;;@urXqmd6Bh&%;iZ|6N+HgKx}SyaE!|TM zb!O840LQ`|#uVo)%Yl+N0!TQ*0g>CfcO=lHj)xW1hf0&WII$F!Z;0|Dm%bj?20%-@ zM-Drw?hnv{KRWFG9kkl>+-b7M3kl?L=7Iaj#E?oG9@#)M+j;)=Y->6#mZ|&8lq+&N zPan$T(>}c`7wlIa7}e}`jWP$ETgo1PD3S7m^8%o73iIC;>C>p0;?~1BGndfdp5o%+ zZKRePt5oyt!k&@`P7mSwf!j5wq{$L#mTvA!D3H+j$Zjt1WQabC;dUlFf~G#jlJ>vG)OS{S^?Io{0} z4aaVC#(Q_%GUK49^&4OcZ+)oPM~je~j)Np7nQ4m64>_cFBOrj>LDbK61Oz^k~5)WO|2zV3ykN z#Cj7k^KIB)LH@}`!}}e|%Dm)#Msw)?AhAyt_!;ct8I_aFiMuO|p1rx?ocHACA$Tk4 ze#7Fg40xX3h&3S`cOHX!>}>rB&(&+tJRKuh&vT}pw$4#cqIFT~FbExhz!?Jptuu72 zW$9igGk8NyxGoN(JOK{j$aTuQWBuTG_pVb?xBE#h{I*MeC#k1i+np!I`tFuSAa?t0 zs3QSY`EWgh5&-oEr+<`UqDtoQ(4WEf@?L*!SV#jaY0}C8AI7CZ{{ZMzc(vmH0EA1! z#tl(kxeLhDvv3&kp$>pcY zgZ=HNZ`9ydK+@XS=#g4o+>a{VBLx2d*DN)R;y5kS1P+CP+%R}Ou~fmf)BHsR^`DtF zt@LHUjdO`NE^<1KO6NTbAEjtp6uQ&oF^(84_WuCxunY&|pXXRUG@eTvYb)p^jjftM ztV)7nd=jh!9l7BB@7BGQ-5xcq4p|+Ru?s-ahTd2MlFgpHA? zo&~*G>}Ii;2J-m5kZnJB=!g8 zZ^_5ylU#pR$`iZ2dMAhUN3qkdtgjtpf@xoBsmT!#6cgx38OLnb1$h%&T}uPtyiUtXl;bLEWw*?El@|vZk9M{B6GJd)_PT<-K5lba+NX!@?*WqCyqS+|<#-)2 z+qGDYzHi~F)y{Z7{d)A8Jade>w7t_`ZNx=KK;2aR4Or7>4X8&U$me0tzyAPUy5k_) zQNU$96=9HTBL4tFhG`OO89vJ@oP<27{-gPORVQM~w)s*R4tiFMT4oKk#nt}+?&gyp zy8YhYm7A&Owkjk=$IXxU2l@V#=q0$dC+zbyY z=Wjn;o;@oMUzh+Q&pAWvRISD0LC0oo5ZY#lo>0m=41&lUBSX;!RmRMH5Fqm?S%7icGRH^*m=e z7~uB$5)LJnpLKb(+S)UFo}b}e7K@_YT3!o_;}BI+0t3iDhqh`fDM}YA9_+4)9xJ+Q zjbdqLeb2`~hH?7VGR~mjWY)B?#d)aSnQ@5ZDl&20kLgZUw1i+H9Ct+(6r!54HBRle zWLVkFd#J?(Xm?K{qLaYMAP?tXOx$XX9oj?x011YlZE&CiZw8GzvrYLiseVA6HS z1?G~m`S8fI+r;6c#^mNR$O?1!c^=EnT(g71nzQ21S4$h5^9;F+=gGSGW3afE;H?boO5NBv7zjOH41s?wZ68NFZ5>USHq+tI5pU%4v ziQ15`(N@L=0$Zbjj?s?4;aT1Zuw5@pmr#|1JgPb0OZ%t-W7^#8yHQSeV*xg z#(#G&f1B{g{-dpWq}tHe^wwKNVI*aI$e8V5ILXJL_3jROSF06hq|RCEE1buQ{4;an zTrI4I+6g6By58R;h@5UCjIa!G>&AKlCZ6^P?2}B=W>2tL{^k-G7eJ$EW+Zhbv(uz! z(~XT%?pqxy*2o|9i4)Cc$M9|#Q|XYvk@T)wNnTTJxPWr;X6a5wN+%KpBfjDO_qHpU zlx;hfFHx`^BU<83ZZoinXUla*b_$8W1%I^6IC2Wba;i z18*{A1zc6A8fAM8#zCDBOA!>r`oy=C3MRMxVbxET)~AhksL^{ z#F^ywUOv0CS}`2&h%UT0qFuvtt=(V6b8!|I>~RSM!FnD@^j<&Rs4O+_4Qh#WZ5PB6 zHMk5zq$#)(6b`*fVO;*Ems;)on$z7zbn8;kM4^9$<2&*G*USJtiBLc}0d*I*(=_F_ zSenk}K(R>}k2``MbN%2HI4!{}*~SPp%{biNop(<}YsIeV!%?-ewbL~@?q=jhE}~Xn zuLrePx$(}K{jl~{`Ze_O$16lx?-4=Tagm75dV5wH-isX>Z*E(C)tn#(^KfL}xnMw@ zzE3sIKfRu{mEpY(;bpdwNs{8_*kA@5Hk4zMM<;0}g$tYgYHakU)NR{i4I!NxRfsXV zZb?-nX9JOw$9=V(apI^%d!z8}konzH%(|N_pyMZ=4ng9f)hD)l`zuFp^lJwpj-gghDX;?#t&l?^w;$|+NCfgoJ?Xkh5$&e>jnqCQn=6Nb zyg8)I7aw|Qm~sLB{{X#eX+Aj8^$SFndmR?m;F3J2joWA)220litjTL{s7EEmvdSck z46hu5fBMy4+e4RFd8E@c+u2e)7cfWWY|l)Anz%(oiOz5BGt%z7Pby6kTWR_PzG1?T zvoXdo{@rPLx}@+#?WK5TXABB~3$6y!xEx8}`g_-rN2ATA7n0#_A(J2kmL`n#_pXu# zglkiWWeMg@m`VQtXl?%h+3Th><2})zJpTZhoyFqocQ)@FULMgHS`xV;^l(`I*a&9_jApfv45g;>wkpPK4te6*Z~g61&)0B4g@XCJ#$ zkC0%4xwz;>dOVu0b@tOOHBUQY29juD9$G*5K_p~!`MPo|J5TW@p*Emxr?|AYgaS;y zUhDzt2e&nUSEX%-^_suTaBm)>o3SO~#_#!JAEtk(6os`C!^^vNjD}@%0)W6AWN}`X z{jqV|UDJQ?9sdBrxh)DPb2P#m`4s-WOuRO7aB+Ow7xmtgwMU*f8vK3+;fydf8l}25V@- zS~K4p`v}6Wz9`YQw?fh~XY*Q;+fKK+1lemEMbIDf&iX&(NUs^Xf#tS=B32Mb4nPZ> zkOt$QbDCXN&8#lhYd8}{k(9Mx31p!_pZOi8qz1(PLZizMR0(}h#8_!D~3bC z{{U$5+Xpn21>A2RC%Qcj=Em+M21{Fo9`L{TRTC+Q;kPOG7_U0F@o$OXiR6#O8kiBV zTpPDx&U+J%)uC_kJH{~Je{D%99j1mkKlCLuy^H9}SjfH6(dwQb(=|xXnG!*8dGn#* zlkPen=Uk}p&Y`91&^y8Q%O}b_k&lIi{= zlF3MXoCC%P`HJM01E;Ucc268zX}bLSZmHp0J)vBAw%%-e*+I(e$ps!*tOq*OvRs^d`Iu z;#Q_LJ$_4)n;53vM_x(K>s^P#ZDKg(g3DNGE>d~EcAM_Pz#WgffwzVk9heMocXR&$ z9&W9l#=l^)ja99qUo&XN8GtzauzwmIPG!ncioMS}`EUksJ!{bZ7Wg&|X6))3wcWk7 zj7Mt5Cr>hCgPbo=I(6$_Zpg?;Zlb#%hH{s^xwI(}GDcf;+XA2peqvjJgN)ZTHD+w5 zW6*508=X(#HO+*KP2QVvI7U(4H#r}eZ@`M7@SDV!7rM3VcNy~?w`X=v2|4HRCnvl0 z117pJgnlHp)h@NIFbtU_^OK$=1r;A|du0LXpP!H`i(NguGw~kgC`j1txi@YX)qL;1mW^{6 z+D;5$$T8PwC0TRRllQvUo@vrFnsu|k-e}d2)Pb5Aq#0WMH^xBfDwl_?yxn#L@>*8} zxL$x`oU!Z&>To;OmC~FV)3C6gMexMhITVbSU~*1-bC1PqZ@}8VrXUtt*5Z+`?yg+rh#$hoza9FX`87co za%Z{Z{cB54&ja$VwMG$Ku;_70yt$i3rjrueSQsHWYb&WBPJiW|?4bMf7V-OQiNwN4oWGl*mdSm&*IYHQ=@v2JS{VI0pb%MUFJ? zY((mL8T^hvI=P~1{{Y#(B(q=nX6zC}pW!tkSvj-GBd1V0S0NaXJ2c>yC+6qBddj^u zQVtKPr3t!oQe6*t)$d@^>}0u=WGKR@Jh|+n{Pg-)nBKEC`6M9ABbRaq1&PTc@v1tn zi8T#E3nY<*zh{qxQM57s^B?bV*XxXA)2-%vh!m`3zTAz6=FY_aDKJvo@pVIj2dvl;qDqV^Ge}HN#Gv8jdV8t8`I!m#TsTE+~%S1d=4~J zU_tX8n~po<`c~r&r{<6-_paJ-iiwk{DC}tXA5XSEBY;C2#?hn5QU(gn0+4w)0Aa96 zIKlPBZfcRi0e8RfO_Y|z{`PBWW08=5c*=yj{Y`V;8@jTKR+Xf?B~}LC3`_&fmPOb< z5()NGT{>RrDn-ripW+Q6^M##dxlb?gY@z%=D(Z|Zj#;-Ssl)3tTHR_9E}o8%2qTeU zci-g~Vi`l@<`_}Qt{Yo*I<3fT?8v|!xB&j5yIo>9bp2-#-3y3{{I9Sf#t#L1bDlHa zxh)RJYd$P7<2L43noR!ybFb=1AB|^HT&9$v&nq3Zq*qox9J7*nOM3E{uYYalo(v}WgWlX_5T12XG_J; z6`h8krG2Pdt<0+vNsqZ1BXgSUz8PFUiaavfSBR&H?;%b0lQ$AN`V-$AbMN&hf$@Ho zeQ)A~k65;lNftb|W%(olfE#^4$#1~siN?Z=xt}TBTWX#F@F5l!)_>abkM*-g7H|9M zo8|lyr+VsqFQ@o(QPr)yP2tEzpt>Jt3y&!r?)iq!&4}1zrF`|`cxAWP={{fwXiRcF zd8nfj3uWBxu|9NCcv5kbk6ss_VMJYqdzZ#-21w(M^G>vue4Vl+NLZ=$z~`S~+upp( zz?xcW`s%duh%N2^0D96$2GwkZLP;YSW)1JOgI?$0^u4k0o|S8;ODvP6x*2@axr33C z2|VylKMKzHf#JcgX+qmfG272{eAk@_27R$e$PZ@uw(xs%P3kmd_&>weI*)`>ZAR1= z)TWa8gN@8Lx5{(wp-&a#UM15%vh|zWLN*AcDyVve{t|wp2ELcDSVg1@XrXP^HCTY@ zuqemrUo-fpP&&Vfbt};1$26p-doM)?-)`E)yR&MkMm9!Wv*PL&#!5)6KA`9NR5s`B zn##v_(n-H~eR=ikRy6xpyT5|n4=*Sg_8gznCX((SJ72}H`_cdhtt4_-_6G2c(9`_S z4r+Ggk1K6|5|uwmR|I|_{cDPU5#QKqE2ns$PoB>51CcyOWQcp7KJuSq&O6t9WQRoX zF#|10214ypTHj5&bKw6J~RaYqDYa?P%L4PuzVBb-`7m+(O_D!^@a|>su9AbC zTE;;fgPozig>m%f({?Uze>U1hm6LWd5c&{Ca60^_k48P~bCr`otu~iDu|@Ws=^2g3 z%e?N|tVd!payn&jep;*I;BR!8G_5-5$$!0JX++PM&=!ol{{VS%cKef#YW9Hk>L-@% zwuU*0f90XtRWhI6Rr+M)4z@&&)?wW7~Jg5q*wM$s+*x7*<)ssDIw)qyv(Z-PD#TM4sZY*mghUbvKahHXMJ|Z z_tUQg49Iz6c^`LjjQ(D=a#`N?D-9LqW*1ZMx!T;YAH;A@NgU;erF!0#;P|d&OX(A8 zTiJ*$ARm6(F}bt;*I&o=Il8f@@fyZF$2Aiz%+g7zMjz~QG_$GdW;wtOkCj6b00TcM zJeK1C!8A~+%X2N`s_in!a!xUTF~%{^UjG1z^-KQ%1nL^3mhjj@E$!vq=S(6{Q#ivA z4s(s%V}f}iuSwwjx_GtUJSBgNXdC|k-oW|4Ox9mtqbF#x{;tvU=DB02T&zaQ%I-!4 zk|Vcf`Fox-Pq6UkjF=xUM!$p(@=OUX`^yzm9CgF*S@5MCutKkfgW+Kg3jy-8W|+jy`~cLA?nYy}*t= zxY-#B2j@(04LnfAKKWcL6DHRi^VsyqP2Zh){{Y+6V(l3sofOXpvx`)?xpdUDJqp~$ zp_mwvn1VWmQG@x_o7)R`Y=rHl=zc&v?-D}$)HmH#W7%7tE7UJM6?dpctg7BfIFByB zb03%)+HwBK6+wO*+=rQGoZ2tlSs+0Bj|Y%=1Mu(l718~Q6yFb5zL&tJ~HqsIOVv|R$m&czB|TLzZb z<}S{vS9+3pVs`_R-`h1GhrSbOli8Qn6|N+3NODF%U@`#51Y@YiN&G7(#1vFHq{sDS zcz!hWx@(y1gmZKROvH`|=RJ8jtD7xW?g`$}zkAkEb+G>c3bsGJkMe8MJVo%j%SkUf zM$2Nak2Wp4qx;`8ZvO!5_Ul})h%K({JU;{j&P9fJe8|3JK33Tj=Z)MQx$9jN^Lka1 zl8$f8sU#)sk zt0t~Tc043xn?GqV~SI*t5xPPpS`y1}YNWtBcoP)>E_pH001nY7}#b(nY zk;9FiVMB%_k;uUN#ACI3?vLS1n+cs_X1Iv61&_-GLKB7FMl#`Z$oY3;w_QneMN7L> zpG=N@F%?nGyA_y7UnG>s&)v`CSiHszrH8ju=Xrh|>*xRx+Q0jk3V*Fsm%=*bu=(sk z&m2t|{!L!vspxn1GE3$^nF<2tWzOY~`^}T-jsffGR0X7W`bFNRlbF^Kx=Bt*eDpv8 z;|Bx&1>UV_^J^88^b{f6J+osXA z7_9PEFm2yxVf7!>{*~dWmcGJJY!3;lTf>Y+Aowz zGDOaLDP{@@{Qm%2pHR}?2<>M;OjE`RgZGPphZz3=WP%U>0A9VO!uHA%rd@<3o8|rtdj6)guJrvsQ?UCUp`b|!F+AVsbFh$gKOiS>O#cAiSnXGMt;BfmfOR&H z#qBJOgL@pY5I7AZ4ah(5;;A1&UWcv07OgGa^{TL&M${z#096!eEsh8qz{jA+CjhrnlkD*{EQtlk^IA_W0dUN?&phDeGD75S;DeIZ z^~Ff#H&D}r00&PN2(Aan|3aLbhQg2ov$@@)WdfyZIfJdZ+Z z&%`8K-8Vtjp+V1-nZ?i(cfVE3q!HiIy{W2c)-n>iBRNTQwvbu73I z?hk*%ipaKkAj?F|yMa@KjDyryW$`9!s24`iu0k2?t+M2GQdq9iI|1d4dW;?`kqdw` zSu&A(Nxz=T>N|@$8Y_f3S5tzu=vw!L^-Uhi(^%9l{K-7jNr^clcTfC#73Q|ny|vUc z!a$NI5-vtYN3DHZsM$l}9}Z6fGMKNe&y^-J2Icu5ABZ)M%`@9aq73`azu) zo-(m*82#ATIr;|tLi=;Y=$C1GrrXO3l}(^1CpZI)AH*NhwR|fyGQ(hQV^-K!DbdG6 zy9H0-Qg}1Lz9D=2wVO>yH20mQd1sKJ-cQUGagqSqI|Gb@L9H!vld%0d&e~Y6H4S0@ z&7xWJb#Q<51^)nO@&0k0Tb~e*6l(hJlW}e>VzH6!7TkA>rtide_x7%jU%l{*Vht|u z$-P@>{{Z940p=+G0DOJl`{>sx2gI)n{{U=_0`@6w+67y5yHzcTSLG_)WO4`1$mg6; zl0#3Krp{W_G;+ypA^q$pWBZZc@$2~2KN0KS*mjQ5fM+A*$>18)@wxuS)@`rOUK_U% zOa9A$3HQqG8SS-eC*rP|6~>omdupf@Ac?%NLY3-=`Q-N`RK{xL)sHG|9$IfMbt&bZ z?pBs}JDtye@UE-C_cA~(C5HX<^^XPaqmO%_Is2ZBeR2i?$pfz=qMzSL4?@4zx}OXz zkZOCOkr1+yqCg4;UPGLN_i#@_bBqJhw}$7PDP11#;rJ5zAWw(2$t`Ce@%yT3KfNeN zWZWyjZS7>!wVTTZf#GN;Xw@9N0fcg=RXd}*+4$@JVGl_ z9Os@t+08?#*-H(qlPL(OLn?vWI4V!Cy*ky|6Ch;cf&Js{?^A0R&2r_@8+HMVj!rqM z_UYsX@&-2?kVg!AlTRgveC9y^0C|9H?eB`oz0`3kMGp$=yUfpDVfj^4k2I8fiTow) zUs5o}BafL6Qh$cBBLJx+k{3VRb~=9$iN~6+n#g4 z{43*e7>aU=r5#&4BBb{<@2+FAMFr6m4uyc@9*2|oW~oQ5*`JnO#rp*0AIFO4wY9y6 zPxEf=%uyE!lE9phdjsuQS|n!sShuzlv|-7^V?7j~>0N4e2*a542G*beh^-yH*?|85 z8q3!%4BA|>6$`ik06z6?bPG%aJ;bt+(=q@nE5wSiX}(-z19$`SHN}|D{EN&fA`L%M zcr21HHdOB7)L2 zc;{Q3xK>!vhB!RsfX`1#?2C$ytQwTQ6lr)G+gi20x}G1iSfGH0xRJK9e9FOB0OXyx zz`^$&{U=Jf*735#TGK9M<1pPrEKK9LR&E>e;=JR+nueRJUc6UJt4PNMVYfykukjz< zsctyqzo@RYExbA)4wLJ;ZP+IBl{5Z^AmHH@Y3Lgzal|SY<+a1OPMiIM4q8s=09!vqxqMnv|O{{3@-jc#iK_ znF)q6V~;(L{{X9>_!VY&<5m}X;_p!{y|aO{b~j+ISDKBV#9bIir%55!G`-TKGbYxZNyi8M&;dP*5692`BHcx!_+Lrzg}i~K zwF`4}=yDz3amVIK`W3D(!+O=$fvnqV7Hx@PeZU@!JE`^~BDbaY7Frbw7E@R4ef$U+r}JQTb-xwAanz``nPX5#JWumak zq26nrNn{YbL5N2nu=U1q{LOCods%H7%H`}^8ieD@iHH2N{{U*4fz^C<*DS6c)5Dep zcUwp%xNr#pwvZQqdlUK9n^-Ni9}lI3i&+z4Zr?~6goZ}Axw$aL zTa!ESo_@I>P;1k4trC43!|(RnnBj`qL&(et#(nczMc9eK$sC52;#1*dpGMUqd1SPe zj5r)RkXsq-7?4N%)#w(|LnO9xLeMp>)C+Q_xl;KVU%R{U&RYlXR|Vt!K0EC$9bzC0 z#-Su|awJZ~eF^=2t4raoq$kp*(yj*M2p18l`^z3q^dDWp{WG|n3|CBKb2!-S?EcbX zYaw%sljYh2@=1u-{{TX5zs2ae>r!e60Xkn77Q@&5k1ziKW~|*p)-{aVNW$t_?*7>m zF!H3djUO!gzu811-wVY%R=!gz!(%iNrOP~u+lkI!c6O3a>4EcSwOJ>fc)DM*+FR(* z8;Pb<<)4&(@;%?X9D4*mLOp9Y!+LGZI_<;PdBm8izgl#!?&x+=O2kpdAziVkRnBk) zB(~{1Wo2E!_6HxzpFHtF8%aBaMaECmf&F^ceeSPs677k%$a(?akUyCf)N?#yg=|=EK)(`Pa?5M-a(cl{{YvTqj%yvu?XD*r}zBV zSjR#*;QfE4cE>2h)cBY=Ts(*kbsa^OtadC7ks)>%&tMNdMt>nv zY8ob^d#5e8+g8p-m&}l@!#3f90-l_7{Hq`AuVZ-BsF61}+gq^`I!7>+#@if5cNJmO z8k#Hkrc;2#dJ$Z1m!w+gcGq!T>iTqWTg{UpPJZhD0Jq1m9cs3-;!A5NA)3>~I&J5c zFd}Mw0U-accxZAR6|h03uYutU^itulRD-Wybw zG9*I#2Gf8JNF(qdit;}b_=WV@;FJ4bOue}Sm_@szgk|0HHb`Hm_*N~Ki)Xc(No{Q` z2$h+J`+vEXErr95HyqbRERPXh3UO-RHTk4IWVbzzE0~;WvC0j?T}!`XbWl}SA504J z-w-rYZx(9y=QCU-$^QUZnc)fl0FNf$&zk6T zEq=>Xv|)dBXJKWcmPvgP-YcIhU z0sK#RUy&?0;Nu0Qk00S*W~Iwa^WNvF_+MF{L$YPGjjrS~Zgk`3k8vlTUPm4MIIl%* zTWi`DP`3@XBIkkf?#cQE-H+D1(@INgDfJ6w`#LqSjDwxVX7bUUzuGx-{{Vg9R)(A6 zsTS#09WLVcYh);OEMw2j@~RLso-*7VXBFY&u&S(-lGN>uMA6w$Lmz#%g^tS0+_}uvca2(;x3=>V1Do^9??2O7~IJZuDDToBY*cg+f0V`Om+n zVVdf6{WDsVP7qwhqS@Kno!P;8hQdk7QWR%zx9MEdp$dN#k)_U{&X>2>R<5&bRE%Ul z-F|RA$?KlJrmEZOcC#xXwMl}%mR3i>8&{p-zVEL+cKtJzacwI=vyEoX&EIQ0mf2}s zCOA8CdV&7{d=Z>fSBSR}$2G>UZubO`s$^wnC+6GBXK~L=QS29Rx;i7S*hd*_(;N_z zWAfyPkO>@d(;Oebnq`%&_jkIR{jJTV{>Fk>$QjCVeMa6t?~(X(o+!PaPyN)l+I&Jt zgY1e%m^u&d5QKL)$RAp{q|K??-%F)DNi#>d5d^W8fRp!?S83V^9lxCu!^u;+d-r{S z>iiF)7~+^+zXn!llQ}53zzj}LLCDYZth>*KdS&h7m|7U^Smc<77`8FP4ukl%jB}o9 z_|?s)&GxS%@Bnt+N9C!HJqZ5x-%r-8_-{~HE@rn&dA@RIb;l8BXu$mVuR5d0Dsrz% z_gx0kdzd%A3w;78uXIaWX)d7-S#EbZmm$7YVtz(BQMeyo4O_Ccou%Cg2s#W$o&YLv z-1-c!wRQTwr4)8d6(ng0K3ccSxx$hLPpJfbNUmkCZhyCQ3wv9Lt;F%?$U9FMInS@k z2pIIu7+g*wr6)SOp>o9JJWb(wC5rJD-EJ)|0a+pkXx-lf*Kr$qZO{7|uQt-J?L0H* zeVOL_EJk#i5}V7&i@9(xK3o)6+sINfabHGwuSaW$Ma{jeZ!3~q70yUgf`1hQ@~@aR z>Gme4XtF3}l1Gt6g$0HiW=AI^knF4vV2&|@Yimffxbv-MwzHJ1F_Y!tQJ5Xr$R`NJ-Oe$A zy8|PW>Kx`{Y7prbQQbBr2frEm4dJozC61#bWSP=u?S*bn z;mtc((Wkl6u62p-cL=V{q=}4qTY3z39)hr<+#v^^D+;f%y*S+UpN_gxwAx3AU|frZ z7Sk~9KqXJ&Gx}GP%tvbOH6I#{Q^8-@w{a6BVM0Fcgb;E+ELR3W00MJSIo#itz&h8{ ze+RWs?Jok@Zer5embHBU0JF;f0OO@c;a@TiE82f&%gGUSJ4abOsYsG0EAla12f^-s zWUS||rPI?xaw{uME5^F)nxtxUc%14iU!e{+6~O!1ec%sj>3ml;&F6wIrLmQkbhq<> z>R-zt${%J5RQ~|IT#v{5twP&Ev0F9~+08D=-yH53bRU|r{{VoB>pmXcuA|`XE@c@* z50rDsAxT`Ht~2=6IauB?x${+n>Y7kxwGm3h{NL<1xBmdDvo$rnxOuN{WO+bki_a`r z5=KIXJe+i`4QuV1H;HfbhLM6=O3@=8tfQg)Mi1juyi=&$2_e$rWm`yNjrOSPBPeDh z{aBAqDQeN1pJUeayTABLyb`6c_P0%~o-r!)$Dkw+z?!Y$xdyZ0Tdg+W{`3NvVag0; z3Hmp0_C;~u2d{39t$Se#O)OF`n%fs4NEpsPuduGl-pSzBE;T!MK1}BfzpD}NoSgpv zwOB$DaP2@IxY%V(76eDeII%N&1$rMwA;o z$w@tjX&TO(0*Gh4*L7>27s#tzibIvN~gYPY4o6pszD{XIs3?K5(dHy}W#NT_84nAIeD||}SuXTN9#6>#`D^+IS zJa}xoWSn>5RDK7V=rJ>V`-@w3mgeb{0m8r(WS-ezY3p*6TgQ9m%_Mo)R$ad-`T#$z zWZvB%F|?v7Pul#i5mDubqdPwPWNcdE}iTs$_Bll!!dGUNvNT>k){U#&?_4HxxG z=x!}$jTro^pY?6q9-vlDjHe<%NkQ@f{Ll3K{c1^KlHDdpky)X_u>%=6ALCXu%SUKA zUbJhBxdD%il! z&wm@m9toLIMhuWZxZDA3Mmp$0Q9Eax5x*rQ$p@`ASC-&(qb`coa~j(WgDKr zmw_V8I3Rz|1PZ^H!v3|+YWkc*TC=#x0e6p?v-}79x&HNXx6{14{K;NII&X5={OFyT zVzX6JaGBtDE&l-4uZOEB-EylQs-er&>94#&X($1M&$NOvn-@P)N&IjtBHr%W0gFvO z**VVU%bfjcvRdBAa~-4O{jNWqNV;|8&443A=rZ5>2qLs~CUjG$1d9h$ye);8L=XJ) zP>1saG@d5pSt~aTTl!S-XreYpxV%iM+*bf++mb((L947k+g4HCu{OdizCR!Eu1d|y zq<&;d#(9!7<9On$-AuD0B4pvi^y`Y~TJJ@Lv2ALO-mM<|{tQU{D%HNHDN-#o*@E}- z)J8ugQT*$zQ)P2WBU9nMuBCBds6}mc9fTJyiqc4u7ikkD1Vtl1d!O^4P>$Qgsi%up zy3@5Z$K9D884uv>%lxZ|@YpvNG%fpW*3R?fsK)B8cp@ zO%CLr`D+7VyBrP0TLARWLtd>Y@xVyOkjG#(UR4 zqU!VMcZ)O!yT77?&*DmjfppeR|Wr9ofeww%7N07Mg5k?n932%8##q_ziO5b6qi%>AnbfRPDLc zW(-K+`D>Anp&9=GzN%?^iI+LK%Iw9~wFz}yTI%|L^|uli#&fql7w85D;Z(bd0Hvws zW@mr!$HcK|*D`5ZpjXl_&dE2PBV)#K=z1TZ^si(1ec@&B&6V_iV3^NzT_na2loSP1 z>OtN4*UdUqO)So!;Q3Rq`iPmorF~(cK{b|#r`p`VnKTyh#UuX!d4hmX=S1pRBc}}> zY2BVd@y6uc_>$Jz2Flx824Uz~j(LTz8Fr zAL|;gi00I-Zf)kXxs?`WIgR0ufsxJ_C>cED`d6wK3JVj!sg|iTx|DtQC(!Wkj2gVx zJ|wx*P(|fK3Piz%?cG?B>G!etR|4?P@x94MWZVNu*XQ@GOWU)l>KC{3ArEmYNsn^G zpTnAGg*Ev!EnenFiEb=qaShZD^DL3`)E*8ws)TNN)n&~$BO=}@8QXvH6Q5(!y3Yi7 zbn2J$N;!tnRCD}NF(F6jYJU*uBHvK9pGQx$Y1hn6xl@z4@v!^n{O2{hsyw;`y`6qoHs9w;AjgHPOjqIMlnbYez6@-YZEW3feY-5w?McN#;L#PkiG)fIaGm z#m^K&9+xhS0&kM%Z<;~qp@U=e6)vlNZ{ZIP!(nQ{?sl9WnPb(+`t+|U0&DxU6Ubzi zSpeML=!y|fx&Hw5RmIqfiCFaQU@i29_w37c zx(s9y{_A&MepU7RtBS#GI!#wl+8PLLhRG%#SouqJjB&U4a(duo<98u+-n)O41c z$Xkz^2LzAaV`~qqE>HWwW36sl$v@bpTXl_7f(kJN$^p+|^7GHG2?_IU# zp%uIg<(DdY7RGDLz8mTn79JFIvjfbLU4xP_8Q$dZdj0Rxw9vGw1Kqqkmv;FW?Q^^C z?m6d@c=kEwzCRa~`)p*Y&ttlrtcfl)OS`#dGeXkKBjP9#!VCe`iN_pv1p8DIUP9Lq zd9tO9WQ;6t<~Bm~W;}jCeQLBS(A=?$$+!8FYmt|b1^NE~i@S8sAXHZ$V1iL1stJkt zeDYia+W_?bb?Z{XQl_>dQ?T@g$TBUxtg?l`^NW>b7zdYcnDoa7KQ&7CY_UfG7V1&_ z%tdY;*94LCHhpo^wHAqUeL7pHAW6x6ybgUj`k%tAu7#*aJW@PSk2Eg!^K!%xJ9Z(P zr?K`F>PrnD;xKY-#hU!6*vqL)5{;lsDGE3skfQ^kHAzO8hjQ9!vFE$(KhrtQ~nTWBPZK|MjnaoY#bis6=Phr{Uv zS0jB-HC73cYaOEbzbFvGr_B6@Yf8XfF)ML@Es`k0nBqw=-`y*mpKN%RQx0a>ZCZpPA*eS=`AZK$mMhwsIz#JVK`g z{K1s%I2|x)bq@~AmI8U=xAF-JKFbzC&q0s?9OLu+`=0}vz;kej*fz)>$K>QE?09vz8hOR7Tj_MZqkb;JI zoF@bk^K|WAhppIMz|&20Znq_HUEH%Bpo5OM&ujtT>shdC2U-)EEm1CFSlN7uJfkM& z+T(K$-A7MK?^%p&n_3d7HhDk9I7XkP84b0g62tu_1$_CRFxpFV_kM1FyIh~cYdJ18 zE1Q*5<;07gc`RHN1OE6m-h6m_2z0n?bonNm%ez;V-7pCYBK7aj_568{gD`!kSGa@8 zNkp>3n0F{LM4iAVJa9YLZ937Z1vvHYt@)8CTXVB(d#8z6poF+2nN<$=>G#-^yQgdq zT=v-0QsNPKwwAG^_YlafJh%@U;C;t(pkr=F9YFN1-tWSXYiT5+Vy;_sajGagf4h(m z=4zgi;c2dQXqxwUfdZMK+zN*Ps1@k8UI?|e)J*Ek6gE&Z zvyHg~btjR7&%dy)g4;!~hSSJ4w&*;+`DGr#`TEy1as1x|OSQV5F80c+EQsSHm)@WN zdBNbE4uhWLdeg1!?XDs+Pje)5E?CBpZtO~&=OkfzfyO}ZUcYywSWhpWw&1biPn4XW z^ZHdc@J^F8#l_dy&zT`YcRPULdVIY-e=<5&l`(Z7ljZx7pEOS?{>HbGONpRpE<#{P z0C3=K2P86#@^eMF87U#I=IG29A&ZrxWsR zDn3&ilb^2Qe@s?I{{V%rBFozZoj=*({{SlJjcha0Rzv1&$d^#mhLE$n+h2dAq<hwaeWJ z#33Y?Pl{(xO?n-tPBW0=WdcC}Hc&k&=-9p%SmI5NT^EB#=X@KvqSQp$)G;=~!U`BVt>*2k@t$9BzDIdzu|PYK*<5^fPo@C%(OoB~8h= zZ_8}}eS24-cxPF*viP&3+FQx~jJBo^E;rc{IV?`m$O;tn>PX2Xl20dLcLlBFj*-J_ z3jnJaCkh8nPbB)1Ni~P4+?_MU5M8F$NTyj>#@P%A3_w3ZMJ`doSDHSQ_{F7vX?!`= zNE@fMQFjr~LWeAW?~UW}u4DFn)Xd%*xU+Iuppzp#fgL|Aes$^UV%0P-F2czT#5j}= zK?Gor=Uxr)QfV|l9%@=G%Wjmj+n+^bVhQ|6QCT^2Q}jJ09#&@jd(%aijv>B8D;@Q^ zNZy`e{G@(CKRV9vC&R1n8D4$1>Dt>$2M<0zX$b7U)b_7)@&5pabWLI#9Ye&oM&8Fw zc9KRvb(MewA6$^24}ZY;zr??YlIu|a0A_e;8&1$-`9dek^C;sidk^-1$mpPxjn6w1 zbvqpwh&*$k_%6`so(HtIo>U{vykHr34&e269gc84=(yG_t@MSyLU+oc_5;f|r#-8~ zkmT1*6Enbou^=eMO=nRlwbH>#e2Lv&<9u0aZMAD#*yULvyNz9O{5S)T$buUr zb8iDlX=A2Oun#)xP(Zr}L9~)U{{VolYpmBTuC9C|eQ|%LT3p>khHTi%o1@x)i{(@I zdhlzZYG(92xuTUuR4aWcwtj3<89GSaS&z)ej#%|S)6$Ra0E05xT|}py%^6|&ik1uO zNr%fcW9oMQ06Is?$CqFdlT=Kd$?xcb*IeHmxI z`=1zKx)p=e2b#7D|^ec@TB$~P4uHQS__O}eoo_^S+93zTCEVbh#rtzMmf zdifY3i0f4%xp*aSEZ{I8)-Fm@xqP-(D@O#EA1Zw^Yi7FWPlE2g?ZU_5T1r z<5OtH-t06srwRsTBy;VG=xi+QEVmnE{p7!S$;m&Bbyvqio{-g(%5!Nxn2dhMrTwIz z_Dg^0ME?LPn%?5Cu>h`45-$tnDxru>PA?VB(@#gqp8pmO5POa7$l}{)5i8<^3 z&3aI)2|IF}^13=ZX>K1+wYHmCw9^YYFfmtuI0nZjqKtw%=Z>8XY3g(8dVZHIIK67!RO0AIhEdD4|6u>p<&J28M|KO~iBKD-X=;{pBZVACp(s z7Sk=&q%+1rNfbCB4Wv8_>JRhJwDegwcYXu{{Up3 zgZs>Q9Dl&L&k(k$rFe>Yn&gYdZ29Z*?$2TV&IrKl20K?JtZEl}mD#w|XL;r$C7Awz zb`{d;_h(elQo{BB0Memc+e+O^>EY1ezJap^_LHJa@ z918lfyO5$3W*+46{d4$K7ZJ23Fvpqb}C{;H=n;a$f$$=&^bopwf|Mm(pl zd?daTjb7Fx<(qkg;4krwyLoS7KT%!IopBHe9>{Gx-f->D1 zoMoqlBp4^4VhG@j{pA((eTRoEBAsT6*$x$d)^pR5^uYqW+VzxWWR0Pr&1t%A-l2cI zMo|Y>oG{zTRU@8B_CJm*dra_^y{qQtLvY=E#!wg>5rN+v{{WtAqmETrfssPvrUh6E zBwW{rjj3IHdmC<7hSDhAL~*DJp+_A6?N)rhT5<|@GHb~A#*odTmjaaWMRQ^m=94T@ z(uydngdt!U+~gVn!4#*hAs`NVRK{Go49~xf_kjYHlRJxhbn9Mqt6P1V2f6ci>a0Aa zRbH>SK1spnfO>ZPN2XlG<`By6yOe@|JaPEfE2wA@DUx`@$*?qxPxrv%k6w7Lnl`lvYUhV<%#gaX4a@?pd%0#%GxX;*o#K!9`&LxT2M8M- z2HYM!*bmIsRBa`Ui)$fy3jXsdpx|`%{{Uoub;CyuP0)>zf_6PMp8#~;eq4SPFdKje zk^cbJtP<{#Sau9lm(Fkj0P*fJKN`@nx<+lgzUuM$f1mOx(NT-Fif-nTE*e6k8@qlO zss8}wnE*&AeP;#aUR}gt(DpUSYW@)s#?Pln`$6ItISbzkdkp0MCc9c`>0cp} zXSljpg&57*^yqX-2_wY(Lo?}?S4AR@>gFN;03PF#PI>xqpMODK1L9p(q-kM_U(AKw(d`$+twvk6?G*Vl!CRZS25r$9(c+NS`Zk*N?wB#a@_l0ZQMIHsk z>|#JZ>_@rvZ*Ok((+KlwXDRZQydgc-i*DM5#Gx%G7)w@CTI1YyQU1~Vjc)4NGkA?x zRMIX)=oChfJ7ddL7zn+WAKl<}Bdv3nQqOsC(@xu%@Gvj{>;UX3EiP_sE(G>-$qa+3 zQ^EElp{ucrcQQ2{I%uzidW;uV>Cv~xfZsxD4-v%ot86VAd!~& z&G*UfNbQrHR=tLSrD@u==A~z49Cz1DURkl0B%GWcqwudi_%Gu*{7-+WrRA&^@}fo* zzz9-4Y%U2r{o|VSjbl}_(d^#mQMiT(SE-S(PtbR+DaFN}#VU$%MmNN}5vX_vUbj63 zqs}6^>EyI4^sFfc+lC#w*4~c8%IDUbIm?#z2sixcTW|m3upQ0}<^gLIkXwuu>U7=qV+{t|72`a*h ztLc@_bNbnRBOmAQLF2Iv3= z-zsRc(E5&R7CA1OL4zkBH$39BE$(E5OXfzdd3+uZN|pddLAk!lS7m$rt^C0EImJ?Z zXXt4d$>xSFyLb1GBDHMbwNfOWM3C@&vUZQBT-Q5X?A;f9A#8#RkfUtV5~5z<5&r<| z{HvhaB4CO+jAF6;Is`0G9Dt}lEc5!+t$O-<4?MlU%OZgkp7`(iSDRa&6?qYHvWb3M zMq6o&jJ-Pn=~9&P#FcQGJvdwB_>hb~%&e_8z|NT_&W0 zJu>rX{osZ1{{VcNt%Lsn2{cLn04$clkLMu&0OBjP;i8q1ik;4PR4O8M2+GSo2H}tS zW~cuEXBCy;WqXUAY{t1VPZkb(b0g#b03EIG6AZKxe*;M|KkyM6f9#c;@XjFCBIS-V zs4zbGw{QMAUVR#O#!Ysytfk9N=UTS6L|`m9`qoE>t>vFp*4c*(GQs}eJHrkcQL5Xi!$I!3(M8rrBi7V$7DR{{ZUSYe-qL zPEP~ZZ%W4TUZtU}i$!Z{)|aS3QEWD&sA2cie7M1E5rfCQKKD@2!525UTon<*J<(TD($u+H_krtb%NMi>( zLgWv;H-C*}bvZPO8hX%a*r1Lm0((Tww9lSP#~0_g{{ZZjei+KGJt~!n{{TjG$Qhn9 z&jW>j@vTq+Oo|3m0RxXb2SLu<<3C?-YOi$dEUM0b`qez6mf(H@tIPZ~EO%y>lPNbB zasiF<2l{>E{c9Q>#>WKY5(plZt71Z0TsM7;e9Y8NU~_>oJSYD7EI(Bh_3~V*+XBY}{{Yut4|q=N?4B&qrfEb-mvQfN(N}QE z{Ig$dT0L#jA41F4mqZ&;)dmMT+|iV!GJ2mJw_>{4E3t;2}IAh{_nMCc!yoH&~4+M{hUVb zBO4cS&-id_EW-y~%_=_krs7k%v?)ze(y!pu>|(pLEMbsjZc?VLb*yPZQEoEPqQ#`3 znoZRcorX1r6yvRPy4>OmIT@vO49r-~a(Ub@lxHE4i0Vd2H>Y z2bj#(snm%q?HHeTrt=ZgnDc;CG`t^^0~Pg>3J z=Am^IqE|WjNH|mT1sNms2k<_%ry`VGqtIutkan**AXC|!7&T7I&SlJ@fgm1n&*fJn z1V@q%0SAGa>V>6AJ+N^vB~< zfG`ie2t(r(_8b})ki(IBXgE)nMKjdWe+j2J!8Fov z4Sp|`;pZG`k668aN2^iZ2Z;XDx4u_{70Z3R!y2BuZK0;{{RnaLiuvRj75DsaHoMyQhDbCceZ1BcJyPM;+Krbamwt_0U;H$>z3 zhATr#2Ww-os#C6QRQejy(MIDS_6C3;)<^Dl9@Wrzhe>TmL-4iTspeY}jz@BiMPlp7 zMXr78wD9Zs-Ura&>ekZSvG(4|asL30*3|WqL^C%*v`a#^i=^D#O8f<8W9IuYLJ1z+ zSEhJk+WrJmJSkxsh6m5fc4qwRiMj$pf=QkTLHXoG48VH=Mt?fm(C)O!96=VZ z;yad6g0U*#egPPd&)T~o?wsyt6RvEJRl2slip?(k$1?0x+O7{kM;Poeo|T@q4n|eg z*ZqbDyPaOn*lM>A0?V|y!MYfgvU8FM9dn+Y>n=?u) zrz5%eQ9UIb@g zs&IX41-YX-*e*@1gAu857Fa3Zb5UwqkdoyZ_RVgTp;*MSAC&RW`4w8)7g!lyXK)vs zik90CAY-_fU;qO>`2JL<6x(4L9nXwqE8&Zl$;_7$#(x6;0Q^NWMU9tI(yh;1Bs_n! z_GnZ2lTrLbA-pt-zw+8fm4Dzw{{ZoIvt*&XTFeIq+GRggpKtO(T`|_9W)j%FspMOd zNg_u`$jA4bFZHU{&L!7xNadk|DD$3~X$h?zLNg=?g8=!lk@z@o=~T2^NTb$umxV}; z=23!kgXOEA)y;G@p%L8WJWniwIS}Gs=09t(*+%J zS=;{rtzMJZ6kc$QgY{{S94cE@~ra~hT2obqE#fnr>M$dWTh z&!B9b-FeUDRujY*7Q2n*l;SePt4SLsGyFXD9XYIvn`mOao=a(Ck{2<^0ycJn-*k@H z`LXSTQBctw?yP^}VJP&+{(>r~>MCtS=Kf>;(G?IJR|DMLY+H^-77reNQzuWu{{XL5 zz^MWM07nD;ukfhF0~V2hD@#G>Xadw0;q74=#`z^b_rz-7)~`T+(Lp|xz8}*@wI#KY zkj8h;`+&{{#2?HGea*93hPIWI$r&oe4ne^qgFqBrfo9J`P)QkJ-1G$gnWj$UOaSRt z0Ll~Ut^AD)WIJRK00|qE?i~kV{xusZAy?c0D%^|}W6w`4e>#rY!(ZBw$P=c2I+|c6 zx`XYu&;j>hMEvtxYQ#dP82M`-Tnv0lV*_bcjY0I_9-obD9uSIE4XCT!!hsr1OgjR8 zW^?|38oPXWi3eF56*Lh6G_D8U#X}-9U)u4)x&Dc)}CfQb0Rw@`|1|GOI(d#F6p*bYrHahhf6`b)zMj9|j`zHpqFev?&5z!CL z+!AUww}*F6)~^^L{uySA{o#@N5luFQv9jhS^v*}~6cSTgc!LZt^P+VsPaJ>s>fp1v z-@$;7XJ7H9dpUydl~CmRjyb^ZNT`{Na&zflb9f^4bv+Kw@*#wlHIOcIg<-gUhQ1k; zZFW=BHTC}h?D>E8&jH!w<&q&8_S){F{{S5eT)3rbe875z6sjo?TKKB&rAF*@#>on& z6(%w&tTPv$Sj=ppF@`-GK9yvjmbSyE1tT#?$kFxAqmr1n~w*wXlS9poAU zLv*G;;)oyOQUhn(u0IOwhusy<>GyHRG;n#*%Od{l>}-_B7(GsXYocCOxp5PnTGFc4 z=7?ic2tKtT6o$B(=a;-t(7U)bS5S}3hC-wO5u9JKhPtv7Bg^3?Z6+H;=HHeNFVT>qY?iAEC~Mq$IyRT_TLievdbQ) zg;`-+Ig4@3gKH^YZe$tySBCr)lgjbTKJgHKo6wW^08imxbqRA}-=UQEKG0|K(6%#y z(y%oXu1Q+gxMJ31#(6RA2fs?>Br|R*%H<0H0x2M5XRUDHQua~uNamT%YQ|Y@ z{J7lVfdw{>N$=a9F;iGu7@|qG<6_9U`hXj!PL-%F2ijbfIZk;VppO0f(rOAJ)BNyV zrjk-F0d5Bj`~lyOf76dqwJ6w>nr7#RJ2Mh*lZv|cPnQq$M}Fg$0P8lrPXaOBswkSs5bYpCpgX~XZgA5 zz;N5M{3KRXsad-ebT_~0R*^QJbO798#;P^HJhMX&v$#O+%(_l_h;n@e(BuA55O?`=DQ(I;%9h_ zu6{$2nvs{%so1PKeXXst5&!};-N`r}-omWJS!8nDgIzYz=e?cCr5x2~>57)(U*CB^ zb>}}!W~<71ug)q_qYUnGYS-8AU*>u-ljJ^X{iv=>YFaI@JjAm(`ZmBnk*^C-cL83* z`%BB_c=iU$c{ z(jO6GVY2=#bYMaHk)P;mb4+qom(!ZcW%;s4Bd7WLX0$9E2{G95R|lw09_uvwRcnVM zfO)CefKqc*nYT7;tD(kc>S{_OV3H4A*vHnPxI^|Q!lp_Qjo3XZeOwQ-Dfu{Y%kht= z`Sqxr7qelQNF(_82D4=&mAFE#IL1b4fEu+l5-fx`RbZf%2cV|hvTco51dQggWuQ^I zJW{HK>qJ=FQOLxx9jZz!mfZs{Y*vPZaXB8T{uPx0%Hp)NL;jM3)kvlV&3QqyBi}Wl z;js5!7Lo{O5y=$5{r#kZ8>i!s&Z_Fmy3#|Aihre4@b&f0t&{zmso(?3lk;Ga-x=#x zwk4eooek^+{iTJ@B?HQRteJO;g17wy?O^V6sw5ulI7yp-=mu z{cD)gWuEs+y1lr&aU#1-CeAXTa-)to9V-5Xadrt7dQP9G93S+l<=H9xc9{>+Mk(N` zN_@&)3@WWq-F|DiwRz^#^LUISK78acCurm4J-U7!YBiTpN0BFv+vUe9WEuQ0YhzWG z`tw<4ZA77N@Hj@hcdRPi$#i-eyY3c=<6e=*H+Vsi?#s=9Hpv{GhE z-^~&Eke>elE4TGpqa@p{B!DplJI)C`x{;CS59wVi-z-rxGJ+SJ3e2+A^qK80ppplJ zcH@@-^`%o8iG{i8>FOM*9Dj)1Nvq8u=t7Us3aMt{X9S#9rO%QWZ-;SVoy5?Vwpan- zAfDmR_|}SU`kEK5nPiU{$mc&yQh0ja;^$a`MFd)-1Cl;cMovFNQHw~Rf@vUTz>MS; z2DbbibS1U6gpc)1Wd8toazC%XN?3}Pr*MpJZ1|x!S$JmLkG|1De-?;V=8N-qgHmkg zVgf(yq_Q<}ZQ@IB5NdiL+x9~Z)I_Vd$%SsAy*M3lU4Mrsh+kga%`|aGJQk&QwmA{= z`sTaiIL2+W8EkFd5-zm$8N*y1-%lz}>x!kMKEbWa{oCnJ{ql-G_FATS+d}{C)9e@wJSn9JDEZ^Igb~_&{^I_N%n$%Z~jh%i*i$lD@0J3b))Z_ZsBY&thuZOhg zAYm2l#k49UJP7_#^Ap>WKRVg*7N=`_q+CI6@4b`|3J^~@$LU-x@i!K?SGSG(v9dKC zG4m>nexI4ITAfJNsV5$Zex?aDTDOl^wzl~%A)DstBxv*b;MIGnnrRiMvA4Eo1ysbC zGHn?dDsb56p1ku}M&iiulH4cF_m#0pU=%n3nn1GW%_-L^*&pZo)s*B~F>bzV$HGJ3KDOBqZ~KX!RX^%XXkseP6| zymqs0`=iiOYMO&zLh9Hd7v?ZR_}U*>8p7B*|B)xjB)aL=&KU6pYo zl;;6PDqCg6^`UWs5?-I~(xUD;6*Z{?c%|5L`^d-Ssz0S^nU`@^AYcylg?32Sn7?U8 ze@c$-QzVkUP7X0uQ5Q~Jr%8@U9SF@wa7=b-at1(V$GGXA*0Hp^`BFg}w&?J9J-E$n z%_HMm8NuCW#DdHxax2ZU&kL>CDSkB$$J?}`k>rD z#;o{7q}y;hL@;Cy#;EW$b+K|Q=;ELbzEtSgM z8^;icr8vZS;j#Td{PXKv+Rpy~Yb%5xWB?9ZsTut%CYobT<%P9F{511w0-c$wX&Q(} zn52{b(I3jEf+$-*cw^D4TN2%lV&>d^r3Yrte!XkxKiPj#^7MO~7h=pV2_NjKP)F(- zyl&#!6jMGA%H00|D)0XQXA8fxc$)6i^_DRI0Qbw4{{Y&exvR$X+9>*+dSFsAQ?2<> zY3?({eD)@qPRO{G$Zz2pr(lqGQ-{jnW35Wdxoe5W%Eesgb(!7^N!}>Tl7wQ24mUP9 z73Mw@E;T!gtClMyJ4;IV%blzwW_18JC$C|FUa@xuT&W zm|RNgy#D}nFW&x?VN_$yPG4)NhIn%6ZahnIFWJ%rl^S(GMp!c_5-CU)@LklXD!NOTJIc09-foKZSapn{^%Cv3QPlNbw(HtW^G>*KQ_)=H-sb z3NtOR$fgzMh^LN8<$+r=#e(bUo}b9nq;XyqS_+-qH#Sb?V19CYW17hE9mFZ8I?Afx z62LQU1dd1RirBcsY7eRAxPKT==PYnX%j7s7zc2@PwhvB~jVv^!jEZA9t2g_7h1tA0 z1gGwQ;D6`W74!GO##Qn4kbaLGss8|u6a6dF{wnyIEfYaHb&C;sc&xFIMq@&LXFd4` zX#B@Y@ck0{O(#^fzlFbhZy=BX-v_7T(!W539!RL1)g^bK_j)F6l~d3Piak6MTfK3A z4m@K9wWZoj&y=`3WN+e%OmYv@ADDqM90BeJKcDGN zQw+AvF$xwk!|8%S{XwprCMi^ft*M(iC@t@xwY*s2C|cCWl0pbpa7kbXV%h9??V5&b zI|S17YqM_4$=t7=tp{Z80MK2t0*N%I3#2qa5Wr!WW-aY>S4%KT*ZL?$6v)AUqH6&6LV8f`VVW(zvfM%{)z(emnJ5^mZ%&!63 zP%;K=_c-K^IvUqFRr*!arLD`WUh2q-bJ$cSoK8(HGhGKEPgPvmIl}y>#Ob&+qH`=%Z_)TSKXJXp)Ig}yD$iXN4D{km+w~!t6l!Y-ZzgslK?92%_0;ErTNijPQBjbBuC16rx!qiE}GH zK+h(zKHmVbffisBT6qhHAxCu3!>I-W_!KN@vf zD2C=tSx30!g(LZ6`BrG1{dueUlrI*Xk1FhADz4mvkCgg-DvhnIw=Px)%MsVmeNA)P zE1A-?^I{CL$i8950{;LGJ?lZP z7@V;!?s+veF#ge5y#}h-^VKCzM^Rd~9w5{#;#i`%{m}jvRcw7S4nGRcE5$}fHC&@T z3&XmEdcpFJw3b(8c#dmm@W;?MPvm{8q;C)EY9uFCMhF4_08%-x7{RWOLh+8Lqp?(R zJ-cwxP4i@X9;4_u_N}7n$Lz1T&c{RJ6=<~ZjM-@J04Jt!4R6V#YIbnjsMWZ33IK?4 z!9UWt&0kuBS%Ni*UN>hY)rUeyW8S?FUcCD?tWvQD37Eg$D~$fN&6d#TjFm|B3i9Pg zEYGM7Nv)pu&%zZa1G?0nDAA;~&}6xG#AqW620ESV&1(2ZPV@IoaJhDDnwR2}6!7$X z<8{I){Fwg$O7?2fg($TJZH{#(Ezd17H+yvh>59Lju?WMlazOOJAL1)G$mfGU;A-xX zoXAK#BmCWu-Nku)?VV00T5_QKQ^2J?XsIZg*8)?Xg>Zk*;Zb7*gVvoH!>)Q!D8z&s z0BNQgd89M}=o(p)%fa)!42sEXBZAAHn+(JCHN?1S7cV(u?klC$Z|z$`(sa04flFBA zn2+OT`GTLI=kXquis^+swP6O`?!d{Eus99HExB#@Cm`|qincA)$t8_1+15D_f=|o^ zX6jvm~I zg3rsI&2T40K5tQuMhDba2B4P!9tp1h0K+$#(QXnzttGrKv4e*!2SJ{ojF1O@G19bx zxS1Z4HZ5*s83nF0^}rzhqPlo)UzcE}1mkcZ9C9#y`1Y(TOF;y-R;88Wk&A{w)U%Lt z^u=@+QCi6~sXQ^fY<^(N^sa@mi!4qp?=-uV4hp(t4D>yZ=a0&`SahJejFKuio%lTb zxb+p)#b<2=qZ@L@HGHZwXK&(p$F)#dV_T*GWrrgi;MBC3Cu0IwE;P+-tghZn=}+@$36u8UB$i(1m$Zd95-y5HSl(ANcSCW=YmxMuU>Y!FU=Dw`IhY_-?R ziI{90vBvGA*0+8S-WyLJ*%R`tlbDJ6soFmmMXp6gdj2)5Wpp&HTTawL0x0gHoa3u3 zYzh9gl|^(~KH;_;W3^5PAo^8JHcj?0vvt}9UmS|@CrUi}mYEU8X;kFmoXbsB#dA7o zs}AQWuUOwj;#)hrS$D@V@+D2YB4Z`9p1fB*<2dJCYfrg}BzWav2q>id&D`Ul`M>en z^r=ud!vKD@&G?B$jiX$$=gC}+o|Ngq##efU&R0Q}-$1riJVSSM*_Ka5jdvf$LUG&X z;=4}_TK$>}i4hqhyPVvjH&qO(NIv_wAI7*O)e}{`5Qx+4^1&n#sUI@tGD|OD*;N4G z0iaGLC zx}~<2X{ue^x0e`Moxa7epiE@f&t;=zh8E1$}*>-Ns;uM}~@35^vkImOXyG4^H*)OXQ}|abI#I6?(9-JOQs)WkfG!ZkHeav_VV26>*O}UFkdn( z4nsa%^XyuubR_`onu1%yVPkb8f}28A`A%^E6*o}+B*8Nt&7`cgnh2kM*jdxxKEV!Tt7ef z_Gz$bMi^#}9wM4x(jx%dhSeK^K7

  • )C`{RoM-f?d&Y>n%Zrh;{;1?n^$Psx!cG52-em?AgJdx(4YZ{@{k%G~0x z?BxM${$aeUa1TFt*0xElW3dcy>ryR7wJLQLLL$8&*iJGRDpZ_yBL=Wzfmu<$*C;0( zM47IF;eXL*0QFKjR#m&7vwYKeW1-p!tmINhF?*49FlH=wwn-Qy=eX@qRJTVr5*)Gl zk1Q~MA9{|nEe;)O!(pNZtB$oQH^@aHkq)A-*fu0a^l)l}b*pwq`OpVnFU%ZQG}ud< z9C7>X{!d*tavHg9Q{=j~8R?V%0FPCnGpv?$+Q7>Su;T@>T)RB0J2vQ4=Q+Ut01B^p zxZd0y&yO{~i{7Ka$9lHxL9q7aG~8X#ZhFs!{B5KZSg&(sA?VHXA^Nv(#8sb)$+@_$-~p{pu4T9Mo~f6wJW z6^~j&(9)i?hoPa2y#=(05=UeqlrY+IdVlrmX~^53-crJUa)Sea1u|y+*y~*Mp5--R z(Ek9d28ZR7{ozRu`ms*O%%ATH$El%bPLxNeN?2kv++DxA208rkQ)!O3UM9PjAG?y+ zI*&jZNAjpH2?theU(g>+&1kt3YAlXdKlJG&p& znB1W>(ep7iBMe4b8)jRznlTwj^Cwi+$*%la|JRUJ-zFQ)(To^mUimfW(=q0<2VN={`xtsZx8s5 z5fgRCgw=@%N!`os7Z1Zvv6`S zK*do7Z7S~aJH58vY{M>PR6^u~+lsfW>89YNmpaIj%Y_j_OM_S6f)vhjMjt6lR zN%w8-{{VKMTzB;6p7m<)R6c4X&ihgr89^fin3E$U@y<;vl#xCR%HBW6ykG z{<$B8b}-Av39lOco$sTUz;W6_`v6DN_2Ocp+>1s1*`aEnnw%!C zNz~+$h|34>RV`Z4Z*9Djjjh+*RE;$=&gZRlrE*)bpB9B}4y|i&ktj%EkO9{Y!2myy zZ_cx|t0~grNun5n+9_@a{WO`r?;SX;FV>!|6`&Wp5V z*B|b?59y!Atw?w^m!yQV(_=+eRc2-gKZkP`$Lm&n-VJ%!q~F466E9gRkh`-Ys*ZOI z*NpsM!~8nFxi0>52$ZRBl|Y#>$M?BDhP`*hswTBDmQ@XqcN|xPd|A2K;JK{~{%-lO zzq$*B%8|(9_(|(s89M#&=kT5w!kdmWPhpy22ELT$7qxw>@N|oxhq^4Tcb~IDNj;cG zp#Hi1>*AqbSANg>!B2-4am>V!WL5UtBah^xAB|^LDp6PH#Cre&u`XrH-6h}!OGpUp zW9J`^)A`lWcbjx7I`yt=T)!=G2}djCu`UNCfd`7ppTr{N-lb+*GP9M2-a#4MFgG%= z8T9R1e{Z*I^tTd!q=eoh_Q|g#)MQ2%&m@@p(qRW&Wb^bt=dOBdYtj9yZ+Rdps}rL* zIoZhM{{WsbUXyCZZ0aM{7jpcVdS@R0c_*`_#c6;luZ*z(E>xNtekohNB+ud zn$e@Rjm6|gah(H=-GS+k!}(R+NJ?V7dt9}|w*FY#{>f|s_1=Ff#&YFX1E3P&0qdGz ze45A8q-ZqTJ9%3&#RO>QKg1Mfrdj#xT#k>lvydZ1oSNePEMNVHz?$5d#_MK>{`>hq z_G;-3&O6tYe$cn%cn(`tY(3QRL=Jr$dHoR5lzFO0$ksa_GF-@#{N7+A`+yvNXZ!_W z5O<%gUXPa|s}cLd^{%m#C*2ggfToj*jgDv}*|uXM>6qglVEX*o{sIxhqfwV`@;Lj;x3H=su4@)h>N8TZuEb<) z?ippr-Q@nB^$j2t^Hwa*3HsG)v1)m0EC*nR5UZY;&2qY@H^E0mP(QEru929XJ|H

    P(3kR?Yl8? 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

    |6ON$WD}U%bS9|alQ76LP7=LkDfAQjf znKhB)%Qvz3C4X%uH$!U-XC9%@AJkS~!!H-<-V*pdq)(}^7Y?8$vK+ges&F&xK|bX5 z>tAC{d%B#5vFbO_Y4(qEcPd40gDmdY09T9n!^iS?j#+fw7&JCO;iu2dDE1wP>-4T4 z#C|E({CBBhMrf=RvlXfzm3`aP``1CCXpz{$8NPXsQ|Vq7ahyX9O~RA+nY* zC!2LK#g>$+0W1duFFA3XlhH>#MSP}RmSS^imwyjd&WTAuXo;@;ohfd7N8;%*VxJ|B zpO2l}vD@69+55d~#(ZDmi{BM$t#_#~Sl25I2!2SH(0ZR!)cXtpU&THn)O=g5z4oUl m!i}*)pK>MiAJq4(k~y!`aCmAs3QCOyWWN*4oZQ{n|Nq&3^*omV diff --git a/Fedding.py b/Fedding.py deleted file mode 100644 index 378608d..0000000 --- a/Fedding.py +++ /dev/null @@ -1,1153 +0,0 @@ -import socket -import binascii -import time -import threading -import struct -import cv2 -import os -from pymodbus.client import ModbusTcpClient -from pymodbus.exceptions import ModbusException -# 添加视觉模块路径 -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), 'src', 'vision')) -# 导入视觉处理模块 -from vision.anger_caculate import predict_obb_best_angle -from vision.resize_tuili_image_main import classify_image_weighted, load_global_rois, crop_and_resize, YOLO - - -class FeedingControlSystem: - def __init__(self, relay_host='192.168.0.18', relay_port=50000): - # 网络继电器配置 - self.relay_host = relay_host - self.relay_port = relay_port - self.relay_modbus_client = ModbusTcpClient(relay_host, port=relay_port) - - # 继电器映射 - self.DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 - self.DOOR_LOWER_1 = 'door_lower_1' # DO1 - 上料斗出砼门 - self.DOOR_LOWER_2 = 'door_lower_2' # DO2 - 下料斗出砼门 - self.BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗破拱 - self.BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗破拱 - - # 继电器命令(原始Socket)mudbus TCP模式 - self.relay_commands = { - self.DOOR_UPPER: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, - self.DOOR_LOWER_1: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'}, - self.DOOR_LOWER_2: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'}, - self.BREAK_ARCH_UPPER: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'}, - self.BREAK_ARCH_LOWER: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'} - } - - # 读取状态命令 - self.read_status_command = '000000000006010100000008' - - # 设备位映射 - self.device_bit_map = { - self.DOOR_UPPER: 0, - self.DOOR_LOWER_1: 1, - self.DOOR_LOWER_2: 2, - self.BREAK_ARCH_UPPER: 3, - self.BREAK_ARCH_LOWER: 4 - } - - # 变频器配置(Modbus RTU 协议) - self.inverter_config = { - 'slave_id': 1, - 'frequency_register': 0x01, # 2001H - 'start_register': 0x00, # 2000H - 'stop_register': 0x00, # 2000H(用于停机) - 'start_command': 0x0013, # 正转点动运行 - 'stop_command': 0x0001 # 停机 - } - - # 变送器配置(Modbus RTU) - self.transmitter_config = { - 1: { # 上料斗 - 'slave_id': 1, - 'weight_register': 0x01, - 'register_count': 2 - },#发出去的内容01 03 00 01 00 02 - 2: { # 下料斗 - 'slave_id': 2, - 'weight_register': 0x01, - 'register_count': 2 - }#发出去的内容02 03 00 01 00 02 - } - - # 系统状态 - self._running = False - self._monitor_thread = None - self._visual_control_thread = None - self._alignment_check_thread = None - - # 下料控制相关 - self.min_required_weight = 500 # 模具车最小需要重量(kg) - self.upper_door_position = 'default' # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:等待上料, 4:等待模具车对齐 - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 (1, 2, 3) - self.upper_feeding_count = 0 # 上料斗已下料次数 (0, 1, 2) - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = time.time() - self.target_vehicle_weight = 5000 # 目标模具车重量(kg) - self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg),每次下料多下这么多 - self.single_batch_weight = 2500 # 单次下料重量(kg) - - - #夹角状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - self.max_error_count = 3 - - # 下料阶段频率(Hz) - self.inverter_max_frequency = 400.0#频率最大值 - self.frequencies = [220.0, 230.0, 240.0] - - # 视觉系统接口 - self.overflow_detected = False # 堆料检测 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - - # 视觉控制参数 - self.angle_threshold = 60.0 # 角度阈值,超过此值认为开口过大 - self.target_angle = 20.0 # 目标角度 - self.min_angle = 10.0 # 最小角度 - self.max_angle = 80.0 # 最大角度 - self.angle_tolerance = 5.0 # 角度容差 - self.visual_control_enabled = True # 视觉控制使能 - self.last_angle = None # 上次检测角度 - self.visual_check_interval = 1.0 # 视觉检查间隔(秒) - self.alignment_check_interval = 0.5 # 对齐检查间隔(秒) - - # 模型路径配置 - self.angle_model_path = "vision/models/angle.pt" - self.overflow_model_path = "vision/models/overflow.pt" - self.alignment_model_path = "vision/models/alig.pt" # 模具车对齐检测模型 - self.roi_file_path = "vision/roi_coordinates/1_rois.txt" - - # 模型实例 - self.angle_model = None # 夹角检测模型实例 - self.overflow_model = None # 堆料检测模型实例 - self.alignment_model = None # 对齐检测模型实例 - - # 摄像头相关配置 - self.camera = None - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - # self.current_image_path = "current_frame.jpg" - - def set_camera_config(self, camera_type="ip", ip=None, port=None, username=None, password=None, channel=1): - """ - 设置摄像头配置 - :param ip: 网络摄像头IP地址 - :param port: 网络摄像头端口 - :param username: 网络摄像头用户名 - :param password: 网络摄像头密码 - :param channel: 摄像头通道号 - """ - self.camera_type = camera_type - if ip: - self.camera_ip = ip - if port: - self.camera_port = port - if username: - self.camera_username = username - if password: - self.camera_password = password - self.camera_channel = channel - - def set_angle_parameters(self, target_angle=20.0, min_angle=10.0, max_angle=80.0, threshold=60.0): - """ - 设置角度控制参数 - """ - self.target_angle = target_angle - self.min_angle = min_angle - self.max_angle = max_angle - self.angle_threshold = threshold - - def set_feeding_parameters(self, target_vehicle_weight=5000, upper_buffer_weight=500, single_batch_weight=2500): - """ - 设置下料参数 - :param target_vehicle_weight: 目标模具车重量(kg) - :param upper_buffer_weight: 上料斗缓冲重量(kg) - :param single_batch_weight: 单次下料重量(kg) - """ - self.target_vehicle_weight = target_vehicle_weight - self.upper_buffer_weight = upper_buffer_weight - self.single_batch_weight = single_batch_weight - - def load_all_models(self): - """ - 加载所有视觉检测模型 - """ - success = True - - # 加载夹角检测模型 - try: - if not os.path.exists(self.angle_model_path): - print(f"夹角检测模型不存在: {self.angle_model_path}") - success = False - else: - # 注意:angle.pt模型通过predict_obb_best_angle函数使用,不需要预加载 - print(f"夹角检测模型路径: {self.angle_model_path}") - except Exception as e: - print(f"检查夹角检测模型失败: {e}") - success = False - - # 加载堆料检测模型 - try: - if not os.path.exists(self.overflow_model_path): - print(f"堆料检测模型不存在: {self.overflow_model_path}") - success = False - else: - self.overflow_model = YOLO(self.overflow_model_path) - print(f"成功加载堆料检测模型: {self.overflow_model_path}") - except Exception as e: - print(f"加载堆料检测模型失败: {e}") - success = False - - # 加载对齐检测模型 - try: - if not os.path.exists(self.alignment_model_path): - print(f"对齐检测模型不存在: {self.alignment_model_path}") - success = False - else: - self.alignment_model = YOLO(self.alignment_model_path) - print(f"成功加载对齐检测模型: {self.alignment_model_path}") - except Exception as e: - print(f"加载对齐检测模型失败: {e}") - success = False - - return success - - def send_relay_command(self, command_hex): - """发送原始Socket命令""" - try: - byte_data = binascii.unhexlify(command_hex) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((self.relay_host, self.relay_port)) - sock.send(byte_data) - response = sock.recv(1024) - print(f"收到继电器响应: {binascii.hexlify(response)}") - return response - except Exception as e: - print(f"继电器通信错误: {e}") - return None - - def get_relay_status(self): - """获取继电器状态""" - response = self.send_relay_command(self.read_status_command) - status_dict = {} - - if response and len(response) >= 10: - status_byte = response[9] - status_bin = f"{status_byte:08b}"[::-1] - for key, bit_index in self.device_bit_map.items(): - status_dict[key] = status_bin[bit_index] == '1' - else: - print("读取继电器状态失败") - - return status_dict - - def control_relay(self, device, action): - """控制继电器""" - if device in self.relay_commands and action in self.relay_commands[device]: - print(f"控制继电器 {device} {action}") - self.send_relay_command(self.relay_commands[device][action]) - time.sleep(0.1) - else: - print(f"无效设备或动作: {device}, {action}") - - def read_transmitter_data_via_relay(self, transmitter_id): - """读取变送器数据(Modbus TCP 转 RS485)""" - try: - if transmitter_id not in self.transmitter_config: - print(f"无效变送器ID: {transmitter_id}") - return None - - config = self.transmitter_config[transmitter_id] - - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return None - - result = self.relay_modbus_client.read_holding_registers( - address=config['weight_register'], - count=config['register_count'], - slave=config['slave_id']#转发给哪台变送器 - ) - - if isinstance(result, Exception): - print(f"读取变送器 {transmitter_id} 失败: {result}") - return None - - # 根据图片示例,正确解析数据 - if config['register_count'] == 2:#读两个寄存器 - # 获取原始字节数组 - raw_data = result.registers - # 组合成32位整数 - weight = (raw_data[0] << 16) + raw_data[1] - weight = weight / 1000.0 # 单位转换为千克 - elif config['register_count'] == 1: - weight = float(result.registers[0]) - else: - print(f"不支持的寄存器数量: {config['register_count']}") - return None - - print(f"变送器 {transmitter_id} 读取重量: {weight}kg") - return weight - - except ModbusException as e: - print(f"Modbus通信错误: {e}") - return None - except Exception as e: - print(f"数据解析错误: {e}") - return None - finally: - self.relay_modbus_client.close() - - def set_inverter_frequency_via_relay(self, frequency): - """设置变频器频率""" - try: - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return False - - # 使用最大频率变量计算百分比 - percentage = frequency / self.inverter_max_frequency # 得到 0~1 的比例 - value = int(percentage * 10000) # 转换为 -10000 ~ 10000 的整数 - - # 限制范围 - value = max(-10000, min(10000, value)) - - result = self.relay_modbus_client.write_register( - self.inverter_config['frequency_register'], - value, - slave=self.inverter_config['slave_id'] - ) - - if isinstance(result, Exception): - print(f"设置频率失败: {result}") - return False - - print(f"设置变频器频率为 {frequency}Hz") - return True - except ModbusException as e: - print(f"变频器Modbus通信错误: {e}") - return False - finally: - self.relay_modbus_client.close() - - def control_inverter_via_relay(self, action): - try: - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return False - - if action == 'start': - result = self.relay_modbus_client.write_register( - address=self.inverter_config['start_register'], - value=self.inverter_config['start_command'], - slave=self.inverter_config['slave_id'] - ) - print("启动变频器") - elif action == 'stop': - result = self.relay_modbus_client.write_register( - address=self.inverter_config['start_register'], - value=self.inverter_config['stop_command'], - slave=self.inverter_config['slave_id'] - ) - print("停止变频器") - else: - print(f"无效操作: {action}") - return False - - if isinstance(result, Exception): - print(f"控制失败: {result}") - return False - - return True - except ModbusException as e: - print(f"变频器控制错误: {e}") - return False - finally: - self.relay_modbus_client.close() - - def check_upper_material_request(self): - """检查是否需要要料""" - current_weight = self.read_transmitter_data_via_relay(1) - - if current_weight is None: - self.upper_weight_error_count += 1 - print(f"上料斗重量读取失败,错误计数: {self.upper_weight_error_count}") - if self.upper_weight_error_count >= self.max_error_count: - print("警告:上料斗传感器连续读取失败,请检查连接") - return False - - self.upper_weight_error_count = 0 - # 判断是否需要要料:当前重量 < 目标重量 + 缓冲重量 - if current_weight < (self.single_batch_weight + self.min_required_weight): - print("上料斗重量不足,通知搅拌楼要料") - self.request_material_from_mixing_building() # 请求搅拌楼下料 - return True - return False - - def request_material_from_mixing_building(self): - """ - 请求搅拌楼下料(待完善) - TODO: 与同事对接通信协议 - """ - print("发送要料请求至搅拌楼...") - self.return_upper_door_to_default() - # 这里需要与同事对接具体的通信方式 - # 可能是Modbus写寄存器、TCP通信、HTTP请求等 - pass - - def wait_for_mixing_building_material(self): - """ - 等待搅拌楼下料完成(待完善) - TODO: 与同事对接信号接收 - """ - print("等待搅拌楼下料完成...") - # 这里需要与同事对接具体的信号接收方式 - # 可能是Modbus读寄存器、TCP通信、HTTP请求等 - # 模拟等待 - time.sleep(5) - print("搅拌楼下料完成") - self.move_upper_door_over_lower() - return True - - def move_upper_door_over_lower(self): - """移动上料斗到下料斗上方""" - print("移动上料斗到下料斗上方") - self.control_relay(self.DOOR_UPPER, 'open') - self.upper_door_position = 'over_lower' - - def return_upper_door(self): - """返回上料斗到搅拌楼""" - print("上料斗返回搅拌楼") - self.control_relay(self.DOOR_UPPER, 'close') - self.upper_door_position = 'returning' - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - self.control_relay(self.DOOR_UPPER, 'close') - self.upper_door_position = 'default' - - def start_lower_feeding(self): - """开始分步下料""" - if self.lower_feeding_stage != 0: - print("下料已在进行中") - return - - # 检查关键设备是否可连接 - if not self._check_device_connectivity(): - print("关键设备连接失败,无法开始下料") - return - - print("开始分步下料过程") - # 重置计数器 - self.lower_feeding_cycle = 0 # 用于记录三阶段下料次数 - self.upper_feeding_count = 0 # 用于记录上料次数 - - # 第一次上料(总共需要上料2次) - self.transfer_material_from_upper_to_lower() - - # 等待模具车对齐并开始第一轮下料 - self.lower_feeding_stage = 4 # 从等待模具车对齐开始 - self.wait_for_vehicle_alignment() - - def _check_device_connectivity(self): - """检查关键设备连接状态""" - try: - # 检查网络继电器连接 - test_response = self.send_relay_command(self.read_status_command) - if not test_response: - print("网络继电器连接失败") - return False - - # 检查变频器连接 - if not self.relay_modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") - return False - - # 尝试读取变频器一个寄存器(测试连接) - test_result = self.relay_modbus_client.read_holding_registers( - address=0x00, - count=1, - slave=self.inverter_config['slave_id'] - ) - - if isinstance(test_result, Exception): - print("变频器连接测试失败") - return False - - # 检查下料斗变送器连接 - test_weight = self.read_transmitter_data_via_relay(2) - if test_weight is None: - print("下料斗变送器连接失败") - return False - - self.relay_modbus_client.close() - return True - except Exception as e: - print(f"设备连接检查失败: {e}") - return False - - def transfer_material_from_upper_to_lower(self): - """上料斗向下料斗下料(基于上料斗重量传感器控制)""" - print(f"上料斗向下料斗下料 (第 {self.upper_feeding_count + 1} 次)") - - # 记录下料前的重量 - initial_upper_weight = self.read_transmitter_data_via_relay(1) - - # 如果无法读取重量,直接报错 - if initial_upper_weight is None: - raise Exception("无法读取上料斗重量传感器数据,下料操作终止") - - target_upper_weight = initial_upper_weight - self.single_batch_weight - target_upper_weight = max(target_upper_weight, 0) # 确保不低于0 - - print(f"上料斗初始重量: {initial_upper_weight:.2f}kg, 目标重量: {target_upper_weight:.2f}kg") - - # 确保下料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_2, 'close') - # 打开上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'open') - - # 等待物料流入下料斗,基于上料斗重量变化控制 - start_time = time.time() - timeout = 30 # 30秒超时 - - while time.time() - start_time < timeout: - current_upper_weight = self.read_transmitter_data_via_relay(1) - - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - break - elif time.time() - start_time > 25: # 如果25秒后重量变化过小 - weight_change = initial_upper_weight - current_upper_weight - if weight_change < 100: # 如果重量变化小于100kg - print("重量变化过小,可能存在堵塞,交由监控系统处理...") - # 不再在这里直接处理破拱,而是依靠监控系统处理 - break - - time.sleep(1) - - # 关闭上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'close') - - # 验证下料结果 - final_upper_weight = self.read_transmitter_data_via_relay(1) - if final_upper_weight is not None: - actual_transferred = initial_upper_weight - final_upper_weight - print(f"实际下料重量: {actual_transferred:.2f}kg") - - # 增加上料计数 - self.upper_feeding_count += 1 - print("上料斗下料完成") - - # 关闭上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'close') - - # 验证下料结果 - final_upper_weight = self.read_transmitter_data_via_relay(1) - if final_upper_weight is not None: - actual_transferred = initial_upper_weight - final_upper_weight - print(f"实际下料重量: {actual_transferred:.2f}kg") - - # 增加上料计数 - self.upper_feeding_count += 1 - print("上料斗下料完成") - - def wait_for_vehicle_alignment(self): - """等待模具车对齐""" - print("等待模具车对齐...") - self.lower_feeding_stage = 4 - - while self.lower_feeding_stage == 4 and self._running: - if self.vehicle_aligned: - print("模具车已对齐,开始下料") - self.lower_feeding_stage = 1 - self.feeding_stage_one() - break - time.sleep(self.alignment_check_interval) - - def feeding_stage_one(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始第一阶段下料:下料斗低速下料") - self.set_inverter_frequency_via_relay(self.frequencies[0]) - self.control_inverter_via_relay('start') - - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - # 打开下料斗出砼门 - self.control_relay(self.DOOR_LOWER_2, 'open') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程,而不是当前批次 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 1: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 2 - self.feeding_stage_two() - break - time.sleep(2) - - def feeding_stage_two(self): - """第二阶段下料:下料斗向模具车下料(中速)""" - print("开始第二阶段下料:下料斗中速下料") - self.set_inverter_frequency_via_relay(self.frequencies[1]) - - # 保持下料斗出砼门打开 - self.control_relay(self.DOOR_LOWER_2, 'open') - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 2: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 3 - self.feeding_stage_three() - break - time.sleep(2) - - def feeding_stage_three(self): - """第三阶段下料:下料斗向模具车下料(高速)""" - print("开始第三阶段下料:下料斗高速下料") - self.set_inverter_frequency_via_relay(self.frequencies[2]) - - # 保持下料斗出砼门打开 - self.control_relay(self.DOOR_LOWER_2, 'open') - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 3: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 4 - self.finish_current_batch() - break - time.sleep(2) - - def finish_current_batch(self): - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - self.control_inverter_via_relay('stop') - self.control_relay(self.DOOR_LOWER_1, 'close') - self.control_relay(self.DOOR_LOWER_2, 'close') - - - # 增加三阶段下料轮次计数 - self.lower_feeding_cycle += 1 - - # 检查是否完成两轮三阶段下料(总共5吨) - if self.lower_feeding_cycle >= 2: - # 完成整个5吨下料任务 - print("完成两轮三阶段下料,5吨下料任务完成") - self.finish_feeding_process() - return - - # 如果只完成一轮三阶段下料,进行第二次上料 - print("第一轮三阶段下料完成,准备第二次上料") - # 上料斗第二次向下料斗下料 - try: - self.transfer_material_from_upper_to_lower() - except Exception as e: - print(f"第二次上料失败: {e}") - print("停止下料流程") - self.finish_feeding_process() # 出现严重错误时结束整个流程 - return - - # 继续等待当前模具车对齐(不需要重新等待对齐,因为是同一辆模具车) - print("第二次上料完成,继续三阶段下料") - self.lower_feeding_stage = 1 # 直接进入第一阶段下料 - self.feeding_stage_one() # 开始第二轮第一阶段下料 - - - def finish_feeding_process(self): - """完成整个下料流程""" - print("整个下料流程完成") - self.lower_feeding_stage = 0 - self.lower_feeding_cycle = 0 - self.upper_feeding_count = 0 - self.return_upper_door_to_default() - - def handle_overflow_control(self, overflow_detected, door_opening_large): - """处理溢料控制""" - if overflow_detected and door_opening_large: - print("检测到溢料且出砼门开口较大,调小出砼门") - self.control_relay(self.DOOR_LOWER_1, 'close') - time.sleep(0.1) - self.control_relay(self.DOOR_LOWER_1, 'open') - time.sleep(0.1) - - def is_lower_door_open(self): - """检查出砼门是否打开""" - return self.lower_feeding_stage in [1, 2] # 只有在下料阶段才认为门是打开的 - - def check_arch_blocking(self): - """检查是否需要破拱""" - current_time = time.time() - - # 检查下料斗破拱(只有在下料过程中才检查) - if self.lower_feeding_stage in [1, 2, 3]: # 在所有下料阶段检查 - lower_weight = self.read_transmitter_data_via_relay(2) - if lower_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(lower_weight - self.last_lower_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("下料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_LOWER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_LOWER, 'close') - - self.last_lower_weight = lower_weight - - # 检查上料斗破拱(在上料斗向下料斗下料时检查) - if (self.upper_door_position == 'over_lower' and - self.lower_feeding_stage in [0, 1, 2, 3, 4]): # 在任何阶段都可能需要上料斗破拱 - upper_weight = self.read_transmitter_data_via_relay(1) - if upper_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(upper_weight - self.last_upper_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("上料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_UPPER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_UPPER, 'close') - - self.last_upper_weight = upper_weight - - # 更新最后读取时间 - if (self.read_transmitter_data_via_relay(1) is not None or - self.read_transmitter_data_via_relay(2) is not None): - self.last_weight_time = current_time - - def monitor_system(self): - """监控系统状态""" - while self._running: - try: - self.check_upper_material_request() - self.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def setup_camera_capture(self, camera_index=0): - """ - 设置摄像头捕获 - :param camera_index: USB摄像头索引或IP摄像头配置 - """ - try: - rtsp_url = f"rtsp://{self.camera_username}:{self.camera_password}@{self.camera_ip}:{self.camera_port}/streaming/channels/{self.camera_channel}01" - self.camera = cv2.VideoCapture(rtsp_url) - - if not self.camera.isOpened(): - print(f"无法打开网络摄像头: {rtsp_url}") - return False - print(f"网络摄像头初始化成功,地址: {rtsp_url}") - return True - except Exception as e: - print(f"摄像头设置失败: {e}") - return False - - def capture_current_frame(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - ret, frame = self.camera.read() - if ret: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def detect_overflow_from_image(self, image_array): - """通过图像检测是否溢料(接受numpy数组)""" - try: - # 检查模型是否已加载 - if self.overflow_model is None: - print("堆料检测模型未加载") - return False - - rois = load_global_rois(self.roi_file_path) - - if not rois: - print("没有有效的ROI配置") - return False - - if image_array is None: - print("输入图像为空") - return False - - crops = crop_and_resize(image_array, rois, 640) - for roi_resized, _ in crops: - final_class, _, _, _ = classify_image_weighted(roi_resized, self.overflow_model, threshold=0.4) - if "大堆料" in final_class or "小堆料" in final_class: - return True - - return False - except Exception as e: - print(f"溢料检测失败: {e}") - return False - - def detect_vehicle_alignment(self, image_array): - """通过图像检测模具车是否对齐(接受numpy数组)""" - try: - # 检查模型是否已加载 - if self.alignment_model is None: - print("对齐检测模型未加载") - return False - - if image_array is None: - print("输入图像为空") - return False - - # 直接使用模型进行推理 - results = self.alignment_model(image_array) - pared_probs = results[0].probs.data.cpu().numpy().flatten() - - # 类别0: 未对齐, 类别1: 对齐 - class_id = int(pared_probs.argmax()) - confidence = float(pared_probs[class_id]) - - # 只有当对齐且置信度>95%时才认为对齐 - if class_id == 1 and confidence > 0.95: - return True - return False - except Exception as e: - print(f"对齐检测失败: {e}") - return False - - def get_current_door_angle(self, image=None, image_path=None): - """ - 通过视觉系统获取当前出砼门角度 - :param image: 图像数组(numpy array) - :param image_path: 图片路径 - """ - try: - # 检查模型是否已加载 - if self.angle_model is None: - print("夹角检测模型未加载") - return None - - angle_deg, _ = predict_obb_best_angle( - model=self.angle_model, # 传递预加载的模型实例 - image=image, # 传递图像数组 - image_path=image_path # 或传递图像路径 - ) - return angle_deg - except Exception as e: - print(f"角度检测失败: {e}") - return None - - def alignment_check_loop(self): - """ - 模具车对齐检查循环 - """ - while self._running: - try: - # 只在需要检查对齐时才检查 - if self.lower_feeding_stage == 4: - current_frame = self.capture_current_frame() - if current_frame is not None: - self.vehicle_aligned = self.detect_vehicle_alignment(current_frame) - if self.vehicle_aligned: - print("检测到模具车对齐") - else: - print("模具车未对齐") - time.sleep(self.alignment_check_interval) - except Exception as e: - print(f"对齐检查循环错误: {e}") - time.sleep(self.alignment_check_interval) - - def visual_control_loop(self): - """ - 视觉控制主循环 - """ - - while self._running and self.visual_control_enabled: - try: - current_frame = self.capture_current_frame() - if current_frame is None: - print("无法获取当前图像,跳过本次调整") - time.sleep(self.visual_check_interval) - continue - - # 检测是否溢料 - overflow = self.detect_overflow_from_image(current_frame) - - # 获取当前角度 - current_angle = self.get_current_door_angle(image=current_frame) - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - time.sleep(self.visual_check_interval) - continue - - print(f"当前角度: {current_angle:.2f}°, 溢料状态: {overflow}, 控制模式: {self.angle_control_mode}") - - # 状态机控制逻辑 - if self.angle_control_mode == "normal": - # 正常模式 - if overflow and current_angle > self.angle_threshold: - # 检测到堆料且角度过大,进入角度减小模式 - print("检测到堆料且角度过大,关闭出砼门开始减小角度") - self.control_relay(self.DOOR_LOWER_2, 'close') - self.angle_control_mode = "reducing" - else: - # 保持正常开门 - self.control_relay(self.DOOR_LOWER_2, 'open') - - elif self.angle_control_mode == "reducing": - # 角度减小模式 - if current_angle <= self.target_angle + self.angle_tolerance: - # 角度已达到目标范围 - if overflow: - # 仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - self.angle_control_mode = "maintaining" - self.control_relay(self.DOOR_LOWER_2, 'open') # 先打开门 - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "recovery" - - elif self.angle_control_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "recovery" - else: - # 继续维持角度控制 - self.pulse_control_door_for_maintaining() - - elif self.angle_control_mode == "recovery":#打开夹爪的过程中又堆料了 - # 恢复模式 - 逐步打开门 - if overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_control_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "normal" - - self.last_angle = current_angle - time.sleep(self.visual_check_interval) - - except Exception as e: - print(f"视觉控制循环错误: {e}") - time.sleep(self.visual_check_interval) - - def pulse_control_door_for_maintaining(self): - """ - 用于维持模式的脉冲控制 - 保持角度在目标范围内 - """ - print("执行维持脉冲控制") - # 关门1秒 - self.control_relay(self.DOOR_LOWER_2, 'close') - time.sleep(1.0) - # 开门1秒 - self.control_relay(self.DOOR_LOWER_2, 'open') - time.sleep(1.0) - - def start_visual_control(self): - """ - 启动视觉控制线程 - """ - if not self.visual_control_enabled: - print("视觉控制未启用") - return - - print("启动视觉控制线程") - self._visual_control_thread = threading.Thread( - target=self.visual_control_loop, - daemon=True - ) - self._visual_control_thread.start() - return self._visual_control_thread - - def start_alignment_check(self): - """ - 启动模具车对齐检查线程 - """ - print("启动模具车对齐检查线程") - self._alignment_check_thread = threading.Thread( - target=self.alignment_check_loop, - daemon=True - ) - self._alignment_check_thread.start() - return self._alignment_check_thread - - def start(self): - """启动系统""" - if self._running: - print("系统已在运行") - return - print("启动控制系统") - self._running = True - self._monitor_thread = threading.Thread(target=self.monitor_system, daemon=True) - self._monitor_thread.start() - - def stop(self): - """停止系统""" - if not self._running: - print("系统未在运行") - return - print("停止控制系统") - self._running = False - if self._monitor_thread is not None: - self._monitor_thread.join() - if self._visual_control_thread is not None: - self._visual_control_thread.join() - if self._alignment_check_thread is not None: - self._alignment_check_thread.join() - if self.camera is not None: - self.camera.release() - print("控制系统已停止") - - -# 使用示例 -if __name__ == "__main__": - system = FeedingControlSystem(relay_host='192.168.0.18', relay_port=50000) - - # 设置角度控制参数 - system.set_angle_parameters( - target_angle=20.0, - min_angle=10.0, - max_angle=80.0, - threshold=60.0 - ) - - # 设置下料参数 - system.set_feeding_parameters( - target_vehicle_weight=5000, # 5吨 - upper_buffer_weight=500, # 0.5吨缓冲 - single_batch_weight=2500 # 每次下2.5吨 - ) - - # 设置摄像头配置 - system.set_camera_config( - camera_type="ip", - ip="192.168.1.51", - port=554, - username="admin", - password="XJ123456", - channel=1 - ) - - # 初始化摄像头 - if not system.setup_camera_capture(): - print("摄像头初始化失败") - exit(1) - - # 加载所有模型 - if not system.load_all_models(): - print("模型加载失败") - exit(1) - - # 启动系统监控 - system.start() - - # 启动视觉控制 - system.start_visual_control() - - # 启动对齐检查 - system.start_alignment_check() - - print("系统准备就绪,5秒后开始下料...") - time.sleep(5) - system.start_lower_feeding() # 启动下料流程 - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - print("收到停止信号") - except Exception as e: - print(f"系统错误: {e}") - finally: - system.stop() diff --git a/core/state.py b/core/state.py deleted file mode 100644 index d520eba..0000000 --- a/core/state.py +++ /dev/null @@ -1,109 +0,0 @@ -from PySide6.QtCore import Signal, QObject -import threading -from enum import IntEnum - -class SystemState(QObject): - """状态中以_开头的属性会发送到OPC通知,不需要的不要加_开头""" - state_updated=Signal(str,object) - def __init__(self): - super().__init__() - # - self._watched_props = [] - self.lock = threading.RLock() - - # 系统运行状态 - self.running = True - - # 上料斗控制相关 - self._upper_door_position = 'default' # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - # 是否破拱 - self._upper_is_arch_=False - self._upper_door_closed=True - self._upper_weight=0 - self._upper_volume=0.0 - - #下料斗状态想着 - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 - self._lower_is_arch_=False - self._lower_weight=0 - self._lower_angle=0.0 - - #模具车状态 - self._mould_weight=0 - self._mould_frequency=220 - self._mould_vibrate_status=0 #1振动中0未振动 - #记录模具开始振动的时间 - self.mould_vibrate_time=0 - - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 - self.upper_feeding_count = 0 # 上料斗已下料次数 - self.upper_feeding_max = 2 #上料斗最大下料次数 - - # 重量相关 - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = 0 - #需要下料的总重量 - self._mould_need_weight=0 - #完成下料的总重量 - self._mould_finish_weight=0 - - self.initial_upper_weight=0 - self.initial_lower_weight=0 - - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - - # 视觉系统状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery - self.overflow_detected = "0" # 堆料检测 - self.current_finish_status=False # 当前是否完成浇筑满 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - self.last_angle = None # 上次检测角度 - - #当前RFID的内容格式为 模块编号,分块号,尺寸规格,方量 - self.rfid_current=None - - #当前生产的管片 - self.current_artifact=None - #当前生产状态 - self._feed_status=FeedStatus.FNone - #每方重量 - self.density=2416.4 - # - self._watched_props = [k for k in self.__dict__ if k.startswith('_')] - - def __setattr__(self, name, value): - super().__setattr__(name, value) - if name in self._watched_props: - with self.lock: - public_name = name.lstrip('_') - self.state_updated.emit(public_name, value) - - -class FeedStatus(IntEnum): - #初始值 - FNone = 0 - # 检查模车(模车到位) - FCheckM = 1 - #RFID检测或匹配 - FRFID=2, - # 开始(管片待生产任务) - FApiCheck = 3 - # 检查盖板(盖板到位) - FCheckGB = 4 - # 上料到下料(上料斗到下料斗) - FUpperToLower=5 - #下料1 - FFeed1 = 6 - # 下料2 - FFeed2 = 7 - # 下料3 - FFeed3 = 8 - #完成(管片生产完成) - FFinished = 11 - - FFeed=12 \ No newline at end of file diff --git a/core/system copy.py b/core/system copy.py deleted file mode 100644 index f28e608..0000000 --- a/core/system copy.py +++ /dev/null @@ -1,288 +0,0 @@ -# core/system.py -import threading -import time -import cv2 -from core.state import SystemState -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -from hardware.RFID.rfid_service import rfid_service -from vision.camera import DualCameraController -from vision.detector import VisionDetector -from feeding.controller import FeedingController -from service.mould_service import app_web_service -from config.settings import app_set_config - - -class FeedingControlSystem: - def __init__(self): - self.state = SystemState() - - # 初始化硬件控制器 - self.relay_controller = RelayController( - host=app_set_config.relay_host, - port=app_set_config.relay_port - ) - - self.inverter_controller = InverterController(self.relay_controller) - self.transmitter_controller = TransmitterController(self.relay_controller) - - # 初始化视觉系统 - self.camera_controller = DualCameraController(app_set_config.camera_configs) - - self.vision_detector = VisionDetector() - - # 初始化RFID控制器 - self.rfid_controller = rfid_service( - host=app_set_config.rfid_host, - port=app_set_config.rfid_port - ) - # 初始化下料控制器 - self.feeding_controller = FeedingController( - self.relay_controller, - self.inverter_controller, - self.transmitter_controller, - self.vision_detector, - self.camera_controller, - self.rfid_controller, - self.state - ) - - # 线程管理 - self.monitor_thread = None - self.visual_control_thread = None - self.alignment_check_thread = None - self.lower_feeding_thread = None - self.led_thread = None - - def initialize(self): - """初始化系统""" - print("初始化控制系统...") - - # self.check_device_connectivity() - - # self.camera_controller.start_cameras() - # if not app_set_config.debug_feeding: - # 启动系统监控(要料,破拱)线程 - self.start_monitoring() - - # 启动视觉控制(角度、溢出)线程 - # self.start_visual_control() - - # 启动对齐检查线程 - self.start_alignment_check() - - # 启动下料线程 - self.start_lower_feeding() - #LED屏 - # self.start_led() - - print("控制系统初始化完成") - - def start_monitoring(self): - """启动系统监控""" - print('振动和要料监控线程启动') - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - def _monitor_loop(self): - """监控循环""" - while self.state.running: - try: - # self.feeding_controller.check_upper_material_request() - self.feeding_controller.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def start_visual_control(self): - """启动视觉控制""" - print('视觉控制线程启动') - self.visual_control_thread = threading.Thread( - target=self._visual_control_loop, - daemon=True, - name='visual_control' - ) - self.visual_control_thread.start() - - def _visual_control_loop(self): - """视觉控制循环""" - while self.state.running: - try: - # print('visual_control') - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - # 执行视觉控制逻辑 - self.feeding_controller.visual_control(current_frame) - time.sleep(app_set_config.visual_check_interval) - except Exception as e: - print(f"视觉控制循环错误: {e}") - time.sleep(app_set_config.visual_check_interval) - - def start_alignment_check(self): - """启动对齐检查""" - print('对齐检查线程启动') - self.alignment_check_thread = threading.Thread( - target=self._alignment_check_loop, - daemon=True, - name='align_check' - ) - self.alignment_check_thread.start() - - def _alignment_check_loop(self): - """对齐检查循环""" - loc_align_status=False - loc_before_status=None - while self.state.running: - try: - if self.state.lower_feeding_stage == 4: # 等待对齐阶段 - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - self.state.vehicle_aligned = self.alignment_check_status() - if self.state.vehicle_aligned: - # loc_count+=1 - print("检测到模具车对齐") - else: - print("模具车未对齐") - # time.sleep(app_set_config.alignment_check_interval) - # loc_align_status=self.alignment_check_status() - # if loc_align_status and not loc_before_status: - # print("模具车由未对齐到对齐") - # self.state.vehicle_aligned=True - # elif not loc_align_status and loc_before_status: - # print("模具车由对齐到未对齐") - # self.state.vehicle_aligned=False - - # if loc_before_status!=loc_align_status: - # loc_before_status=loc_align_status - - except Exception as e: - print(f"对齐检查循环错误: {e}") - finally: - time.sleep(app_set_config.alignment_check_interval) - - - def alignment_check_status(self)->bool: - """对齐检查循环""" - loc_aligned=False - loc_count=0 - for i in range(4): - try: - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - loc_aligned = self.vision_detector.detect_vehicle_alignment(current_frame) - if loc_aligned: - loc_count+=1 - print("检测到模具车对齐") - else: - loc_count=0 - print("模具车未对齐") - time.sleep(app_set_config.alignment_check_interval) - except Exception as e: - print(f"对齐检查循环错误: {e}") - time.sleep(app_set_config.alignment_check_interval) - - if loc_count>=3: - loc_aligned=True - else: - loc_aligned=False - return loc_aligned - - def start_lower_feeding(self): - """启动下料流程""" - self.lower_feeding_thread = threading.Thread( - target=self._start_lower_feeding, - name="Feeding", - daemon=True - ) - self.lower_feeding_thread.start() - - def _start_lower_feeding(self): - """启动下料流程""" - while self.state.running: - self.feeding_controller.start_feeding() - time.sleep(app_set_config.lower_feeding_interval) - - def start_led(self): - """启动LED流程""" - self.led_thread = threading.Thread( - target=self._start_led, - name="LED", - daemon=True - ) - self.led_thread.start() - - def _start_led(self): - """启动LED流程""" - while self.state.running: - led_info = app_web_service.get_pouring_led() - if led_info: - if self.state.current_artifact.MouldCode==led_info.MouldCode: - led_info.RingTypeCode=self.state.current_artifact.RingTypeCode - led_info.UpperWeight=self.state._upper_weight - led_info.LowerWeight=self.state._lower_weight - led_info.VibrationFrequency=self.state._mould_frequency - - #发送到LED屏 - - time.sleep(app_set_config.led_interval) - - def check_device_connectivity(self) -> bool: - """检查关键设备连接状态""" - try: - # 检查网络继电器连接 - test_response = self.relay_controller.send_command(self.relay_controller.read_status_command) - if not test_response: - print("网络继电器连接失败") - return False - - # 检查变频器连接 - if not self.relay_controller.modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") - return False - - # 尝试读取变频器一个寄存器(测试连接) - # test_result = self.relay_controller.modbus_client.read_holding_registers( - # address=0x00, - # count=1, - # slave=self.inverter_controller.config['slave_id'] - # ) - - # if isinstance(test_result, Exception): - # print("变频器连接测试失败") - # return False - - # 检查下料斗变送器连接 - test_weight = self.transmitter_controller.read_data(2) - if test_weight is None: - print("下料斗变送器连接失败") - return False - - self.relay_controller.modbus_client.close() - return True - except Exception as e: - print(f"设备连接检查失败: {e}") - return False - - def stop(self): - """停止系统""" - print("停止控制系统...") - self.state.running = False - - # 等待线程结束 - if self.monitor_thread: - self.monitor_thread.join() - if self.visual_control_thread: - self.visual_control_thread.join() - if self.alignment_check_thread: - self.alignment_check_thread.join() - if self.lower_feeding_thread: - self.lower_feeding_thread.join() - - # 释放摄像头资源 - self.camera_controller.release() - print("控制系统已停止") diff --git a/core/system.py b/core/system.py index 0e70505..25e3c22 100644 --- a/core/system.py +++ b/core/system.py @@ -27,62 +27,70 @@ class FeedingControlSystem: port=ini_manager.relay_port ) - self.inverter_controller = InverterController() self.transmitter_controller = TransmitterController(self.relay_controller) + + self.plc_service = OmronFinsPollingService(ini_manager.upper_plc_ip, ini_manager.upper_plc_port) + + # 初始化下料控制器 + self.feeding_controller = VisualCallback( + relay_controller=self.relay_controller, + transmitter_controller=self.transmitter_controller, + state=self.state + ) + + self.plc_service.register_data_callback(self.feeding_controller.on_plc_update) + #小屏修改过屏幕 self.vf_auto_mode=True # 初始化 OPC UA 客户端 self.opcua_client_feed = OpcuaClientFeed() - - # 初始化 RFID 控制器 - # self.rfid_controller = rfid_service( - # host=app_set_config.rfid_host, - # port=app_set_config.rfid_port - # ) - - # self.plc_service = OmronFinsPollingService(ini_manager.upper_plc_ip, ini_manager.upper_plc_port) - - # 初始化下料控制器 - self.feeding_controller = VisualCallback(self.state) - - # 初始化 OPC 队列监听线程 - self.opc_queue_thread = None # 线程管理 - self.monitor_thread = None - self.visual_control_thread = None - self.alignment_check_thread = None - self.lower_feeding_thread = None - self.led_thread = None + self.feed_thread = None + self.vf_thread = None + self.arch_thread = None + self.api_thread = None + self.pd_jbl_thread = None + # 初始化 OPC 队列监听线程,用于处理队列中的数据 + self.opc_queue_thread = None - def initialize(self): + def initialize(self)->bool: """初始化系统""" print("初始化控制系统...") - # self.check_device_connectivity() - # self.camera_controller.start_cameras() - # self.start_monitoring() - # 启动下料线程 - # self.start_lower_feeding() - # 启动OPC队列处理线程 - # self.opcua_client_feed.start() - - # self.start_opc_queue_thread() - - #启用API线程 - self.start_api_thread() - + #启用上料斗PLC + self.plc_service.start_polling(interval=2.0) + #启用下料线程 + self.start_feed_thread() + #启用变频器线程 self.start_vf_thread() - # self.feeding_controller.get_current_mould() - # self.feeding_controller._cur_mould_model.MouldCode='SHR2L1-5' - # self.feeding_controller.send_pd_data() + #启用破拱线程 + self.start_arch_thread() + #启用推送模型数据线程 + self.feeding_controller.start_visual_thread() + #启用API(对接PD API数据),线程 + self.start_api_thread() #启用派单线程 - # self.start_pd_thread() + self.start_pd_thread() + # 启动OPC队列处理线程,维护连接的断开重连等 + self.opcua_client_feed.start() + + self.start_opc_queue_thread() + print("控制系统初始化完成") - + return True + + def start_feed_thread(self): + "下料线程控制,主要控制下料斗(视觉控制)以及上料斗" + self.feed_thread = threading.Thread( + target=self.feeding_controller._run_feed, + daemon=True + ) + self.feed_thread.start() + def start_opc_queue_thread(self): - """启动OPC队列处理线程""" + """启动OPC队列处理线程(从控制系统中获取数据,通过OPC外发)""" print('启动OPC队列处理线程') self.opc_queue_thread = threading.Thread( target=self._process_opc_queue, @@ -102,7 +110,7 @@ class FeedingControlSystem: self.api_thread.start() def start_vf_thread(self): - """启动变频器控制线程""" + """启动变频器控制线程(控制变频器开始启动,以及频率变换)""" # print('启动API处理线程,从API获取未浇筑数据') self.vf_thread = threading.Thread( target=self._process_vf, @@ -157,7 +165,6 @@ class FeedingControlSystem: print(f"处理变频器数据时发生错误: {e}") time.sleep(2) - def _process_api_db(self): from service.mould_service import app_web_service """处理API队列中的数据""" @@ -296,41 +303,16 @@ class FeedingControlSystem: self.feeding_controller.shutdown() self.stop() - - def start_monitoring(self): - """启动系统监控""" + def start_arch_thread(self): + """启动系统监控和要料""" print('振动和要料监控线程启动') - self.monitor_thread = threading.Thread( - target=self._monitor_loop, + #启动振动线程 + self.arch_thread = threading.Thread( + target=self.feeding_controller._arch_loop, daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - def _monitor_loop(self): - """监控循环""" - while self.state.running: - try: - # self.feeding_controller.check_upper_material_request() - self.feeding_controller.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def start_lower_feeding(self): - """启动下料流程""" - self.lower_feeding_thread = threading.Thread( - target=self._start_lower_feeding, - name="Feeding", - daemon=True - ) - self.lower_feeding_thread.start() - - def _start_lower_feeding(self): - """启动下料流程""" - while self.state.running: - self.feeding_controller.start_feeding() - time.sleep(app_set_config.lower_feeding_interval) + name='arch' + ) + self.arch_thread.start() def check_device_connectivity(self) -> bool: """检查关键设备连接状态""" @@ -346,17 +328,6 @@ class FeedingControlSystem: print("无法连接到网络继电器Modbus服务") return False - # 尝试读取变频器一个寄存器(测试连接) - # test_result = self.relay_controller.modbus_client.read_holding_registers( - # address=0x00, - # count=1, - # slave=self.inverter_controller.config['slave_id'] - # ) - - # if isinstance(test_result, Exception): - # print("变频器连接测试失败") - # return False - # 检查下料斗变送器连接 test_weight = self.transmitter_controller.read_data(2) if test_weight is None: @@ -368,8 +339,7 @@ class FeedingControlSystem: except Exception as e: print(f"设备连接检查失败: {e}") return False - - + def start_pd_thread(self): """启动PD线程""" print('启动派单处理线程,从API获取未浇筑数据') @@ -401,7 +371,34 @@ class FeedingControlSystem: _start_time=None _isFinish=False time.sleep(5) - + + def start_led(self): + """启动LED流程""" + self.led_thread = threading.Thread( + target=self._start_led, + name="LED", + daemon=True + ) + self.led_thread.start() + + def _start_led(self): + """启动LED流程""" + from service.mould_service import app_web_service + + while self.state.running: + led_info = app_web_service.get_pouring_led() + if led_info: + if self.state.current_artifact.MouldCode==led_info.MouldCode: + led_info.RingTypeCode=self.state.current_artifact.RingTypeCode + led_info.UpperWeight=self.state._upper_weight + led_info.LowerWeight=self.state._lower_weight + led_info.VibrationFrequency=self.state._mould_frequency + + #发送到LED屏 + + time.sleep(app_set_config.led_interval) + + @property def _is_finish(self): """检查系统是否运行""" @@ -428,20 +425,21 @@ class FeedingControlSystem: self.state.running = False # 等待线程结束 - if self.monitor_thread: - self.monitor_thread.join() - if self.visual_control_thread: - self.visual_control_thread.join() - if self.alignment_check_thread: - self.alignment_check_thread.join() - if self.lower_feeding_thread: - self.lower_feeding_thread.join() if self.opc_queue_thread: self.opc_queue_thread.join() if self.vf_thread: self.vf_thread.join() if self.api_thread: self.api_thread.join() + if self.pd_jbl_thread: + self.pd_jbl_thread.join() + if self.feed_thread: + self.feed_thread.join() + if self.arch_thread: + self.arch_thread.join() + if self.plc_service: + self.plc_service.stop_polling() + self.feeding_controller.shutdown() # 释放摄像头资源 # self.camera_controller.release() print("控制系统已停止") diff --git a/core/system_state.py b/core/system_state.py index 96a50aa..6afc367 100644 --- a/core/system_state.py +++ b/core/system_state.py @@ -25,6 +25,7 @@ class SystemState: self._upper_door_closed=True self._upper_weight=0 self._upper_volume=0.0 + #下料比例变频 self.vf_frequencys=[{'radio':0,'fre':230},{'radio':0.3,'fre':230},{'radio':0.6,'fre':230}] #使用 @@ -32,55 +33,26 @@ class SystemState: self._mould_vibrate_status=False #True振动中False未振动 #记录模具开始振动的时间 self.mould_vibrate_time=0 - - #下料斗状态想着 - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 + #下料斗状态 self._lower_is_arch_=False self._lower_weight=0 - self._lower_angle=0.0 #模具车状态 self._mould_weight=0 - - - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 - self.upper_feeding_count = 0 # 上料斗已下料次数 - self.upper_feeding_max = 2 #上料斗最大下料次数 - # 重量相关 - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = 0 #需要下料的总重量 self._mould_need_weight=0 #完成下料的总重量 self._mould_finish_weight=0 - - self.initial_upper_weight=0 - self.initial_lower_weight=0 - - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - + # 视觉系统状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery self.overflow_detected = "0" # 堆料检测 - self.current_finish_status=False # 当前是否完成浇筑满 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - self.last_angle = None # 上次检测角度 - - #当前RFID的内容格式为 模块编号,分块号,尺寸规格,方量 - self.rfid_current=None #当前生产的管片 self.current_artifact=None #当前生产状态 self._feed_status=FeedStatus.FNone - #每方重量 - self.density=2416.4 + self.bll_artifact=ArtifactBll() self.bll_pdrecord=PDRecordBll() #记录正在生产code模具编号,status:2正生产3完成生成,weight:完成重量 diff --git a/db/messages.db b/db/messages.db index 0cf6ae724e13f9ab05f14c0e788ae6d38271ee53..420f04a6d6d5f2baccab2f4b602faa2bba79b7d8 100644 GIT binary patch literal 20480 zcmeI3ZERcB8OQC|v7N-e;bkmiTgQd5(bA;7uTFCIMkuBWPnwqCXsZzGLhGVPO#(?a zgeFA2Zf#e#(xHw3Ero7H?U-69DSJtR&?G40<~ zyaQWW5?yQ8Mz_w6-MgzW^S~~=qT4o8c;GvQ$?g7^u?-`+fqYKQ58OJGQ+@AUJvlZ} z=*E_7_Uw!%yH>4=?z@gNCkit&WA_zi#NU=Wicrh7?ZT6x23+~6b zZ&%fx9#v=RBwgC>>Q)V=J~%c#IX-#ciq9LL*>?Z*6nZsNsP$KX)Z22m4~!1wRjr$d z7k;~-FCRZuIk8as&C8X=$Cgk2qO^B@ef34@#1ATm_O9S>pDv8ykj#!x6nru9PL}o` zDJ?FP=YIYE+_@EPca6==Zkw3eF}`ygJ6GojZ(KgQyL9N_h1brM5C5);xVW^x{KQM8 zpC4OaK5`EKBX!TK7nYtbJ@(|qr61LO!Wz23k9K}QbFa1Y1LYm%5yi-qGEZh8{bBlS zdPlme`d(^X^1bBYSdf5v_l`&R7Ame*SzY`Ho5=jfs6hREL|zYIUcBVZ;YgKa6(EG_LWoqTk8 z-wUM!r^-()Ebn=>{K~${>7Q2S7b}a;8k&*q(exh8R&|Zpeay&SLIOj;emMXB-0A-U zvwL&{e^kAf*(THb5ayRa7qH9q%x!5h+h$s?7v@Nyg@9T8Otb4@T!QrhV0w?CQ#q0ngl)hC2Xt5_Ah#ke=yGuL~19OA@RVu!ehP zGrOOen%6T&f}2CYOr2SEp4p5`utvZd4%23a#x$e8XIz5S0u~@ly>A7KOK?*dn8B>N z0kD})5_~Z*%%TA>oV8h<8jb{C2n5snRo!Nm&J4F_9tmy?2J2HPAn$1EH3>c+0*3QI zcNV(dQ-yH}ZU_N0aG}%Yh`W-2aS5&u12a7XQ0Yz)$a|{k>ImoU>dXBg-!#2Fp}UiAz)@N)9b?28JFPG z!C<|ro@EwphBa7Cf@{OTEN1#)B*CWytf9?O=iM-ImvFajj|5!;*3jlyZK>fAU3W*5 z^SK0{3=BiV44-F?1lI(EWmQ~-H~^lW)g<^t7?{S4T9{QBNpN)tnAzt!)ae|?CAcaa zOq^2TekNdAFI*W0MuxkiS%r}VR|JOPLd2s}oo9{&onc_Ot=1+j_6%Vpfg)gytpMH< zS3vE=g*?NBP7-7Utg#h+{T!ARPi`;i0p3bmDw2K}QIfi6_WD7?+@3z#8_9 z4js6&dZ&gXL7RXD@Qlvv+!GfX0ECeQsSq%umua;F3WsqCl0ji~?S$tV?rA9YOh}Lj z0ke9U;q4j1xCE_XU^qj)o)L^Bh=+mU+VQSUf{_HVFfiS{5aU}A!AOD@0c&iYndG*H zQ=zs6)g*`tSYz|dRJDG(60Ti{>0H8#@wiF3`Do`~Blsg9(gEp!bU->F9gq%42c!ei z0qKBrKsq2DkPiIcIFOFURz*e0MxL(!O-#&p&UfzYyjJG_{lx@s%3kLGW&Xdu&V=Vp zsb&K60y6*KSZ$8SGoI`hU!zxTl==Tl)iZhW^D_V6U=5hZGoI`h^USGCpp1>o|NH9Z zT+f_jzt=OF|G#vtVb!x*vfuTrCdvH&CFdDtCr!-R$^3sqHLcZohMqaees`YLB$@yJ zsCkCFz0CgyI6w0w`#tjvGt#^yK<58HVxEa)f4yfP-~2x>383r$>s!v#_5TgZAC>)? ze`H?B+>?GM{YcvAINp)(NVb2seNEe++935|>TF6){vo+H@wddO#NDlLw>}*IOZ?fm z68n8jLxg-t2R@DtKwi|#T(0;A?;OFKJv3e~gS#Eo82P25a#t!++(ZZM<=2jt4?e$n z%ZAZ`^3k7^4jeB%b*gmY?DDzinR@p(#wT;r(^Jz}$fITTd(_I|vlkW)R*sx0KeuoB z%<;;Zx$@D4%E=eY$BN}gAFnJvb8+d%<+J%1@`FbYF)|7!lE3t{SMm zC9gd8OGV|*=0s=Vodr}==jjlaU`vSTu(Hf{?)nkK$)%#Qxk=ICT{ZsbNP^*T(HT_F z?zIZ5>+GVksY%h{oiqOENP?j-(K&Uu-spJUEeKeEMT~`fyi(H(BdMs|(HyJr`x}$W zAUz%85)6vy0xV)`Nu}^Um<3-KumD!!#hC8v8JCL6#^zXsw^i_hMNf2Gg4;!O0j#2y zzRi%6{h$Og0L>Is+gz*yfdYF}kuL)QXtFU%^WzRId zsC>0aR$(?#$Ao`~$Nstw= jAXYi|qk63(si@dZvI>tg{^&>oOGFpMDs1UrfF%6~l8@bm delta 33 pcmZozz}S#5L7LTsfq{W}qJll6$;N~Q{F@9|1U9oM{N`sg1^}4}2-yGt diff --git a/db/three.db b/db/three.db index 5177849cdbacc0ccff45416cc9548bbb45eb26b5..f57ab3ffd9dc914f26064561f43ff571850d1b07 100644 GIT binary patch literal 262144 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 delta 355 zcmZo@5NKGyIYFA&j)8$e6^OxLqJ}xE9fMw%(8iQ)dQ1$xlV$WDF|qkCCiu`{_DHOG6H3{*NI2JllB`7+>%+$yIOvwx5w(U^=4$Q|b2DD~#NXn;SRQ^G!2g zV%g5}fbnlUKYuF&3m-27-%37Ses}&u8yo-fvNTHccun8j>Tk}+wx diff --git a/db/three.db-shm b/db/three.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..a6bb6c3f704e068cdd7c28ca51ff8636750c9215 GIT binary patch literal 32768 zcmeI*u?>Py5C&jvtf>@+Ucj6rCq*=S$-;seHZHVXp84{v#+y5a_ReH*{sg%TXj#r{2x{giF;qOjL%csPLqKE z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U tAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!CtM3&hLdLV&<81l}L#C@cT~ literal 0 HcmV?d00001 diff --git a/db/three.db-wal b/db/three.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..abcfaf8d3a09f35bebef093b32eac39f1f9f2757 GIT binary patch literal 4152 zcmcha-)kII6vwBrNhYmpBZ?-bX`*Y5C_9|{bLL)@#?@~7kcV0YD^${_f^R;Epf63j zmZA|NXc4QJ6cqDfMTp%H`wPK;z&D|Sm275%KKLT|R6H|xwwYOHF1}3eu)DCs9nR;T z@A;lH{+WTZPVM&*#~F5_b!l{V_}fnBbi1*Cb@xNRy7<>2{7iTMKm5bin_GhKP zjXN&v+IwTqtvyRaSKVI*rw3;H`*u&<^Gx5XyWVmx>>IyNS6=CFM_2S@Tqn$r?c1$7 z&)V)=@6Er~e03x?@uhOib)^huopcbCn~%22c}87$87F z<2dC~0z5?oK8^jlecN~!z zM2rIwr7S=sSwzY-=N`xaq>qS?+7Ro^AW9?CQ_JNdo{m>Ki**(w*0EebNb2V!)>#&j zFfEwpGoLDgh?7>tM5w}K@$<=Y5f@KB`S*{D8AK!0E@FXD>pemRsRejMNDdMaC#;Bx zP`N>@-Ygd}CyPd?2_h>HYCR)VOreSp6)5g=G~>6ehFjK6f=+%QeYizV&dlNdRIbtG zI=Q;*Qaa@CXT*VNhOcU^= z!i(R#>|AjwZUvX?y5rGrC23VrFRY_H(*}}}o^Bo-vG0}XyC&jW!=nb znax6DSwr9fRV;R{{no@JWlc3_d z0|xO#xy5xS$GKRHXq<}*mXDb2KTOj=gYPi}lu9=ru}Xl}R9Zi{8MN>{jxz|Ocs`@n z^NA%ORWfCr+4=J^66iX6QS<7N0HK;PPcj-1f&My@M$GqDsmzoW{ybNz%5(iSxa+Sz zFCgskR}h*=PcR{a06SAF4Vdq*Xj8Gq!`0tP1L z<%XSGni9iKq^AnpCtI5TG1w=ph-Ngzo*KWTJzX+l?r6A;D5NTIpPtcB;AHiuHvpx1-5_qeS literal 0 HcmV?d00001 diff --git a/feeding/__init__.py b/feeding/__init__.py deleted file mode 100644 index 144cb4c..0000000 --- a/feeding/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# feeding/__init__.py -""" -下料控制模块 -包含下料流程控制和管理 -""" -from .process import FeedingProcess -from .controller import FeedingController - -__all__ = ['FeedingProcess', 'FeedingController'] diff --git a/feeding/__pycache__/__init__.cpython-39.pyc b/feeding/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 386b49b18ebf2a1603bcc1ed80f364681ea133bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 365 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 diff --git a/feeding/controller.py b/feeding/controller.py deleted file mode 100644 index d89ee3d..0000000 --- a/feeding/controller.py +++ /dev/null @@ -1,1195 +0,0 @@ - -from pickle import FALSE -from cv2.gapi import ov -from core.system_state import FeedStatus,Upper_Door_Position,SystemState,Upper_PLC_Status -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime -import logging -import queue -from hardware.upper_plc import OmronFinsPollingService -from vision.muju_cls.muju_utils import run_stable_classification_loop -from vision.camera_picture import save_camera_picture - -class FeedingController: - # 下料控制 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, relay_controller, inverter_controller, - transmitter_controller,plc_service, state): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.relay_controller = relay_controller - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.plc_service = plc_service - self.state=state - - # 线程安全的参数传递 - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - - #diff参数 - self._is_processing_diff = threading.Lock() - self._new_data_diff = threading.Event() - self._current_diff=0 - self._current_diff_area=[] - self._is_diff_save=False - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - #是否启动后的第一个模具 - self._is_first_module=True - self.init_val() - # self._setup_logging_2() - #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) - self._max_f_angle_ratio=0.7 - #完成多少,调整角度比例 ,多于0.8就没记录了(注意) - self._max_angle_radio=0.8 - - #重量大于95%,停留时间2秒,其他的1秒 - self._weight_ratio_955=0.955 - #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 - - self._mould_accept_aligned=None - self._mould_before_aligned=False - #模具开始浇筑时间 - self._time_mould_begin='' - #模具结束浇筑时间 - self._time_mould_end='' - #重量判断为空次数 - self.max_weight_none=5 - #当前为空的次数 - self.cur_weight_none=0 - - self.state._mould_need_weight=1 - - - def start_feed_thread(self): - - self.plc_service.register_data_callback(self.on_plc_update) - # self.plc_service.register_status_callback(self.on_status_change) - self.plc_service.start_polling(interval=2.0) - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - """启动系统监控""" - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - - self.diff_thread = threading.Thread( - target=self._diff_temp, - daemon=True - ) - self.diff_thread.start() - - def init_val(self): - #初始化值 - """初始化视觉回调处理器""" - self.angle_mode = "normal" - self.overflow = False - self.is_start_visual=False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=True - #未浇筑满时间,用于确定是否进入未浇筑满 - self._before_finish_time=None - #进入未浇筑满状态标志位 - self._is_before_finish=False - #是否浇筑满标志位 - self._is_finish=False - - #用于保存diff标志位 - # self._is_diff_save=False - #用于判断当前判断是否对齐(diff) - self._is_diff_unaligned=False - - #浇筑完成比例(重量) - self._is_finish_ratio=0 - - #下料阶段,用于控制下料斗的振动阶段 - self._is_feed_stage=0 - self._feed_status=FeedStatus.FNone - #振动相关参数 - self._last_arch_one_weight=0 - self._last_arch_two_weight=0 - self._last_arch_three_weight=0 - self._last_arch_four_weight=0 - self._last_arch_five_weight=0 - self._last_arch_time=0 - #是否为F块 - self._is_small_f=None - #采集数据用,下料重量=之前下的重量+最后一阶段(下-->模具车)重量差 - #记录最后一次下料斗到模具车前的重量 - self._mould_finish_weight=0 - - #记录最后一次下料斗到车初始重量 - self._inital_finish_lweight=0 - #记录视觉停止下料时的重量(计算后面加了多少) - self._last_lower_weight=0 - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._is_running=True - self._initialized = True - self.plc_data=None - - #用于下料阶段初始值 - self._first_lower_weight_one=0 - self._is_first_lower_one=True - self._first_lower_weight_two=0 - self._is_first_lower_two=True - self._first_lower_weight_three=0 - self._is_first_lower_three=True - - def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - if mould_aligned is not None: - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到对齐:{mould_aligned}") - self._mould_accept_aligned=mould_aligned - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - def diff_visual_callback(self, current_diff,current_area): - """ - 视觉模型diff回调 - """ - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing_diff.acquire(blocking=False): - print("222回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - if current_diff is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到diff:{current_diff}") - self._current_diff = current_diff - if current_area is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到area:{current_area}") - self._current_diff_area = current_area - # 通知线程有新数据可用 - self._new_data_diff.set() - finally: - # 释放处理锁 - self._is_processing_diff.release() - - def _diff_temp(self): - """ - 接受视觉回调数据 - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - _temp_diff_count=0 - _temp_area_count=0 - _temp_diff_str2='' - _temp_area_str2='' - while not self._stop_event.is_set(): - # print('-----等待diff 数据------') - # 等待新数据可用 - self._new_data_diff.wait() - # 重置事件 - self._new_data_diff.clear() - #_is_diff_save是否完成此片 - if self._is_diff_save: - # print('-----进入diff 数据------') - #完成了此片,然后是对齐状态 - if not self._is_diff_unaligned: - # 处理数据 - # print('-----进入对齐数据------') - if self._current_diff is not None and self._current_diff_area is not None: - _timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if _temp_diff_count<=10 and self._current_diff!=0: - _temp_diff_str=f"diff , {_timestamp} , {self._current_diff}\n" - _temp_diff_count+=1 - with open('weight.txt', 'a') as f: - f.write(_temp_diff_str+'\n') - # print('-----保存成功(diff 数据)------') - - if _temp_area_count<=10 and self._current_diff_area!=[]: - _temp_area_str=f"area , {_timestamp} , {str(self._current_diff_area)}\n" - _temp_area_count+=1 - with open('weight.txt', 'a') as f: - f.write(_temp_area_str+'\n') - # print('-----保存成功(area 数据)------') - if _temp_diff_count>=10 and _temp_area_count>=10: - self._is_diff_save=False - time.sleep(1) - continue - # else: - #变成了未对齐,拉起盖板后,重新计数 - # if _temp_diff_count>=10 and _temp_area_count>=10: - # _temp_diff_count=0 - # _temp_area_count=0 - # self._is_diff_save=False - # _temp_diff_str='' - # _temp_area_str='' - - self._current_diff=0 - self._current_diff_area=[] - _temp_diff_count=0 - _temp_area_count=0 - _temp_diff_str='' - _temp_area_str='' - time.sleep(1) - - def _monitor_loop(self): - """监控循环""" - while self._is_running: - try: - current_time = time.time() - # 检查下料斗破拱(只有在下料过程中才检查) - if self._is_feed_stage==1: # 下料斗--》模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_one_weight) - #_last_arch_one_weight默认为0,一开始就进入振动 - print(f'---------------第一阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed< 200) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第一阶段振动5秒(小于200KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) - self._last_arch_one_weight = _arch_weight - continue - self._last_arch_one_weight = _arch_weight - - elif self._is_feed_stage==2: #上料斗到下料斗,料多,谨慎振动 - _arch_weight = self.transmitter_controller.read_data(1) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_two_weight) - print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 100) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第二阶段振动3秒-----------------') - self.relay_controller.control_arch_upper_open_sync(3) - self._last_arch_two_weight = _arch_weight - continue - self._last_arch_two_weight = _arch_weight - elif self._is_feed_stage==3: #第二次下料斗-》模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - #刚开始不需要振动,料太多 - if self._last_arch_three_weight>0: - _weight_changed=abs(_arch_weight - self._last_arch_three_weight) - # 检查重量变化是否过慢 - print(f'---------------第三阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 100) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第三阶段振动5秒(小于100KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) - self._last_arch_three_weight = _arch_weight - continue - self._last_arch_three_weight = _arch_weight - - elif self._is_feed_stage==4: #上料斗--》下料斗 - _arch_weight = self.transmitter_controller.read_data(1) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_four_weight) - print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 200) and \ - (current_time - self._last_arch_time) > 2: - self._last_arch_time = current_time - print('---------------------第四阶段振动5秒-----------------') - self.relay_controller.control_arch_upper_open_sync(5) - self._last_arch_four_weight = _arch_weight - continue - self._last_arch_four_weight = _arch_weight - elif self._is_feed_stage==5: #下料斗->模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - if self._last_arch_five_weight>0: - _weight_changed=abs(_arch_weight - self._last_arch_five_weight) - print(f'---------------第五阶段,重量变化:{_weight_changed}------------------') - _min_arch_weight=20 - if self._is_finish_ratio= 2: - self._last_arch_time = current_time - print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg))-----------------') - self.relay_controller.control_arch_lower_open_sync(3) - self._last_arch_five_weight = _arch_weight - continue - self._last_arch_five_weight = _arch_weight - - # 更新最后读取时间 - self._last_arch_time = current_time - time.sleep(2) - except Exception as e: - print(f"监控线程错误: {e}") - - def _aligned_get_times(self,flag): - """ - 获取对齐,1为对齐,0为未对齐 - """ - _current_times=time.time() - _temp_aligned_count=0 - if flag==1: - while time.time()-_current_times<=2: - print(f'-------------{self._mould_accept_aligned}-----------------') - if self._mould_accept_aligned=='盖板对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') - time.sleep(0.2) - self._mould_accept_aligned='' - if _temp_aligned_count>=8: - return True - else: - return False - elif flag==2: - while time.time()-_current_times<=5: - - if self._mould_accept_aligned=='盖板未对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') - - time.sleep(0.2) - - self._mould_accept_aligned='' - if _temp_aligned_count>=20: - self._is_diff_unaligned=True - return True - else: - self._is_diff_unaligned=False - return False - - def _no_aligned_diff(self): - """ - diff 未对齐检测 - """ - _current_times=time.time() - _temp_aligned_count=0 - while time.time()-_current_times<=1: - # print(f'-------------{self._mould_accept_aligned}-----------------') - if self._mould_accept_aligned=='盖板未对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - # print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') - time.sleep(0.2) - if _temp_aligned_count>=3: - return True - else: - return False - - def _run_thread_loop(self): - """ - 接受视觉回调数据 - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - - def _run_feed(self): - - while True: - # print("------------已启动----------------") - if self._is_feed_start: - - # if self.plc_data==5: - #_is_finish_ratio完成 比例,根据重量过滤一下 - - if self._is_first_module and self._overflow_detected=='未堆料': - #第一次打开 ,未堆料,检测对齐 - _is_aligned=self._aligned_get_times(1) - if _is_aligned: - print('------------进入第一块111111-------------') - self._is_first_module=False - self._mould_before_aligned=True - # self.is_start_visual=True - self.run_feed_all() - elif self._is_finish and self._is_finish_ratio>=0.7: - #后续流程--》检查到未对齐,--》后又对齐+未堆料 - print('------------------进入连续块检测------------------') - if self._mould_before_aligned: - #未对齐,检测对齐 - _is_not_aligned=self._aligned_get_times(2) - if _is_not_aligned: - #标志位 - self._mould_before_aligned=False - print('------------连续盖板未对齐-------------') - else: - _is_aligned=self._aligned_get_times(1) - if _is_aligned and self._overflow_detected=='未堆料': - print('------------连续盖板已对齐-------------') - self._mould_before_aligned=True - print('-----------进入连续块111111-----------') - # self.is_start_visual=True - if self._last_lower_weight>0: - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - with open('weight.txt', 'a') as f: - f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) - self.init_val() - self.run_feed_all() - - # else: - # print("-----------上料斗未就位----------------") - # print("---------3--上料斗未就位----------------") - - time.sleep(0.2) - - def safe_control_lower_close(self,duration=3): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - # print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - #print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - #print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - - def close_lower_door_visual(self): - """关闭下料斗门""" - self.is_start_visual=False - time.sleep(0.5) - self.safe_control_lower_close() - - def _visual_close(self): - self.is_start_visual=False - self._is_finish=True - self._is_feed_stage=0 - print(f'--------进入关闭(浇筑满)-----------') - self.safe_control_lower_close(3) - print(f'--------关闭完成-----------') - # try: - # self.db_queue.put_nowait({ - # "f":self._is_small_f, - # "Status": 3 - # }) - # except queue.Full: - # print("数据库队列已满,无法添加数据") - #记录重量 - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - self._last_lower_weight=_current_weight - self.state._mould_finish_weight= self.state._mould_finish_weight+(self._inital_finish_lweight-_current_weight) - with open('weight.txt', 'a') as f: - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if self._is_small_f: - f.write(f"{self._time_mould_begin},{timestamp},F,{self.state._mould_finish_weight}\n") - else: - f.write(f"{self._time_mould_begin},{timestamp},B,{self.state._mould_finish_weight}\n") - #开启保存diff - self._is_diff_save=True - - #保存图片 - save_camera_picture() - - def run_feed_all(self): - """ - 全流程下料:包括判断模具类型 - """ - _is_f= run_stable_classification_loop() - print(f'------------已判断出模具类型: {_is_f}-------------') - if _is_f is not None: - if _is_f=='模具车1': - self._is_small_f=True - print('-------------F块模具--------------') - print('-------------F块模具--------------') - print('-------------F块模具--------------') - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.state._mould_need_weight=0.54*2416 - self.state._mould_finish_weight=0 - self.run_feed_f() - elif _is_f=='模具车2': - self._is_small_f=False - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.state._mould_need_weight=1.91*2416 - self.state._mould_finish_weight=0 - self.run_feed() - print('-------------其他模具---------------') - - if self._is_small_f is None: - print('-----------未判断出模具类型--------------') - return - - def run_feed_f(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(F块)--------------------") - # loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return - #初始下料斗重量 - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight>100: - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为下料斗未就位,跳出循环 - print('------------f下到模具车,下料斗重量异常----------------') - print('------------f下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - #视觉处理关闭,异常的话重量没有生效 - continue - self.cur_weight_none=0 - self.state._mould_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(self.state._mould_finish_weight)/self.state._mould_need_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: - #关5秒 - #大于0.7后不再检测了,直接交给视觉控制夹脚 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - time.sleep(1) - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(F): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(F): {self.state._mould_finish_weight}kg-------------') - - print(f'------------已完成-------------') - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(普通块)--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - if self._feed_status==FeedStatus.FFeed1: - self.is_start_visual=True - if self._is_first_lower_one: - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return - self._first_lower_weight_one=initial_lower_weight - self._is_first_lower_one=False - - if self._first_lower_weight_one>100: - #下料斗的料全部下完 - self._is_feed_stage=1 - - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - print("-----------下料斗重量异常(第一次下到模具车)--------------") - self.close_lower_door_visual() - return - #continue - self.cur_weight_none=0 - if current_weight<250 and current_weight>0: - self.close_lower_door_visual() - self._feed_status=FeedStatus.FFeed2 - time.sleep(1) - elif self._feed_status==FeedStatus.FFeed2: - # if self._is - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常---------") - return - self.state._mould_finish_weight=initial_lower_weight-_current_lower_weight - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(第一次): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(第一次): {self.state._mould_finish_weight}kg-------------') - - - self._is_feed_stage=0 - - while self.plc_data!=Upper_PLC_Status.PLC_ZDS_Finish: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') - time.sleep(1) - - if self.plc_data==Upper_PLC_Status.PLC_ZDS_Finish: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') - #打开上料斗出砼门,开5就,开三分之一下 - - loc_relay.control_upper_open_sync(6) - self._is_feed_stage=2 - loc_time_count=1 - upper_open_time=time.time() - - while not self._is_finish: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第一次上到下,上料斗重量异常----------------') - print('------------第一次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(5+loc_time_count) - return - continue - self.cur_weight_none=0 - _two_lower_weight=loc_mitter.read_data(2) - if _two_lower_weight is None: - _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: - #关5秒,loc_time_count多关一秒 - loc_relay.control_upper_close_sync(5+loc_time_count) - break - else: - if time.time()-upper_open_time>5: - if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.8) - loc_time_count=loc_time_count+0.8 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(6+loc_time_count) - - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-------下料斗重量异常(第二次下料到模具车)---------") - return - self._is_feed_stage=3 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - print("-------下料斗重量异常(第二次下料到模具车)---------") - self.close_lower_door_visual() - return - continue - self.cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight - if current_weight<250: - self.close_lower_door_visual() - break - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常(第二次下到模)---------") - return - self.state._mould_finish_weight+=initial_lower_weight-_current_lower_weight - print(f'------------已下料(第二次): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(第二次): {self.state._mould_finish_weight}kg-------------') - - self._is_feed_stage=0 - if self.plc_data==Upper_PLC_Status.PLC_ZDS_Finish: - #第二次上料斗向下料斗转移 - loc_relay.control_upper_open_sync(12) - loc_time_count=1 - upper_open_time=time.time() - upper_open_time_2=None - #第二次到下料斗还需要的量 - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None - self._is_feed_stage=4 - while not self._is_finish: - # print(f'------------上料斗向下料斗转移22222-------------') - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第二次上到下,上料斗重量异常----------------') - print('------------第二次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(15) - break - continue - self.cur_weight_none=0 - if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: - if upper_open_time_2 is None: - upper_open_time_2=time.time() - if current_upper_weight<400 or time.time()-upper_open_time_2>5: - loc_relay.control_arch_upper_open_async(5) - # loc_relay.control_arch_upper_open() - loc_relay.control_upper_open_sync(5) - # start_time=None - #5秒后关闭 - loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) - break - time.sleep(1) - else: - if time.time()-upper_open_time>2: - # if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(1.2) - loc_time_count=loc_time_count+1 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(15) - # time.sleep(0.4) - - #第三次下料斗转移到模具车 - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight is None: - print("-------下料斗重量异常(第三次下到模具车)---------") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #重量异常退出 - print('------------第三次下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - continue - self.cur_weight_none=0 - second_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(second_finish_weight+self.state._mould_finish_weight)/self.state._mould_need_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: - #关5秒 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - time.sleep(1) - - # _current_lower_weight=loc_mitter.read_data(2) - - - print(f'------------已完成-------------') - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - # print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected}") - - if overflow_detected == "未浇筑满" or self._is_before_finish: - if self._before_finish_time is None: - self._before_finish_time=current_time - self.safe_control_lower_close(3) - print('-----------------关闭(未浇筑满)--------------------') - # time.sleep(3) - else: - if overflow_detected=='浇筑满': - self._visual_close() - return - # print(f'--------已关闭已关闭-----------') - elif overflow_detected=="大堆料": - print(f'--------未浇筑满,大堆料-----------') - self._pulse_control('open',0.3) - time.sleep(0.3) - self._pulse_control('close',0.4) - print(f'--------比例:{self._is_finish_ratio}-----------') - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True - else: - # self._pulse_control('open',0.5) - # time.sleep(0.3) - # self._pulse_control('close',0.6) - # print(f'--------比例:{self._is_finish_ratio}-----------') - self._pulse_control('open',0.6) - time.sleep(0.3) - self._pulse_control('close',0.7) - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True - if self._is_finish_ratio<=self._max_ignore_radio: - #如果重量未达到最大忽略角度,需要跳出 - self._is_before_finish=False - return - elif overflow_detected == "浇筑满": - self._visual_close() - return - else: - self._before_finish_time=None - if self._is_finish_ratio>=self._max_angle_radio or (self._is_finish_ratio>self._max_f_angle_ratio and self._is_small_f): - if overflow_detected == "大堆料": - TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 35.0 # 12.25由25--》35 - else: - if self._is_feed_stage==1 or self._is_feed_stage==3: - #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 45.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 - else: - #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.2 # 比例系数 - KI = 0 # 积分系数 - KD = 0 # 微分系数 - # KP = 0.15 # 比例系数 - # KI = 0.008 # 积分系数 - # KD = 0.08 # 微分系数 - # if TARGET_ANGLE <= 25.0: - # KP, KI, KD = 0.18, 0.008, 0.08 # 小角度,强控制 - # elif TARGET_ANGLE <= 40.0: - # KP, KI, KD = 0.15, 0.01, 0.06 # 中角度 - # else: - # KP, KI, KD = 0.12, 0.012, 0.04 # 大角度,温和控制 - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " - f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output,TARGET_ANGLE) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output,target_angle): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 🎯 修复1: 添加强制控制机制 - - - # 基于PID输出的智能控制 - control_threshold = 2 # 从2.0减小到0.5,提高灵敏度 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - error = current_angle - target_angle - abs_error = abs(error) - - # 强制控制:如果误差超过5度,强制控制 - if abs_error > 5: - if error > 0: # 当前角度 > 目标角度,需要关门 - pulse_time=0.1 # 根据误差计算脉冲时间 - self._pulse_control("close", pulse_time) - print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") - else: # 当前角度 < 目标角度,需要开门 - pulse_time =0.1 - self._pulse_control("open", pulse_time) - print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") - return - else: - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _open_door(self, duration=0.5): - """打开门""" - self._pulse_control("open", 0.3) - - def _close_door(self, duration=0.5): - """关闭门""" - self._pulse_control("close", 1) - - def on_plc_update(self,data: int, binary: str): - #4即将振捣室5振捣室 64即将搅拌楼 66到达搅拌楼 - # print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}") - self.plc_data=data - self.state.upper_door_position=data - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - - self._is_running=False - self._is_finish=True - self.is_start_visual=False - # #关闭下料斗 - # self.safe_control_lower_close() - if self.plc_service: - self.plc_service.stop_polling() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - - if self.feed_thread.is_alive(): - self.feed_thread.join(timeout=1.0) - - if self.monitor_thread.is_alive(): - self.monitor_thread.join(timeout=1.0) - - # self.relay_controller._close_lower_5s - - def __del__(self): - """析构函数,确保线程安全关闭""" - self.shutdown() - -# 创建默认实例 -# visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -# def angle_visual_callback(current_angle, overflow_detected): -# """ -# 兼容旧版本的函数调用方式 -# 将调用转发到默认实例的angle_visual_callback方法 -# """ -# visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) \ No newline at end of file diff --git a/feeding/process copy.py b/feeding/process copy.py deleted file mode 100644 index e33dd97..0000000 --- a/feeding/process copy.py +++ /dev/null @@ -1,167 +0,0 @@ -from enum import IntEnum -from core.state import FeedStatus -from service.mould_service import MouldService -from busisness.blls import ArtifactBll -from busisness.models import ArtifactInfoModel,ArtifactInfo -import time -from datetime import datetime -from hardware.RFID.rfid_service import rfid_service -from config.settings import app_set_config - -class FeedingProcess: - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, vision_detector, - camera_controller, state): - self.relay_controller = relay_controller - self.artifact_bll = ArtifactBll() - self.mould_service = MouldService() - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.vision_detector = vision_detector - self.camera_controller = camera_controller - self.state = state - self.state._feed_status = FeedStatus.FNone - - #标志位用,是否是第一次运行 - self.is_first_flag=True - - def start_feeding(self): - loc_state=self.state - loc_state._upper_weight=self.transmitter_controller.read_data(1) - loc_state._lower_weight=self.transmitter_controller.read_data(2) - # loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - """开始生产管片""" - if loc_state._feed_status == FeedStatus.FNone: - return - elif loc_state._feed_status == FeedStatus.FCheckM: - print("---------------初始化数据------------------") - loc_state._mould_need_weight= 1200 - loc_state._feed_status = FeedStatus.FFeed - return - elif loc_state._feed_status == FeedStatus.FFeed: - print("----------------下料------------------") - loc_state.mould_vibrate_time=time.time() - loc_state._mould_frequency=app_set_config.frequencies[0] - loc_state._mould_vibrate_status=1 - #上料斗重量 - loc_state.initial_upper_weight=loc_state._upper_weight - #下料斗重量 - loc_state.initial_lower_weight=loc_state._lower_weight - self.feeding_stage(loc_state) - # if loc_state._mould_need_weight>loc_state.initial_lower_weight: - # self.transfer_material_from_upper_to_lower(loc_state,loc_state.initial_upper_weight,loc_state.initial_lower_weight) - # loc_state._feed_status = FeedStatus.FFeed1 - # else: - # loc_state._feed_status = FeedStatus.FFeed1 - return - elif loc_state._feed_status == FeedStatus.FFinished: - """完成当前批次下料""" - print("振捣完成") - print("关闭所有网络继电器") - self.relay_controller.close_all() - return - - def _start_feeding_stage(self): - """启动指定下料阶段""" - """开始分步下料""" - print("开始分步下料过程") - self.transfer_material_from_upper_to_lower() - - def transfer_material_from_upper_to_lower(self,loc_state,initial_upper_weight,initial_lower_weight): - - """target_upper_weight:转移后剩下的上料斗重量""" - # 如果低于单次,全部卸掉 - _max_lower_weight=app_set_config.max_lower_volume*loc_state.density - # if (initial_lower_weight+feed_weight>_max_lower_weight): - feed_weight=_max_lower_weight-initial_lower_weight - - target_upper_weight=initial_upper_weight-feed_weight - target_upper_weight = max(target_upper_weight, 0) - # 确保下料斗出砼门关闭,同步关5秒 - self.relay_controller.control_lower_close() - # 打开上料斗出砼门 - # self.relay_controller.control_upper_open() - #一直打开3秒 - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(3) - loc_state._upper_door_closed=False - # 等待物料流入下料斗,基于上料斗重量变化控制 - - start_time = time.time() - # timeout = 30 # 30秒超时 - - while loc_state.running: - # self.relay_controller.control_upper_open_sync() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'close') - current_upper_weight = self.transmitter_controller.read_data(1) - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - - loc_state._upper_weight=current_upper_weight - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - print(f"花费时间 {time.time() - start_time:.2f}秒") - break - # time.sleep(1) - # self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.2) - - loc_state._upper_door_closed=True - # 关闭上料斗出砼门d - self.relay_controller.control_upper_close() - - #测试用 - print("上料斗下料完成") - - def feeding_stage(self,loc_state): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始下料") - # self.relay_controller.control - first_finish_weight=0 - while True: - current_weight = loc_state._lower_weight - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - first_finish_weight=loc_state._mould_finish_weight - if current_weight<100: - #关5秒 - self.relay_controller.control_lower_close() - break - - time.sleep(1) - - #打开上料斗出砼门 - self.relay_controller.control_upper_open_sync(5) - while True: - - if loc_state._upper_weight<3000: - #关5秒 - self.relay_controller.control_upper_close() - break - loc_state.initial_lower_weight=loc_state.lower_weight - while True: - current_weight = loc_state._lower_weight - loc_state._mould_finish_weight=first_finish_weight+loc_state.initial_lower_weight-current_weight - if current_weight<100: - #关5秒 - self.relay_controller.control_lower_close() - break - - time.sleep(1) - - - - - - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - # self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'open') - self.state._upper_door_position = 'default' diff --git a/feeding/process.py b/feeding/process.py deleted file mode 100644 index 5eeff0d..0000000 --- a/feeding/process.py +++ /dev/null @@ -1,359 +0,0 @@ - -from pickle import FALSE -from cv2.gapi import ov -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime -import logging -import queue -from hardware.upper_plc import OmronFinsPollingService -from vision.muju_cls.muju_utils import run_stable_classification_loop -from vision.camera_picture import save_camera_picture - -class FeedingProcess: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, state): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.relay_controller = relay_controller - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - - # 线程安全的参数传递 - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - - #diff参数 - self._is_processing_diff = threading.Lock() - self._new_data_diff = threading.Event() - self._current_diff=0 - self._current_diff_area=[] - self._is_diff_save=False - - - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - #是否启动后的第一个模具 - self._is_first_module=True - self.init_val() - # self._setup_logging_2() - #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) - self._max_f_angle_ratio=0.7 - #完成多少,调整角度比例 ,多于0.8就没记录了(注意) - self._max_angle_radio=0.8 - - #重量大于95%,停留时间2秒,其他的1秒 - self._weight_ratio_955=0.955 - #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 - - self._mould_accept_aligned=None - self._mould_before_aligned=False - #模具开始浇筑时间 - self._time_mould_begin='' - #模具结束浇筑时间 - self._time_mould_end='' - - - def run_feed_f(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(F块)--------------------") - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - max_weight_none=5 - cur_weight_none=0 - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return - first_finish_weight=0 - self._finish_weight=first_finish_weight - self._inital_finish_lweight=initial_lower_weight - need_total_weight=0.54*2416 - if initial_lower_weight>100: - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为下料斗未就位,跳出循环 - print('------------f下到模具车,下料斗重量异常----------------') - print('------------f下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - #视觉处理关闭,异常的话重量没有生效 - continue - cur_weight_none=0 - first_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(first_finish_weight)/need_total_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: - #关5秒 - #大于0.7后不再检测了,直接交给视觉控制夹脚 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(F): {first_finish_weight}kg-------------') - print(f'------------已下料(F): {first_finish_weight}kg-------------') - - print(f'------------已完成-------------') - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(普通块)--------------------") - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - max_weight_none=5 - cur_weight_none=0 - - initial_lower_weight=loc_mitter.read_data(2) - # initial_upper_weight=loc_mitter.read_data(1) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return - first_finish_weight=0 - need_total_weight=1.91*2416 - # start_time=None - self.is_start_visual=True - if initial_lower_weight>100: - #下料斗的料全部下完 - self._is_feed_stage=1 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - print("-----------下料斗重量异常(第一次下到模具车)--------------") - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - if current_weight<250 and current_weight>0: - self.close_lower_door_visual() - break - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常---------") - return - first_finish_weight=initial_lower_weight-_current_lower_weight - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(第一次): {first_finish_weight}kg-------------') - print(f'------------已下料(第一次): {first_finish_weight}kg-------------') - self._is_feed_stage=0 - - while self.plc_data!=5: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') - time.sleep(1) - - if self.plc_data==5: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') - #打开上料斗出砼门,开5就,开三分之一下 - - loc_relay.control_upper_open_sync(6) - self._is_feed_stage=2 - loc_time_count=1 - upper_open_time=time.time() - - while not self._is_finish: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第一次上到下,上料斗重量异常----------------') - print('------------第一次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(5+loc_time_count) - return - continue - cur_weight_none=0 - _two_lower_weight=loc_mitter.read_data(2) - if _two_lower_weight is None: - _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: - #关5秒,loc_time_count多关一秒 - loc_relay.control_upper_close_sync(5+loc_time_count) - break - else: - if time.time()-upper_open_time>5: - if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.8) - loc_time_count=loc_time_count+0.8 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(6+loc_time_count) - - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-------下料斗重量异常(第二次下料到模具车)---------") - return - self._is_feed_stage=3 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - print("-------下料斗重量异常(第二次下料到模具车)---------") - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight - if current_weight<250: - self.close_lower_door_visual() - break - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常(第二次下到模)---------") - return - first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight - print(f'------------已下料(第二次): {first_finish_weight}kg-------------') - print(f'------------已下料(第二次): {first_finish_weight}kg-------------') - - self._is_feed_stage=0 - if self.plc_data==5: - #第二次上料斗向下料斗转移 - loc_relay.control_upper_open_sync(12) - loc_time_count=1 - upper_open_time=time.time() - upper_open_time_2=None - #第二次到下料斗还需要的量 - #loc_left_need_weight=need_total_weight-first_finish_weight - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None - self._is_feed_stage=4 - while not self._is_finish: - # print(f'------------上料斗向下料斗转移22222-------------') - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第二次上到下,上料斗重量异常----------------') - print('------------第二次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(15) - break - continue - cur_weight_none=0 - if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: - if upper_open_time_2 is None: - upper_open_time_2=time.time() - if current_upper_weight<400 or time.time()-upper_open_time_2>5: - loc_relay.control_arch_upper_open_async(5) - # loc_relay.control_arch_upper_open() - loc_relay.control_upper_open_sync(5) - # start_time=None - #5秒后关闭 - loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) - break - time.sleep(1) - else: - if time.time()-upper_open_time>2: - # if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(1.2) - loc_time_count=loc_time_count+1 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(15) - # time.sleep(0.4) - - #第三次下料斗转移到模具车 - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - self._finish_weight=first_finish_weight - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight is None: - print("-------下料斗重量异常(第三次下到模具车)---------") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #重量异常退出 - print('------------第三次下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - second_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(second_finish_weight+first_finish_weight)/need_total_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: - #关5秒 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - # _current_lower_weight=loc_mitter.read_data(2) - # first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight - # print(f'------------已下料: {first_finish_weight}kg-------------') - # print(f'------------已下料: {first_finish_weight}kg-------------') - - - print(f'------------已完成-------------') - - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - - -# 创建默认实例 -# visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -# def angle_visual_callback(current_angle, overflow_detected): -# """ -# 兼容旧版本的函数调用方式 -# 将调用转发到默认实例的angle_visual_callback方法 -# """ -# visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) \ No newline at end of file diff --git a/feeding/process_bak.py b/feeding/process_bak.py deleted file mode 100644 index 8690307..0000000 --- a/feeding/process_bak.py +++ /dev/null @@ -1,450 +0,0 @@ -from enum import IntEnum -from core.state import FeedStatus -from service.mould_service import MouldService -from busisness.blls import ArtifactBll -from busisness.models import ArtifactInfoModel,ArtifactInfo -import time -from datetime import datetime -from hardware.RFID.rfid_service import rfid_service -from config.settings import app_set_config - -class FeedingProcess22: - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, vision_detector, - camera_controller, state): - self.relay_controller = relay_controller - self.artifact_bll = ArtifactBll() - self.mould_service = MouldService() - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.vision_detector = vision_detector - self.camera_controller = camera_controller - self.state = state - self.state._feed_status = FeedStatus.FCheckM - - #标志位用,是否是第一次运行 - self.is_first_flag=True - - #RFID服务 - self.rfid_service=rfid_service() - # self.rfid_service.callback_signal.connect(self._rfid_callback) - self.rfid_flag_succ=False - self.rfid_data='' - # self.rfid_service.start_receiver(self._rfid_callback) - - - def _rfid_callback(self,status,data): - try: - if status==1: - #成功读取到RFID标签 - #检查标识是否符号要求 - if data: - loc_array=data.split(',') - if len(loc_array)==4: - if self.state.current_artifact is None or loc_array[0]!=self.state.current_artifact.MouldCode: - self.rfid_flag_succ=True - self.state.current_artifact={ - 'MouldCode':loc_array[0], - 'BlockNumber':loc_array[1], - 'SizeSpecification':loc_array[2], - 'BetonVolume':float(loc_array[3]) - } - print(f"RFID-生产模具车号:{loc_array[0]}") - else: - print("RFID-生产模具车号与当前模具车号一致") - else: - print("RFID标签格式错误") - print(f"成功读取到RFID标签:{data}") - else: - self.rfid_flag_succ=False - print("读取RFID标签失败") - except Exception as e: - print(f"RFID回调处理异常: {e}") - - #读取失败 - - - - def start_feeding(self): - loc_state=self.state - loc_state._upper_weight=self.transmitter_controller.read_data(1) - loc_state._lower_weight=self.transmitter_controller.read_data(2) - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - """开始生产管片""" - if loc_state._feed_status == FeedStatus.FNone: - # loc_state._feed_status = FeedStatus.FCheckM - return - elif loc_state._feed_status == FeedStatus.FCheckM: - """开始生产管片""" - print("检查盖板对齐,") - loc_state.lower_feeding_stage = 4 - - self.rfid_flag_succ=False - # if app_set_config.debug_feeding: - # loc_state._feed_status = FeedStatus.FApiCheck - if self.state.vehicle_aligned: - loc_state._feed_status = FeedStatus.FApiCheck - print("检查模车") - return - elif loc_state._feed_status == FeedStatus.FApiCheck: - print("————————————————生产已开始————————————————————") - # time.sleep(2) - #模拟数据 - module_obj=ArtifactInfoModel() - module_obj.ArtifactID="GR2B13082624F" - module_obj.MouldCode="SHR2B2-4" - module_obj.BetonVolume=1.9 - loc_modules=[module_obj] - # loc_modules =self.mould_service.get_not_pour_artifacts() - if loc_modules and loc_modules[0].ArtifactID : - # 取第一个未浇筑的管片 - #后续放入队列处理 - - loc_module = loc_modules[0] - #API - loc_module.Source = 1 - loc_module.BeginTime=datetime.now() - - # self.artifact_bll.insert_artifact_task(loc_module) - loc_state.current_artifact = loc_module - loc_state._mould_need_weight=loc_module.BetonVolume*self.state.density - print(f"已获取到未浇筑的管片:{loc_module.MouldCode}") - # self.artifact_bll.finish_artifact_task(loc_state.current_artifact.ArtifactID,1.92) - loc_state._feed_status = FeedStatus.FCheckGB - else: - #未读取到AIP接口数据. - print("未获取到未浇筑的管片") - loc_artifacting_task=self.artifact_bll.get_artifacting_task() - if loc_artifacting_task: - loc_state.current_artifact = loc_artifacting_task - loc_state._mould_need_weight=loc_artifacting_task.BetonVolume*self.state.density - loc_state._feed_status = FeedStatus.FCheckGB - else: - loc_state.current_artifact = None - return - elif loc_state._feed_status == FeedStatus.FRFID: - print("检查RFID") - - #RFID格式:模具编号,分块号,尺寸规格,方量 - while loc_state.running: - if self.rfid_flag_succ: - loc_state._feed_status = FeedStatus.FCheckGB - break - else: - time.sleep(1) - return - elif loc_state._feed_status == FeedStatus.FCheckGB: - # print("检查盖板对齐,") - # time.sleep(5) - loc_state._feed_status = FeedStatus.FUpperToLower - #计算本次生产需要的总重量 - print(f"本次生产需要的总重量:{self.state._mould_need_weight}") - return - elif loc_state._feed_status == FeedStatus.FUpperToLower: - print("上料斗向下料斗转移") - #上料斗重量 - loc_state.initial_upper_weight=loc_state._upper_weight - #下料斗重量 - loc_state.initial_lower_weight=loc_state._lower_weight - #需要的总重量 - # loc_state._mould_need_weight=loc_state.current_artifact.BetonVolume*loc_state.density - if loc_state._mould_need_weight > loc_state.initial_upper_weight + loc_state.initial_lower_weight: - # 等待上料斗重量增加(多久不够报警,可能出现F块不足的情况) - print('重量不够,需要增加') - return - - if loc_state._mould_need_weight>loc_state.initial_lower_weight: - if self.state._upper_door_position != 'over_lower': - #是否需要等待上料斗下料,如果下料斗够重量,则不需要等待 - return - else: - # 需要等待上料斗下料 - # 最后一块进行尾数控制 - # 最后一块F块,前面多要0.25,0.3,F块直接下料(先多下0.3后续) - # loc_FWeight=0.3*loc_state.density - # loc_feed_weight=loc_state.need_total_weight-loc_state.initial_lower_weight-loc_FWeight - self.transfer_material_from_upper_to_lower(loc_state,loc_state.initial_upper_weight,loc_state.initial_lower_weight) - #完成了上料斗重量转移才进入下料斗 - #测试返回 - loc_state._feed_status = FeedStatus.FFeed1 - # loc_state._feed_status = FeedStatus.FNone - else: - loc_state._feed_status = FeedStatus.FFeed1 - - # time.sleep(10) - return - elif loc_state._feed_status == FeedStatus.FFeed1: - #下料 - # self._start_feeding_stage() - self.feeding_stage_one(loc_state) - print("下料1") - return - elif loc_state._feed_status == FeedStatus.FFeed2: - #上料 - # self._start_feeding_stage() - self.feeding_stage_two(loc_state) - print("下料2") - return - elif loc_state._feed_status == FeedStatus.FFeed3: - #下料 - # self._start_feeding_stage() - self.feeding_stage_three(loc_state) - print("下料3") - return - elif loc_state._feed_status == FeedStatus.FFinished: - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - - if loc_state.overflow_detected=="浇筑满": - - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - # self.relay_controller.control_upper_close() - #更新数据库状态 - # self.artifact_bll.finish_artifact_task(loc_state.current_artifact.ArtifactID,loc_state._mould_finish_weight/loc_state.density) - print("浇筑完成") - - # loc_state._feed_status = FeedStatus.FCheckM - # if loc_state.mould_vibrate_time>0: - # while True: - # if loc_state.mould_vibrate_time-time.time()>=5*60: - # self.inverter_controller.control('stop') - # loc_state._mould_vibrate_status=0 - # break - # time.sleep(1) - print("振捣完成") - return - - def _start_feeding_stage(self): - """启动指定下料阶段""" - """开始分步下料""" - print("开始分步下料过程") - self.transfer_material_from_upper_to_lower() - - def transfer_material_from_upper_to_lower(self,loc_state,initial_upper_weight,initial_lower_weight): - - """target_upper_weight:转移后剩下的上料斗重量""" - # 如果低于单次,全部卸掉 - _max_lower_weight=app_set_config.max_lower_volume*loc_state.density - # if (initial_lower_weight+feed_weight>_max_lower_weight): - feed_weight=_max_lower_weight-initial_lower_weight - - target_upper_weight=initial_upper_weight-feed_weight - target_upper_weight = max(target_upper_weight, 0) - # 确保下料斗出砼门关闭 - self.relay_controller.control_lower_close() - # 打开上料斗出砼门 - # self.relay_controller.control_upper_open() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(3) - loc_state._upper_door_closed=False - # 等待物料流入下料斗,基于上料斗重量变化控制 - - start_time = time.time() - # timeout = 30 # 30秒超时 - - while loc_state.running: - # self.relay_controller.control_upper_open_sync() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'close') - current_upper_weight = self.transmitter_controller.read_data(1) - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - loc_state._upper_weight=current_upper_weight - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - print(f"花费时间 {time.time() - start_time:.2f}秒") - break - elif time.time() - start_time > 25: # 如果25秒后重量变化过小 - weight_change = initial_upper_weight - current_upper_weight - if weight_change < 100: # 如果重量变化小于100kg - #需要增加报警处理 - print("重量变化过小,可能存在堵塞") - time.sleep(1) - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - - loc_state._upper_door_closed=True - # 关闭上料斗出砼门 - self.relay_controller.control_upper_close() - - #测试用 - print("上料斗下料完成") - - def wait_for_vehicle_alignment(self): - """等待模具车对齐""" - print("等待模具车对齐...") - self.state.lower_feeding_stage = 4 - - import time - while self.state.lower_feeding_stage == 4 and self.state.running: - if self.state.vehicle_aligned: - print("模具车已对齐,开始下料") - self.state.lower_feeding_stage = 1 - # self.feeding_stage_one() - break - time.sleep(app_set_config.alignment_check_interval) - - def feeding_stage_one(self,loc_state): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始第一阶段下料:下料斗低速下料") - if self.is_first_flag: - # self.inverter_controller.set_frequency(app_set_config.frequencies[0]) - # self.inverter_controller.control('start') - loc_state.mould_vibrate_time=time() - loc_state._mould_frequency=app_set_config.frequencies[0] - loc_state._mould_vibrate_status=1 - - # 确保上料斗出砼门关闭 - # self.relay_controller.control(self.relay_controller.DOOR_UPPER_CLOSE, 'close') - # 打开下料斗出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - loc_cur_weight = loc_state._lower_weight - if loc_cur_weight is None: - #报警处理 - print("无法获取初始重量,取消下料") - # self.finish_feeding_process() - return - loc_state.initial_lower_weight=loc_cur_weight - self.is_first_flag=False - - - start_time = time.time() - - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = loc_state._mould_need_weight/3 - # or (time.time() - start_time) > 30 - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFeed2 - loc_state.lower_feeding_stage = 2 - self.is_first_flag=True - return - else: - time.sleep(1) - - def feeding_stage_two(self,loc_state): - """第二阶段下料:下料斗向模具车下料(中速)""" - if self.is_first_flag: - print("开始第二阶段下料:下料斗中速下料") - # self.inverter_controller.set_frequency(app_set_config.frequencies[1]) - # self.inverter_controller.control('start') - # 保持下料斗出砼门打开 - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - # 确保上料斗出砼门关闭 - self.relay_controller.control_upper_close() - - loc_state._mould_frequency=app_set_config.frequencies[1] - loc_state._mould_vibrate_status=1 - # loc_cur_weight = self.transmitter_controller.read_data(2) - # if loc_cur_weight is None: - # #报警处理 - # print("无法获取初始重量,取消下料") - # # self.finish_feeding_process() - # return - # loc_state.initial_lower_weight=loc_cur_weight - self.is_first_flag=False - - start_time = time.time() - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = (loc_state._mould_need_weight/3)*2 - # or (time.time() - start_time) > 30 - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFeed3 - loc_state.lower_feeding_stage = 3 - self.is_first_flag=True - return - else: - time.sleep(1) - - def feeding_stage_three(self,loc_state): - """第三阶段下料:下料斗向模具车下料(高速)""" - if self.is_first_flag: - print("开始第三阶段下料:下料斗高速下料") - # self.inverter_controller.set_frequency(app_set_config.frequencies[2]) - # self.inverter_controller.control('start') - loc_state._mould_frequency=app_set_config.frequencies[2] - # 保持下料斗出砼门打开 - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - # 确保上料斗出砼门关闭 - self.relay_controller.control_upper_close() - self.is_first_flag=False - - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = loc_state._mould_need_weight - - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFinished - loc_state.lower_feeding_stage = 5 - self.is_first_flag=True - return - else: - time.sleep(1) - - def finish_current_batch(self): - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - # self.inverter_controller.control('stop') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_2, 'close') - - # 增加三阶段下料轮次计数 - self.state.lower_feeding_cycle += 1 - - # 检查是否完成两轮三阶段下料 - if self.state.lower_feeding_cycle >= self.state.upper_feeding_max: - print("完成两轮三阶段下料,5吨下料任务完成") - self.finish_feeding_process() - return - - # 如果只完成一轮三阶段下料,进行第二次上料 - print("第一轮三阶段下料完成,准备第二次上料") - # 上料斗第二次向下料斗下料 - try: - self.transfer_material_from_upper_to_lower() - except Exception as e: - print(f"第二次上料失败: {e}") - print("停止下料流程") - self.finish_feeding_process() # 出现严重错误时结束整个流程 - return - - # 继续等待当前模具车对齐(不需要重新等待对齐,因为是同一辆模具车) - print("第二次上料完成,继续三阶段下料") - self.state.lower_feeding_stage = 1 # 直接进入第一阶段下料 - self.feeding_stage_one() # 开始第二轮第一阶段下料 - - def finish_feeding_process(self): - """完成整个下料流程""" - print("整个下料流程完成") - self.state.lower_feeding_stage = 0 - self.state.lower_feeding_cycle = 0 - self.state.upper_feeding_count = 0 - # self.return_upper_door_to_default() - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'open') - self.state._upper_door_position = 'default' diff --git a/hardware/relay.py b/hardware/relay.py index 1deda92..eca3895 100644 --- a/hardware/relay.py +++ b/hardware/relay.py @@ -3,7 +3,6 @@ import socket import binascii import time import threading -from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ModbusException from config.settings import app_set_config @@ -28,7 +27,7 @@ class RelayController: def __init__(self, host='192.168.250.62', port=50000): self.host = host self.port = port - self.modbus_client = ModbusTcpClient(host, port=port) + # self.modbus_client = ModbusTcpClient(host, port=port) #遥1 DO 7 左 DO8 右 角度 摇2:DO 15下 13上 12 启动振捣 14停止振捣 下料斗DO7开 D09关 # 继电器命令(原始Socket) self.relay_commands = { diff --git a/main.py b/main.py deleted file mode 100644 index 1048d54..0000000 --- a/main.py +++ /dev/null @@ -1,183 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -import threading -import time -import cv2 -import vision.visual_callback_1203 as angle_visual - - -def main(): - # 加载配置 - # 初始化系统 - # angle_visual.angle_visual_callback(2,'未堆料') - replay_controller=RelayController() - # transmitter_controller=TransmitterController(replay_controller) - # upper_weight=transmitter_controller.read_data(2) - # print(upper_weight) - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - #5-10度徘徊 - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - #5-15度徘徊 - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.4) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - time.sleep(3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.4) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.close_all() - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(4) - # while True: - # # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(2) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.1) - - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(3) - - - - # inverter_controller=InverterController(replay_controller) - # inverter_controller.control('start') - - # inverter_controller.control('stop') - - - - # return - # replay_controller.control_upper_open() - #3秒开关 - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - # while True: - # time.sleep(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system = FeedingControlSystem() - - # system.state.vehicle_aligned=True - #假设在振捣室 - # system.state._upper_door_position='over_lower' - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.initialize() - - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/main2.py b/main2.py deleted file mode 100644 index 371555e..0000000 --- a/main2.py +++ /dev/null @@ -1,133 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -import threading -import time -import cv2 -import os -from core.state import FeedStatus - - -def main(): - system = FeedingControlSystem() - system.relay_controller.close_all() - # 启动视觉控制 - # system.camera_controller.start_cameras() - # system.start_visual_control() - system.state._feed_status = FeedStatus.FCheckM - system.start_lower_feeding() - system.state.overflow_detected='未堆料' - system.initialize() - while True: - time.sleep(5) - - - # time.sleep(1) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system.vision_detector.detect_angle() - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(5) - # system.state.vehicle_aligned=True - #假设在 fertilize room - # system.state._upper_door_position='over_lower' - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(4) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # 加载配置 - # 初始化系统 - - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control_upper_open() - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(5) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # image=cv2.imread(os.path.join(app_set_config.project_root,'test.jpeg')) - # image=cv2.flip(image, 0) - # cv2.imshow('test',image) - # cv2.waitKey(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/opc/opcua_server.py b/opc/opcua_server.py index 52988b7..bb3ea46 100644 --- a/opc/opcua_server.py +++ b/opc/opcua_server.py @@ -9,14 +9,14 @@ import time import random import threading from datetime import datetime -from core.system import SystemState -from config.ini_manager import ini_manager +# from core.system import SystemState +# from config.ini_manager import ini_manager class SimpleOPCUAServer: - def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + # def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + def __init__(self, endpoint='', name="Feed_Server"): """ 初始化OPC UA服务器 - Args: endpoint: 服务器端点地址 name: 服务器名称 @@ -24,7 +24,7 @@ class SimpleOPCUAServer: self.server = Server() self.server.set_endpoint(endpoint) self.server.set_server_name(name) - self.state = state + # self.state = state # 设置服务器命名空间 self.namespace = self.server.register_namespace("Feed_Control_System") @@ -38,46 +38,118 @@ class SimpleOPCUAServer: # 运行标志 self.running = False + # 订阅和监控项 + self.subscription = None + self.monitored_items = [] + + # 记录上次值用于检测变化 + self._last_values = {} + def create_object_structure(self): """创建OPC UA对象结构""" # 创建上料斗对象 self.upper = self.objects.add_object(self.namespace, "upper") self.lower=self.objects.add_object(self.namespace, "lower") self.sys=self.objects.add_object(self.namespace, "sys") + self.mould=self.objects.add_object(self.namespace, "mould") + self.pd=self.objects.add_object(self.namespace, "pd") # 创建变量 self.create_variables() def create_variables(self): """创建OPC UA变量""" - # 上料斗重量变量 - self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", 0.0) - self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0) + # 创建变量时显式指定数据类型和初始值 + #上料斗 + self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", ua.Variant(0.0, ua.VariantType.Float)) + self.upper_is_arch = self.upper.add_variable(self.namespace, "upper_is_arch", ua.Variant(False, ua.VariantType.Boolean)) + self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Boolean)) + self.upper_volume = self.upper.add_variable(self.namespace, "upper_volume", ua.Variant(0.0, ua.VariantType.Float)) + self.upper_door_position = self.upper.add_variable(self.namespace, "upper_door_position", ua.Variant(0, ua.VariantType.Int16)) + + #下料斗 + self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", ua.Variant(0.0, ua.VariantType.Float)) + self.lower_is_arch = self.lower.add_variable(self.namespace, "lower_is_arch", ua.Variant(False, ua.VariantType.Boolean)) + + #模具车 + self.mould_finish_weight = self.mould.add_variable(self.namespace, "mould_finish_weight", ua.Variant(0.0, ua.VariantType.Float)) + self.mould_need_weight = self.mould.add_variable(self.namespace, "mould_need_weight", ua.Variant(0.0, ua.VariantType.Float)) + self.mould_frequency = self.mould.add_variable(self.namespace, "mould_frequency", ua.Variant(230, ua.VariantType.Int32)) + self.mould_vibrate_status = self.mould.add_variable(self.namespace, "mould_vibrate_status", ua.Variant(False, ua.VariantType.Boolean)) + self.feed_status = self.mould.add_variable(self.namespace, "feed_status", ua.Variant(0, ua.VariantType.Int16)) + self.pd_data=self.pd.add_variable(self.namespace, "pd_data", ua.Variant("", ua.VariantType.String)) + + # 在创建变量后立即设置可写权限(不需要等待服务器启动) + self.upper_weight.set_writable(True) + self.lower_weight.set_writable(True) + self.upper_is_arch.set_writable(True) + self.upper_door_closed.set_writable(True) + self.upper_volume.set_writable(True) + self.upper_door_position.set_writable(True) + self.lower_is_arch.set_writable(True) + self.mould_finish_weight.set_writable(True) + self.mould_need_weight.set_writable(True) + self.mould_frequency.set_writable(True) + self.mould_vibrate_status.set_writable(True) + self.feed_status.set_writable(True) + self.pd_data.set_writable(True) - # 设置变量为可写 - # self.upper_weight.set_writable() - # self.lower_weight.set_writable() + print("[变量创建] 变量创建完成,AccessLevel权限已设置") - def setup_state_listeners(self): - """设置状态监听器 - 事件驱动更新""" - if hasattr(self.state, 'state_updated'): - self.state.state_updated.connect(self.on_state_changed) - print("状态监听器已设置 - 事件驱动模式") + # 验证并打印当前的AccessLevel属性 + # try: + # al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) + # ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) + # print(f"[变量创建] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") + + # al2 = self.lower_weight.get_attribute(ua.AttributeIds.AccessLevel) + # ual2 = self.lower_weight.get_attribute(ua.AttributeIds.UserAccessLevel) + # print(f"[变量创建] lower_weight AccessLevel: {al2.Value.Value}, UserAccessLevel: {ual2.Value.Value}") + + # except Exception as e: + # print(f"[变量创建] 获取权限属性失败: {e}") - def on_state_changed(self, property_name, value): - """状态变化时的回调函数""" + def setup_variable_permissions(self): + """设置变量权限 - 在服务器启动后调用""" try: - # 根据属性名更新对应的OPC UA变量 - if property_name == "upper_weight": - self.upper_weight.set_value(value) - elif property_name == "lower_weight": - self.lower_weight.set_value(value) - - # 可以在这里添加更多状态映射 - print(f"状态更新: {property_name} = {value}") - + # 重新设置变量为可写,确保权限生效 + self.upper_weight.set_writable(True) + self.lower_weight.set_writable(True) + print("[权限设置] 变量权限已重新设置") + + # 验证权限 + try: + al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) + ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) + print(f"[权限设置] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") + except Exception as e: + print(f"[权限设置] 验证失败: {e}") + except Exception as e: - print(f"状态更新错误: {e}") + print(f"[权限设置] 设置权限失败: {e}") + print("[权限设置] 尝试强制设置...") + + + # def setup_state_listeners(self): + # """设置状态监听器 - 事件驱动更新""" + # if hasattr(self.state, 'state_updated'): + # self.state.state_updated.connect(self.on_state_changed) + # print("状态监听器已设置 - 事件驱动模式") + + # def on_state_changed(self, property_name, value): + # """状态变化时的回调函数""" + # try: + # # 根据属性名更新对应的OPC UA变量 + # if property_name == "upper_weight": + # self.upper_weight.set_value(value) + # elif property_name == "lower_weight": + # self.lower_weight.set_value(value) + + # # 可以在这里添加更多状态映射 + # print(f"状态更新: {property_name} = {value}") + + # except Exception as e: + # print(f"状态更新错误: {e}") def start(self): """启动服务器""" @@ -85,15 +157,27 @@ class SimpleOPCUAServer: self.server.start() self.running = True print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") + print("=" * 60) + + # 【关键修复】在设置监听器之前,先设置变量权限 + # 这确保 AccessLevel 属性在客户端写入前已正确设置 + # self.setup_variable_permissions() + + print("=" * 60) + + # 设置客户端写入监听器 + # self.setup_write_listeners() + + print("=" * 60) # 初始化当前值 - if self.state: - self.upper_weight.set_value(self.state._upper_weight) - self.lower_weight.set_value(self.state._lower_weight) - print("已同步初始状态值") + # if self.state: + # self.upper_weight.set_value(self.state._upper_weight) + # self.lower_weight.set_value(self.state._lower_weight) + # print("已同步初始状态值") # 设置状态监听器 - 关键步骤! - self.setup_state_listeners() + # self.setup_state_listeners() # # 只有在没有状态系统时才使用模拟线程 # if not self.state: @@ -107,36 +191,35 @@ class SimpleOPCUAServer: def stop(self): """停止服务器""" + # 移除监听器 + # self.remove_write_listeners() + self.running = False self.server.stop() print("OPC UA服务器已停止") # 断开状态监听器 - if hasattr(self.state, 'state_updated'): - try: - self.state.state_updated.disconnect(self.on_state_changed) - except: - pass + # if hasattr(self.state, 'state_updated'): + # try: + # self.state.state_updated.disconnect(self.on_state_changed) + # except: + # pass def main(): """主函数""" # 创建系统状态实例 - state = SystemState() + # state = SystemState() # 创建并启动服务器 server = SimpleOPCUAServer( - state=state, endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", name="工业自动化 OPC UA 服务器" ) try: - server.start() - - + server.start() print("服务器正在运行,按 Ctrl+C 停止...") - # 保持服务器运行 while True: time.sleep(1) diff --git a/opc/opcua_server_bak.py b/opc/opcua_server_bak.py new file mode 100644 index 0000000..52988b7 --- /dev/null +++ b/opc/opcua_server_bak.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +简单的OPC UA服务器示例 +用于工业自动化数据通信 +""" + +from opcua import Server, ua +import time +import random +import threading +from datetime import datetime +from core.system import SystemState +from config.ini_manager import ini_manager + +class SimpleOPCUAServer: + def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + """ + 初始化OPC UA服务器 + + Args: + endpoint: 服务器端点地址 + name: 服务器名称 + """ + self.server = Server() + self.server.set_endpoint(endpoint) + self.server.set_server_name(name) + self.state = state + + # 设置服务器命名空间 + self.namespace = self.server.register_namespace("Feed_Control_System") + + # 获取对象节点 + self.objects = self.server.get_objects_node() + + # 创建自定义对象 + self.create_object_structure() + + # 运行标志 + self.running = False + + def create_object_structure(self): + """创建OPC UA对象结构""" + # 创建上料斗对象 + self.upper = self.objects.add_object(self.namespace, "upper") + self.lower=self.objects.add_object(self.namespace, "lower") + self.sys=self.objects.add_object(self.namespace, "sys") + + # 创建变量 + self.create_variables() + + def create_variables(self): + """创建OPC UA变量""" + # 上料斗重量变量 + self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", 0.0) + self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0) + + # 设置变量为可写 + # self.upper_weight.set_writable() + # self.lower_weight.set_writable() + + def setup_state_listeners(self): + """设置状态监听器 - 事件驱动更新""" + if hasattr(self.state, 'state_updated'): + self.state.state_updated.connect(self.on_state_changed) + print("状态监听器已设置 - 事件驱动模式") + + def on_state_changed(self, property_name, value): + """状态变化时的回调函数""" + try: + # 根据属性名更新对应的OPC UA变量 + if property_name == "upper_weight": + self.upper_weight.set_value(value) + elif property_name == "lower_weight": + self.lower_weight.set_value(value) + + # 可以在这里添加更多状态映射 + print(f"状态更新: {property_name} = {value}") + + except Exception as e: + print(f"状态更新错误: {e}") + + def start(self): + """启动服务器""" + try: + self.server.start() + self.running = True + print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") + + # 初始化当前值 + if self.state: + self.upper_weight.set_value(self.state._upper_weight) + self.lower_weight.set_value(self.state._lower_weight) + print("已同步初始状态值") + + # 设置状态监听器 - 关键步骤! + self.setup_state_listeners() + + # # 只有在没有状态系统时才使用模拟线程 + # if not self.state: + # print("使用模拟数据模式") + # self.simulation_thread = threading.Thread(target=self.simulate_data) + # self.simulation_thread.daemon = True + # self.simulation_thread.start() + + except Exception as e: + print(f"启动服务器失败: {e}") + + def stop(self): + """停止服务器""" + self.running = False + self.server.stop() + print("OPC UA服务器已停止") + + # 断开状态监听器 + if hasattr(self.state, 'state_updated'): + try: + self.state.state_updated.disconnect(self.on_state_changed) + except: + pass + + +def main(): + """主函数""" + # 创建系统状态实例 + state = SystemState() + + # 创建并启动服务器 + server = SimpleOPCUAServer( + state=state, + endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", + name="工业自动化 OPC UA 服务器" + ) + + try: + server.start() + + + print("服务器正在运行,按 Ctrl+C 停止...") + + # 保持服务器运行 + while True: + time.sleep(1) + + except KeyboardInterrupt: + print("\n正在停止服务器...") + server.stop() + + except Exception as e: + print(f"服务器运行错误: {e}") + server.stop() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/opc/opcua_server_test.py b/opc/opcua_server_test.py deleted file mode 100644 index bb3ea46..0000000 --- a/opc/opcua_server_test.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -""" -简单的OPC UA服务器示例 -用于工业自动化数据通信 -""" - -from opcua import Server, ua -import time -import random -import threading -from datetime import datetime -# from core.system import SystemState -# from config.ini_manager import ini_manager - -class SimpleOPCUAServer: - # def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): - def __init__(self, endpoint='', name="Feed_Server"): - """ - 初始化OPC UA服务器 - Args: - endpoint: 服务器端点地址 - name: 服务器名称 - """ - self.server = Server() - self.server.set_endpoint(endpoint) - self.server.set_server_name(name) - # self.state = state - - # 设置服务器命名空间 - self.namespace = self.server.register_namespace("Feed_Control_System") - - # 获取对象节点 - self.objects = self.server.get_objects_node() - - # 创建自定义对象 - self.create_object_structure() - - # 运行标志 - self.running = False - - # 订阅和监控项 - self.subscription = None - self.monitored_items = [] - - # 记录上次值用于检测变化 - self._last_values = {} - - def create_object_structure(self): - """创建OPC UA对象结构""" - # 创建上料斗对象 - self.upper = self.objects.add_object(self.namespace, "upper") - self.lower=self.objects.add_object(self.namespace, "lower") - self.sys=self.objects.add_object(self.namespace, "sys") - self.mould=self.objects.add_object(self.namespace, "mould") - self.pd=self.objects.add_object(self.namespace, "pd") - - # 创建变量 - self.create_variables() - - def create_variables(self): - """创建OPC UA变量""" - # 创建变量时显式指定数据类型和初始值 - #上料斗 - self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", ua.Variant(0.0, ua.VariantType.Float)) - self.upper_is_arch = self.upper.add_variable(self.namespace, "upper_is_arch", ua.Variant(False, ua.VariantType.Boolean)) - self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Boolean)) - self.upper_volume = self.upper.add_variable(self.namespace, "upper_volume", ua.Variant(0.0, ua.VariantType.Float)) - self.upper_door_position = self.upper.add_variable(self.namespace, "upper_door_position", ua.Variant(0, ua.VariantType.Int16)) - - #下料斗 - self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", ua.Variant(0.0, ua.VariantType.Float)) - self.lower_is_arch = self.lower.add_variable(self.namespace, "lower_is_arch", ua.Variant(False, ua.VariantType.Boolean)) - - #模具车 - self.mould_finish_weight = self.mould.add_variable(self.namespace, "mould_finish_weight", ua.Variant(0.0, ua.VariantType.Float)) - self.mould_need_weight = self.mould.add_variable(self.namespace, "mould_need_weight", ua.Variant(0.0, ua.VariantType.Float)) - self.mould_frequency = self.mould.add_variable(self.namespace, "mould_frequency", ua.Variant(230, ua.VariantType.Int32)) - self.mould_vibrate_status = self.mould.add_variable(self.namespace, "mould_vibrate_status", ua.Variant(False, ua.VariantType.Boolean)) - self.feed_status = self.mould.add_variable(self.namespace, "feed_status", ua.Variant(0, ua.VariantType.Int16)) - self.pd_data=self.pd.add_variable(self.namespace, "pd_data", ua.Variant("", ua.VariantType.String)) - - # 在创建变量后立即设置可写权限(不需要等待服务器启动) - self.upper_weight.set_writable(True) - self.lower_weight.set_writable(True) - self.upper_is_arch.set_writable(True) - self.upper_door_closed.set_writable(True) - self.upper_volume.set_writable(True) - self.upper_door_position.set_writable(True) - self.lower_is_arch.set_writable(True) - self.mould_finish_weight.set_writable(True) - self.mould_need_weight.set_writable(True) - self.mould_frequency.set_writable(True) - self.mould_vibrate_status.set_writable(True) - self.feed_status.set_writable(True) - self.pd_data.set_writable(True) - - print("[变量创建] 变量创建完成,AccessLevel权限已设置") - - # 验证并打印当前的AccessLevel属性 - # try: - # al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) - # ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - # print(f"[变量创建] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") - - # al2 = self.lower_weight.get_attribute(ua.AttributeIds.AccessLevel) - # ual2 = self.lower_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - # print(f"[变量创建] lower_weight AccessLevel: {al2.Value.Value}, UserAccessLevel: {ual2.Value.Value}") - - # except Exception as e: - # print(f"[变量创建] 获取权限属性失败: {e}") - - def setup_variable_permissions(self): - """设置变量权限 - 在服务器启动后调用""" - try: - # 重新设置变量为可写,确保权限生效 - self.upper_weight.set_writable(True) - self.lower_weight.set_writable(True) - print("[权限设置] 变量权限已重新设置") - - # 验证权限 - try: - al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) - ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - print(f"[权限设置] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") - except Exception as e: - print(f"[权限设置] 验证失败: {e}") - - except Exception as e: - print(f"[权限设置] 设置权限失败: {e}") - print("[权限设置] 尝试强制设置...") - - - # def setup_state_listeners(self): - # """设置状态监听器 - 事件驱动更新""" - # if hasattr(self.state, 'state_updated'): - # self.state.state_updated.connect(self.on_state_changed) - # print("状态监听器已设置 - 事件驱动模式") - - # def on_state_changed(self, property_name, value): - # """状态变化时的回调函数""" - # try: - # # 根据属性名更新对应的OPC UA变量 - # if property_name == "upper_weight": - # self.upper_weight.set_value(value) - # elif property_name == "lower_weight": - # self.lower_weight.set_value(value) - - # # 可以在这里添加更多状态映射 - # print(f"状态更新: {property_name} = {value}") - - # except Exception as e: - # print(f"状态更新错误: {e}") - - def start(self): - """启动服务器""" - try: - self.server.start() - self.running = True - print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") - print("=" * 60) - - # 【关键修复】在设置监听器之前,先设置变量权限 - # 这确保 AccessLevel 属性在客户端写入前已正确设置 - # self.setup_variable_permissions() - - print("=" * 60) - - # 设置客户端写入监听器 - # self.setup_write_listeners() - - print("=" * 60) - - # 初始化当前值 - # if self.state: - # self.upper_weight.set_value(self.state._upper_weight) - # self.lower_weight.set_value(self.state._lower_weight) - # print("已同步初始状态值") - - # 设置状态监听器 - 关键步骤! - # self.setup_state_listeners() - - # # 只有在没有状态系统时才使用模拟线程 - # if not self.state: - # print("使用模拟数据模式") - # self.simulation_thread = threading.Thread(target=self.simulate_data) - # self.simulation_thread.daemon = True - # self.simulation_thread.start() - - except Exception as e: - print(f"启动服务器失败: {e}") - - def stop(self): - """停止服务器""" - # 移除监听器 - # self.remove_write_listeners() - - self.running = False - self.server.stop() - print("OPC UA服务器已停止") - - # 断开状态监听器 - # if hasattr(self.state, 'state_updated'): - # try: - # self.state.state_updated.disconnect(self.on_state_changed) - # except: - # pass - - -def main(): - """主函数""" - # 创建系统状态实例 - # state = SystemState() - - # 创建并启动服务器 - server = SimpleOPCUAServer( - endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", - name="工业自动化 OPC UA 服务器" - ) - - try: - server.start() - print("服务器正在运行,按 Ctrl+C 停止...") - # 保持服务器运行 - while True: - time.sleep(1) - - except KeyboardInterrupt: - print("\n正在停止服务器...") - server.stop() - - except Exception as e: - print(f"服务器运行错误: {e}") - server.stop() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index de26e5b..0000000 --- a/test.py +++ /dev/null @@ -1,32 +0,0 @@ -import socket - -# 设备信息 -IP = "192.168.250.63" -PORT = 502 -TIMEOUT = 5 # 超时时间(秒) - -# 创建TCP socket -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.settimeout(TIMEOUT) # 设置超时,避免一直阻塞 - # 连接设备 - s.connect((IP, PORT)) - print(f"✅ 已通过TCP连接到 {IP}:{PORT}") - - # 尝试接收数据(不发送任何请求,纯等待) - print("等待设备发送数据...(若5秒内无响应则超时)") - data = s.recv(1024) # 最多接收1024字节 - - if data: - # 打印收到的原始数据(16进制和字节列表) - # print(f"收到数据(16进制):{data.hex()}") - print(f"收到数据(字节列表):{list(data)}") - else: - print("❌ 未收到任何数据(设备未主动发送)") - - except ConnectionRefusedError: - print(f"❌ 连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)") - except socket.timeout: - print(f"❌ 超时:{TIMEOUT}秒内未收到设备数据(设备未主动发送)") - except Exception as e: - print(f"❌ 发生错误:{str(e)}") \ No newline at end of file diff --git a/test_angle.py b/test_angle.py deleted file mode 100644 index 60f4d6b..0000000 --- a/test_angle.py +++ /dev/null @@ -1,193 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -import threading -import time -import cv2 -import os -import sys -import io - - -class OutputRedirector(io.StringIO): - """ - 自定义输出重定向器,用于处理所有线程的输出 - 确保输入提示能在其他线程输出后重新显示,且保留用户当前输入 - """ - def __init__(self, original_stdout): - super().__init__() - self.original_stdout = original_stdout - self.input_prompt = "请输入新值:" - self.input_thread_active = False - self.current_input = "" # 跟踪用户当前输入 - self.lock = threading.Lock() - - def write(self, text): - with self.lock: - # 写入原始输出 - self.original_stdout.write(text) - self.original_stdout.flush() - - # 如果输入线程活跃,并且输出是换行符,重新显示输入提示和当前输入 - if self.input_thread_active and '\n' in text: - # 清除当前行并重新显示输入提示和用户当前输入 - self.original_stdout.write("\r" + " " * 100 + "\r") - self.original_stdout.write(f"{self.input_prompt}{self.current_input}") - self.original_stdout.flush() - - def flush(self): - with self.lock: - self.original_stdout.flush() - - -def main(): - # 加载配置 - # 初始化系统 - - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control_upper_open() - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(5) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # image=cv2.imread(os.path.join(app_set_config.project_root,'test.jpeg')) - # image=cv2.flip(image, 0) - # cv2.imshow('test',image) - # cv2.waitKey(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - system = FeedingControlSystem() - # system.vision_detector.detect_angle() - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(5) - - # 初始设置 - system.state.vehicle_aligned=True - #假设在 fertilize room - system.state._upper_door_position='over_lower' - - # 创建输出重定向器 - original_stdout = sys.stdout - output_redirector = OutputRedirector(original_stdout) - sys.stdout = output_redirector - - # 创建输入线程函数 - def input_thread(): - import sys - import threading - - # 初始提示只显示一次 - initial_prompt = """ - 输入线程启动提示 - -------------------------- - 可以随时输入新值来更新系统状态 - 格式:需求重量,完成重量,溢出状态 - 例如:500,300,大堆料 或 500,300,小堆料 或 500,300,未堆料 - 输入'q'退出程序 - -------------------------- - """ - print(initial_prompt) - - # 标记输入线程为活跃状态 - output_redirector.input_thread_active = True - - while True: - try: - import termios - import tty - - # 获取当前终端设置 - old_settings = termios.tcgetattr(sys.stdin) - try: - tty.setcbreak(sys.stdin.fileno()) # 设置为cbreak模式,允许逐字符读取 - - # 显示初始提示和当前输入 - sys.stdout.write("\r" + " " * 100 + "\r") - sys.stdout.write(f"{output_redirector.input_prompt}{output_redirector.current_input}") - sys.stdout.flush() - - while True: - char = sys.stdin.read(1) # 读取一个字符 - - if char == '\x03': # Ctrl+C - raise KeyboardInterrupt - elif char in ['\r', '\n']: # 回车键 - sys.stdout.write("\n") - sys.stdout.flush() - break - elif char == '\x7f': # 退格键 - if len(output_redirector.current_input) > 0: - output_redirector.current_input = output_redirector.current_input[:-1] - # 从显示中删除最后一个字符 - sys.stdout.write("\b \b") - sys.stdout.flush() - else: - output_redirector.current_input += char - sys.stdout.write(char) - sys.stdout.flush() - finally: - # 恢复终端设置 - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) - - # 获取最终输入并重置当前输入 - user_input = output_redirector.current_input - output_redirector.current_input = "" # 重置为下一次输入准备 - - if user_input.lower() == 'q': - print("\n收到退出信号,正在关闭系统...") - output_redirector.input_thread_active = False - system.stop() - break - - # 分割输入值,处理不同参数数量 - input_parts = [part.strip() for part in user_input.split(',')] - if len(input_parts) >= 2: - # 更新基本参数 - system.state._mould_need_weight = float(input_parts[0]) - system.state._mould_finish_weight = float(input_parts[1]) - - if system.state._mould_finish_weight>400: - system.state.overflow_detected='大堆料' - - - # 输出更新结果,使用换行符分隔 - update_msg = f"\n已更新:\n 需求重量 = {system.state._mould_need_weight} kg\n 完成重量 = {system.state._mould_finish_weight} kg" - print(f"溢出状态 = {system.state.overflow_detected}") - print(update_msg) - else: - print("\n输入格式错误:至少需要输入需求重量和完成重量") - - except ValueError as e: - print(f"\n输入格式错误,请重新输入。错误信息:{e}") - except Exception as e: - print(f"\n发生错误:{e}") - output_redirector.input_thread_active = False - break - - # 启动输入线程 - input_thread = threading.Thread(target=input_thread, daemon=True) - input_thread.start() - system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # 启动视觉控制 - system.camera_controller.start_cameras() - system.start_visual_control() - - # 主线程保持运行 - while True: - # print(f'当前重量22:{system.state._mould_finish_weight:.2f}kg, 目标重量:{system.state._mould_need_weight:.2f}kg') - time.sleep(1) - - -if __name__ == "__main__": - main() diff --git a/test_dal_debug.py b/test_dal_debug.py deleted file mode 100644 index 0127847..0000000 --- a/test_dal_debug.py +++ /dev/null @@ -1,31 +0,0 @@ -from busisness.dals import ArtifactDal - -# 测试 exists_by_module_code 方法 -dal = ArtifactDal() - -# 测试1: 不存在的模具编号 -print("=== 测试1: 不存在的模具编号 ===") -result = dal.exists_by_module_code("不存在的模具编号") -print(f"结果: {result}") -print() - -# 测试2: 存在的模具编号(如果有数据的话) -print("=== 测试2: 存在的模具编号 ===") -result = dal.exists_by_module_code("TEST001") -print(f"结果: {result}") -print() - -# 测试3: 直接查询数据库验证 -print("=== 测试3: 直接SQL查询验证 ===") -db_dao = dal.db_dao -sql = "SELECT count(1) as cnt FROM ArtifactTask WHERE MouldCode = ?" -results = db_dao.execute_read(sql, ("不存在的模具编号",)) -print(f"查询结果类型: {type(results)}") -print(f"查询结果: {results}") -rows = list(results) -print(f"rows: {rows}") -if rows: - print(f"rows[0]: {rows[0]}") - print(f"rows[0][0]: {rows[0][0]}") -else: - print("rows为空列表") \ No newline at end of file diff --git a/test_door_close_startup.py b/test_door_close_startup.py deleted file mode 100644 index 7d90343..0000000 --- a/test_door_close_startup.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -测试脚本:模拟程序启动时门关闭的情况 -验证初始角度很小(门关闭)时的控制逻辑行为 -""" - -import time -from vision.visual_callback_1203 import VisualCallback -from datetime import datetime - -# 修改VisualCallback类,添加调试信息 -original_pulse_control = VisualCallback._pulse_control - -# 重写_pulse_control方法,添加详细调试信息 -def debug_pulse_control(self, action, duration): - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} [DEBUG] 准备执行 {action} 脉冲,持续 {duration:.2f} 秒") - result = original_pulse_control(self, action, duration) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} [DEBUG] {action} 脉冲执行完成") - return result - -# 替换原始方法 -VisualCallback._pulse_control = debug_pulse_control - -def test_door_close_startup(): - """测试程序启动时门关闭的情况""" - print("=== 测试:程序启动时门关闭的情况 ===") - - # 创建VisualCallback实例(程序启动) - callback = VisualCallback() - - # 打印初始属性 - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始angle_mode: {callback.angle_mode}") - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始overflow: {callback.overflow}") - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始_last_overflow_state: {callback._last_overflow_state}") - - # 模拟初始状态:门关闭(角度0°),无堆料 - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:门关闭(角度0°),无堆料") - callback.angle_visual_callback(0, "未堆料") - - # 等待一段时间,观察控制行为 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 发送角度1°,无堆料 - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:角度1°,无堆料") - callback.angle_visual_callback(1, "未堆料") - - # 等待一段时间 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 发送角度6°,无堆料(超过MIN_ANGLE) - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:角度6°,无堆料") - callback.angle_visual_callback(6, "未堆料") - - # 等待一段时间 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 测试关闭机制 - callback.shutdown() - print("\n=== 测试完成 ===") - -if __name__ == "__main__": - test_door_close_startup() diff --git a/test_overflow_state_change.py b/test_overflow_state_change.py deleted file mode 100644 index e4fe7cc..0000000 --- a/test_overflow_state_change.py +++ /dev/null @@ -1,59 +0,0 @@ -import time -from vision.visual_callback_1203 import VisualCallback - -# 测试堆料状态变化处理 -def test_overflow_state_change(): - print("=== 测试堆料状态变化处理 ===") - - # 获取单例实例 - callback = VisualCallback() - - # VisualCallback初始化时会自动启动线程 - time.sleep(1) # 等待线程初始化 - - try: - # 1. 初始状态:非堆料,应该立即处理 - print("\n1. 初始状态 - 非堆料") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 2. 变为堆料状态,应该立即处理(不受间隔限制) - print("\n2. 变为堆料状态") - callback.angle_visual_callback(30, "小堆料") - time.sleep(0.5) - - # 3. 保持堆料状态,应该立即处理(堆料优先) - print("\n3. 保持堆料状态") - callback.angle_visual_callback(30, "小堆料") - time.sleep(0.5) - - # 4. 从堆料变为非堆料,应该立即处理(状态变化) - print("\n4. 从堆料变为非堆料(状态变化)") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 5. 再次非堆料,应该受2秒间隔限制 - print("\n5. 再次非堆料(无状态变化)") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 6. 等待2秒后再次非堆料,应该处理 - print("\n6. 等待2秒后再次非堆料") - time.sleep(2) - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 7. 从非堆料变为大堆料,应该立即处理 - print("\n7. 从非堆料变为大堆料") - callback.angle_visual_callback(30, "大堆料") - time.sleep(0.5) - - finally: - # 关闭线程 - callback.shutdown() - time.sleep(1) - - print("\n=== 测试完成 ===") - -if __name__ == "__main__": - test_overflow_state_change() diff --git a/test_visiton.zip b/test_visiton.zip deleted file mode 100644 index 27239c8eef2c9c5539c6c3aeb4940f5c0e9097da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6952 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 diff --git a/test_visiton/config/settings.py b/test_visiton/config/settings.py deleted file mode 100644 index d5006ad..0000000 --- a/test_visiton/config/settings.py +++ /dev/null @@ -1,94 +0,0 @@ -# config/settings.py -import os - -class Settings: - def __init__(self): - # 项目根目录 - self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - self.test_need_weight=2000 - # 网络继电器配置 - self.relay_host = '192.168.250.62' - self.relay_port = 50000 - - self.debug_feeding=False - #调试模式上,网络继点器禁用,模型推理启用 - self.debug_mode=False - - # 摄像头配置 - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - - self.camera_configs = { - # 'cam1': { - # 'type': 'ip', - # 'ip': '192.168.250.60', - # 'port': 554, - # 'username': 'admin', - # 'password': 'XJ123456', - # 'channel': 1 - # }, - 'cam2': { - 'type': 'ip', - 'ip': '192.168.250.61', - 'port': 554, - 'username': 'admin', - 'password': 'XJ123456', - 'channel': 1 - } - } - - # 下料控制参数 - self.min_required_weight = 500 # 模具车最小需要重量(kg) - self.target_vehicle_weight = 5000 # 目标模具车重量(kg) - self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg) - self.single_batch_weight = 2500 # 单次下料重量(kg) - - - # 角度控制参数 - self.target_angle = 30.0 # 目标角度 - self.min_angle = 10.0 # 最小角度 - self.max_angle = 80.0 # 最大角度 - self.angle_threshold = 50.0 # 角度阈值 - self.angle_tolerance = 5.0 # 角度容差 - - # 变频器配置 - self.inverter_max_frequency = 400.0 # 频率最大值 - self.frequencies = [220.0, 230.0, 240.0] # 下料阶段频率(Hz) - - # 模型路径配置 - self.models_dir = os.path.join(self.project_root, 'vision') - self.angle_model_path = os.path.join(self.models_dir, 'obb_angle_model', 'obb.rknn') - self.overflow_model_path = os.path.join(self.models_dir,'overflow_model', 'yiliao_cls.rknn') - # self.alignment_model_path = os.path.join(self.models_dir, 'align_model', 'yolov11_cls_640v6.rknn') - - # ROI路径配置 - self.roi_file_path = os.path.join(self.models_dir, 'overflow_model', 'roi_coordinates', '1_rois.txt') - - # 系统控制参数 - self.visual_check_interval = 1.0 # 视觉检查间隔(秒) - self.alignment_check_interval = 0.5 # 对齐检查间隔(秒) - self.max_error_count = 3 # 最大错误计数 - self.lower_feeding_interval = 0.1 # 下料轮询间隔(秒) - - # RFID配置 - self.rfid_host = '192.168.1.190' - self.rfid_port = 6000 - - #是否在线生产 - self.is_online_control = True # 是否API在线 - #最后一块进行尾数控制 - # self.block_numbers=['B1','B2','B3','L1','L2','F'] - #需核实上下位漏斗容量 - self.max_upper_volume = 2.4 # 上料斗容量(方) - #下料到下料斗最大下到多少,并非最大容量 - self.max_lower_volume = 2.2 # 下料斗容量(方) - - #led - self.led_interval = 2 # LED闪烁间隔(秒) - -app_set_config = Settings() - diff --git a/test_visiton/hardware/relay.py b/test_visiton/hardware/relay.py deleted file mode 100644 index fce64b6..0000000 --- a/test_visiton/hardware/relay.py +++ /dev/null @@ -1,219 +0,0 @@ -# hardware/relay.py -import socket -import binascii -import time -import threading -from pymodbus.client import ModbusTcpClient -from pymodbus.exceptions import ModbusException -from config.settings import app_set_config - - -class RelayController: - # 继电器映射 - RING = 'ring' # DO1 - 响铃 - UPPER_TO_JBL = 'upper_to_jbl' # DO2 - 上料斗到搅拌楼 - UPPER_TO_ZD = 'upper_to_zd' # DO3 - 上料斗到振捣室 - # DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 - DOOR_LOWER_OPEN = 'door_lower_open' # DO1 - 下料斗出砼门开角度 - DOOR_LOWER_CLOSE = 'door_lower_close' # DO2 - 下料斗出砼门关角度(角度在7.5以下可关闭信号) - DOOR_UPPER_OPEN = 'door_upper_open' # DO3 - 上料斗开 - DOOR_UPPER_CLOSE = 'door_upper_close' # DO4 - 上料斗关 - BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗震动 - BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗震动 - DIRECT_LOWER_FRONT = 'direct_lower_front' # DO5 - 下料斗前 - DIRECT_LOWER_BEHIND = 'direct_lower_behind' # DO6 - 下料斗后 - DIRECT_LOWER_TOP = 'direct_lower_top' # DO7 - 下料斗上 - DIRECT_LOWER_BELOW = 'direct_lower_below' # DO8 - 下料斗下 - - def __init__(self, host='192.168.250.62', port=50000): - self.host = host - self.port = port - self.modbus_client = ModbusTcpClient(host, port=port) -#遥1 DO 7 左 DO8 右 角度 摇2:DO 15下 13上 12 往后 14往前 下料斗DO7开 D09关 - # 继电器命令(原始Socket) - self.relay_commands = { - self.RING: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, - self.UPPER_TO_JBL: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'}, - self.UPPER_TO_ZD: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'}, - self.DOOR_LOWER_OPEN: {'open': '00000000000601050006FF00', 'close': '000000000006010500060000'}, - self.DOOR_LOWER_CLOSE: {'open': '00000000000601050008FF00', 'close':'000000000006010500080000'}, - self.DOOR_UPPER_OPEN: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'}, - self.DOOR_UPPER_CLOSE: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'}, - self.BREAK_ARCH_UPPER: {'open': '0000000000060105000AFF00', 'close': '0000000000060105000A0000'}, - self.BREAK_ARCH_LOWER: {'open': '00000000000601050005FF00', 'close': '000000000006010500050000'}, - self.DIRECT_LOWER_FRONT: {'open': '0000000000060105000DFF00', 'close': '0000000000060105000D0000'}, - self.DIRECT_LOWER_BEHIND: {'open': '0000000000060105000BFF00', 'close': '0000000000060105000B0000'}, - self.DIRECT_LOWER_TOP: {'open': '0000000000060105000CFF00', 'close': '0000000000060105000C0000'}, - self.DIRECT_LOWER_BELOW: {'open': '0000000000060105000EFF00', 'close': '0000000000060105000E0000'} - } - - # 读取状态命令 - self.read_status_command = '000000000006010100000008' - - # 设备位映射 - self.device_bit_map = { - self.RING: 0, - self.UPPER_TO_JBL: 1, - self.UPPER_TO_ZD: 2, - self.BREAK_ARCH_UPPER: 3, - self.BREAK_ARCH_LOWER: 4 - } - - def send_command(self, command_hex): - """发送原始Socket命令""" - if app_set_config.debug_mode: - return None - - try: - byte_data = binascii.unhexlify(command_hex) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((self.host, self.port)) - sock.send(byte_data) - response = sock.recv(1024) - print(f"收到继电器响应: {binascii.hexlify(response)}") - return response - except Exception as e: - print(f"继电器通信错误: {e}") - return None - - def get_status(self): - """获取继电器状态""" - response = self.send_command(self.read_status_command) - status_dict = {} - - if response and len(response) >= 10: - status_byte = response[9] - status_bin = f"{status_byte:08b}"[::-1] - for key, bit_index in self.device_bit_map.items(): - status_dict[key] = status_bin[bit_index] == '1' - else: - print("读取继电器状态失败") - - return status_dict - - def control(self, device, action): - """控制继电器""" - if device in self.relay_commands and action in self.relay_commands[device]: - print(f"发送控制继电器命令 {device} {action}") - self.send_command(self.relay_commands[device][action]) - else: - print(f"无效设备或动作: {device}, {action}") - - def control_upper_close(self): - """控制上料斗关""" - # 关闭上料斗出砼门 - self.control(self.DOOR_UPPER_OPEN, 'close') - self.control(self.DOOR_UPPER_CLOSE, 'open') - # 异步5秒后关闭 - threading.Thread(target=self._close_upper_s, daemon=True,name="close_upper_s").start() - - def control_lower_close(self): - """控制下料斗关""" - # 关闭下料斗出砼门 - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'open') - time.sleep(5) - self.control(self.DOOR_LOWER_CLOSE, 'close') - # 异步5秒后关闭 - # threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start() - - def control_upper_open_sync(self): - self.control(self.DOOR_UPPER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'open') - - def control_upper_close_sync(self): - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'open') - - def control_upper_open(self): - #关闭信号才能生效 - self.control(self.DOOR_UPPER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - #保持8秒 - time.sleep(8) - #8秒后再开5秒 - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - - def control_ring_open(self): - """控制下料斗关""" - # 关闭下料斗出砼门 - self.control(self.RING, 'open') - # 异步5秒后关闭 - threading.Thread(target=self._close_ring, daemon=True,name="close_ring").start() - - def _close_upper_s(self): - time.sleep(16) - self.control(self.DOOR_UPPER_CLOSE, 'close') - print("上料斗关闭完成") - - def _close_lower_5s(self): - time.sleep(6) - self.control(self.DOOR_LOWER_CLOSE, 'close') - - def _close_ring(self): - time.sleep(3) - self.control(self.RING, 'close') - - def close_all(self): - """关闭所有继电器""" - self.control(self.UPPER_TO_JBL, 'close') - self.control(self.UPPER_TO_ZD, 'close') - self.control(self.BREAK_ARCH_UPPER, 'close') - self.control(self.BREAK_ARCH_LOWER, 'close') - self.control(self.RING, 'close') - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'close') - self.control(self.DOOR_UPPER_CLOSE, 'close') diff --git a/test_visiton/vision/visual_callback.py b/test_visiton/vision/visual_callback.py deleted file mode 100644 index 7a708b2..0000000 --- a/test_visiton/vision/visual_callback.py +++ /dev/null @@ -1,222 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 内部方法,实际处理视觉回调逻辑 - 在异步线程中执行 - """ - try: - # print('current_angle:', current_angle, 'overflow_detected:', overflow_detected) - # return - # 检测溢出状态 - self.overflow = overflow_detected in ["大堆料", "小堆料"] - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"当前角度: {current_angle:.2f}°") - - if overflow_detected != "浇筑满": - # 状态机控制逻辑 - if self.angle_mode == "normal": - # 正常模式大于app_set_config.angle_threshold=60度 - if self.overflow: - self.angle_mode = "reducing" - else: - # 保持正常开门 - print(f'当前重量:{self.mould_finish_weight:.2f}kg, 目标重量:{self.mould_need_weight:.2f}kg') - if self.mould_need_weight > 0: - if self.mould_finish_weight / self.mould_need_weight >= 0.8: - print(f"完成重量占比{self.mould_finish_weight/self.mould_need_weight:.2f},半开出砼门") - # 半开出砼门 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.3) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.32) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - elif self.angle_mode == "reducing": - # 角度减小模式 - if self.overflow: - if current_angle <= app_set_config.target_angle: - # 角度已达到目标范围,仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - if current_angle <= app_set_config.min_angle: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.1) - self.angle_mode = "maintaining" - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.angle_mode = "normal" - - elif self.angle_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not self.overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.angle_mode = "normal" - else: - # 继续维持角度控制 - print("进入维持模式") - # 关门时间 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.2) - # 开门时间 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.25) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - - elif self.angle_mode == "recovery": - # 恢复模式 - 逐步打开门 - if self.overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.angle_mode = "normal" - else: - # 浇筑满,关闭下料门 - self.relay_controller.control_lower_close() - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/test_visual_callback.py b/test_visual_callback.py deleted file mode 100644 index f15dbf2..0000000 --- a/test_visual_callback.py +++ /dev/null @@ -1,56 +0,0 @@ -import time -import threading -from vision.visual_callback import VisualCallback, angle_visual_callback - -# 创建VisualCallback实例 -visual_callback = VisualCallback() - -# 模拟safe_control_lower_close执行 -def simulate_safe_close(): - - - while True: - time.sleep(1) - """模拟safe_control_lower_close执行""" - print("\n=== 开始模拟safe_control_lower_close执行 ===") - - # 1. 首先,发送一些视觉回调数据,观察正常情况下的行为 - print("\n1. 正常运行阶段:") - - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - # 2. 模拟safe_control_lower_close开始执行 - print("\n2. 模拟safe_control_lower_close开始执行:") - visual_callback._is_safe_closing = True - - # 3. 再次发送视觉回调数据,观察是否会跳过relay操作 - print("\n3. safe_control_lower_close执行中:") - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - # 4. 模拟safe_control_lower_close执行完毕 - print("\n4. 模拟safe_control_lower_close执行完毕:") - visual_callback._is_safe_closing = False - - # 5. 再次发送视觉回调数据,观察是否恢复正常 - print("\n5. 恢复正常运行:") - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - print("\n=== 测试结束 ===") - visual_callback.shutdown() - -# 启动测试 -try: - simulate_safe_close() -except KeyboardInterrupt: - print("\n\n接收到Ctrl+C,正在停止服务...") -finally: - # 确保服务正确停止 - visual_callback.shutdown() - visual_callback.relay_controller.close_all() - print("服务已安全停止") diff --git a/test_weight.py b/test_weight.py deleted file mode 100644 index 9765dd9..0000000 --- a/test_weight.py +++ /dev/null @@ -1,26 +0,0 @@ -# main.py -import time -from datetime import datetime -from hardware import relay -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time - - -def main(): - relay_c = RelayController() - transmitter_c = TransmitterController(relay_c) - while True: - # data = '上料斗:' + str(int(transmitter_c.read_data(1))) + ',' +"下料斗:" + str(int(transmitter_c.read_data(2))) - data = str(int(transmitter_c.read_data(2))) - timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] - output = f"[{timestamp}] {data}" - print(output) - # 保存到文件 - with open('output.log', 'a') as f: - f.write(output + '\n') - time.sleep(0.5) - - -if __name__ == "__main__": - main() diff --git a/tests/Investor485test.py b/tests/Investor485test.py deleted file mode 100644 index 2de5d74..0000000 --- a/tests/Investor485test.py +++ /dev/null @@ -1,259 +0,0 @@ -import serial -import time -import struct - - -class InovanceMD520: - def __init__(self, port='COM4', baudrate=9600, timeout=1): - """ - 初始化汇川MD520变频器通信 - :param port: 串口名称,Windows为COMx,Linux为/dev/ttyUSBx - :param baudrate: 波特率,默认9600 - :param timeout: 超时时间,秒 - """ - self.port = port - self.baudrate = baudrate - self.timeout = timeout - self.ser = None - - def connect(self): - """连接串口""" - try: - self.ser = serial.Serial( - port=self.port, - baudrate=self.baudrate, - bytesize=serial.EIGHTBITS, - parity=serial.PARITY_NONE, - stopbits=serial.STOPBITS_ONE, - timeout=self.timeout - ) - print(f"成功连接到串口 {self.port}") - return True - except serial.SerialException as e: - print(f"连接串口失败: {e}") - return False - - def disconnect(self): - """断开串口连接""" - if self.ser and self.ser.is_open: - self.ser.close() - print("串口连接已关闭") - - def calculate_crc(self, data): - """ - 计算Modbus CRC16校验码 - :param data: 字节数据 - :return: CRC校验码(低位在前,高位在后) - """ - crc = 0xFFFF - for byte in data: - crc ^= byte - for _ in range(8): - if crc & 0x0001: - crc = (crc >> 1) ^ 0xA001 - else: - crc = crc >> 1 - return struct.pack('> 8) & 0xFF, # 寄存器地址高字节 - register_addr & 0xFF, # 寄存器地址低字节 - (register_count >> 8) & 0xFF, # 寄存器数量高字节 - register_count & 0xFF # 寄存器数量低字节 - ]) - - # 计算CRC - crc = self.calculate_crc(cmd_data) - full_cmd = cmd_data + crc - - print(f"发送读取指令: {full_cmd.hex().upper()}") - - try: - self.ser.reset_input_buffer() - self.ser.write(full_cmd) - time.sleep(0.01) - - # 计算预期响应长度 - expected_length = 5 + 2 * register_count # 地址1 + 功能码1 + 字节数1 + 数据2*N + CRC2 - response = self.ser.read(expected_length) - - if len(response) < expected_length: - print(f"响应数据长度不足: {len(response)} 字节,期望 {expected_length} 字节") - return None - - print(f"收到响应: {response.hex().upper()}") - - # 验证CRC - received_crc = response[-2:] - calculated_crc = self.calculate_crc(response[:-2]) - if received_crc != calculated_crc: - print("CRC校验失败") - return None - - # 解析数据 - data_length = response[2] - data_bytes = response[3:3 + data_length] - - results = [] - for i in range(0, len(data_bytes), 2): - value = (data_bytes[i] << 8) | data_bytes[i + 1] - results.append(value) - - return results - - except Exception as e: - print(f"通信错误: {e}") - return None - - -def main(): - # 创建变频器对象 - inverter = InovanceMD520(port='COM3', baudrate=9600) - - # 连接串口 - if not inverter.connect(): - return - - try: - while True: - print("\n" + "=" * 50) - print("汇川MD520变频器频率查询") - print("=" * 50) - - # 查询运行频率 - frequency = inverter.query_frequency(slave_addr=0x01) - - if frequency is not None: - print(f"✅ 当前运行频率: {frequency:.2f} Hz") - else: - print("❌ 频率查询失败") - - # 可选:读取其他监控参数 - print("\n--- 其他监控参数 ---") - - # 读取母线电压 (地址1002H) - voltage_data = inverter.read_register(0x01, 0x1002) - if voltage_data: - voltage = voltage_data[0] / 10.0 # 单位0.1V - print(f"母线电压: {voltage:.1f} V") - - # 读取输出电压 (地址1003H) - output_voltage_data = inverter.read_register(0x01, 0x1003) - if output_voltage_data: - output_voltage = output_voltage_data[0] # 单位1V - print(f"输出电压: {output_voltage} V") - - # 读取输出电流 (地址1004H) - current_data = inverter.read_register(0x01, 0x1004) - if current_data: - current = current_data[0] / 100.0 # 单位0.01A - print(f"输出电流: {current:.2f} A") - - # 等待5秒后再次查询 - print("\n等待5秒后继续查询...") - time.sleep(5) - - except KeyboardInterrupt: - print("\n用户中断查询") - finally: - # 断开连接 - inverter.disconnect() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/close_test.py b/tests/close_all_relay.py similarity index 100% rename from close_test.py rename to tests/close_all_relay.py diff --git a/test_led.py b/tests/led-app.py similarity index 100% rename from test_led.py rename to tests/led-app.py diff --git a/core/pd_system.py b/tests/pd_system.py similarity index 100% rename from core/pd_system.py rename to tests/pd_system.py diff --git a/tests/test_relay_controller.py b/tests/relay_controller_pytest.py similarity index 100% rename from tests/test_relay_controller.py rename to tests/relay_controller_pytest.py diff --git a/tests/test_rfid.py b/tests/rfid_app.py similarity index 100% rename from tests/test_rfid.py rename to tests/rfid_app.py diff --git a/tests/test_feeding_process.py b/tests/test_feeding_process.py deleted file mode 100644 index c45fe5c..0000000 --- a/tests/test_feeding_process.py +++ /dev/null @@ -1,54 +0,0 @@ -# tests/test_feeding_process.py -import unittest -from unittest.mock import patch, MagicMock -import sys -import os -from config.settings import app_set_config - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from feeding.process import FeedingProcess - - -class TestFeedingProcess(unittest.TestCase): - - @patch('feeding.process.RelayController') - @patch('feeding.process.InverterController') - @patch('feeding.process.TransmitterController') - def test_initialization(self, mock_transmitter, mock_inverter, mock_relay): - """测试初始化""" - # 创建模拟对象 - mock_relay_instance = MagicMock() - mock_relay.return_value = mock_relay_instance - - mock_inverter_instance = MagicMock() - mock_inverter.return_value = mock_inverter_instance - - mock_transmitter_instance = MagicMock() - mock_transmitter.return_value = mock_transmitter_instance - - # 创建系统实例 - system = FeedingProcess() - - # 验证初始化 - self.assertIsNotNone(system) - self.assertFalse(system.state.running) - - def test_set_feeding_parameters(self): - """测试设置下料参数""" - with patch('feeding.process.RelayController'), \ - patch('feeding.process.InverterController'), \ - patch('feeding.process.TransmitterController'): - system = FeedingProcess() - #修改参数 app_set_config.single_batch_weight = 1500 - app_set_config.min_required_weight = 300 - app_set_config.target_vehicle_weight = 3000 - - self.assertEqual(app_set_config.target_vehicle_weight, 3000) - self.assertEqual(app_set_config.min_required_weight, 300) - self.assertEqual(app_set_config.single_batch_weight, 1500) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_inverter_controller.py b/tests/test_inverter_controller.py deleted file mode 100644 index d2c24ad..0000000 --- a/tests/test_inverter_controller.py +++ /dev/null @@ -1,161 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock -import sys -import os - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from hardware.inverter import InverterController -from pymodbus.exceptions import ModbusException - - -class TestInverterController(unittest.TestCase): - - def setUp(self): - """测试前的准备工作""" - # 创建模拟的继电器控制器 - self.mock_relay = MagicMock() - self.mock_relay.modbus_client = MagicMock() - - # 创建变频器控制器实例 - self.inverter = InverterController(relay_controller=self.mock_relay) - - def test_inverter_initialization(self): - """测试变频器控制器初始化""" - self.assertEqual(self.inverter.relay_controller, self.mock_relay) - self.assertEqual(self.inverter.max_frequency, 400.0) - - # 检查配置 - self.assertIn('slave_id', self.inverter.config) - self.assertIn('frequency_register', self.inverter.config) - self.assertIn('start_register', self.inverter.config) - self.assertIn('stop_register', self.inverter.config) - self.assertIn('start_command', self.inverter.config) - self.assertIn('stop_command', self.inverter.config) - - def test_set_frequency_success(self): - """测试设置频率成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证调用 - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - self.assertTrue(result) - - def test_set_frequency_connection_failed(self): - """测试设置频率时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_set_frequency_modbus_exception(self): - """测试设置频率时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.write_register.side_effect = ModbusException("Modbus错误") - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证结果 - self.assertFalse(result) - - def test_set_frequency_value_clamping(self): - """测试频率值限制""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 测试超过最大值的频率 - result = self.inverter.set_frequency(500.0) # 超过400.0最大值 - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_start_success(self): - """测试启动变频器成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 启动变频器 - result = self.inverter.control('start') - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_stop_success(self): - """测试停止变频器成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 停止变频器 - result = self.inverter.control('stop') - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_invalid_action(self): - """测试无效的控制动作""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 使用无效动作 - result = self.inverter.control('invalid_action') - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_not_called() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_control_connection_failed(self): - """测试控制时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 启动变频器 - result = self.inverter.control('start') - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_control_modbus_exception(self): - """测试控制时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.write_register.side_effect = ModbusException("Modbus错误") - - # 启动变频器 - result = self.inverter.control('start') - - # 验证结果 - self.assertFalse(result) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_transmitter_controller.py b/tests/test_transmitter_controller.py deleted file mode 100644 index 8300026..0000000 --- a/tests/test_transmitter_controller.py +++ /dev/null @@ -1,165 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock -import sys -import os - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from hardware.transmitter import TransmitterController -from pymodbus.exceptions import ModbusException - - -class TestTransmitterController(unittest.TestCase): - - def setUp(self): - """测试前的准备工作""" - # 创建模拟的继电器控制器 - self.mock_relay = MagicMock() - self.mock_relay.modbus_client = MagicMock() - - # 创建变送器控制器实例 - self.transmitter = TransmitterController(relay_controller=self.mock_relay) - - def test_transmitter_initialization(self): - """测试变送器控制器初始化""" - self.assertEqual(self.transmitter.relay_controller, self.mock_relay) - - # 检查配置 - self.assertIn(1, self.transmitter.config) # 上料斗 - self.assertIn(2, self.transmitter.config) # 下料斗 - - # 检查上料斗配置 - upper_config = self.transmitter.config[1] - self.assertEqual(upper_config['slave_id'], 1) - self.assertEqual(upper_config['weight_register'], 0x01) - self.assertEqual(upper_config['register_count'], 2) - - # 检查下料斗配置 - lower_config = self.transmitter.config[2] - self.assertEqual(lower_config['slave_id'], 2) - self.assertEqual(lower_config['weight_register'], 0x01) - self.assertEqual(lower_config['register_count'], 2) - - def test_read_data_valid_transmitter_id(self): - """测试读取有效变送器ID的数据""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [0, 1500] # 表示1500kg - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取上料斗数据 - weight = self.transmitter.read_data(1) - - # 验证调用 - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.read_holding_registers.assert_called_once() - self.assertEqual(weight, 1.5) # 1500kg / 1000 = 1.5kg - - def test_read_data_invalid_transmitter_id(self): - """测试读取无效变送器ID的数据""" - # 读取无效ID的数据 - weight = self.transmitter.read_data(99) - - # 验证结果 - self.assertIsNone(weight) - self.mock_relay.modbus_client.connect.assert_not_called() - self.mock_relay.modbus_client.read_holding_registers.assert_not_called() - - def test_read_data_connection_failed(self): - """测试读取数据时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.read_holding_registers.assert_not_called() - - def test_read_data_modbus_exception(self): - """测试读取数据时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.read_holding_registers.side_effect = ModbusException("Modbus错误") - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - def test_read_data_register_count_2(self): - """测试读取2个寄存器的数据""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 (高位寄存器=1,低位寄存器=0xE848,表示128000) - mock_result = MagicMock() - mock_result.registers = [1, 0xE848] # 1 * 65536 + 59464 = 125000 - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 (125000 / 1000 = 125kg) - self.assertEqual(weight, 125.0) - - def test_read_data_register_count_1(self): - """测试读取1个寄存器的数据(模拟配置变更)""" - # 临时修改配置以测试单寄存器读取 - self.transmitter.config[1]['register_count'] = 1 - - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [1500] # 表示1500kg - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertEqual(weight, 1500.0) # 1500kg - - def test_read_data_invalid_register_count(self): - """测试不支持的寄存器数量""" - # 临时修改配置以测试无效寄存器数量 - self.transmitter.config[1]['register_count'] = 3 - - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [0, 0, 0] - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - def test_read_data_result_is_exception(self): - """测试读取结果为异常对象""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.read_holding_registers.return_value = Exception("读取错误") - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_vision.py b/tests/test_vision.py deleted file mode 100644 index b17c457..0000000 --- a/tests/test_vision.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import sys -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) -import unittest -from unittest.mock import MagicMock -from vision.camera import CameraController - -# from core.vision import Vision - -class TestVision(unittest.TestCase): - - def setUp(self): - self.jj=2 - # self.testclass2 = MagicMock() - # self.testclass = TestClass(self.testclass2) - - def test_capture_frame(self): - # 测试capture_frame方法 - camera=CameraController() - result = camera.capture_frame() - self.assertIsNone(result, msg="capture_frame方法测试失败") - camera.capture_frame_exec.assert_called_once() - - # def test_first(self): - # 测试TestClass的add方法 - # mock_testclass2 = MagicMock() - # # mock_testclass2.i = 1 - # # mock_testclass2.j = 2 - - # test_class = TestClass(mock_testclass2) - # result = test_class.add() - - # # 验证结果 - # self.assertEqual(result, 3, msg="add方法测试失败") - - # def test_second(self): - # 测试TestClass2的mock行为 - # mock_testclass2 = MagicMock(spec=TestClass2) - # mock_testclass2.sub.return_value = 1 - # result = mock_testclass2.sub() - - # # 测试返回值 - # self.assertEqual(result, 1, msg="sub方法测试失败") - # mock_testclass2.sub.assert_called_once() - - -class TestClass: - def __init__(self,testclass2): - self.testclass2 = testclass2 - pass - def add(self): - return self.testclass2.i + self.testclass2.j - -class TestClass2: - def __init__(self): - self.i = 1 - self.j = 2 - pass - - def sub(self): - return self.j - self.i - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/upper_plc.py b/tests/upper_plc.py similarity index 100% rename from upper_plc.py rename to tests/upper_plc.py diff --git a/tests/485test.py b/tests/vf_control.py similarity index 100% rename from tests/485test.py rename to tests/vf_control.py diff --git a/test copy.py b/tests/weight-app.py similarity index 100% rename from test copy.py rename to tests/weight-app.py diff --git a/upper_to_lower.py b/upper_to_lower.py deleted file mode 100644 index 57f27a8..0000000 --- a/upper_to_lower.py +++ /dev/null @@ -1,159 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -import threading -import time -import cv2 -import vision.visual_callback_1203 as angle_visual - - -def main(): - # 加载配置 - # 初始化系统 - # angle_visual.angle_visual_callback(2,'未堆料') - replay_controller=RelayController() - # transmitter_controller=TransmitterController(replay_controller) - # upper_weight=transmitter_controller.read_data(2) - # print(upper_weight) - - - - replay_controller.close_all() - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(4) - # while True: - # # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(2) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.1) - - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(3) - - - - # inverter_controller=InverterController(replay_controller) - # inverter_controller.control('start') - - # inverter_controller.control('stop') - - - - # return - # replay_controller.control_upper_open() - #3秒开关 - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - # while True: - # time.sleep(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system = FeedingControlSystem() - - # system.state.vehicle_aligned=True - #假设在振捣室 - # system.state._upper_door_position='over_lower' - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.initialize() - - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/view/main_window.py b/view/main_window.py index 5a3ae92..cf46dfc 100644 --- a/view/main_window.py +++ b/view/main_window.py @@ -35,15 +35,16 @@ class MainWindow(QWidget): def __init__(self): super().__init__() self.initWindow() + # 保存管片任务信息的字典 task1: ArtifactInfoModel1.... (用于显示管片任务详情) + self.artifact_dict = {} + # 当前点击/选中的 管片任务详情对应的任务名(task1\task2\task3) (用于刷新选中的管片任务详情) + self.current_selected_segment_detail_name = None self.createSubWidgets() # 创建子部件 self.initSubWidgets() # 初始化子部件 self.setupLayout() # 设置布局 self.connectSignalToSlot() - # 保存管片任务信息的字典 task1: ArtifactInfoModel1.... (用于显示管片任务详情) - self.artifact_dict = {} - # 当前点击/选中的 管片任务详情对应的任务名(task1\task2\task3) (用于刷新选中的管片任务详情) - self.current_selected_segment_detail_name = None + # 安装事件过滤器,处理计划方量的 QLineEdit的失去焦点事件 self.installEventFilter(self) @@ -127,7 +128,7 @@ class MainWindow(QWidget): # self.dispatch_task_widget.set_task_id("task3", "PD0003") # 初始化 管片任务 和 派单任务显示的数据 - # self._init_segment_tasks() + self._init_segment_tasks() self._init_dispatch_tasks() def convert_to_ampm(self, time_str: str) -> str: diff --git a/vision/.py b/vision/.py deleted file mode 100644 index 7c4da50..0000000 --- a/vision/.py +++ /dev/null @@ -1,137 +0,0 @@ -# vision/camera.py -import cv2 - - -class CameraController: - def __init__(self): - self.camera = None - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - - def set_config(self, camera_type="ip", ip=None, port=None, username=None, password=None, channel=1): - """ - 设置摄像头配置 - """ - self.camera_type = camera_type - if ip: - self.camera_ip = ip - if port: - self.camera_port = port - if username: - self.camera_username = username - if password: - self.camera_password = password - self.camera_channel = channel - - def setup_capture(self, camera_index=0): - """ - 设置摄像头捕获 - """ - try: - rtsp_url = f"rtsp://{self.camera_username}:{self.camera_password}@{self.camera_ip}:{self.camera_port}/streaming/channels/{self.camera_channel}01" - self.camera = cv2.VideoCapture(rtsp_url) - - if not self.camera.isOpened(): - print(f"无法打开网络摄像头: {rtsp_url}") - return False - print(f"网络摄像头初始化成功,地址: {rtsp_url}") - return True - except Exception as e: - print(f"摄像头设置失败: {e}") - return False - - def capture_frame_exec(self): - """捕获当前帧并返回numpy数组,设置5秒总超时""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - # 设置总超时时间为5秒 - total_timeout = 5.0 # 5秒总超时时间 - start_time = time.time() - - # 跳20帧,获取最新图像 - frames_skipped = 0 - while frames_skipped < 20: - # 检查总超时 - if time.time() - start_time > total_timeout: - print("捕获图像总超时") - return None - self.camera.grab() - time.sleep(0.05) # 稍微增加延迟,确保有新帧到达 - frames_skipped += 1 - - # 尝试读取帧,使用同一超时计时器 - read_attempts = 0 - max_read_attempts = 3 - if self.camera.grab(): - while read_attempts < max_read_attempts: - # 使用同一个超时计时器检查 - if time.time() - start_time > total_timeout: - print("捕获图像总超时") - return None - - ret, frame = self.camera.retrieve() - if ret: - return frame - else: - print(f"尝试读取图像帧失败,重试 ({read_attempts+1}/{max_read_attempts})") - read_attempts += 1 - # 短暂延迟后重试 - time.sleep(0.05) - - print("多次尝试后仍无法捕获有效图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def capture_frame(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - # self.set_config() - self.setup_capture() - - - frame = self.capture_frame_exec() - if frame is not None: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def capture_frame_bak(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - ret, frame = self.camera.read() - if ret: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def release(self): - """释放摄像头资源""" - if self.camera is not None: - self.camera.release() - self.camera = None - - def __del__(self): - """析构函数,确保资源释放""" - self.release() diff --git a/vision/test_feed.py b/vision/test_feed.py deleted file mode 100644 index 809b9a5..0000000 --- a/vision/test_feed.py +++ /dev/null @@ -1,62 +0,0 @@ -import time -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -from test_feed import start_feeding - -def start_feeding(): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始下料") - # self.relay_controller.control - loc_relay=RelayController() - loc_mitter=TransmitterController(loc_relay) - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - # mould_need_weight=4000 - while True: - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - loc_relay.control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(5) - while True: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<3500: - #关5秒 - loc_relay.control_upper_close() - break - time.sleep(1) - - initial_lower_weight=loc_mitter.read_data(2) - while True: - current_weight = loc_mitter.read_data(2) - first_finish_weight=first_finish_weight+initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - loc_relay.control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - -if __name__ == "__main__": - start_feeding() - - \ No newline at end of file diff --git a/vision/test_safe_close.py b/vision/test_safe_close.py deleted file mode 100644 index 4d0ddb2..0000000 --- a/vision/test_safe_close.py +++ /dev/null @@ -1,52 +0,0 @@ -import time -import threading -import sys -import os - -# 添加项目根目录到sys.path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from visual_callback import VisualCallback - -# 创建VisualCallback实例 -visual_callback = VisualCallback() - -# 模拟safe_control_lower_close执行 -def simulate_safe_close(): - """模拟safe_control_lower_close执行""" - print("\n=== 开始模拟safe_control_lower_close执行 ===") - # 直接调用safe_control_lower_close函数 - # 注意:这里需要访问_run_feed方法中的内部函数,所以我们需要一个间接的方式来测试 - # 我们可以通过修改标志位来模拟这个过程 - - # 1. 首先,让线程循环运行一段时间,观察正常情况下的行为 - print("\n1. 正常运行阶段 (5秒):") - time.sleep(5) - - # 2. 模拟safe_control_lower_close开始执行 - print("\n2. 模拟safe_control_lower_close开始执行:") - visual_callback._is_safe_closing = True - - # 3. 让线程循环运行一段时间,观察是否会跳过relay操作 - print("\n3. safe_control_lower_close执行中 (5秒):") - time.sleep(5) - - # 4. 模拟safe_control_lower_close执行完毕 - print("\n4. 模拟safe_control_lower_close执行完毕:") - visual_callback._is_safe_closing = False - - # 5. 再次观察正常运行 - print("\n5. 恢复正常运行 (5秒):") - time.sleep(5) - - print("\n=== 测试结束 ===") - -# 创建测试线程 -test_thread = threading.Thread(target=simulate_safe_close) -test_thread.start() - -# 主线程等待测试线程结束 -test_thread.join() - -# 关闭视觉回调实例 -visual_callback.shutdown() \ No newline at end of file diff --git a/vision/visual_callback copy 2.py b/vision/visual_callback copy 2.py deleted file mode 100644 index 4e7cd05..0000000 --- a/vision/visual_callback copy 2.py +++ /dev/null @@ -1,277 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - - - def __del__(self): - self.relay_controller.close_all() - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 修复版控制逻辑 - 增加控制间隔和状态稳定性 - 堆料情况优先处理,非堆料情况保持适当控制间隔 - 堆料状态变化时立即处理,不受间隔限制 - """ - try: - # 先检查堆料状态 - is_overflow = overflow_detected in ["大堆料", "小堆料"] - self.overflow = is_overflow - - print(f"{self.angle_mode}") - - # 添加控制间隔检查 - current_time = time.time() - # 检查堆料状态是否发生变化 - state_changed = self._last_overflow_state != is_overflow - - if hasattr(self, '_last_control_time'): - time_since_last = current_time - self._last_control_time - # 防止抖动逻辑: - # 1. 非堆料且状态未变化:2秒控制间隔 - # 2. 其他情况(堆料或状态变化):0.5秒最小间隔,避免频繁操作 - if not is_overflow and not state_changed: - # 正常情况:2秒控制间隔 - if time_since_last < 2: - return - else: - # 堆料或状态变化情况:0.5秒最小间隔,防止抖动 - MIN_INTERVAL = 0.5 - if time_since_last < MIN_INTERVAL: - return - # 更新最后控制时间和堆料状态 - self._last_control_time = current_time - self._last_overflow_state = is_overflow - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 定义控制参数 - TARGET_ANGLE = 30.0 - UPPER_DEAD_ZONE = 35.0 # 上死区 - LOWER_DEAD_ZONE = 25.0 # 下死区 - MIN_ANGLE = 5.0 # 最小安全角度 - - # 状态机逻辑 - if self.angle_mode == "normal": - if self.overflow and current_angle > UPPER_DEAD_ZONE: - # 只有角度较高且有堆料时才切换到reducing - self.angle_mode = "reducing" - print("检测到堆料且角度偏高,切换到减小模式") - else: - # 正常模式:维持适当开度 - if current_angle < MIN_ANGLE: - # 角度过小,适当开门 - self._pulse_control("open", 0.3) - elif current_angle > 60.0: # 安全上限 - self._pulse_control("close", 0.5) - else: - # 在正常范围内,根据重量比例控制 - if hasattr(self, 'mould_need_weight') and hasattr(self, 'mould_finish_weight'): - weight_ratio = self.mould_finish_weight / self.mould_need_weight if self.mould_need_weight > 0 else 0 - if weight_ratio < 0.8: - # 需要大量下料,保持开门 - if current_angle < 40.0: - self._pulse_control("open", 0.2) - else: - # 接近完成,半开控制 - if current_angle > 15.0: - self._pulse_control("close", 0.1) - else: - # 默认保持中等开度 - if current_angle < 20.0: - self._pulse_control("open", 0.2) - elif current_angle > 40.0: - self._pulse_control("close", 0.2) - - elif self.angle_mode == "reducing": - if not self.overflow: - # 堆料消失,但需要角度达到安全范围才切换 - if current_angle <= TARGET_ANGLE + 5.0: # 留有余量 - self.angle_mode = "normal" - print("堆料消失且角度合适,返回正常模式") - else: - # 堆料消失但角度仍高,缓慢恢复 - self._pulse_control("close", 0.1) - else: - # 仍有堆料,继续减小角度 - if current_angle > TARGET_ANGLE: - # 角度仍高,继续关门 - pulse_time = min(0.5, (current_angle - TARGET_ANGLE) * 0.02) - self._pulse_control("close", pulse_time) - else: - # 角度已达标,切换到维持模式 - self.angle_mode = "maintaining" - print(f"角度已降至{current_angle:.1f}°,进入维持模式") - - elif self.angle_mode == "maintaining": - if not self.overflow: - self.angle_mode = "normal" - print("堆料消除,返回正常模式") - else: - # 维持模式精确控制 - error = current_angle - TARGET_ANGLE - dead_zone = 3.0 - - if abs(error) < dead_zone: - # 在死区内,停止动作 - self._stop_door() - print(f"角度{current_angle:.1f}°在目标附近,保持静止") - elif error > 0: - # 角度偏高,轻微关门 - pulse_time = min(0.3, error * 0.01) - self._pulse_control("close", pulse_time) - print(f"角度偏高{error:.1f}°,关门{pulse_time:.2f}秒") - else: - # 角度偏低,轻微开门 - pulse_time = min(0.3, abs(error) * 0.01) - self._pulse_control("open", pulse_time) - print(f"角度偏低{abs(error):.1f}°,开门{pulse_time:.2f}秒") - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - if duration <= 0: - return - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"关门脉冲: {duration:.2f}秒") - - def _stop_door(self): - """停止门运动""" - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy 3.py b/vision/visual_callback copy 3.py deleted file mode 100644 index e40259c..0000000 --- a/vision/visual_callback copy 3.py +++ /dev/null @@ -1,273 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 修复版控制逻辑 - 增加控制间隔和状态稳定性 - 堆料情况优先处理,非堆料情况保持适当控制间隔 - 堆料状态变化时立即处理,不受间隔限制 - """ - try: - # 先检查堆料状态 - is_overflow = overflow_detected in ["大堆料", "小堆料"] - self.overflow = is_overflow - - print(f"{self.angle_mode}") - - # 添加控制间隔检查 - current_time = time.time() - # 检查堆料状态是否发生变化 - state_changed = self._last_overflow_state != is_overflow - - if hasattr(self, '_last_control_time'): - time_since_last = current_time - self._last_control_time - # 防止抖动逻辑: - # 1. 非堆料且状态未变化:2秒控制间隔 - # 2. 其他情况(堆料或状态变化):0.5秒最小间隔,避免频繁操作 - # if not is_overflow and not state_changed: - if 1==1: - # 正常情况:2秒控制间隔 - if time_since_last < 1: - return - else: - # 堆料或状态变化情况:0.5秒最小间隔,防止抖动 - MIN_INTERVAL = 0.5 - if time_since_last < MIN_INTERVAL: - return - # 更新最后控制时间和堆料状态 - self._last_control_time = current_time - self._last_overflow_state = is_overflow - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 定义控制参数 - TARGET_ANGLE = 30.0 - UPPER_DEAD_ZONE = 35.0 # 上死区 - LOWER_DEAD_ZONE = 25.0 # 下死区 - MIN_ANGLE = 5.0 # 最小安全角度 - - # 状态机逻辑 - if self.angle_mode == "normal": - if self.overflow and current_angle > UPPER_DEAD_ZONE: - # 只有角度较高且有堆料时才切换到reducing - self.angle_mode = "reducing" - print("检测到堆料且角度偏高,切换到减小模式") - else: - # 正常模式:维持适当开度 - if current_angle < MIN_ANGLE: - # 角度过小,适当开门 - self._pulse_control("open", 0.3) - elif current_angle > 60.0: # 安全上限 - self._pulse_control("close", 0.5) - else: - # 在正常范围内,根据重量比例控制 - if hasattr(self, 'mould_need_weight') and hasattr(self, 'mould_finish_weight'): - weight_ratio = self.mould_finish_weight / self.mould_need_weight if self.mould_need_weight > 0 else 0 - if weight_ratio < 0.8: - # 需要大量下料,保持开门 - if current_angle < 40.0: - self._pulse_control("open", 0.2) - else: - # 接近完成,半开控制 - if current_angle > 15.0: - self._pulse_control("close", 0.1) - else: - # 默认保持中等开度 - if current_angle < 20.0: - self._pulse_control("open", 0.2) - elif current_angle > 40.0: - self._pulse_control("close", 0.2) - - elif self.angle_mode == "reducing": - if not self.overflow: - # 堆料消失,但需要角度达到安全范围才切换 - if current_angle <= TARGET_ANGLE + 5.0: # 留有余量 - self.angle_mode = "normal" - print("堆料消失且角度合适,返回正常模式") - else: - # 堆料消失但角度仍高,缓慢恢复 - self._pulse_control("close", 0.1) - else: - # 仍有堆料,继续减小角度 - if current_angle > TARGET_ANGLE: - # 角度仍高,继续关门 - pulse_time = min(0.5, (current_angle - TARGET_ANGLE) * 0.02) - self._pulse_control("close", pulse_time) - else: - # 角度已达标,切换到维持模式 - self.angle_mode = "maintaining" - print(f"角度已降至{current_angle:.1f}°,进入维持模式") - - elif self.angle_mode == "maintaining": - if not self.overflow: - self.angle_mode = "normal" - print("堆料消除,返回正常模式") - else: - # 维持模式精确控制 - error = current_angle - TARGET_ANGLE - dead_zone = 3.0 - - if abs(error) < dead_zone: - # 在死区内,停止动作 - self._stop_door() - print(f"角度{current_angle:.1f}°在目标附近,保持静止") - elif error > 0: - # 角度偏高,轻微关门 - pulse_time = min(0.3, error * 0.01) - self._pulse_control("close", pulse_time) - print(f"角度偏高{error:.1f}°,关门{pulse_time:.2f}秒") - else: - # 角度偏低,轻微开门 - pulse_time = min(0.3, abs(error) * 0.01) - self._pulse_control("open", pulse_time) - print(f"角度偏低{abs(error):.1f}°,开门{pulse_time:.2f}秒") - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - if duration <= 0: - return - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"关门脉冲: {duration:.2f}秒") - - def _stop_door(self): - """停止门运动""" - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy 4.py b/vision/visual_callback copy 4.py deleted file mode 100644 index d4a4a15..0000000 --- a/vision/visual_callback copy 4.py +++ /dev/null @@ -1,452 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - self.is_start_visual=False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=False - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - def _run_feed(self): - while True: - print("------------已启动----------------") - if self._is_feed_start: - print("------------下料启动----------------") - print("------------下料启动----------------") - print("------------下料启动----------------") - self.run_feed() - break - time.sleep(0.5) - - - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - self.is_start_visual=True - - def safe_control_lower_close(): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - loc_relay.control_lower_close() - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - # 破拱控制 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(4) - upper_open_time=time.time() - while True: - print(f'------------上料斗向下料斗转移-------------') - loc_mitter.is_start_upper=True - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<4000: - #关5秒 - loc_relay.control_upper_close() - loc_mitter.is_start_upper=False - break - else: - if time.time()-upper_open_time>2: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.5) - else: - time.sleep(0.5) - # time.sleep(0.4) - - self.is_start_visual=True - loc_mitter.is_start_lower=False - loc_mitter.test_lower_weight=2000 - initial_lower_weight=loc_mitter.read_data(2) - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - second_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - - safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - print(f'------------已完成-------------') - print(f'------------已完成-------------') - print(f'------------已完成-------------') - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 35.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 56.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.1 # 比例系数 - KI = 0.01 # 积分系数 - KD = 0.05 # 微分系数 - - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 基于PID输出的智能控制 - control_threshold = 2.0 # 控制死区 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy.py b/vision/visual_callback copy.py deleted file mode 100644 index 1beb39b..0000000 --- a/vision/visual_callback copy.py +++ /dev/null @@ -1,240 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - - - def __del__(self): - self.relay_controller.close_all() - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 内部方法,实际处理视觉回调逻辑 - 在异步线程中执行 - """ - try: - # print('current_angle:', current_angle, 'overflow_detected:', overflow_detected) - # return - # 检测溢出状态 - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - if True: - # 状态机控制逻辑 - if self.angle_mode == "normal": - # 正常模式大于app_set_config.angle_threshold=60度 - if self.overflow: - self.angle_mode = "reducing" - else: - # 保持正常开门 - # print(f'当前重量:{self.mould_finish_weight:.2f}kg, 目标重量:{self.mould_need_weight:.2f}kg') - if self.mould_need_weight > 0: - if self.mould_finish_weight / self.mould_need_weight >= 0.8: - print(f"完成重量占比{self.mould_finish_weight/self.mould_need_weight:.2f},半开出砼门") - # 半开出砼门 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.3) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.32) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - #self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE,'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE,'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN,'open') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - elif self.angle_mode == "reducing": - # 角度减小模式 - if self.overflow: - if current_angle <= app_set_config.target_angle: - # 角度已达到目标范围,仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - if current_angle <= app_set_config.min_angle: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.1) - self.angle_mode = "maintaining" - else: - print(f"角度大于30,继续关闭") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.angle_mode = "normal" - - elif self.angle_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not self.overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.angle_mode = "normal" - else: - # 继续维持角度控制 - print("进入维持模式") - # 关门时间 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - #time.sleep(0.3) - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - #time.sleep(0.32) - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - - elif self.angle_mode == "recovery": - # 恢复模式 - 逐步打开门 - if self.overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.angle_mode = "normal" - # else: - # 浇筑满,关闭下料门 - # self.relay_controller.control_lower_close() - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback.py b/vision/visual_callback.py index 4ebbb1f..97b2982 100644 --- a/vision/visual_callback.py +++ b/vision/visual_callback.py @@ -298,7 +298,6 @@ class VisualCallback: timestamp = datetime.now().strftime("%H:%M:%S") f.write(f"{timestamp} - {self._finish_weight}\n") - def run_feed_all(self): """ 全流程下料:包括判断模具类型 @@ -374,7 +373,6 @@ class VisualCallback: print(f'------------已完成-------------') - def run_feed(self): """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(普通块)--------------------") diff --git a/vision/visual_callback_1203.py b/vision/visual_callback_1203.py deleted file mode 100644 index 5476bde..0000000 --- a/vision/visual_callback_1203.py +++ /dev/null @@ -1,480 +0,0 @@ - -from cv2.gapi import ov -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - self.is_start_visual=True - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=False - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - # self.feed_thread.start() - - self._before_finish_time=None - self._is_finish=False - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - def _run_feed(self): - while True: - print("------------已启动----------------") - if self._is_feed_start: - print("------------下料启动----------------") - print("------------下料启动----------------") - print("------------下料启动----------------") - self.run_feed() - break - time.sleep(0.5) - - - def safe_control_lower_close(self,duration=3): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - self.is_start_visual=True - - - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - # 破拱控制 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - self.safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(6) - loc_time_count=1 - upper_open_time=time.time() - while True: - print(f'------------上料斗向下料斗转移-------------') - loc_mitter.is_start_upper=True - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<4000: - #关5秒 - loc_relay.control_upper_close_sync(4+loc_time_count) - loc_mitter.is_start_upper=False - break - else: - if time.time()-upper_open_time>3: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.5) - loc_time_count=loc_time_count+0.5 - else: - time.sleep(0.5) - # time.sleep(0.4) - - self.is_start_visual=True - loc_mitter.is_start_lower=False - loc_mitter.test_lower_weight=2000 - initial_lower_weight=loc_mitter.read_data(2) - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - second_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - - self.safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - print(f'------------已完成-------------') - print(f'------------已完成-------------') - print(f'------------已完成-------------') - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - if overflow_detected == "未浇筑满": - if self._before_finish_time is None: - self._before_finish_time=current_time - self.safe_control_lower_close(1) - if time.time()-self._before_finish_time>3: - TARGET_ANGLE=25 - elif overflow_detected == "浇筑满": - self.is_start_visual=False - self._is_finish=True - self.safe_control_lower_close(3) - return - else: - TARGET_ANGLE=25 - # 根据溢料状态动态调整目标角度 - # if overflow_detected == "大堆料": - # TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - # elif overflow_detected == "小堆料": - # TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 - # else: - # TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.1 # 比例系数 - KI = 0.01 # 积分系数 - KD = 0.05 # 微分系数 - - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 基于PID输出的智能控制 - control_threshold = 2.0 # 控制死区 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _open_door(self, duration=0.5): - """打开门""" - self._pulse_control("open", 0.3) - - def _close_door(self, duration=0.5): - """关闭门""" - self._pulse_control("close", 1) - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback_dq.py b/vision/visual_callback_dq.py index 299ecf4..ef94117 100644 --- a/vision/visual_callback_dq.py +++ b/vision/visual_callback_dq.py @@ -33,14 +33,17 @@ class VisualCallback: # cls._instance = super().__new__(cls) # return cls._instance - def __init__(self,state:SystemState=None): + def __init__(self, + relay_controller:RelayController, + transmitter_controller:TransmitterController, + state:SystemState=None): """初始化视觉回调处理器""" # 避免重复初始化 if hasattr(self, '_initialized') and self._initialized: return - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) + self.relay_controller = relay_controller + self.transmitter_controller = transmitter_controller self.pd_record_bll=PDRecordBll() self.state=state @@ -87,48 +90,9 @@ class VisualCallback: self._cur_mould_model=None # self.db_queue=queue.Queue() - # self.plc_data=5 - self.plc_service = OmronFinsPollingService("192.168.250.233") - self.plc_service.register_data_callback(self.on_plc_update) - # self.plc_service.register_status_callback(self.on_status_change) - self.plc_service.start_polling(interval=2.0) - - # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 启动下料线程检测 - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - #启动振动线程 - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - #获取diff(diff_visual_callback推送的数据)数据线程 - self.diff_thread = threading.Thread( - target=self._diff_temp, - daemon=True - ) - self.diff_thread.start() - - """启动数据库监控""" - # self.db_thread = threading.Thread( - # target=self._monitor_db_loop, - # daemon=True, - # name='db_monitor' - # ) - # self.db_thread.start() + # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 + self.callback_thread = None + self.diff_thread = None def init_val(self): #初始化值 @@ -197,6 +161,20 @@ class VisualCallback: self._point_speed_grade=0 self._point_weight=0 + def start_visual_thread(self)->bool: + """浇筑状态回调线程""" + self.callback_thread = threading.Thread( + target=self._run_thread_loop, + daemon=True + ) + self.callback_thread.start() + #diff线程值传递后处理 + self.diff_thread = threading.Thread( + target=self._diff_temp, + daemon=True + ) + self.diff_thread.start() + return True def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """ @@ -313,7 +291,7 @@ class VisualCallback: _temp_area_str='' time.sleep(1) - def _monitor_loop(self): + def _arch_loop(self): """破拱线程""" while self._is_running: try: @@ -1380,17 +1358,12 @@ class VisualCallback: self.is_start_visual=False # #关闭下料斗 # self.safe_control_lower_close() - if self.plc_service: - self.plc_service.stop_polling() + # 等待线程结束 if self.callback_thread.is_alive(): self.callback_thread.join(timeout=1.0) - - if self.feed_thread.is_alive(): - self.feed_thread.join(timeout=1.0) - - if self.monitor_thread.is_alive(): - self.monitor_thread.join(timeout=1.0) + if self.diff_thread: + self.diff_thread.join(timeout=1.0) # self.relay_controller._close_lower_5s diff --git a/vision/visual_callback_dq copy.py b/vision/visual_callback_dq0209.py similarity index 68% rename from vision/visual_callback_dq copy.py rename to vision/visual_callback_dq0209.py index 8129c7e..299ecf4 100644 --- a/vision/visual_callback_dq copy.py +++ b/vision/visual_callback_dq0209.py @@ -1,5 +1,6 @@ from pickle import FALSE +from re import S from cv2.gapi import ov from config.settings import app_set_config from hardware.relay import RelayController @@ -12,20 +13,27 @@ import queue from hardware.upper_plc import OmronFinsPollingService from vision.muju_cls.muju_utils import run_stable_classification_loop from vision.camera_picture import save_camera_picture +from busisness.blls import ArtifactBll,PDRecordBll +from busisness.models import ArtifactInfoModel,PDRecordModel +from service.mould_service import app_web_service +from core.system_state import SystemState,FeedStatus +from dataclasses import asdict +import json +import math class VisualCallback: # 类变量,用于存储实例引用,实现单例检测 _instance = None _lock = threading.Lock() - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance + # def __new__(cls,*args, **kwargs): + # """检测实例是否存在,实现单例模式""" + # with cls._lock: + # if cls._instance is None: + # cls._instance = super().__new__(cls) + # return cls._instance - def __init__(self): + def __init__(self,state:SystemState=None): """初始化视觉回调处理器""" # 避免重复初始化 if hasattr(self, '_initialized') and self._initialized: @@ -33,6 +41,8 @@ class VisualCallback: self.relay_controller = RelayController() self.transmitter_controller = TransmitterController(self.relay_controller) + self.pd_record_bll=PDRecordBll() + self.state=state # 线程安全的参数传递 self._new_data_available = threading.Event() @@ -54,6 +64,7 @@ class VisualCallback: self._current_controlling_thread = None #是否启动后的第一个模具 self._is_first_module=True + self.init_val() # self._setup_logging_2() #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) @@ -64,7 +75,7 @@ class VisualCallback: #重量大于95%,停留时间2秒,其他的1秒 self._weight_ratio_955=0.955 #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 + self._max_ignore_radio=0.8 self._mould_accept_aligned=None self._mould_before_aligned=False @@ -72,7 +83,8 @@ class VisualCallback: self._time_mould_begin='' #模具结束浇筑时间 self._time_mould_end='' - + #记录当前模具信息model + self._cur_mould_model=None # self.db_queue=queue.Queue() # self.plc_data=5 @@ -81,20 +93,21 @@ class VisualCallback: # self.plc_service.register_status_callback(self.on_status_change) self.plc_service.start_polling(interval=2.0) - # 创建并启动单个持续运行的线程 + # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 self.callback_thread = threading.Thread( target=self._run_thread_loop, daemon=True ) self.callback_thread.start() + # 启动下料线程检测 self.feed_thread = threading.Thread( target=self._run_feed, daemon=True ) self.feed_thread.start() - """启动系统监控""" + #启动振动线程 self.monitor_thread = threading.Thread( target=self._monitor_loop, daemon=True, @@ -102,7 +115,7 @@ class VisualCallback: ) self.monitor_thread.start() - + #获取diff(diff_visual_callback推送的数据)数据线程 self.diff_thread = threading.Thread( target=self._diff_temp, daemon=True @@ -143,6 +156,9 @@ class VisualCallback: #用于判断当前判断是否对齐(diff) self._is_diff_unaligned=False + self._diff_f_val=0 + self._diff_f_area=[] + #浇筑完成比例(重量) self._is_finish_ratio=0 @@ -165,14 +181,23 @@ class VisualCallback: self._inital_finish_lweight=0 #记录视觉停止下料时的重量(计算后面加了多少) self._last_lower_weight=0 + #每片开始下料斗的重量 + self._init_lower_weight=0 # 初始化控制间隔和堆料状态跟踪属性 self._last_overflow_state = False self._last_control_time = 0 self._is_running=True + self._is_stop_one_seconds=False self._initialized = True self.plc_data=None + self._mould_need_weight=0 + #点动等级 + self._point_speed_grade=0 + self._point_weight=0 + + def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """ 视觉控制主逻辑,供外部推送数据 @@ -217,10 +242,12 @@ class VisualCallback: # 更新参数 if current_diff is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到diff:{current_diff}") - self._current_diff = current_diff + self._current_diff = current_diff + self._diff_f_val=current_diff if current_area is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到area:{current_area}") - self._current_diff_area = current_area + self._current_diff_area = current_area + self._diff_f_area = current_area # 通知线程有新数据可用 self._new_data_diff.set() finally: @@ -287,7 +314,7 @@ class VisualCallback: time.sleep(1) def _monitor_loop(self): - """监控循环""" + """破拱线程""" while self._is_running: try: current_time = time.time() @@ -349,7 +376,8 @@ class VisualCallback: (current_time - self._last_arch_time) > 2: self._last_arch_time = current_time print('---------------------第四阶段振动5秒-----------------') - self.relay_controller.control_arch_upper_open_sync(5) + #重量不准,暂时不振动 + # self.relay_controller.control_arch_upper_open_sync(5) self._last_arch_four_weight = _arch_weight continue self._last_arch_four_weight = _arch_weight @@ -385,12 +413,13 @@ class VisualCallback: _temp_aligned_count=0 if flag==1: while time.time()-_current_times<=2: - print(f'-------------{self._mould_accept_aligned}-----------------') + # print(f'-------------{self._mould_accept_aligned}-----------------') if self._mould_accept_aligned=='盖板对齐': _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') + if _temp_aligned_count>0: + print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' if _temp_aligned_count>=8: @@ -404,8 +433,8 @@ class VisualCallback: _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') - + if _temp_aligned_count>0: + print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' @@ -460,7 +489,7 @@ class VisualCallback: time.sleep(0.1) def _run_feed(self): - + _is_api_request=True while True: # print("------------已启动----------------") if self._is_feed_start: @@ -470,38 +499,62 @@ class VisualCallback: if self._is_first_module and self._overflow_detected=='未堆料': #第一次打开 ,未堆料,检测对齐 + if _is_api_request: + self.get_current_mould() + _is_api_request=False _is_aligned=self._aligned_get_times(1) if _is_aligned: - print('------------进入第一块111111-------------') + _is_api_request=True + print('------------启动程序后,进入第一块-------------') self._is_first_module=False self._mould_before_aligned=True + _current_weight=self.transmitter_controller.read_data(2) + if _current_weight: + self._init_lower_weight=_current_weight + else: + print('------------获取上料斗重量失败-------------') + return + + self.state._feed_status=FeedStatus.FCheckGB # self.is_start_visual=True self.run_feed_all() elif self._is_finish and self._is_finish_ratio>=0.7: #后续流程--》检查到未对齐,--》后又对齐+未堆料 - print('------------------进入连续块检测------------------') + #print('------------------进入连续块检测------------------') if self._mould_before_aligned: #未对齐,检测对齐 _is_not_aligned=self._aligned_get_times(2) if _is_not_aligned: #标志位 self._mould_before_aligned=False - print('------------连续盖板未对齐-------------') + #print('------------连续盖板未对齐-------------') else: - _is_aligned=self._aligned_get_times(1) - if _is_aligned and self._overflow_detected=='未堆料': - print('------------连续盖板已对齐-------------') - self._mould_before_aligned=True - print('-----------进入连续块111111-----------') - # self.is_start_visual=True - if self._last_lower_weight>0: - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - with open('weight.txt', 'a') as f: - f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) - self.init_val() - self.run_feed_all() - + if _is_api_request: + self.get_current_mould() + _is_api_request=False + _is_aligned=self._aligned_get_times(1) + if _is_aligned and self._overflow_detected=='未堆料': + print('------------进入连续生产-------------') + self._mould_before_aligned=True + _is_api_request=True + + _current_weight=self.transmitter_controller.read_data(2) + if not _current_weight: + print('------------获取上料斗重量失败-------------') + return + + + # print('-----------进入连续块111111-----------') + # self.is_start_visual=True + if self._last_lower_weight>0: + with open('weight.txt', 'a') as f: + f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) + + self.init_val() + self._init_lower_weight=_current_weight + self.state._feed_status=FeedStatus.FCheckGB + self.run_feed_all() + # else: # print("-----------上料斗未就位----------------") # print("---------3--上料斗未就位----------------") @@ -517,7 +570,7 @@ class VisualCallback: try: with self._door_control_lock: self._current_controlling_thread = thread_name - #print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") + print(f"关闭下料斗{duration}秒") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') time.sleep(duration) @@ -538,10 +591,12 @@ class VisualCallback: def _visual_close(self): self.is_start_visual=False self._is_finish=True + self.state.vf_status=3 + self.state._feed_status=FeedStatus.FFinished self._is_feed_stage=0 print(f'--------进入关闭(浇筑满)-----------') self.safe_control_lower_close(3) - print(f'--------关闭完成-----------') + print(f'--------浇筑完成-----------') # try: # self.db_queue.put_nowait({ # "f":self._is_small_f, @@ -557,9 +612,15 @@ class VisualCallback: with open('weight.txt', 'a') as f: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if self._is_small_f: - f.write(f"{self._time_mould_begin},{timestamp},F,{self._finish_weight}\n") + if self._cur_mould_model: + f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},F,{self._finish_weight}\n") + else: + f.write(f"{self._time_mould_begin},{timestamp},F,{self._finish_weight}\n") else: - f.write(f"{self._time_mould_begin},{timestamp},B,{self._finish_weight}\n") + if self._cur_mould_model: + f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},B,{self._finish_weight}\n") + else: + f.write(f"{self._time_mould_begin},{timestamp},B,{self._finish_weight}\n") #开启保存diff self._is_diff_save=True @@ -576,13 +637,15 @@ class VisualCallback: if _is_f=='模具车1': self._is_small_f=True print('-------------F块模具--------------') - print('-------------F块模具--------------') - print('-------------F块模具--------------') + # print('-------------F块模具--------------') + # print('-------------F块模具--------------') + # self.send_pd_data() self.run_feed_f() elif _is_f=='模具车2': self._is_small_f=False + print('-------------B-L模具---------------') + # self.send_pd_data() self.run_feed() - print('-------------其他模具---------------') if self._is_small_f is None: print('-----------未判断出模具类型--------------') @@ -596,21 +659,17 @@ class VisualCallback: loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return + initial_lower_weight=self._init_lower_weight first_finish_weight=0 self._finish_weight=first_finish_weight self._inital_finish_lweight=initial_lower_weight - need_total_weight=0.54*2416 + self._mould_need_weight=0.54*2416 + need_total_weight=self._mould_need_weight if initial_lower_weight>100: + self.state._feed_status=FeedStatus.FFeed5 + self.state.vf_status=2 if not self._is_finish: self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return self._is_feed_stage=5 while not self._is_finish: current_weight = loc_mitter.read_data(2) @@ -628,19 +687,19 @@ class VisualCallback: first_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio=(first_finish_weight)/need_total_weight print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: + # if self._is_finish_ratio>=1: #关5秒 #大于0.7后不再检测了,直接交给视觉控制夹脚 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break + # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) # initial_lower_weight=_current_lower_weight print(f'------------已下料(F): {first_finish_weight}kg-------------') print(f'------------已下料(F): {first_finish_weight}kg-------------') - print(f'------------已完成-------------') + # print(f'------------已完成-------------') def run_feed(self): """第一阶段下料:下料斗向模具车下料(低速)""" @@ -651,15 +710,15 @@ class VisualCallback: max_weight_none=5 cur_weight_none=0 - initial_lower_weight=loc_mitter.read_data(2) + initial_lower_weight=self._init_lower_weight # initial_upper_weight=loc_mitter.read_data(1) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return first_finish_weight=0 - need_total_weight=1.91*2416 + self._mould_need_weight=1.91*2416 + need_total_weight=self._mould_need_weight # start_time=None self.is_start_visual=True + self.state._feed_status=FeedStatus.FFeed1 + self.state.vf_status=1 if initial_lower_weight>100: #下料斗的料全部下完 self._is_feed_stage=1 @@ -673,7 +732,12 @@ class VisualCallback: return continue cur_weight_none=0 + self._is_finish_ratio=(initial_lower_weight-current_weight)/need_total_weight + print(f'------------已下料比例: {self._is_finish_ratio}-------------') if current_weight<250 and current_weight>0: + # if current_weight>100: + #100,上面粘贴的,振动一下 + # self.relay_controller.control_arch_lower_open_async(5) self.close_lower_door_visual() break time.sleep(1) @@ -687,13 +751,13 @@ class VisualCallback: print(f'------------已下料(第一次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - while self.plc_data!=5: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') + while self.plc_data not in [5,37]: + #print('------------上料斗未就位----------------') + # print('------------上料斗未就位----------------') time.sleep(1) - if self.plc_data==5: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') + if self.plc_data==5 or self.plc_data==37: + print(f'------------上料斗就位(上料斗往下料斗阶段)-------------') #打开上料斗出砼门,开5就,开三分之一下 loc_relay.control_upper_open_sync(6) @@ -716,12 +780,12 @@ class VisualCallback: _two_lower_weight=loc_mitter.read_data(2) if _two_lower_weight is None: _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: + if (current_upper_weight<3200 and current_upper_weight>0) or _two_lower_weight>3200: #关5秒,loc_time_count多关一秒 loc_relay.control_upper_close_sync(5+loc_time_count) break else: - if time.time()-upper_open_time>5: + if time.time()-upper_open_time>=4: if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(0.8) @@ -747,12 +811,15 @@ class VisualCallback: return continue cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight + self._is_finish_ratio=(first_finish_weight+initial_lower_weight-current_weight)/need_total_weight if current_weight<250: + # if current_weight>100: + #100,上面粘贴的,振动一下 + # self.relay_controller.control_arch_lower_open_async(5) self.close_lower_door_visual() break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) _current_lower_weight=loc_mitter.read_data(2) if _current_lower_weight is None: print("-------下料斗重量异常(第二次下到模)---------") @@ -762,7 +829,7 @@ class VisualCallback: print(f'------------已下料(第二次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - if self.plc_data==5: + if self.plc_data==5 or self.plc_data==37: #第二次上料斗向下料斗转移 loc_relay.control_upper_open_sync(12) loc_time_count=1 @@ -770,8 +837,8 @@ class VisualCallback: upper_open_time_2=None #第二次到下料斗还需要的量 #loc_left_need_weight=need_total_weight-first_finish_weight - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None + #initial_upper_weight=loc_mitter.read_data(1) + #start_time=None self._is_feed_stage=4 while not self._is_finish: # print(f'------------上料斗向下料斗转移22222-------------') @@ -797,9 +864,9 @@ class VisualCallback: #5秒后关闭 loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) break - time.sleep(1) + time.sleep(0.5) else: - if time.time()-upper_open_time>2: + if time.time()-upper_open_time>=1: # if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(1.2) @@ -834,13 +901,13 @@ class VisualCallback: second_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio=(second_finish_weight+first_finish_weight)/need_total_weight print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: + # if self._is_finish_ratio>=1: #关5秒 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break + # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) # _current_lower_weight=loc_mitter.read_data(2) # first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight @@ -848,7 +915,7 @@ class VisualCallback: # print(f'------------已下料: {first_finish_weight}kg-------------') - print(f'------------已完成-------------') + # print(f'------------已完成-------------') def _process_angle_callback(self, current_angle, overflow_detected): """ @@ -871,43 +938,171 @@ class VisualCallback: if current_angle is None: return - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected}") + print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected},diff_f_val:{self._diff_f_val},diff_f_area:{self._diff_f_area}") + if self._is_small_f: + if self._is_finish_ratio>=1.02: + print('重量达到最大比例,浇筑满关闭') + self._visual_close() + return + elif self._is_finish_ratio>=0.9: + if (self._diff_f_val>=427 and self._diff_f_val<=450): + print('------------diff到达浇筑满-------------') + self._visual_close() + return + elif (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=33400 and self._diff_f_area[-1]<=34500): + print('------------area到达浇筑满-------------') + self._visual_close() + return + else: + if self._is_finish_ratio>=1.01: + print('重量达到最大比例,浇筑满关闭') + self._visual_close() + return + elif self._is_finish_ratio>=0.93: + if (self._diff_f_val>=460 and self._diff_f_val<=510): + print('------------diff到达浇筑满-------------') + self._visual_close() + return + if (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=38200 and self._diff_f_area[-1]<=41000): + print('------------area到达浇筑满-------------') + self._visual_close() + return + + if overflow_detected == "未浇筑满" or self._is_before_finish: if self._before_finish_time is None: self._before_finish_time=current_time - self.safe_control_lower_close(3) + self.safe_control_lower_close(1) print('-----------------关闭(未浇筑满)--------------------') # time.sleep(3) else: + if not self._is_stop_one_seconds: + #根据角度来计算还需要多久完全关闭 + if current_angle>=20: + self.safe_control_lower_close(2) + elif current_angle>=10 and current_angle<20: + self.safe_control_lower_close(1) + elif current_angle>=6 and current_angle<10: + self.safe_control_lower_close(0.5) + self._is_stop_one_seconds=True + elif current_angle>7: + #点动状态下,如果关闭后角度大于7度,关紧 + self.safe_control_lower_close(0.2) + _open_time=0.3 + _sleep_time=0.3 + _close_time=0.5 if overflow_detected=='浇筑满': - self._visual_close() - return + if self._is_small_f: + self._visual_close() + return + else: + if self._diff_f_val>=410 and self._diff_f_val<450: + #排除这个范围的关闭 + print(f'浇筑满状态,diff_f_val:{self._diff_f_val},不关闭') + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 + else: + self._visual_close() + return + # print(f'--------已关闭已关闭-----------') elif overflow_detected=="大堆料": print(f'--------未浇筑满,大堆料-----------') - self._pulse_control('open',0.3) - time.sleep(0.3) - self._pulse_control('close',0.4) - print(f'--------比例:{self._is_finish_ratio}-----------') - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True + elif overflow_detected=="小堆料": + print(f'--------未浇筑满,小堆料-----------') + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 else: - # self._pulse_control('open',0.5) - # time.sleep(0.3) - # self._pulse_control('close',0.6) - # print(f'--------比例:{self._is_finish_ratio}-----------') - self._pulse_control('open',0.6) - time.sleep(0.3) - self._pulse_control('close',0.7) - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) + if self._is_small_f: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 else: + if self._is_finish_ratio<0.9: + _open_time=1 + _sleep_time=0.3 + _close_time=1.2 + #之前慢的参数 + # _open_time=0.8 + # _sleep_time=0.3 + # _close_time=1 + elif self._is_finish_ratio<0.95 and self._is_finish_ratio>=0.9: + if self._point_weight>=10: + _open_time=0.7 + _sleep_time=0.3 + _close_time=0.9 + else: + #之前慢的参数 + # _open_time=0.8 + # _sleep_time=0.3 + # _close_time=1 + _open_time=0.9 + _sleep_time=0.3 + _close_time=1.1 + else: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 + + if self._point_speed_grade==1: + if _open_time>0.6: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 + elif self._point_speed_grade==2: + if _open_time>0.5: + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 + elif self._point_speed_grade==3: + if _open_time>0.4: + _open_time=0.4 + _sleep_time=0.3 + _close_time=0.6 + elif self._point_speed_grade==4: + if _open_time>0.3: + _open_time=0.3 + _sleep_time=0.3 + _close_time=0.5 + _last_finish_ratio=self._is_finish_ratio + print(f'--------比例开始:{_last_finish_ratio}-----------') + self._pulse_control('open',_open_time) + time.sleep(_sleep_time) + self._pulse_control('close',_close_time) + print(f'--------比例结束:{self._is_finish_ratio}-----------') + + self._point_weight=(self._is_finish_ratio-_last_finish_ratio)*self._mould_need_weight + print(f'--------流速:{self._point_weight}-----------') + if self._is_small_f: + time.sleep(2.5) + else: + # if self._is_finish_ratio>= 0.93: + # time.sleep(2) + # print('--------重量已到95.5%,需要2秒休息-----------') + # else: time.sleep(1) - self._is_before_finish=True + #下得过快,需要2秒休息 + if self._point_weight>=65: + time.sleep(5) + self._point_speed_grade=4 + elif self._point_weight>=50 and self._point_weight<65: + time.sleep(4) + self._point_speed_grade=3 + elif self._point_weight>=35 and self._point_weight<50: + time.sleep(3) + self._point_speed_grade=2 + elif self._point_weight>=25 and self._point_weight<35: + time.sleep(2) + self._point_speed_grade=1 + elif self._point_weight>=15 and self._point_weight<25: + time.sleep(1) + self._point_speed_grade=0 + else: + self._point_speed_grade=0 + self._is_before_finish=True if self._is_finish_ratio<=self._max_ignore_radio: #如果重量未达到最大忽略角度,需要跳出 self._is_before_finish=False @@ -917,7 +1112,15 @@ class VisualCallback: return else: self._before_finish_time=None - if self._is_finish_ratio>=self._max_angle_radio or (self._is_finish_ratio>self._max_f_angle_ratio and self._is_small_f): + #2160KG + if self._is_finish_ratio>=0.85 and not self._is_small_f: + if overflow_detected == "大堆料": + TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 35.0 # 12.25由25--》35 + elif (self._is_finish_ratio>0.7 and self._is_small_f): if overflow_detected == "大堆料": TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": @@ -928,19 +1131,31 @@ class VisualCallback: if self._is_feed_stage==1 or self._is_feed_stage==3: #根据溢料状态动态调整目标角度 if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + if not self.state.mould_vibrate_status: + TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小 + else: + TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": - TARGET_ANGLE = 45.0 # 小堆料时控制在35度左右 + TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 else: + if self._is_small_f: #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 + if overflow_detected == "大堆料": + TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 else: - TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 + #根据溢料状态动态调整目标角度 + if overflow_detected == "大堆料": + TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 # 确保目标角度在硬件范围内(5-56度) TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) @@ -971,8 +1186,8 @@ class VisualCallback: # PID输出 pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " - f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") + #print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " + # f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") # 更新历史值 self._last_error = error self._last_control_time = current_time @@ -989,6 +1204,9 @@ class VisualCallback: self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) except Exception as e: + print("处理视觉回调时发生异常: ") + print("处理视觉回调时发生异常: ") + print("处理视觉回调时发生异常: ") print(f"处理视觉回调时发生异常: {e}") def _normal_mode_advanced(self, current_angle, pid_output,target_angle): @@ -1025,11 +1243,11 @@ class VisualCallback: if error > 0: # 当前角度 > 目标角度,需要关门 pulse_time=0.1 # 根据误差计算脉冲时间 self._pulse_control("close", pulse_time) - print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") + #print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") else: # 当前角度 < 目标角度,需要开门 pulse_time =0.1 self._pulse_control("open", pulse_time) - print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") + #print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") return else: self._stop_door() @@ -1051,7 +1269,7 @@ class VisualCallback: # 使用PID输出计算控制量 pulse_time = min(0.5, max(0.1, pid_output * 0.15)) self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") + # print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") else: self.angle_mode = "maintaining" print("角度已达标,进入维持模式") @@ -1091,11 +1309,11 @@ class VisualCallback: return thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") + #print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") with self._door_control_lock: self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") + # print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") if action == "open": self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') @@ -1111,7 +1329,7 @@ class VisualCallback: print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") + #print(f"[{thread_name}] 释放下料斗控制权") def _stop_door(self): """停止门运动""" @@ -1122,15 +1340,15 @@ class VisualCallback: return thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") + #print(f"[{thread_name}] 尝试停止门运动...") with self._door_control_lock: self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") + #print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") + #print(f"[{thread_name}] 释放下料斗控制权") def _open_door(self, duration=0.5): """打开门""" @@ -1145,10 +1363,10 @@ class VisualCallback: # print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}") self.plc_data=data - @classmethod - def instance_exists(cls): + # @classmethod + # def instance_exists(cls): """检测实例是否存在""" - return cls._instance is not None + # return cls._instance is not None def shutdown(self): """关闭线程,清理资源""" @@ -1176,6 +1394,86 @@ class VisualCallback: # self.relay_controller._close_lower_5s + def send_pd_data(self): + """ + 发送PD数据到OPC队列 + """ + # 构建PD数据 + _cur_mould=self._cur_mould_model + if _cur_mould is not None: + if _cur_mould.MouldCode: + _pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode) + if _pdrecords: + _pdrecord=_pdrecords[0] + if _pdrecord.TaskID: + if _pdrecord.BlockNumber=='F': + print(f'{_pdrecord.MouldCode} F块,不发送派单数据') + print(f'{_pdrecord.MouldCode} F块,不发送派单数据') + print(f'{_pdrecord.MouldCode} F块,不发送派单数据') + return True + _fact_volumn=self.get_fact_volumn(_pdrecord.MouldCode,_pdrecord.BlockNumber) + if _fact_volumn>0: + _pdrecord.FBetonVolume=_fact_volumn + print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') + print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') + print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') + self.state._pd_data=_pdrecord + return True + else: + return False + else: + print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)') + return False + else: + print(f'接口数据异常') + return False + else: + return None + def get_fact_volumn(self,mould_code:str,block_number:str='') -> float: + """获取实际派单发量""" + _now_volumn=0 + _pd_volumn=0 + print(f'get_fact_volumn当前重量:{self._init_lower_weight}') + _now_volumn=self._init_lower_weight/2500 + if not block_number and '-' in mould_code: + block_number = mould_code.split('-')[0][-2:] + if block_number in ['B1','B2','B3']: + _pd_volumn=1.9 + elif block_number=='L1': + _pd_volumn=2.0 + if _now_volumn>0.5: + #保证至少0.5方 + _pd_volumn=1.9-_now_volumn+0.5 + _pd_volumn=math.ceil(_pd_volumn*10)/10 + + if _pd_volumn<0.8: + _pd_volumn=0.8 + #调整 + elif block_number=='L2': + #2.4方,大约L2和F的量 + _pd_volumn=2.4 + # if _weight>1300: + #留0.15 math.floor(_now_volumn*10)/10 保留一位小数,丢掉其他的 + _pd_volumn=_pd_volumn-math.floor(_now_volumn*10)/10+0.1 + _pd_volumn=math.ceil(_pd_volumn*10)/10 + if _pd_volumn>2.1: + _pd_volumn=2.1 + elif _pd_volumn<0.8: + _pd_volumn=0.8 + + return _pd_volumn + + def get_current_mould(self): + """获取当前要浇筑的管片""" + _not_poured=app_web_service.get_not_pour_artifacts() + if _not_poured is not None and len(_not_poured)>=1: + _cur_poured_model=_not_poured[-1] + if _cur_poured_model.MouldCode: + self._cur_mould_model=_cur_poured_model + print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}') + else: + print('当前没有未浇筑的管片') + def __del__(self): """析构函数,确保线程安全关闭""" self.shutdown() diff --git a/vision/weight.txt b/vision/weight.txt deleted file mode 100644 index 1a54c9c..0000000 --- a/vision/weight.txt +++ /dev/null @@ -1,654 +0,0 @@ -2026-01-06 07:00:53,2026-01-06 07:06:04,B,3154 -diff , 2026-01-06 07:06:04 , 632 - -area , 2026-01-06 07:06:04 , [292.2259399848001, 223.00896842952304, 36723] - -diff , 2026-01-06 07:06:05 , 454 - -area , 2026-01-06 07:06:05 , [291.8715470887836, 223.00896842952304, 36503] - -diff , 2026-01-06 07:06:06 , 453 - -area , 2026-01-06 07:06:06 , [225.656819085974, 226.92069099136816, 36164] - -diff , 2026-01-06 07:06:07 , 456 - -area , 2026-01-06 07:06:07 , [229.40139493908924, 223.00896842952304, 36277] - -diff , 2026-01-06 07:06:08 , 457 - -area , 2026-01-06 07:06:08 , [226.947130407062, 223.00896842952304, 36278] - -diff , 2026-01-06 07:06:09 , 635 - -area , 2026-01-06 07:06:09 , [230.10649708341572, 223.8861317723811, 36710] - -diff , 2026-01-06 07:06:10 , 456 - -area , 2026-01-06 07:06:10 , [288.2568299277573, 227.343352662883, 37054] - -diff , 2026-01-06 07:06:11 , 453 - -area , 2026-01-06 07:06:11 , [227.30596120647607, 223.8861317723811, 36971] - -diff , 2026-01-06 07:06:12 , 453 - -area , 2026-01-06 07:06:12 , [230.10649708341572, 223.4390297150433, 37326] - -diff , 2026-01-06 07:06:13 , 455 - -area , 2026-01-06 07:06:13 , [233.15445524372893, 223.4390297150433, 36853] - -2026-01-06 07:24:40,2026-01-06 07:29:52,B,4623 -diff , 2026-01-06 07:29:53 , 458 - -area , 2026-01-06 07:29:53 , [288.1336495447902, 223.8861317723811, 36642] - -diff , 2026-01-06 07:29:54 , 459 - -area , 2026-01-06 07:29:54 , [226.59214461229675, 226.80828909014767, 36083] - -diff , 2026-01-06 07:29:55 , 461 - -area , 2026-01-06 07:29:55 , [284.6928169097352, 226.80828909014767, 36640] - -diff , 2026-01-06 07:29:56 , 461 - -area , 2026-01-06 07:29:56 , [226.59214461229675, 230.70760715676457, 36218] - -diff , 2026-01-06 07:29:57 , 464 - -area , 2026-01-06 07:29:57 , [281.18677067031444, 230.27375013231534, 36645] - -diff , 2026-01-06 07:29:58 , 463 - -area , 2026-01-06 07:29:58 , [280.71693928225994, 230.27375013231534, 36969] - -diff , 2026-01-06 07:29:59 , 462 - -area , 2026-01-06 07:29:59 , [277.2183255125822, 234.18368858654523, 36761] - -diff , 2026-01-06 07:30:00 , 463 - -area , 2026-01-06 07:30:00 , [219.1255347968374, 234.61031520374377, 36405] - -diff , 2026-01-06 07:30:01 , 467 - -area , 2026-01-06 07:30:01 , [222.8542124349459, 234.61031520374377, 36479] - -area , 2026-01-06 07:30:02 , [223.5799633240868, 231.1579546543878, 36852] - -diff , 2026-01-06 07:30:03 , 465 - -area , 2026-01-06 07:30:03 , [219.1255347968374, 234.61031520374377, 36812] - -补料:2 -2026-01-06 07:32:05,2026-01-06 07:36:39,B,4602 -补料:4 -2026-01-06 07:39:31,2026-01-06 07:41:51,F,1316 -补料:2 -2026-01-06 07:46:40,2026-01-06 07:51:03,B,4471 -补料:8 -2026-01-06 08:00:43,2026-01-06 08:05:26,B,4430 -补料:24 -2026-01-06 08:08:22,2026-01-06 08:13:52,B,4526 -补料:20 -2026-01-06 08:17:08,2026-01-06 08:21:25,B,4593 -补料:14 -2026-01-06 08:24:34,2026-01-06 08:29:49,B,4609 -补料:11 -2026-01-06 08:38:57,2026-01-06 08:44:04,B,4554 -diff , 2026-01-06 08:44:04 , 456 - -area , 2026-01-06 08:44:04 , [232.14219780126146, 218.8880992653552, 35193] - -diff , 2026-01-06 08:44:05 , 448 - -area , 2026-01-06 08:44:05 , [221.79495034828903, 222.80035906613796, 35124] - -diff , 2026-01-06 08:44:06 , 633 - -area , 2026-01-06 08:44:06 , [224.61077445216202, 222.80035906613796, 35255] - -diff , 2026-01-06 08:44:07 , 650 - -area , 2026-01-06 08:44:07 , [286.8449058289165, 212.9530464679949, 36314] - -diff , 2026-01-06 08:44:08 , 456 - -area , 2026-01-06 08:44:08 , [283.70583356709466, 228.70505022845472, 35764] - -diff , 2026-01-06 08:44:09 , 446 - -area , 2026-01-06 08:44:09 , [215.60380330597138, 225.94247055390008, 35407] - -diff , 2026-01-06 08:44:10 , 456 - -area , 2026-01-06 08:44:10 , [284.48198536990003, 229.65408770583642, 36252] - -diff , 2026-01-06 08:44:11 , 634 - -area , 2026-01-06 08:44:11 , [222.14409737825582, 225.94247055390008, 35237] - -diff , 2026-01-06 08:44:12 , 457 - -area , 2026-01-06 08:44:12 , [229.40139493908924, 223.4390297150433, 35324] - -diff , 2026-01-06 08:44:13 , 630 - -area , 2026-01-06 08:44:13 , [284.48198536990003, 225.94247055390008, 36088] - -补料:13 -2026-01-06 08:45:55,2026-01-06 08:49:57,B,4557 -补料:34 -2026-01-06 09:00:18,2026-01-06 09:04:15,B,4571 -diff , 2026-01-06 09:04:16 , 636 - -area , 2026-01-06 09:04:16 , [225.656819085974, 227.56098083810414, 36387] - -diff , 2026-01-06 09:04:17 , 637 - -area , 2026-01-06 09:04:17 , [222.8542124349459, 230.70760715676457, 36409] - -diff , 2026-01-06 09:04:18 , 632 - -area , 2026-01-06 09:04:18 , [229.40139493908924, 227.56098083810414, 36588] - -diff , 2026-01-06 09:04:19 , 624 - -area , 2026-01-06 09:04:19 , [219.1255347968374, 234.39496581624786, 36588] - -diff , 2026-01-06 09:04:20 , 450 - -area , 2026-01-06 09:04:20 , [226.947130407062, 230.70760715676457, 36692] - -diff , 2026-01-06 09:04:21 , 450 - -area , 2026-01-06 09:04:21 , [226.01327394646538, 227.7827912727386, 36614] - -diff , 2026-01-06 09:04:22 , 638 - -area , 2026-01-06 09:04:22 , [226.01327394646538, 227.343352662883, 36655] - -diff , 2026-01-06 09:04:23 , 627 - -area , 2026-01-06 09:04:23 , [222.8542124349459, 232.13142829009604, 36405] - -diff , 2026-01-06 09:04:24 , 642 - -area , 2026-01-06 09:04:24 , [222.8542124349459, 234.61031520374377, 36148] - -diff , 2026-01-06 09:04:25 , 451 - -area , 2026-01-06 09:04:25 , [291.7601754866486, 232.13142829009604, 36663] - -补料:10 -2026-01-06 09:07:13,2026-01-06 09:11:16,B,4589 -补料:6 -2026-01-06 09:22:40,2026-01-06 09:27:26,B,4489 -diff , 2026-01-06 09:27:27 , 630 - -area , 2026-01-06 09:27:27 , [290.6217472936256, 227.1299187689724, 35628] - -diff , 2026-01-06 09:27:28 , 625 - -area , 2026-01-06 09:27:28 , [291.7601754866486, 226.71568097509268, 35404] - -area , 2026-01-06 09:27:29 , [286.6304240655552, 227.56098083810414, 35718] - -diff , 2026-01-06 09:27:30 , 630 - -area , 2026-01-06 09:27:30 , [241.6112580158466, 223.6604569431083, 35214] - -diff , 2026-01-06 09:27:31 , 623 - -area , 2026-01-06 09:27:31 , [291.3485884640597, 223.6604569431083, 35546] - -diff , 2026-01-06 09:27:32 , 630 - -area , 2026-01-06 09:27:32 , [225.55043781824057, 226.71568097509268, 35207] - -area , 2026-01-06 09:27:33 , [224.61077445216202, 227.56098083810414, 35130] - -diff , 2026-01-06 09:27:34 , 630 - -area , 2026-01-06 09:27:34 , [237.80664414603726, 230.63390904201404, 35448] - -diff , 2026-01-06 09:27:35 , 627 - -area , 2026-01-06 09:27:35 , [295.7363690857112, 219.76350925483513, 35487] - -diff , 2026-01-06 09:27:36 , 621 - -area , 2026-01-06 09:27:36 , [287.7846417027844, 227.56098083810414, 35986] - -diff , 2026-01-06 09:27:37 , 629 - -area , 2026-01-06 09:27:37 , [224.61077445216202, 227.56098083810414, 35270] - -diff , 2026-01-06 09:27:38 , 618 - -补料:23 -2026-01-06 09:29:56,2026-01-06 09:34:04,B,4500 -补料:39 -2026-01-06 09:37:15,2026-01-06 09:41:23,B,4513 -补料:29 -2026-01-06 09:44:22,2026-01-06 09:48:35,B,4555 -补料:2 -2026-01-06 09:51:26,2026-01-06 09:55:19,B,4538 -补料:8 -2026-01-06 09:57:42,2026-01-06 09:59:52,F,1326 -补料:3 -2026-01-06 10:04:27,2026-01-06 10:10:54,B,4515 -补料:11 -2026-01-06 10:13:40,2026-01-06 10:19:42,B,4528 -补料:35 -2026-01-06 10:22:11,2026-01-06 10:26:25,B,4526 -补料:3 -2026-01-06 10:28:55,2026-01-06 10:32:54,B,4619 -补料:8 -2026-01-06 10:35:56,2026-01-06 10:40:40,B,4575 -补料:7 -2026-01-06 10:42:46,2026-01-06 10:45:18,F,1281 -补料:4 -2026-01-06 10:49:14,2026-01-06 10:53:45,B,4091 -补料:30 -2026-01-06 10:55:44,2026-01-06 10:59:57,B,4368 -补料:16 -2026-01-06 11:02:16,2026-01-06 11:07:24,B,4518 -补料:22 -2026-01-06 11:09:53,2026-01-06 11:15:08,B,4295 -补料:15 -2026-01-06 12:41:11,2026-01-06 12:47:05,B,4593 -diff , 2026-01-06 12:47:05 , 445 - -area , 2026-01-06 12:47:05 , [291.8715470887836, 218.47654336335515, 35671] - -diff , 2026-01-06 12:47:06 , 444 - -area , 2026-01-06 12:47:06 , [291.8715470887836, 218.47654336335515, 36236] - -diff , 2026-01-06 12:47:07 , 444 - -area , 2026-01-06 12:47:07 , [287.4665197896966, 222.39604313026794, 36312] - -diff , 2026-01-06 12:47:08 , 445 - -area , 2026-01-06 12:47:08 , [291.8715470887836, 218.47654336335515, 35942] - -diff , 2026-01-06 12:47:09 , 445 - -area , 2026-01-06 12:47:09 , [287.4665197896966, 222.39604313026794, 35242] - -diff , 2026-01-06 12:47:10 , 444 - -area , 2026-01-06 12:47:10 , [222.8542124349459, 220.0772591614136, 35374] - -diff , 2026-01-06 12:47:11 , 441 - -area , 2026-01-06 12:47:11 , [222.8542124349459, 220.0772591614136, 35156] - -diff , 2026-01-06 12:47:12 , 450 - -area , 2026-01-06 12:47:12 , [287.8975512226528, 219.86586820150143, 35537] - -diff , 2026-01-06 12:47:13 , 447 - -area , 2026-01-06 12:47:13 , [287.8975512226528, 222.39604313026794, 35955] - -diff , 2026-01-06 12:47:14 , 447 - -area , 2026-01-06 12:47:14 , [228.7116088002531, 219.86586820150143, 34972] - -补料:31 - -================================2026-01-06 12:57:50,2026-01-06 13:04:33,B,4616 -diff , 2026-01-06 13:04:33 , 443 - -area , 2026-01-06 13:04:33 , [295.8462438497403, 223.8861317723811, 36251] - -diff , 2026-01-06 13:04:34 , 445 - -area , 2026-01-06 13:04:34 , [295.8462438497403, 226.92069099136816, 36584] - -diff , 2026-01-06 13:04:35 , 444 - -area , 2026-01-06 13:04:35 , [295.4268098869837, 223.8861317723811, 36546] - -diff , 2026-01-06 13:04:36 , 444 - -area , 2026-01-06 13:04:36 , [295.8462438497403, 223.4390297150433, 36709] - -diff , 2026-01-06 13:04:38 , 439 - -area , 2026-01-06 13:04:38 , [291.8715470887836, 227.7827912727386, 36495] - -diff , 2026-01-06 13:04:39 , 439 - -area , 2026-01-06 13:04:39 , [295.8462438497403, 223.4390297150433, 36476] - -diff , 2026-01-06 13:04:40 , 443 - -area , 2026-01-06 13:04:40 , [295.8462438497403, 226.92069099136816, 37179] - -diff , 2026-01-06 13:04:41 , 445 - -area , 2026-01-06 13:04:41 , [291.44639301250584, 227.7827912727386, 36543] - -diff , 2026-01-06 13:04:42 , 445 - -area , 2026-01-06 13:04:42 , [291.8715470887836, 227.343352662883, 36556] - -diff , 2026-01-06 13:04:43 , 445 - -area , 2026-01-06 13:04:43 , [291.8715470887836, 227.7827912727386, 36611] - -补料:23 - -================================2026-01-06 13:06:34,2026-01-06 13:11:47,B,4620 -diff , 2026-01-06 13:11:47 , 446 - -area , 2026-01-06 13:11:47 , [295.7363690857112, 234.39496581624786, 37311] - -diff , 2026-01-06 13:11:48 , 445 - -area , 2026-01-06 13:11:48 , [219.1255347968374, 234.39496581624786, 36681] - -diff , 2026-01-06 13:11:49 , 447 - -area , 2026-01-06 13:11:49 , [222.28360263411244, 231.68297304722245, 36683] - -diff , 2026-01-06 13:11:50 , 446 - -area , 2026-01-06 13:11:50 , [222.8542124349459, 234.39496581624786, 36658] - -diff , 2026-01-06 13:11:51 , 448 - -area , 2026-01-06 13:11:51 , [226.01327394646538, 231.68297304722245, 36662] - -diff , 2026-01-06 13:11:53 , 451 - -area , 2026-01-06 13:11:53 , [222.8542124349459, 234.39496581624786, 36783] - -diff , 2026-01-06 13:11:54 , 447 - -area , 2026-01-06 13:11:54 , [300.38974682901545, 231.68297304722245, 37410] - -diff , 2026-01-06 13:11:55 , 450 - -area , 2026-01-06 13:11:55 , [295.8462438497403, 234.61031520374377, 37612] - -diff , 2026-01-06 13:11:56 , 448 - -area , 2026-01-06 13:11:56 , [299.1738624947039, 231.68297304722245, 37302] - -diff , 2026-01-06 13:11:57 , 447 - -area , 2026-01-06 13:11:57 , [299.5496619927988, 228.00877176108818, 37456] - -补料:7 - -================================2026-01-06 13:13:42,2026-01-06 13:16:06,F,1309 -diff , 2026-01-06 13:16:07 , 440 - -area , 2026-01-06 13:16:07 , [294.89828755013144, 219.10043359153812, 36251] - -diff , 2026-01-06 13:16:08 , 440 - -area , 2026-01-06 13:16:08 , [295.4268098869837, 219.10043359153812, 36394] - -diff , 2026-01-06 13:16:09 , 440 - -area , 2026-01-06 13:16:09 , [291.44639301250584, 223.00896842952304, 36546] - -diff , 2026-01-06 13:16:10 , 440 - -area , 2026-01-06 13:16:10 , [297.2742841215836, 219.53815158190613, 36711] - -diff , 2026-01-06 13:16:11 , 440 - -area , 2026-01-06 13:16:11 , [296.0760037557924, 219.53815158190613, 37139] - -diff , 2026-01-06 13:16:12 , 440 - -area , 2026-01-06 13:16:12 , [292.1044333795706, 223.4390297150433, 36799] - -diff , 2026-01-06 13:16:13 , 440 - -area , 2026-01-06 13:16:13 , [292.4790590794493, 223.4390297150433, 36876] - -diff , 2026-01-06 13:16:14 , 438 - -area , 2026-01-06 13:16:14 , [292.1044333795706, 223.4390297150433, 36701] - -diff , 2026-01-06 13:16:15 , 436 - -area , 2026-01-06 13:16:15 , [295.62983611266304, 219.53815158190613, 37240] - -diff , 2026-01-06 13:16:16 , 436 - -area , 2026-01-06 13:16:16 , [296.0760037557924, 219.53815158190613, 36894] - -补料:20 - -================================2026-01-06 13:20:23,2026-01-06 13:24:22,B,3787 -area , 2026-01-06 13:24:22 , [222.8542124349459, 219.9931817125249, 35708] - -diff , 2026-01-06 13:24:23 , 628 - -area , 2026-01-06 13:24:23 , [226.59214461229675, 223.00896842952304, 35809] - -diff , 2026-01-06 13:24:24 , 628 - -area , 2026-01-06 13:24:24 , [222.49719099350446, 226.71568097509268, 35928] - -diff , 2026-01-06 13:24:25 , 628 - -area , 2026-01-06 13:24:25 , [291.44639301250584, 219.9931817125249, 36442] - -diff , 2026-01-06 13:24:26 , 445 - -area , 2026-01-06 13:24:26 , [226.59214461229675, 223.00896842952304, 36080] - -diff , 2026-01-06 13:24:27 , 451 - -area , 2026-01-06 13:24:27 , [291.8715470887836, 223.00896842952304, 36566] - -diff , 2026-01-06 13:24:28 , 450 - -area , 2026-01-06 13:24:28 , [232.14219780126146, 226.71568097509268, 35976] - -diff , 2026-01-06 13:24:29 , 449 - -area , 2026-01-06 13:24:29 , [236.24775131204953, 223.00896842952304, 36068] - -diff , 2026-01-06 13:24:30 , 447 - -area , 2026-01-06 13:24:30 , [226.59214461229675, 223.00896842952304, 36061] - -diff , 2026-01-06 13:24:31 , 451 - -area , 2026-01-06 13:24:31 , [226.59214461229675, 223.00896842952304, 36082] - -diff , 2026-01-06 13:24:32 , 448 - -area , 2026-01-06 13:24:32 , [228.7116088002531, 226.92069099136816, 36133] - -补料:14 - -================================2026-01-06 13:27:18,2026-01-06 13:31:49,B,4587 -area , 2026-01-06 13:31:50 , [225.656819085974, 226.71568097509268, 36039] - -diff , 2026-01-06 13:31:51 , 446 - -area , 2026-01-06 13:31:51 , [288.1336495447902, 227.1299187689724, 36865] - -diff , 2026-01-06 13:31:52 , 446 - -area , 2026-01-06 13:31:52 , [229.7520402520944, 223.00896842952304, 36074] - -diff , 2026-01-06 13:31:53 , 447 - -area , 2026-01-06 13:31:53 , [222.8542124349459, 226.71568097509268, 36004] - -diff , 2026-01-06 13:31:54 , 447 - -area , 2026-01-06 13:31:54 , [288.1336495447902, 226.71568097509268, 36669] - -diff , 2026-01-06 13:31:55 , 449 - -area , 2026-01-06 13:31:55 , [298.51130631853795, 215.5388596054085, 36684] - -diff , 2026-01-06 13:31:56 , 449 - -area , 2026-01-06 13:31:56 , [287.8975512226528, 226.92069099136816, 36957] - -diff , 2026-01-06 13:31:57 , 449 - -area , 2026-01-06 13:31:57 , [292.2259399848001, 223.00896842952304, 36961] - -diff , 2026-01-06 13:31:59 , 450 - -area , 2026-01-06 13:31:59 , [288.1336495447902, 223.6604569431083, 36748] - -diff , 2026-01-06 13:32:00 , 450 - -area , 2026-01-06 13:32:00 , [291.2541845192958, 218.27734651126764, 36470] - -diff , 2026-01-06 13:32:01 , 451 - -area , 2026-01-06 13:32:01 , [291.44639301250584, 218.68013169924697, 36329] - -补料:23 - -================================2026-01-06 13:34:34,2026-01-06 13:38:21,B,4445 -area , 2026-01-06 13:38:22 , [226.59214461229675, 223.00896842952304, 36074] - -diff , 2026-01-06 13:38:23 , 446 - -area , 2026-01-06 13:38:23 , [226.59214461229675, 223.00896842952304, 36009] - -diff , 2026-01-06 13:38:24 , 446 - -area , 2026-01-06 13:38:24 , [255.93163149560078, 223.8861317723811, 36109] - -diff , 2026-01-06 13:38:25 , 445 - -area , 2026-01-06 13:38:25 , [259.763353843455, 226.92069099136816, 36393] - -diff , 2026-01-06 13:38:26 , 447 - -area , 2026-01-06 13:38:26 , [226.59214461229675, 223.4390297150433, 36062] - -diff , 2026-01-06 13:38:27 , 447 - -area , 2026-01-06 13:38:27 , [219.49259668608414, 223.8861317723811, 36108] - -diff , 2026-01-06 13:38:28 , 447 - -area , 2026-01-06 13:38:28 , [222.8542124349459, 223.8861317723811, 36100] - -diff , 2026-01-06 13:38:29 , 447 - -area , 2026-01-06 13:38:29 , [222.8542124349459, 223.8861317723811, 36432] - -diff , 2026-01-06 13:38:30 , 447 - -area , 2026-01-06 13:38:30 , [227.30596120647607, 220.4654167891191, 36374] - -diff , 2026-01-06 13:38:31 , 449 - -area , 2026-01-06 13:38:31 , [226.59214461229675, 223.00896842952304, 36393] - -diff , 2026-01-06 13:38:32 , 449 - -area , 2026-01-06 13:38:32 , [222.49719099350446, 223.8861317723811, 36231] - -补料:13 - -================================2026-01-06 13:41:18,2026-01-06 13:46:19,B,4585 -diff , 2026-01-06 13:46:19 , 445 - -area , 2026-01-06 13:46:19 , [299.81661061388843, 217.55459085020476, 36444] - -diff , 2026-01-06 13:46:20 , 446 - -area , 2026-01-06 13:46:20 , [296.98484809834997, 223.4390297150433, 36393] - -diff , 2026-01-06 13:46:21 , 569 - -area , 2026-01-06 13:46:21 , [222.8542124349459, 223.8861317723811, 36042] - -diff , 2026-01-06 13:46:22 , 569 - -area , 2026-01-06 13:46:22 , [293.025596151599, 227.343352662883, 36691] - -diff , 2026-01-06 13:46:23 , 569 - -area , 2026-01-06 13:46:23 , [288.51343122981297, 231.0411218809327, 36527] - -diff , 2026-01-06 13:46:24 , 446 - -area , 2026-01-06 13:46:24 , [292.6106628269038, 223.8861317723811, 36411] - -diff , 2026-01-06 13:46:25 , 446 - -area , 2026-01-06 13:46:25 , [255.93163149560078, 227.343352662883, 36103] - -diff , 2026-01-06 13:46:26 , 449 - -area , 2026-01-06 13:46:26 , [241.6112580158466, 227.56098083810414, 35767] - -diff , 2026-01-06 13:46:27 , 450 - -area , 2026-01-06 13:46:27 , [293.025596151599, 227.343352662883, 36360] - -diff , 2026-01-06 13:46:28 , 570 - -area , 2026-01-06 13:46:28 , [252.10513679812237, 231.25094594401122, 35970] - -补料:5 - -================================2026-01-06 13:48:52,2026-01-06 13:53:47,B,3040 -area , 2026-01-06 13:53:48 , [253.3081127796739, 234.61031520374377, 39025] - -area , 2026-01-06 13:53:49 , [226.01327394646538, 239.40342520523802, 39706] - -area , 2026-01-06 13:53:50 , [278.4977558257876, 239.17566765873153, 39501] - -area , 2026-01-06 13:53:51 , [226.37358503146962, 242.42524621004307, 39117] - -area , 2026-01-06 13:53:52 , [277.09384691833196, 239.17566765873153, 39129] - -area , 2026-01-06 13:53:53 , [230.10649708341572, 239.17566765873153, 39036] - -area , 2026-01-06 13:53:54 , [233.8482413874434, 235.74774654278247, 38873] - -diff , 2026-01-06 13:53:55 , 484 - -area , 2026-01-06 13:53:55 , [237.59840066801797, 235.74774654278247, 40196] - -diff , 2026-01-06 13:53:56 , 484 - -area , 2026-01-06 13:53:56 , [227.1057022621845, 243.0740627874558, 39249] - -diff , 2026-01-06 13:53:57 , 479 - -area , 2026-01-06 13:53:57 , [227.1057022621845, 243.0740627874558, 39206] - -diff , 2026-01-06 13:53:58 , 480 - -area , 2026-01-06 13:53:58 , [227.1057022621845, 243.0740627874558, 39102] - -diff , 2026-01-06 13:53:59 , 485 - -diff , 2026-01-06 13:54:00 , 484 - -diff , 2026-01-06 13:54:01 , 484 - -diff , 2026-01-06 13:54:02 , 484 - -diff , 2026-01-06 13:54:03 , 483 - -diff , 2026-01-06 13:54:04 , 481 -