From 26ed8df50234f58f00c99357c60eb842e30f6868 Mon Sep 17 00:00:00 2001 From: fujinliang Date: Fri, 21 Nov 2025 14:55:52 +0800 Subject: [PATCH] 1121 --- .gitignore | 2 + busisness/blls.py | 2 +- busisness/models.py | 38 ++++ config/ini_manager.py | 24 +++ config/settings.py | 12 +- controller/main_controller.py | 9 +- core/state.py | 9 +- core/system.py | 172 ++++++++++----- db/messages.db | Bin 12288 -> 12288 bytes db/three.db | Bin 53248 -> 53248 bytes db/three.db-shm | Bin 32768 -> 0 bytes db/three.db-wal | 0 doc/~$ble表设计.doc | Bin 162 -> 0 bytes doc/~$宝-生产信息接口文档-20250911.doc | Bin 162 -> 0 bytes doc/~$系统对接接口文档-20251020.doc | Bin 162 -> 0 bytes doc/控制程序对接.docx | Bin 0 -> 19357 bytes doc/控制程序对接文档.docx | Bin 17376 -> 0 bytes feeding/__pycache__/controller.cpython-39.pyc | Bin 5478 -> 5142 bytes feeding/__pycache__/process.cpython-39.pyc | Bin 8539 -> 8620 bytes feeding/controller.py | 77 +++---- feeding/process.py | 183 +++++++++------- hardware/RFID/command_hex.py | 12 +- hardware/RFID/rfid_service.py | 2 +- hardware/relay.py | 64 ++++-- hardware/transmitter.py | 130 +++++------ main.py | 74 ++++--- opc/opcua_client_subscription.py | 203 ++++++++++++++++++ opc/opcua_client_test.py | 30 ++- opc/opcua_server.py | 26 +-- service/mould_service.py | 157 +++++++++----- settings.ini | 5 + tests/test_feeding_process.py | 13 +- tests/test_rfid.py | 12 +- vision/camera.py | 34 ++- vision/detector.py | 49 ++--- vision/roi_coordinates/1_rois.txt | 2 +- 36 files changed, 908 insertions(+), 433 deletions(-) delete mode 100644 db/three.db-shm delete mode 100644 db/three.db-wal delete mode 100644 doc/~$ble表设计.doc delete mode 100644 doc/~$宝-生产信息接口文档-20250911.doc delete mode 100644 doc/~$系统对接接口文档-20251020.doc create mode 100644 doc/控制程序对接.docx delete mode 100644 doc/控制程序对接文档.docx create mode 100644 opc/opcua_client_subscription.py diff --git a/.gitignore b/.gitignore index 62b0bd5..054d570 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ PySide2_Fluent_Widgets.egg-info/ __pycache__ /core/__pycache__ /vision/__pycache__ +/opcua/__pycache__ +/feeding/__pycache__ diff --git a/busisness/blls.py b/busisness/blls.py index f4f8153..098e8c4 100644 --- a/busisness/blls.py +++ b/busisness/blls.py @@ -60,7 +60,7 @@ class ArtifactBll: def get_artifacting_task(self) -> ArtifactInfoModel: """获取正在进行的管片任务数据""" - loc_item= self.dal.get_top_artifact(1,"","Status=2") + loc_item= self.dal.get_top_artifact(1,"ID desc","Status=2") if loc_item: return loc_item[0] else: diff --git a/busisness/models.py b/busisness/models.py index 6b9b36f..e0b68d3 100644 --- a/busisness/models.py +++ b/busisness/models.py @@ -308,3 +308,41 @@ class PDRecordModel: CreateTime: str="" #派单时间(下发) OptTime: str="" + +@dataclass +class LEDInfo: + """LED信息模型""" + # 任务单号 + TaskID: str + # 盘方量 + PlateVolume: str + # 模具编号 + MouldCode: str + # 生产投料时间 + ProduceStartTime: str + # 管片编号 + ArtifactID: str + # 环类型编码(无) + RingTypeCode: str + # 累计盘次 + PlateIDSerial: str + # 检验结果 + CheckResult: str + # 高斗称值(无) + UpperWeight:float + # 砼出料温度温度 + Temper: str + # 车间温度 + WorkshopTemperature: str + # 低桶称量值 + LowBucketWeighingValue: str + # 振动频率 + VibrationFrequency: str + # 总计量 + TotMete: str + # 已浇筑方量 + BetonVolumeAlready: str + # 水温 + WaterTemperature: str + # 配方比例 + FormulaProportion: str \ No newline at end of file diff --git a/config/ini_manager.py b/config/ini_manager.py index f58499c..4f62c01 100644 --- a/config/ini_manager.py +++ b/config/ini_manager.py @@ -141,7 +141,31 @@ class IniManager: def log_path(self): """获取日志文件路径""" return self._read_config_value('app', 'LOG_PATH', 'logs/app.log', str) + + @property + def opcua_endpoint(self): + """获取OPC UA服务器端点""" + return self._read_config_value('app', 'OPCUA_ENDPOINT', 'opc.tcp://192.168.250.64:4840/zjsh_feed/server/', str) + @property + def upper_transmitter_ip(self): + """获取上料斗变送器IP""" + return self._read_config_value('app', 'UPPER_TRANSMITTER_IP', '192.168.250.63', str) + + @property + def upper_transmitter_port(self): + """获取上料斗变送器端口""" + return self._read_config_value('app', 'UPPER_TRANSMITTER_PORT', 502, int) + + @property + def lower_transmitter_ip(self): + """获取下料斗变送器IP""" + return self._read_config_value('app', 'LOWER_TRANSMITTER_IP', '192.168.250.66', str) + + @property + def lower_transmitter_port(self): + """获取下料斗变送器端口""" + return self._read_config_value('app', 'LOWER_TRANSMITTER_PORT', 8234, int) ini_manager = IniManager() diff --git a/config/settings.py b/config/settings.py index f86cc91..3c26960 100644 --- a/config/settings.py +++ b/config/settings.py @@ -10,7 +10,9 @@ class Settings: self.relay_host = '192.168.250.62' self.relay_port = 50000 - self.debug_feeding=True + self.debug_feeding=False + #调试模式上,网络继点器和摄像头禁用,模型推理禁用 + self.debug_mode=False # 摄像头配置 self.camera_type = "ip" @@ -46,7 +48,7 @@ class Settings: self.single_batch_weight = 2500 # 单次下料重量(kg) # 角度控制参数 - self.target_angle = 20.0 # 目标角度 + self.target_angle = 30.0 # 目标角度 self.min_angle = 10.0 # 最小角度 self.max_angle = 80.0 # 最大角度 self.angle_threshold = 60.0 # 角度阈值 @@ -83,5 +85,9 @@ class Settings: self.max_upper_volume = 2.4 # 上料斗容量(方) #下料到下料斗最大下到多少,并非最大容量 self.max_lower_volume = 2.4 # 下料斗容量(方) - + + #led + self.led_interval = 2 # LED闪烁间隔(秒) + +app_set_config = Settings() diff --git a/controller/main_controller.py b/controller/main_controller.py index 1eb4045..31be6ca 100644 --- a/controller/main_controller.py +++ b/controller/main_controller.py @@ -7,7 +7,6 @@ from .bottom_control_controller import BottomControlController from .hopper_controller import HopperController from service.msg_recorder import MessageRecorder -from config.settings import Settings from core.system import FeedingControlSystem from opc.opcua_server import SimpleOPCUAServer @@ -15,8 +14,7 @@ class MainController: def __init__(self): # 主界面 self.main_window = MainWindow() - self.settings = Settings() - self.system = FeedingControlSystem(self.settings) + self.system = FeedingControlSystem() self.system.initialize() self.system.state.state_updated.connect(self.update_ui_notify) self.opcua_server = SimpleOPCUAServer(self.system.state) @@ -63,7 +61,7 @@ class MainController: def update_ui_notify(self, prop:str,value): """更新UI状态""" - print(f"更新UI状态: {prop} = {value}") + # print(f"更新UI状态: {prop} = {value}") def __connectSignals(self): self.main_window.about_to_close.connect(self.handleMainWindowClose) # 处理主界面关闭 @@ -71,6 +69,9 @@ class MainController: def handleMainWindowClose(self): """主界面关闭""" self.msg_recorder.normal_record("关闭自动智能浇筑系统") + self.system.stop() + self.opcua_server.stop() + # 停止系统底部控制器中的线程 if hasattr(self, 'bottom_control_controller'): self.bottom_control_controller.stop_threads() \ No newline at end of file diff --git a/core/state.py b/core/state.py index 58d4425..7ceb71a 100644 --- a/core/state.py +++ b/core/state.py @@ -23,7 +23,7 @@ class SystemState(QObject): self._upper_volume=0.0 #下料斗状态想着 - self._lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 + self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 self._lower_is_arch_=False self._lower_weight=0 self._lower_angle=0.0 @@ -69,11 +69,8 @@ class SystemState(QObject): #当前生产状态 self._feed_status=FeedStatus.FNone #每方重量 - self.density=2500 - - - - # + self.density=2416.4 + # self._watched_props = [k for k in self.__dict__ if k.startswith('_')] def __setattr__(self, name, value): diff --git a/core/system.py b/core/system.py index d8bea07..0f41802 100644 --- a/core/system.py +++ b/core/system.py @@ -2,7 +2,6 @@ import threading import time import cv2 -from config.settings import Settings from core.state import SystemState from hardware.relay import RelayController from hardware.inverter import InverterController @@ -11,31 +10,32 @@ 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, settings: Settings): - self.settings = settings + def __init__(self): self.state = SystemState() # 初始化硬件控制器 self.relay_controller = RelayController( - host=settings.relay_host, - port=settings.relay_port + 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(settings.camera_configs) + self.camera_controller = DualCameraController(app_set_config.camera_configs) - self.vision_detector = VisionDetector(settings) + self.vision_detector = VisionDetector() # 初始化RFID控制器 self.rfid_controller = rfid_service( - host=settings.rfid_host, - port=settings.rfid_port + host=app_set_config.rfid_host, + port=app_set_config.rfid_port ) # 初始化下料控制器 self.feeding_controller = FeedingController( @@ -45,8 +45,7 @@ class FeedingControlSystem: self.vision_detector, self.camera_controller, self.rfid_controller, - self.state, - settings + self.state ) @@ -56,6 +55,7 @@ class FeedingControlSystem: self.visual_control_thread = None self.alignment_check_thread = None self.lower_feeding_thread = None + self.led_thread = None def initialize(self): """初始化系统""" @@ -63,12 +63,12 @@ class FeedingControlSystem: # 设置摄像头配置 # self.camera_controller.set_config( - # camera_type=self.settings.camera_type, - # ip=self.settings.camera_ip, - # port=self.settings.camera_port, - # username=self.settings.camera_username, - # password=self.settings.camera_password, - # channel=self.settings.camera_channel + # camera_type=app_set_config.camera_type, + # ip=app_set_config.camera_ip, + # port=app_set_config.camera_port, + # username=app_set_config.camera_username, + # password=app_set_config.camera_password, + # channel=app_set_config.camera_channel # ) # # 初始化摄像头 @@ -78,30 +78,33 @@ class FeedingControlSystem: # 加载视觉模型 # if not self.vision_detector.load_models(): # raise Exception("视觉模型加载失败") - if not self.settings.debug_feeding: - if not self.check_device_connectivity(): - raise Exception("设备连接失败") - self.camera_controller.start_cameras() + + self.check_device_connectivity() + + # self.camera_controller.start_cameras() + # if not app_set_config.debug_feeding: # 启动系统监控(要料,破拱)线程 - self.start_monitoring() + self.start_monitoring() + # 启动视觉控制(角度、溢出)线程 + # self.start_visual_control() - # 启动视觉控制(角度、溢出)线程 - self.start_visual_control() - - # 启动对齐检查线程 - self.start_alignment_check() + # 启动对齐检查线程 + # self.start_alignment_check() # 启动下料线程 self.start_lower_feeding() + #LED屏 + # self.start_led() print("控制系统初始化完成") def start_monitoring(self): """启动系统监控""" - self.state.running = True + print('振动和要料监控线程启动') self.monitor_thread = threading.Thread( target=self._monitor_loop, - daemon=True + daemon=True, + name='monitor' ) self.monitor_thread.start() @@ -109,7 +112,7 @@ class FeedingControlSystem: """监控循环""" while self.state.running: try: - self.feeding_controller.check_upper_material_request() + # self.feeding_controller.check_upper_material_request() self.feeding_controller.check_arch_blocking() time.sleep(1) except Exception as e: @@ -117,9 +120,11 @@ class FeedingControlSystem: def start_visual_control(self): """启动视觉控制""" + print('视觉控制线程启动') self.visual_control_thread = threading.Thread( target=self._visual_control_loop, - daemon=True + daemon=True, + name='visual_control' ) self.visual_control_thread.start() @@ -132,37 +137,79 @@ class FeedingControlSystem: if current_frame is not None: # 执行视觉控制逻辑 self.feeding_controller.visual_control(current_frame) - time.sleep(self.settings.visual_check_interval) + time.sleep(app_set_config.visual_check_interval) except Exception as e: print(f"视觉控制循环错误: {e}") - time.sleep(self.settings.visual_check_interval) + 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 + 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: # 等待对齐阶段 + 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.vision_detector.detect_vehicle_alignment(current_frame) + self.state.vehicle_aligned = self.alignment_check_status() if self.state.vehicle_aligned: + # loc_count+=1 print("检测到模具车对齐") else: print("模具车未对齐") - else: - print('未检测到图像') - time.sleep(self.settings.alignment_check_interval) + # 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}") - time.sleep(self.settings.alignment_check_interval) + 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): """启动下料流程""" @@ -177,7 +224,34 @@ class FeedingControlSystem: """启动下料流程""" while self.state.running: self.feeding_controller.start_feeding() - time.sleep(self.settings.lower_feeding_interval) + 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 and self.state.current_artifact: + 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: """检查关键设备连接状态""" @@ -194,15 +268,15 @@ class FeedingControlSystem: return False # 尝试读取变频器一个寄存器(测试连接) - test_result = self.relay_controller.modbus_client.read_holding_registers( - address=0x00, - count=1, - slave=self.inverter_controller.config['slave_id'] - ) + # 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 + # if isinstance(test_result, Exception): + # print("变频器连接测试失败") + # return False # 检查下料斗变送器连接 test_weight = self.transmitter_controller.read_data(2) diff --git a/db/messages.db b/db/messages.db index 20aab85040a59386ffa939aa3c2d34966e22dbd2..f16024440bbbcff67bd13f826ecf38e0c5ed72eb 100644 GIT binary patch delta 645 zcmZ9{J5Iwu5P)F?{7MB9B483@^Kcr7<=KZFPfI~D4M(uJ0VSfz1(1Mn1X_eF?tr*O z#5Jh!u0qz{_Sd&Fvx|CBKaB^|=gHMz+SG45+o`i!`nwm&@OHQ#d|l1t8-rXL1HG1C!`nn|T6E8BuPGNpxlm2L#zS zI?<^ytUzA$jZSpZF&x?4GAdEhF$|foj7k)DOpe^Thp9xz#z2L_nxhj%|EB%8Kv8vf ztrJZ;CP%?6qY{mc;Uz+E7objbq)nMXfkL7*#v}?mMj*G1N_6-VWwCz41L=dli3BdHx|}%ZeA}LAjT*P0L$qLk^lez diff --git a/db/three.db b/db/three.db index 047574e62914218770eae901ee637e21b532ea35..f3c3c061213ad883db65d77abaa922361b87bbae 100644 GIT binary patch delta 206 zcmZozz}&Ead4rigD;EO;!^w$_XEw7N{OuRv;EH7A>EmPIjpone+rfR1n};iMGoyeb zmtUg+mn4Han=F&Gx}dzIAiFXLCnG0^BZH%`u&{fSf{&50frXKYfrT3b)1t@}&H`6= z0|OH?Bey8VbHN@_K1RAGFjdCJ7N(O|o>g=)G&I#UFk~_^Ff!FOG}JXTS1>fSGBvg` nHPtgUGB7bPLX$DEGBDM%Ff=tZGI%y){nH7{fM_$%=`Sn*fKW4} delta 52 zcmV-40L%Y?paX!Q1F$wP1qlEE0LhVo%(DkD{f`m{3k(4ajt&3~TMupyxC_Ax46^|c K_6oBM%k%`MJrFbi diff --git a/db/three.db-shm b/db/three.db-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3<5~lzF diff --git a/doc/~$宝-生产信息接口文档-20250911.doc b/doc/~$宝-生产信息接口文档-20250911.doc deleted file mode 100644 index 7018ce26fd80327f93bbf1af55e4201cb8bd5bbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 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 W76bd#P5=P*^TYG0;6H``DE%KPY)&Zv literal 0 HcmV?d00001 diff --git a/doc/控制程序对接文档.docx b/doc/控制程序对接文档.docx deleted file mode 100644 index e2292ddc6f796575ca970a641ea87888289297d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17376 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 ZW>i52;zK?D_(%XOp!dVn!U+9Q`ac8OzPbPa diff --git a/feeding/__pycache__/controller.cpython-39.pyc b/feeding/__pycache__/controller.cpython-39.pyc index ff4f14368e58c1de770681cdd4c16dad9f9610c3..54fd2150436989877eacd250388b46dfd16b50b4 100644 GIT binary patch delta 1896 zcmb7FO>7%Q6rP#g^^WcJI(FbsJI{`jv)2Q-jLFS+>j7LJ;8fp69QEa*xJvt@4fln zoA=&4FYTP$ktt^~DFUCncQ#krvzblw$m~Op4htfzx_yqH;*>l~L_*l_5@AoXQ%OOO zlMu1y`dnb)i215YEP`B9tHPteU2S0)L zh*U|33OY~Uq7fCWP2{-x$=LB~XjKz6OCAkPVNO#DXF3jN0dwXVh*dk})nv_(??n`B zQ#B$lgRPCW5$UAo3GCs=Nu!{x9f7u&MN%_3s%(C$9P9DnIMsaJmtMX3hA&GQnS_wy zr1Z1stLYs#Y_ogq3DP zS&5g6F6GoTICE(VR-7i7L%BMixNB_&#zU%UpO`XWWx}KLd>a-a>5zE>*&tO~WuZ|u zYfOF~T7q_uR!wwl!608OV!~)s&~c~+S|Jv~o#NI!JlLt7Zo z7LIx@xoJ=P0XW>^t&2-@mzL%}Ui#qror??0-+a5g@cx~d>&qW4EZ+KRWMo7RfYsQF z09>)uV)Eg!6S3KB`Hk3`s5b-unH2`AQeSL_m2D07fMU4`0sBlBQVzl@Hp-`i zI9D$RA=XJ9O>O9pv%Qj40pdti{s-@AB(M^?)$Bhp?*#km6_f(@&eEV z+W-jT{a90|A}dh$PsXsSd8lq4B;4pjnR+`tN^hy}($90aBG|9CN;&(d_!V+ZmjGo| zF48qNL*;11W{CVHGQuSEu!(CZmD4MAy5oR#x|$IgtOBlk5?8poE?Mn?H(t^VSzw@Y zXpPd`Uks6(0at@9_<$^ktSwF?s zFs!=1$MYdLVqav4#1&KYUo-AG?0sSp*tN{#jjwwV;}%l)c>Y}fIB&y z6^&Cp(xN84iKOy0kPhf;lkd6Si*D}gW&c0r(FI$Lcq458TemhZc|E4^lB~ON(>+KN z+o8+kHuXpL_n}h1#JL$~g&+D8p@)O&3=Aq5O6lBVw$mM2>WSQjjokrNZcNsFFFY&# z;B2!lB#v)6qR!_k#ke1)d?=gs?j}#)CAF9v8*PJ8PzNAZA2c?$Hw-O}!|)54!7m#& zT$YD+&ttor4$IR5JOd1*D$MV>@nwEHJ=P5#s3wt*kY5jvmF(_)?8X8zc>v)cLJ46P zLJ=Xi3VB>T3Gk!ZQkXu9!&2@>z#*v>YmD_ay(0ly9N>Wq0LLhD&e!n_-^ou()Rj`Q lq<$+L9*ncy|GW|S`wJ_L$v|QPWkT&OZgVl`c*XVC_zNei!rK4< delta 2237 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 bZjg;QObux650384_U0gDvCRjC5T)>cwwaVO delta 4023 zcmb_fU2Ggz6`ngYJ3Bi&`{VU`{g*hl<2czk35naJBv2rZV^fqmPMoGR1xAkVB-ypS z>)aiuEsVPo<_T4z(G@~aO4kyUA4U8K3Gr0%$U{`ANboR4h)6{!5<&tY9^i%V+*#Y( zD7+wcG-vP6Irp4<&v(Cj=ia&Zr?QKgjHBUK`CEVG7iaHg_ps}CCl-pc8n=1sD;iI2 z>epOuEof!u?sq(yV_zSzNx%K0vX1v0-o~7Kht1~0WT<&ruE9i~yHd`{oc;|vF*8@M z`B7T_Odptx?1mo%mDUvSN%kDV_vg6_0IbTBudE4HmL03-qe7tcxN1n<4WUMc8gP(f&COEiVCmtC|#_ zf|gBMWM55*dDI=M2WhTvGNyG{+pxZmc4Lz@nHIRM3^%u+0@G|Ox20|9p)P(E>O9e} zZ5Z4N4KE*bwF+ArO5M;Zwt7NCSs3KbD=BAGVl|tqDR`w0hrP@C>r70;Wm1J=ZJio)8grkBzJ>8uU|!8f?~E~xI@9fCV~hPa6K4OD0`4E!`mD5Rr~Re_t6kK96QSK zm*F@=j@4AN6gWH#(dsnOl~a!Qy`jDN=t2#_To=R8$&)$shFc@t+MjR9_rREr@R`1*5xCuLl-{z^UABd19xf*m@Epl&YgA z@plUE*r3*)2j)$9_iV|z0QVSw8sI2-?qOs!p#+pdWWa>0?b0x&mGxC$SmZSIY<+!&pR03U{w8&Nf5BU&x>rMZ-b;Sv=B+?cN|X}*BKP=W zuqc^x)J}50)~E!p$fM3rjuG)h$)$QN5cL(sMUgvw@#1Cg!o}y$U-nLXs5U7l(x;g% z{q$k^y)n8r`Q9a@rtk!`IpEvD%T2&a9${J_Kze=v0{E zxn&K(^ zw7aIyQ9j?o+aL#;nI}Xc>yBQ~3&0T}#Z`!Nn2r_~Ko|gy3WOcByFicpTGu?gCjZzq zp1O)zvZ^Vr%3Pt}z8W$$Z+WaR%lhS=!tsegI4P&%xYWEx(V)T?^He%Of&#{&{9fSz zf+UaUERl@>JNwnHgkS86c8eZX21Qsf^x+k+WmO zsxDy~rdd#Cj;9BKZ>7n^yJ*RViRDD&5Ak@R0nEAB5&sPG)h^f+aFR2xn`dy{VH5=P zid|fthg2=+)<2GQl5Z7<oIPz4>qmX@Ho;ip(_t#~7L*HP9I7 zP5~7)7eE}9PnC|~cC`B=0@@*=-=TtZY2re_cChm})L1Pd8Vg7<+PK(mNyXUVxWRc^ z;Ite-fGCZqsEwPZ!ax8QJHQDt$TIjX$ap!F^E@@EaVzFPXfx45YYt49X#6xDG-SYm z104K1=&BZA*UeLqMX(hdD;zkj9?(b|nq^nP;LrE&eIU>F)|!;#IQ7FQ&4>ZWIq@8| zUM2B7#FcVMsQfE>P}kzmQXieN;sp{a+mGRx4r-wk3YGlMyf@MKH1(*&@8;c%^L~lC zu90|=L}^F#Qi<@4-KaIjQP6On*{n#3x9QB=O?OrwMGu{2rvAm@nfRhGFX~Hvqaj;; z-I-1t7;>%D)BHc>y}jkJ_;lyz7I>5i|-_=pMIYK9URxLy|J9N2`k*lF)4PYt z=Sj7DeQmAdHn~(^$0cl*+&zLl4{g`w{h{BVrd5t`f4i}`z7oupt(Y%WG!$3lJ~uy) zTicb3-e;yR#2=7P(SWl!s*ba&EDYaj&QQarNl?IIfy5aS1aU!2Bd(Jmui^%Y-EdS% z?FahWB){sb6lZal{iPWUgU&1c-R{YR;{vY|OK?MK+ diff --git a/feeding/controller.py b/feeding/controller.py index cb7e9fa..327cb62 100644 --- a/feeding/controller.py +++ b/feeding/controller.py @@ -3,12 +3,13 @@ import time from core.state import FeedStatus from feeding.process import FeedingProcess from busisness.blls import ArtifactBll +from config.settings import app_set_config class FeedingController: def __init__(self, relay_controller, inverter_controller, transmitter_controller, vision_detector, - camera_controller, rfid_controller,state, settings): + camera_controller, rfid_controller,state): self.relay_controller = relay_controller self.inverter_controller = inverter_controller self.transmitter_controller = transmitter_controller @@ -16,14 +17,13 @@ class FeedingController: self.camera_controller = camera_controller self.rfid_controller = rfid_controller self.state = state - self.settings = settings self.artifact_bll = ArtifactBll() # 初始化下料流程 self.process = FeedingProcess( relay_controller, inverter_controller, transmitter_controller, vision_detector, - camera_controller, state, settings + camera_controller, state ) @@ -42,19 +42,21 @@ class FeedingController: if current_weight is None: self.state.upper_weight_error_count += 1 print(f"上料斗重量读取失败,错误计数: {self.state.upper_weight_error_count}") - if self.state.upper_weight_error_count >= self.settings.max_error_count: + if self.state.upper_weight_error_count >= app_set_config.max_error_count: print("警告:上料斗传感器连续读取失败,请检查连接") return False #需要搅拌楼通知下完料后移到上料斗上方 - if self.state._upper_door_position != 'over_lower': - self.state._upper_door_position = 'over_lower' + self.state.upper_weight_error_count = 0 # 判断是否需要要料:当前重量 < 目标重量 + 缓冲重量 - if self.state.feed_status != FeedStatus.FUpperToLower: - if current_weight < (self.settings.min_required_weight): + if self.state._feed_status != FeedStatus.FUpperToLower: + if current_weight < (app_set_config.min_required_weight): print("上料斗重量不足,通知搅拌楼要料") self.request_material_from_mixing_building() # 请求搅拌楼下料 return True + else: + if self.state._upper_door_position != 'over_lower': + self.state._upper_door_position = 'over_lower' return False def request_material_from_mixing_building(self): @@ -62,7 +64,7 @@ class FeedingController: 请求搅拌楼下料 """ print("发送要料请求至搅拌楼...") - # self.settings. + # self.process.return_upper_door_to_default() @@ -73,35 +75,35 @@ class FeedingController: def check_arch_blocking(self): """检查是否需要破拱""" current_time = time.time() - + # 检查下料斗破拱(只有在下料过程中才检查) - if self.state._lower_feeding_stage in [1, 2, 3]: # 在所有下料阶段检查 - lower_weight = self.transmitter_controller.read_data(2) - if lower_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(lower_weight - self.state.last_lower_weight) < 0.1) and \ - (current_time - self.state.last_weight_time) > 10: - print("下料斗可能堵塞,启动破拱") - self.state._lower_is_arch_=True - self.relay_controller.control(self.relay_controller.BREAK_ARCH_LOWER, 'open') - time.sleep(2) - self.relay_controller.control(self.relay_controller.BREAK_ARCH_LOWER, 'close') - self.state._lower_is_arch_=False + # if self.state.lower_feeding_stage in [1, 2, 3]: # 在所有下料阶段检查 + # lower_weight = self.transmitter_controller.read_data(2) + # if lower_weight is not None: + # # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) + # if (abs(lower_weight - self.state.last_lower_weight) < 0.1) and \ + # (current_time - self.state.last_weight_time) > 10: + # print("下料斗可能堵塞,启动破拱") + # self.state._lower_is_arch_=True + # self.relay_controller.control(self.relay_controller.BREAK_ARCH_LOWER, 'open') + # time.sleep(2) + # self.relay_controller.control(self.relay_controller.BREAK_ARCH_LOWER, 'close') + # self.state._lower_is_arch_=False - self.state.last_lower_weight = lower_weight + # self.state.last_lower_weight = lower_weight # 检查上料斗破拱(在上料斗向下料斗下料时检查) - if (self.state._upper_door_position == 'over_lower' and - self.state._lower_feeding_stage in [0, 1, 2, 3, 4]): # 在任何阶段都可能需要上料斗破拱 + if self.state._feed_status == FeedStatus.FUpperToLower: # 在任何阶段都可能需要上料斗破拱 + print('上料斗振动线程启用中...') upper_weight = self.transmitter_controller.read_data(1) if upper_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(upper_weight - self.state.last_upper_weight) < 0.1) and \ - (current_time - self.state.last_weight_time) > 10: + # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒),觉得有点小。改成 + if (abs(upper_weight - self.state.last_upper_weight) < 100) and \ + (current_time - self.state.last_weight_time) > 5: print("上料斗可能堵塞,启动破拱") self.state._upper_is_arch_=True self.relay_controller.control(self.relay_controller.BREAK_ARCH_UPPER, 'open') - time.sleep(2) + time.sleep(5) self.relay_controller.control(self.relay_controller.BREAK_ARCH_UPPER, 'close') self.state._upper_is_arch_=False @@ -124,6 +126,7 @@ class FeedingController: print("无法获取当前角度,跳过本次调整") return self.state.last_angle = current_angle + self.state._lower_angle=current_angle print(f"当前角度: {current_angle:.2f}°, 溢料状态: {overflow}, 溢料返回: {self.state.overflow_detected}") return if self.state.overflow_detected!="浇筑满": @@ -135,17 +138,17 @@ class FeedingController: # 状态机控制逻辑 if self.state.angle_control_mode == "normal": - # 正常模式大于self.settings.angle_threshold=60度 - if overflow and current_angle > self.settings.angle_threshold: + # 正常模式大于app_set_config.angle_threshold=60度 + if overflow and current_angle > app_set_config.angle_threshold: # 检测到堆料且角度过大,进入角度减小模式 print("检测到堆料且角度过大,关闭出砼门开始减小角度") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') self.state.angle_control_mode = "reducing" else: - # 保持正常开门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - if current_angle >self.settings.target_angle: + # 保持正常开门 30 + # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') + 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') @@ -155,7 +158,7 @@ class FeedingController: elif self.state.angle_control_mode == "reducing": # 角度减小模式 - if current_angle <= self.settings.target_angle + self.settings.angle_tolerance: + if current_angle <= app_set_config.target_angle + app_set_config.angle_tolerance: # 角度已达到目标范围 if overflow: # 仍有堆料,进入维持模式 @@ -196,9 +199,7 @@ class FeedingController: self.state.angle_control_mode = "normal" else: self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(5) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') + self.relay_controller.control_lower_close() def pulse_control_door_for_maintaining(self): diff --git a/feeding/process.py b/feeding/process.py index 6da32ef..5e4d88d 100644 --- a/feeding/process.py +++ b/feeding/process.py @@ -5,11 +5,12 @@ from busisness.blls import ArtifactBll from busisness.models import ArtifactInfoModel import time from datetime import datetime +from config.settings import app_set_config class FeedingProcess: def __init__(self, relay_controller, inverter_controller, transmitter_controller, vision_detector, - camera_controller, state, settings): + camera_controller, state): self.relay_controller = relay_controller self.artifact_bll = ArtifactBll() self.mould_service = MouldService() @@ -18,50 +19,60 @@ class FeedingProcess: self.vision_detector = vision_detector self.camera_controller = camera_controller self.state = state - self.state.feed_status = FeedStatus.FNone + self.state._feed_status = FeedStatus.FCheckM #标志位用,是否是第一次运行 self.is_first_flag=True - self.settings = settings 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) """开始生产管片""" - if loc_state.feed_status == FeedStatus.FNone: - loc_state.feed_status = FeedStatus.FCheckM + if loc_state._feed_status == FeedStatus.FNone: + # loc_state._feed_status = FeedStatus.FCheckM return - elif loc_state.feed_status == FeedStatus.FCheckM: + elif loc_state._feed_status == FeedStatus.FCheckM: """开始生产管片""" - loc_state._lower_feeding_stage = 4 + loc_state.lower_feeding_stage = 4 - if self.settings.debug_feeding: - loc_state.feed_status = FeedStatus.FApiCheck + # if app_set_config.debug_feeding: + # loc_state._feed_status = FeedStatus.FApiCheck if self.state.vehicle_aligned: - loc_state.feed_status = FeedStatus.FCheckGB + loc_state._feed_status = FeedStatus.FApiCheck print("检查模车") return - elif loc_state.feed_status == FeedStatus.FApiCheck: + elif loc_state._feed_status == FeedStatus.FApiCheck: print("生产已开始") - time.sleep(2) + # time.sleep(2) loc_modules = self.mould_service.get_not_pour_artifacts() if loc_modules: # 取第一个未浇筑的管片 #后续放入队列处理 + loc_module = loc_modules[0] #API loc_module.Source = 1 loc_module.BeginTime=datetime.now() self.artifact_bll.insert_artifact_task(loc_module) - self.state.current_artifact = 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) - self.state.feed_status = FeedStatus.FCheckGB + loc_state._feed_status = FeedStatus.FCheckGB else: #未读取到AIP接口数据. - self.state.current_artifact = None + 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: + self.state.current_artifact = None return - elif loc_state.feed_status == FeedStatus.FRFID: + elif loc_state._feed_status == FeedStatus.FRFID: print("生产已检查RFID") #RFID格式:模具编号,分块号,尺寸规格,方量 rfid_info ='' @@ -71,7 +82,7 @@ class FeedingProcess: loc.BetonVolume=1.56 if self.state.current_artifact: #检测是否和RFID识别的管理一致 - self.state.feed_status = FeedStatus.FCheckGB + loc_state._feed_status = FeedStatus.FCheckGB else: #以RFID为准 loc_module= ArtifactInfoModel() @@ -82,23 +93,23 @@ class FeedingProcess: self.state.current_artifact = loc_module #确认是否保存到数据库 - self.state.feed_status = FeedStatus.FCheckGB + loc_state._feed_status = FeedStatus.FCheckGB return - elif loc_state.feed_status == FeedStatus.FCheckGB: + elif loc_state._feed_status == FeedStatus.FCheckGB: print("检查盖板对齐,") - time.sleep(10) - loc_state.feed_status = FeedStatus.FUpperToLower + # time.sleep(5) + loc_state._feed_status = FeedStatus.FUpperToLower #计算本次生产需要的总重量 print(f"本次生产需要的总重量:{self.state._mould_need_weight}") return - elif loc_state.feed_status == FeedStatus.FUpperToLower: + elif loc_state._feed_status == FeedStatus.FUpperToLower: print("上料斗向下料斗转移") #上料斗重量 - loc_state.initial_upper_weight=self.transmitter_controller.read_data(1) + loc_state.initial_upper_weight=loc_state._upper_weight #下料斗重量 - loc_state.initial_lower_weight=self.transmitter_controller.read_data(2) + loc_state.initial_lower_weight=loc_state._lower_weight #需要的总重量 - loc_state._mould_need_weight=loc_state.current_artifact.BetonVolume*loc_state.density + # 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('重量不够,需要增加') @@ -114,40 +125,46 @@ class FeedingProcess: # 最后一块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.initial_upper_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 + #测试返回 + # loc_state._feed_status = FeedStatus.FFeed1 + loc_state._feed_status = FeedStatus.FNone + + # time.sleep(10) return - elif loc_state.feed_status == FeedStatus.FFeed1: + 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: + 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: + 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: + elif loc_state._feed_status == FeedStatus.FFinished: """完成当前批次下料""" print("当前批次下料完成,关闭出砼门") if loc_state.overflow_detected=="浇筑满": 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') + + loc_state._mould_vibrate_status=0 + 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) - loc_state.feed_status = FeedStatus.FCheckM + loc_state._feed_status = FeedStatus.FCheckM return def _start_feeding_stage(self): @@ -156,34 +173,35 @@ class FeedingProcess: print("开始分步下料过程") self.transfer_material_from_upper_to_lower() - def transfer_material_from_upper_to_lower(self,initial_upper_weight,initial_lower_weight): + def transfer_material_from_upper_to_lower(self,loc_state,initial_upper_weight,initial_lower_weight): """target_upper_weight:转移后剩下的上料斗重量""" # 如果低于单次,全部卸掉 - _max_lower_weight=self.settings.max_lower_volume*self.state.density - if (initial_lower_weight+feed_weight>_max_lower_weight): - feed_weight=_max_lower_weight-initial_lower_weight + _max_lower_weight=app_set_config.max_lower_volume*self.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(self.relay_controller.DOOR_LOWER_2, 'close') + self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') + # self.relay_controller.control_lower_close() # 打开上料斗出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'open') + self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') # 等待物料流入下料斗,基于上料斗重量变化控制 import time start_time = time.time() # timeout = 30 # 30秒超时 - while self.state.running: + while loc_state.running: 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 print(f"上料斗当前重量: {current_upper_weight:.2f}kg") # 如果达到目标重量,则关闭上料斗出砼门 @@ -198,35 +216,40 @@ class FeedingProcess: print("重量变化过小,可能存在堵塞") time.sleep(1) # 关闭上料斗出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') + self.relay_controller.control_upper_close() + + #测试用 print("上料斗下料完成") + def wait_for_vehicle_alignment(self): """等待模具车对齐""" print("等待模具车对齐...") - self.state._lower_feeding_stage = 4 + self.state.lower_feeding_stage = 4 import time - while self.state._lower_feeding_stage == 4 and self.state.running: + while self.state.lower_feeding_stage == 4 and self.state.running: if self.state.vehicle_aligned: print("模具车已对齐,开始下料") - self.state._lower_feeding_stage = 1 + self.state.lower_feeding_stage = 1 # self.feeding_stage_one() break - time.sleep(self.settings.alignment_check_interval) + 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(self.settings.frequencies[0]) - self.inverter_controller.control('start') + # self.inverter_controller.set_frequency(app_set_config.frequencies[0]) + # self.inverter_controller.control('start') + loc_state._mould_frequency=app_set_config.frequencies[0] + loc_state._mould_vibrate_status=1 # 确保上料斗出砼门关闭 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') + # self.relay_controller.control(self.relay_controller.DOOR_UPPER_CLOSE, 'close') # 打开下料斗出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_2, 'open') - loc_cur_weight = self.transmitter_controller.read_data(2) + 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("无法获取初始重量,取消下料") @@ -238,7 +261,7 @@ class FeedingProcess: start_time = time.time() - current_weight = self.transmitter_controller.read_data(2) + current_weight = loc_state._lower_weight if current_weight is None: #报警处理 print("无法获取当前重量,取消下料") @@ -246,25 +269,28 @@ class FeedingProcess: return loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight target_weight = loc_state._mould_need_weight/3 - - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight) or (time.time() - start_time) > 30: - loc_state.feed_status = FeedStatus.FFeed2 - loc_state._lower_feeding_stage = 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.FFeed2 + loc_state.lower_feeding_stage = 2 self.is_first_flag=True return else: time.sleep(1) - def feeding_stage_two(self): + def feeding_stage_two(self,loc_state): """第二阶段下料:下料斗向模具车下料(中速)""" if self.is_first_flag: print("开始第二阶段下料:下料斗中速下料") - self.inverter_controller.set_frequency(self.settings.frequencies[1]) - + # self.inverter_controller.set_frequency(app_set_config.frequencies[1]) + # self.inverter_controller.control('start') # 保持下料斗出砼门打开 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_2, 'open') + self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') # 确保上料斗出砼门关闭 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') + 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: # #报警处理 @@ -275,7 +301,7 @@ class FeedingProcess: self.is_first_flag=False start_time = time.time() - current_weight = self.transmitter_controller.read_data(2) + current_weight = loc_state._lower_weight if current_weight is None: #报警处理 print("无法获取当前重量,取消下料") @@ -283,26 +309,29 @@ class FeedingProcess: return loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight target_weight = (loc_state._mould_need_weight/3)*2 - - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight) or (time.time() - start_time) > 30: - loc_state.feed_status = FeedStatus.FFeed3 - loc_state._lower_feeding_stage = 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.FFeed3 + loc_state.lower_feeding_stage = 3 self.is_first_flag=True return else: time.sleep(1) - def feeding_stage_three(self): + + def feeding_stage_three(self,loc_state): """第三阶段下料:下料斗向模具车下料(高速)""" if self.is_first_flag: print("开始第三阶段下料:下料斗高速下料") - self.inverter_controller.set_frequency(self.settings.frequencies[2]) + # self.inverter_controller.set_frequency(app_set_config.frequencies[2]) + + loc_state._mould_frequency=app_set_config.frequencies[2] # 保持下料斗出砼门打开 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_2, 'open') + self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') # 确保上料斗出砼门关闭 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') + self.relay_controller.control_upper_close() self.is_first_flag=False - current_weight = self.transmitter_controller.read_data(2) + current_weight = loc_state._lower_weight if current_weight is None: #报警处理 print("无法获取当前重量,取消下料") @@ -312,8 +341,8 @@ class FeedingProcess: 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 + loc_state._feed_status = FeedStatus.FFinished + loc_state.lower_feeding_stage = 5 self.is_first_flag=True return else: @@ -348,13 +377,13 @@ class FeedingProcess: # 继续等待当前模具车对齐(不需要重新等待对齐,因为是同一辆模具车) print("第二次上料完成,继续三阶段下料") - self.state._lower_feeding_stage = 1 # 直接进入第一阶段下料 + 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_stage = 0 self.state.lower_feeding_cycle = 0 self.state.upper_feeding_count = 0 # self.return_upper_door_to_default() diff --git a/hardware/RFID/command_hex.py b/hardware/RFID/command_hex.py index 3448350..ff38a5b 100644 --- a/hardware/RFID/command_hex.py +++ b/hardware/RFID/command_hex.py @@ -470,9 +470,10 @@ class command_hex: """ # 寻找连续三个0x2C的情况 - for idx in range(len(response) - 2): - if response[idx] == 0x2C and response[idx+1] == 0x2C and response[idx+2] == 0x2C: - response=response[:idx] + # for idx in range(len(response) - 2): + # if response[idx] == 0x2C and response[idx+1] == 0x2C and response[idx+2] == 0x2C: + # response=response[:idx] + # break # 验证响应长度 if len(response) <= 1 or len(response) != response[0] + 1: raise ValueError("应答数据长度不正确") @@ -499,7 +500,12 @@ class command_hex: loc_string = '' if data_part: try: + loc_string = data_part.decode('ascii') + first_empty = loc_string.find(',,,') + if first_empty != -1: + loc_string = loc_string[:first_empty] + print('收到数据:',loc_string) except UnicodeDecodeError: print(f"无法将数据转换为ASCII字符串: {data_part}") return loc_string diff --git a/hardware/RFID/rfid_service.py b/hardware/RFID/rfid_service.py index 6eaa2b3..87953a9 100644 --- a/hardware/RFID/rfid_service.py +++ b/hardware/RFID/rfid_service.py @@ -380,7 +380,7 @@ class rfid_service: 接收线程的主循环,用于接收RFID推送的数据 """ while self._thread_signal: - + self._pause_receive=False if self._pause_receive: time.sleep(1) continue diff --git a/hardware/relay.py b/hardware/relay.py index 39f6a5e..5f82b12 100644 --- a/hardware/relay.py +++ b/hardware/relay.py @@ -1,14 +1,16 @@ # 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 Settings +from config.settings import app_set_config class RelayController: # 继电器映射 - FAST_STOP = 'fast_stop' # DO1 - 急停 + RING = 'ring' # DO1 - 响铃 UPPER_TO_JBL = 'upper_to_jbl' # DO2 - 上料斗到搅拌楼 UPPER_TO_ZD = 'upper_to_zd' # DO3 - 上料斗到振捣室 # DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 @@ -20,38 +22,37 @@ class RelayController: BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗震动 DIRECT_LOWER_FRONT = 'direct_lower_front' # DO5 - 下料斗前 DIRECT_LOWER_BEHIND = 'direct_lower_behind' # DO6 - 下料斗后 - DIRECT_LOWER_LEFT = 'direct_lower_left' # DO7 - 下料斗左 - DIRECT_LOWER_RIGHT = 'direct_lower_right' # DO8 - 下料斗右 + 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 12上 13下 14 往后 15往前 +#遥1 DO 7 左 DO8 右 角度 摇2:DO 15下 13上 12 往后 14往前 下料斗DO7开 D09关 # 继电器命令(原始Socket) self.relay_commands = { - self.FAST_STOP: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, + 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': '00000000000601050006FF00'}, - self.DOOR_LOWER_CLOSE: {'open': '00000000000601050007FF00', 'close': '000000000006010500070000'}, + 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': '00000000000601050005FF00', 'close': '000000000006010500050000'}, - self.DIRECT_LOWER_BEHIND: {'open': '00000000000601050005FF00', 'close': '000000000006010500050000'}, - self.DIRECT_LOWER_LEFT: {'open': '00000000000601050005FF00', 'close': '000000000006010500050000'}, - self.DIRECT_LOWER_RIGHT: {'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.settings = Settings() # 读取状态命令 self.read_status_command = '000000000006010100000008' # 设备位映射 self.device_bit_map = { - self.FAST_STOP: 0, + self.RING: 0, self.UPPER_TO_JBL: 1, self.UPPER_TO_ZD: 2, self.BREAK_ARCH_UPPER: 3, @@ -60,7 +61,7 @@ class RelayController: def send_command(self, command_hex): """发送原始Socket命令""" - if not self.settings.debug_feeding: + if app_set_config.debug_mode: return None try: @@ -93,7 +94,40 @@ class RelayController: def control(self, device, action): """控制继电器""" if device in self.relay_commands and action in self.relay_commands[device]: - print(f"控制继电器 {device} {action}") + 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_CLOSE, 'open') + # 异步5秒后关闭 + threading.Thread(target=self._close_upper_5s, daemon=True,name="close_upper_5s").start() + + def control_lower_close(self): + """控制下料斗关""" + # 关闭下料斗出砼门 + self.control(self.DOOR_LOWER_CLOSE, 'open') + # 异步5秒后关闭 + threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start() + + 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_5s(self): + time.sleep(5) + self.control(self.DOOR_UPPER_CLOSE, 'close') + + def _close_lower_5s(self): + time.sleep(5) + self.control(self.DOOR_LOWER_CLOSE, 'close') + + def _close_ring(self): + time.sleep(3) + self.control(self.RING, 'close') diff --git a/hardware/transmitter.py b/hardware/transmitter.py index 09e2b66..cb764e4 100644 --- a/hardware/transmitter.py +++ b/hardware/transmitter.py @@ -1,11 +1,14 @@ # hardware/transmitter.py from pymodbus.exceptions import ModbusException +import socket +from config.ini_manager import ini_manager +from config.settings import app_set_config + class TransmitterController: def __init__(self, relay_controller): self.relay_controller = relay_controller - # 变送器配置 self.config = { 1: { # 上料斗 @@ -75,88 +78,57 @@ class TransmitterController: Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗 return: 读取成功返回重量 weight: int, 失败返回 None """ + TIMEOUT = 2 # 超时时间为 2秒 + BUFFER_SIZE= 1024 + IP = None + PORT = None + weight = 0 + + if transmitter_id == 1: # 上料斗变送器的信息: - IP = "192.168.250.63" - PORT = 502 - TIMEOUT = 2 # 超时时间为 2秒 - BUFFER_SIZE= 1024 - weight = None - if self.relay_controller.settings.debug_feeding: - return 0 - - import socket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.settimeout(TIMEOUT) - s.connect((IP, PORT)) - # print(f"连接上料斗变送器 {IP}:{PORT} 成功") - - # 接收数据(变送器主动推送,recv即可获取数据) - data = s.recv(BUFFER_SIZE) - if data: - # print(f"收到原始数据:{data}") - - # 提取出完整的一个数据包 (\r\n结尾) - packet = self.get_latest_valid_packet(data) - if not packet: - print("未获取到有效数据包!!") - return None - # 解析重量 - weight = self.parse_weight(packet) - else: - print("未收到设备数据") - - except ConnectionRefusedError: - print(f"变送器连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)") - except socket.timeout: - print(f"读取变送器数据超时:{TIMEOUT}秒内未收到数据") - except Exception as e: - print(f"读取异常:{e}") - - # 成功返回重量(int),失败返回None - return weight - + IP = ini_manager.upper_transmitter_ip + PORT = ini_manager.upper_transmitter_port elif transmitter_id == 2: - # 上料斗变送器的信息: - IP = "192.168.250.66" - PORT = 8234 - TIMEOUT = 2 # 超时时间为 2秒 - BUFFER_SIZE= 1024 - weight = None - if self.relay_controller.settings.debug_feeding: - return 0 - import socket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.settimeout(TIMEOUT) - s.connect((IP, PORT)) - # print(f"连接上料斗变送器 {IP}:{PORT} 成功") - - # 接收数据(变送器主动推送,recv即可获取数据) - data = s.recv(BUFFER_SIZE) - if data: - # print(f"收到原始数据:{data}") - - # 提取出完整的一个数据包 (\r\n结尾) - packet = self.get_latest_valid_packet(data) - if not packet: - print("未获取到有效数据包!!") - return None - # 解析重量 - weight = self.parse_weight(packet) - else: - print("未收到设备数据") - - except ConnectionRefusedError: - print(f"变送器连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)") - except socket.timeout: - print(f"读取变送器数据超时:{TIMEOUT}秒内未收到数据") - except Exception as e: - print(f"读取异常:{e}") + # 下料斗变送器的信息: + IP = ini_manager.lower_transmitter_ip + PORT = ini_manager.lower_transmitter_port - # 成功返回重量(int),失败返回None - return weight + if not IP or not PORT: + print(f"未配置变送器 {transmitter_id} 的IP或PORT") + return 0 + if app_set_config.debug_mode: + print(f"调试模式,未读数据({transmitter_id},IP: {IP}:{PORT})") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.settimeout(TIMEOUT) + s.connect((IP, PORT)) + # print(f"连接上料斗变送器 {IP}:{PORT} 成功") + + # 接收数据(变送器主动推送,recv即可获取数据) + data = s.recv(BUFFER_SIZE) + if data: + # print(f"收到原始数据:{data}") + + # 提取出完整的一个数据包 (\r\n结尾) + packet = self.get_latest_valid_packet(data) + if not packet: + print("未获取到有效数据包!!") + return None + # 解析重量 + weight = self.parse_weight(packet) + else: + print("未收到设备数据") + + except ConnectionRefusedError: + print(f"变送器连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)") + except socket.timeout: + print(f"读取变送器数据超时:{TIMEOUT}秒内未收到数据") + except Exception as e: + print(f"读取异常:{e}") + + # 成功返回重量(int),失败返回None + return weight def get_latest_valid_packet(self, raw_data): """ diff --git a/main.py b/main.py index ee63ec2..670259c 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,49 @@ # main.py import time -from config.settings import Settings +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 def main(): # 加载配置 - settings = Settings() - # 初始化系统 - system = FeedingControlSystem(settings) - # system.camera_controller.start_cameras() + # relay_c=RelayController() + # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') + system = FeedingControlSystem() + # 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') + # system.relay_controller._close_upper_5s() + + # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') + # system.state.vehicle_aligned=True + # system.state._upper_door_position='over_lower' + # system.initialize() + + # system.state._feed_status=FeedStatus.FCheckM + while True: + time.sleep(2) + + # 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') @@ -27,29 +59,17 @@ def main(): # 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() + - try: - # 系统初始化 - # system.initialize() - - print("系统准备就绪,5秒后开始下料...") - time.sleep(5) - - # 启动下料流程 - # system.start_lower_feeding() - - # 保持运行 - while True: - time.sleep(1) - - except KeyboardInterrupt: - print("收到停止信号") - except Exception as e: - print(f"系统错误: {e}") - finally: - system.stop() - - + if __name__ == "__main__": main() diff --git a/opc/opcua_client_subscription.py b/opc/opcua_client_subscription.py new file mode 100644 index 0000000..ff896ed --- /dev/null +++ b/opc/opcua_client_subscription.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +OPC UA 客户端订阅模式示例 +使用订阅机制实现实时数据更新 +""" + +from opcua import Client, Subscription +from opcua.ua import DataChangeNotification +import time +import sys +import threading + +class SubHandler: + """ + 订阅处理器,处理数据变化通知 + """ + def __init__(self): + self.data_changes = {} + self.change_count = 0 + + def datachange_notification(self, node, val, data): + """ + 数据变化时的回调函数 + """ + self.change_count += 1 + node_name = node.get_display_name().Text + self.data_changes[node_name] = { + 'value': val, + 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'), + 'node_id': str(node) + } + + print(f"🔔 数据变化 #{self.change_count}") + print(f" 节点: {node_name}") + print(f" 数值: {val}") + print(f" 时间: {self.data_changes[node_name]['timestamp']}") + print(f" 节点ID: {node}") + print("-" * 50) + +class OPCUAClientSubscription: + """ + 使用订阅机制的 OPC UA 客户端 + """ + def __init__(self, server_url="opc.tcp://localhost:4840/zjsh_feed/server/"): + self.client = Client(server_url) + self.connected = False + self.subscription = None + self.handler = SubHandler() + self.monitored_nodes = {} + + def connect(self): + """连接到服务器""" + try: + self.client.connect() + self.connected = True + print(f"✅ 成功连接到 OPC UA 服务器: {self.client.server_url}") + return True + except Exception as e: + print(f"❌ 连接服务器失败: {e}") + return False + + def disconnect(self): + """断开连接""" + if self.subscription: + self.subscription.delete() + print("🗑️ 已删除订阅") + + if self.connected: + self.client.disconnect() + self.connected = False + print("🔌 已断开与 OPC UA 服务器的连接") + + def setup_subscription(self, publishing_interval=500): + """ + 设置订阅 + + Args: + publishing_interval: 发布间隔(毫秒) + """ + if not self.connected: + print("请先连接到服务器") + return False + + try: + # 创建订阅 + self.subscription = self.client.create_subscription(publishing_interval, self.handler) + print(f"📡 已创建订阅,发布间隔: {publishing_interval}ms") + + # 获取要监控的节点 + objects = self.client.get_objects_node() + upper_device = objects.get_child("2:upper") + lower_device = objects.get_child("2:lower") + + # 订阅重量数据 + upper_weight_node = upper_device.get_child("2:upper_weight") + lower_weight_node = lower_device.get_child("2:lower_weight") + + # 开始监控 + upper_handle = self.subscription.subscribe_data_change(upper_weight_node) + lower_handle = self.subscription.subscribe_data_change(lower_weight_node) + + self.monitored_nodes = { + 'upper_weight': {'node': upper_weight_node, 'handle': upper_handle}, + 'lower_weight': {'node': lower_weight_node, 'handle': lower_handle} + } + + print(f"📊 已订阅 {len(self.monitored_nodes)} 个数据节点") + return True + + except Exception as e: + print(f"❌ 设置订阅失败: {e}") + return False + + def get_current_values(self): + """获取当前值""" + if not self.monitored_nodes: + return {} + + values = {} + for name, info in self.monitored_nodes.items(): + try: + values[name] = info['node'].get_value() + except Exception as e: + values[name] = f"读取失败: {e}" + + return values + + def run_subscription_test(self, duration=30): + """ + 运行订阅测试 + + Args: + duration: 测试持续时间(秒) + """ + if not self.setup_subscription(): + return + + print(f"\n🚀 开始订阅模式测试,持续 {duration} 秒...") + print("💡 等待数据变化通知...") + print("=" * 60) + + start_time = time.time() + last_stats_time = start_time + + try: + while time.time() - start_time < duration: + current_time = time.time() + + # 每5秒显示一次统计信息 + if current_time - last_stats_time >= 5: + elapsed = current_time - start_time + changes_per_minute = (self.handler.change_count / elapsed) * 60 + + print(f"\n📈 统计信息 (运行时间: {elapsed:.1f}s)") + print(f" 总变化次数: {self.handler.change_count}") + print(f" 变化频率: {changes_per_minute:.1f} 次/分钟") + + if self.handler.data_changes: + print(f" 最新数据:") + for name, data in list(self.handler.data_changes.items())[-2:]: # 显示最后2个 + print(f" {name}: {data['value']} ({data['timestamp']})") + + last_stats_time = current_time + + time.sleep(0.1) # 小延迟避免CPU占用过高 + + except KeyboardInterrupt: + print("\n⏹️ 测试被用户中断") + + finally: + print(f"\n🏁 测试完成") + print(f"📊 总变化次数: {self.handler.change_count}") + print(f"⏱️ 平均变化频率: {(self.handler.change_count / duration) * 60:.1f} 次/分钟") + +def main(): + """主函数""" + client = OPCUAClientSubscription("opc.tcp://localhost:4840/zjsh_feed/server/") + + try: + # 连接到服务器 + if not client.connect(): + return + + # 运行订阅测试 + client.run_subscription_test(duration=60) # 运行60秒 + + except Exception as e: + print(f"❌ 客户端运行错误: {e}") + finally: + client.disconnect() + +if __name__ == "__main__": + if len(sys.argv) > 1: + server_url = sys.argv[1] + client = OPCUAClientSubscription(server_url) + else: + client = OPCUAClientSubscription() + + try: + main() + except KeyboardInterrupt: + print("\n👋 用户中断程序") + sys.exit(0) \ No newline at end of file diff --git a/opc/opcua_client_test.py b/opc/opcua_client_test.py index 303b193..852ef84 100644 --- a/opc/opcua_client_test.py +++ b/opc/opcua_client_test.py @@ -52,36 +52,30 @@ class OPCUAClientTest: objects = self.client.get_objects_node() print(f"对象节点: {objects}") - # 浏览 IndustrialDevice 节点 upper_device = objects.get_child("2:upper") - print(f"\n工业设备节点: {upper_device}") + print(f"\n上料斗对象: {upper_device}") - # 获取传感器节点 lower_device = objects.get_child("2:lower") - print(f"传感器节点: {lower_device}") + print(f"下料斗对象: {lower_device}") - - print(f"温度传感器: {upper_device}") - print(f"压力传感器: {lower_device}") - - # 获取变量值 - print("\n=== 当前传感器数据 ===") - self.read_sensor_values(upper_device, lower_device) + print("\n=== 当前对象属性===") + self.read_object_properties(upper_device, lower_device) except Exception as e: print(f"浏览节点时出错: {e}") - def read_sensor_values(self, upper_device, lower_device): - """读取传感器数值""" + def read_object_properties(self, upper_device, lower_device): + """读取重量数值""" try: - # 读取温度 - temp_value = upper_device.get_child("2:upper_weight").get_value() - temp_unit = upper_device.get_child("2:lower_weight").get_value() - print(f"温度: {temp_value} {temp_unit}") + # 读取重量 + upper_weight = upper_device.get_child("2:upper_weight").get_value() + lower_weight = lower_device.get_child("2:lower_weight").get_value() + print(f"上料斗重量: {upper_weight}") + print(f"下料斗重量: {lower_weight}") except Exception as e: - print(f"读取传感器数据时出错: {e}") + print(f"读取数据时出错: {e}") def monitor_data(self, duration=30): """监控数据变化""" diff --git a/opc/opcua_server.py b/opc/opcua_server.py index 98cbcff..52988b7 100644 --- a/opc/opcua_server.py +++ b/opc/opcua_server.py @@ -10,9 +10,10 @@ 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="opc.tcp://0.0.0.0:4840/zjsh_feed/server/", name="Feed_Server"): + def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): """ 初始化OPC UA服务器 @@ -54,8 +55,8 @@ class SimpleOPCUAServer: self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0) # 设置变量为可写 - self.upper_weight.set_writable() - self.lower_weight.set_writable() + # self.upper_weight.set_writable() + # self.lower_weight.set_writable() def setup_state_listeners(self): """设置状态监听器 - 事件驱动更新""" @@ -83,9 +84,7 @@ class SimpleOPCUAServer: try: self.server.start() self.running = True - print(f"OPC UA服务器启动成功!") print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") - print(f"命名空间: {self.namespace}") # 初始化当前值 if self.state: @@ -109,8 +108,6 @@ class SimpleOPCUAServer: def stop(self): """停止服务器""" self.running = False - if hasattr(self, 'simulation_thread'): - self.simulation_thread.join(timeout=2) self.server.stop() print("OPC UA服务器已停止") @@ -121,21 +118,6 @@ class SimpleOPCUAServer: except: pass - def simulate_data(self): - """模拟数据更新""" - while self.running: - try: - # 更新变量值 - self.upper_weight.set_value(self.state.upper_weight) - self.lower_weight.set_value(self.state.lower_weight) - - # 模拟延迟 - time.sleep(1) - - except Exception as e: - print(f"数据更新错误: {e}") - continue - def main(): """主函数""" diff --git a/service/mould_service.py b/service/mould_service.py index 86263c4..028adec 100644 --- a/service/mould_service.py +++ b/service/mould_service.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from common.sqlite_handler import SQLiteHandler from typing import Optional, List from .api_http_client import api_http_client -from busisness.models import ArtifactInfo, TaskInfo, LoginRequest +from busisness.models import ArtifactInfo, TaskInfo, LoginRequest, LEDInfo from config.ini_manager import ini_manager @@ -80,59 +80,114 @@ class MouldService: except Exception as e: print(f"请求未浇筑管片信息异常: {e}") return None - - -if __name__ == "__main__": - # 创建模具服务实例 - mould_service = MouldService() - db = SQLiteHandler.get_instance("db/three.db", max_readers=50, busy_timeout=4000) + def get_pouring_led(self) -> Optional[LEDInfo]: + """ + 获取生产动态信息 + + Returns: + LEDInfo对象,如果失败返回None + """ + url = f"{self._host}/api/ext/produce/pouring_led" + + try: + # 调用API获取数据 + response_data = self._api_client.get(url, auth=True) + + # 检查响应状态 + if response_data.get('Code') != 200: + print(f"获取生产动态信息失败: {response_data.get('Message')}") + return None + + # 解析数据 + data = response_data.get('Data', {}) + if not data: + print(f"未获取到 pouring_led 信息") + return None + + # 转换为管片信息对象列表 - 使用更安全的字段过滤方式 + # 只提取 LEDInfo 类中定义的字段,忽略多余字段和缺失字段 + led_info = LEDInfo( + TaskID=data.get('TaskID', ''), + PlateVolume=data.get('PlateVolume', ''), + MouldCode=data.get('MouldCode', ''), + ProduceStartTime=data.get('ProduceStartTime', ''), + ArtifactID=data.get('ArtifactID', ''), + RingTypeCode=data.get('RingTypeCode', ''), + PlateIDSerial=data.get('PlateIDSerial', ''), + CheckResult=data.get('CheckResult', ''), + UpperWeight=data.get('UpperWeight', 0.0), + Temper=data.get('Temper', ''), + WorkshopTemperature=data.get('WorkshopTemperature', ''), + LowBucketWeighingValue=data.get('LowBucketWeighingValue', ''), + VibrationFrequency=data.get('VibrationFrequency', ''), + TotMete=data.get('TotMete', ''), + BetonVolumeAlready=data.get('BetonVolumeAlready', ''), + WaterTemperature=data.get('WaterTemperature', ''), + FormulaProportion=data.get('FormulaProportion', '') + ) + return led_info + + except Exception as e: + print(f"请求 pouring_led 信息异常: {e}") + return None + +app_web_service = MouldService() + +# if __name__ == "__main__": +# # 创建模具服务实例 +# mould_service = MouldService() +# led_info = mould_service.get_pouring_led() +# if led_info: +# print(led_info) + + # db = SQLiteHandler.get_instance("db/three.db", max_readers=50, busy_timeout=4000) # 测试获取未浇筑管片信息 - not_poured = mould_service.get_not_pour_artifacts() - if not_poured: - for item in not_poured: - artifact = db.fetch_one("SELECT * FROM ArtifactTask WHERE ArtifactID = ?", (item.ArtifactID,)) - if not artifact: - dict={ - "ArtifactID": item.ArtifactID, - "ArtifactActionID": item.ArtifactActionID, - "ArtifactIDVice1": item.ArtifactIDVice1, - "ProduceRingNumber": item.ProduceRingNumber, - "MouldCode": item.MouldCode, - "SkeletonID": item.SkeletonID, - "RingTypeCode": item.RingTypeCode, - "SizeSpecification": item.SizeSpecification, - "BuriedDepth": item.BuriedDepth, - "BlockNumber": item.BlockNumber, - "BetonVolume": item.BetonVolume, - "BetonTaskID": item.BetonTaskID, - "HoleRingMarking": item.HoleRingMarking, - "GroutingPipeMarking": item.GroutingPipeMarking, - "PolypropyleneFiberMarking": item.PolypropyleneFiberMarking, - "Status": 1, - "Source": 1 - } - db.insert("ArtifactTask", dict) + # not_poured = mould_service.get_not_pour_artifacts() + # if not_poured: + # for item in not_poured: + # artifact = db.fetch_one("SELECT * FROM ArtifactTask WHERE ArtifactID = ?", (item.ArtifactID,)) + # if not artifact: + # dict={ + # "ArtifactID": item.ArtifactID, + # "ArtifactActionID": item.ArtifactActionID, + # "ArtifactIDVice1": item.ArtifactIDVice1, + # "ProduceRingNumber": item.ProduceRingNumber, + # "MouldCode": item.MouldCode, + # "SkeletonID": item.SkeletonID, + # "RingTypeCode": item.RingTypeCode, + # "SizeSpecification": item.SizeSpecification, + # "BuriedDepth": item.BuriedDepth, + # "BlockNumber": item.BlockNumber, + # "BetonVolume": item.BetonVolume, + # "BetonTaskID": item.BetonTaskID, + # "HoleRingMarking": item.HoleRingMarking, + # "GroutingPipeMarking": item.GroutingPipeMarking, + # "PolypropyleneFiberMarking": item.PolypropyleneFiberMarking, + # "Status": 1, + # "Source": 1 + # } + # db.insert("ArtifactTask", dict) - dict={ - "TaskID": item.BetonTaskID, - "ProjectName": "上海市轨道交通19号线工程盾构区间管片生产2标", - "ProduceMixID": "20251030-02", - "VinNo": "", - "BetonVolume": item.BetonVolume, - "MouldCode": item.MouldCode, - "SkeletonID": item.SkeletonID, - "RingTypeCode": item.RingTypeCode, - "SizeSpecification": item.SizeSpecification, - "BuriedDepth": item.BuriedDepth, - "BlockNumber": item.BlockNumber, - "Mode": 1, - "Status": 1, - "Source": 1, - "OptTime": str(datetime.now() - timedelta(minutes=5)), - "CreateTime": str(datetime.now()) - } - db.insert("PDRecord", dict) + # dict={ + # "TaskID": item.BetonTaskID, + # "ProjectName": "上海市轨道交通19号线工程盾构区间管片生产2标", + # "ProduceMixID": "20251030-02", + # "VinNo": "", + # "BetonVolume": item.BetonVolume, + # "MouldCode": item.MouldCode, + # "SkeletonID": item.SkeletonID, + # "RingTypeCode": item.RingTypeCode, + # "SizeSpecification": item.SizeSpecification, + # "BuriedDepth": item.BuriedDepth, + # "BlockNumber": item.BlockNumber, + # "Mode": 1, + # "Status": 1, + # "Source": 1, + # "OptTime": str(datetime.now() - timedelta(minutes=5)), + # "CreateTime": str(datetime.now()) + # } + # db.insert("PDRecord", dict) # for i in range(2, 5): # row = db.fetch_one("SELECT * FROM ArtifactTask WHERE ID = ?", (i,)) # if row: diff --git a/settings.ini b/settings.ini index c491aa3..d7010b7 100644 --- a/settings.ini +++ b/settings.ini @@ -9,4 +9,9 @@ login_model = {"Program": 11, "SC": "1000000001", "loginName": "leduser", "passw [app] log_path = logs/app.log db_path = db/three.db +opcua_endpoint = opc.tcp://192.168.250.64:4840/zjsh_feed/server/ +upper_transmitter_ip = 192.168.250.63 +upper_transmitter_port = 502 +lower_transmitter_ip = 192.168.250.66 +lower_transmitter_port = 8234 diff --git a/tests/test_feeding_process.py b/tests/test_feeding_process.py index fd8f25b..c45fe5c 100644 --- a/tests/test_feeding_process.py +++ b/tests/test_feeding_process.py @@ -3,6 +3,7 @@ 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__), '..')) @@ -40,13 +41,13 @@ class TestFeedingProcess(unittest.TestCase): patch('feeding.process.InverterController'), \ patch('feeding.process.TransmitterController'): system = FeedingProcess() - # 通过settings修改参数 system.settings.single_batch_weight = 1500 - system.settings.min_required_weight = 300 - system.settings.target_vehicle_weight = 3000 + #修改参数 app_set_config.single_batch_weight = 1500 + app_set_config.min_required_weight = 300 + app_set_config.target_vehicle_weight = 3000 - self.assertEqual(system.settings.target_vehicle_weight, 3000) - self.assertEqual(system.settings.min_required_weight, 300) - self.assertEqual(system.settings.single_batch_weight, 1500) + 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__': diff --git a/tests/test_rfid.py b/tests/test_rfid.py index 75ef5eb..301bb4b 100644 --- a/tests/test_rfid.py +++ b/tests/test_rfid.py @@ -19,7 +19,7 @@ def test_rfid_functions(): 测试RFIDHardware的主要功能 """ # 初始化RFID控制器 - rfid = rfid_service(host='192.168.1.190', port=6000) + rfid = rfid_service(host='192.168.250.67', port=6000) # print("=== RFID硬件测试开始 ===") @@ -41,6 +41,12 @@ def test_rfid_functions(): # rfid.set_working_mode(address=0x00, mode_params={ # 'word_num': 0x1E # }) + + # mode_data = rfid.read_working_mode(address=0x00) + # if mode_data: + # print("读取到工作模式参数:") + # for key, value in mode_data.items(): + # print(f" {key}: {value:02X} ({value})") # # 测试读取读写器信息 # print("\n3. 测试读取读写器信息:") @@ -54,7 +60,7 @@ def test_rfid_functions(): # 测试设置功率 (仅演示,实际使用时根据需要调整) # print("\n3. 测试设置功率 (演示,不实际执行):") - # power_success = rfid.set_power(address=0x00, power_value=6) + # power_success = rfid.set_power(address=0x00, power_value=16) # print(f"功率设置{'成功' if power_success else '失败'}") # # 测试设置读卡间隔 (仅演示) @@ -87,7 +93,7 @@ def test_rfid_functions(): # rfid._process_collected_data() rfid.start_receiver(callback=test_data_callback) # print("接收线程已启动,等待接收数据...") - # 等待5秒模拟接收过程 + # 等待5秒模拟接收过程1111111111111 time.sleep(60*60) finally: # 确保停止接收线程 diff --git a/vision/camera.py b/vision/camera.py index 540a780..664108e 100644 --- a/vision/camera.py +++ b/vision/camera.py @@ -62,6 +62,30 @@ class DualCameraController: # print('aaaaa') ret, frame = cap.read() if ret and frame is not None: + # 在帧右上角添加时间戳 + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + # 获取帧尺寸 + height, width = frame.shape[:2] + # 设置文字参数 + font = cv2.FONT_HERSHEY_SIMPLEX + font_scale = 0.6 + thickness = 2 + color = (0, 255, 0) # 绿色 + + # 计算文字位置(右上角) + text_size = cv2.getTextSize(current_time, font, font_scale, thickness)[0] + text_x = width - text_size[0] - 10 # 距离右边10像素 + text_y = 30 # 距离顶部30像素 + + # 添加文字背景(半透明) + overlay = frame.copy() + cv2.rectangle(overlay, (text_x - 5, text_y - text_size[1] - 5), + (text_x + text_size[0] + 5, text_y + 5), (0, 0, 0), -1) + cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame) + + # 添加时间戳文字 + cv2.putText(frame, current_time, (text_x, text_y), font, font_scale, color, thickness) + # 使用高精度时间戳 timestamp = time.time() # 检查队列是否已满 @@ -216,11 +240,11 @@ class DualCameraController: dt_t1, frame_latest = self.frame_queues['cam1'].queue[-1] # 获取cam2的最新帧,选择时间戳更新的那个 - if frame_latest is None: - if not self.frame_queues['cam2'].empty(): - dt_t2, frame2 = self.frame_queues['cam2'].queue[-1] - if dt_t1 is None or dt_t2 > dt_t1: - frame_latest = frame2 + # if frame_latest is None: + if not self.frame_queues['cam2'].empty(): + dt_t2, frame2 = self.frame_queues['cam2'].queue[-1] + if dt_t1 is None or dt_t2 > dt_t1: + frame_latest = frame2 # 返回最新帧的副本(如果找到) return frame_latest.copy() if frame_latest is not None else None diff --git a/vision/detector.py b/vision/detector.py index d1568f0..d87c5a7 100644 --- a/vision/detector.py +++ b/vision/detector.py @@ -4,11 +4,12 @@ import cv2 from vision.angle_detector import get_current_door_angle from vision.overflow_detector import detect_overflow_from_image from vision.alignment_detector import detect_vehicle_alignment +from config.settings import app_set_config class VisionDetector: - def __init__(self, settings): - self.settings = settings + def __init__(self): + pass #model路径在对应的模型里面 # self.alignment_model = os.path.join(current_dir, "align_model/yolov11_cls_640v6.rknn") @@ -21,36 +22,36 @@ class VisionDetector: # 加载夹角检测模型 try: - if not os.path.exists(self.settings.angle_model_path): - print(f"夹角检测模型不存在: {self.settings.angle_model_path}") + if not os.path.exists(app_set_config.angle_model_path): + print(f"夹角检测模型不存在: {app_set_config.angle_model_path}") success = False else: # 注意:angle.pt模型通过predict_obb_best_angle函数使用,不需要预加载 - print(f"夹角检测模型路径: {self.settings.angle_model_path}") + print(f"夹角检测模型路径: {app_set_config.angle_model_path}") except Exception as e: print(f"检查夹角检测模型失败: {e}") success = False # 加载堆料检测模型 try: - if not os.path.exists(self.settings.overflow_model_path): - print(f"堆料检测模型不存在: {self.settings.overflow_model_path}") + if not os.path.exists(app_set_config.overflow_model_path): + print(f"堆料检测模型不存在: {app_set_config.overflow_model_path}") success = False else: - self.overflow_model = YOLO(self.settings.overflow_model_path) - print(f"成功加载堆料检测模型: {self.settings.overflow_model_path}") + self.overflow_model = YOLO(app_set_config.overflow_model_path) + print(f"成功加载堆料检测模型: {app_set_config.overflow_model_path}") except Exception as e: print(f"加载堆料检测模型失败: {e}") success = False # 加载对齐检测模型 try: - if not os.path.exists(self.settings.alignment_model_path): - print(f"对齐检测模型不存在: {self.settings.alignment_model_path}") + if not os.path.exists(app_set_config.alignment_model_path): + print(f"对齐检测模型不存在: {app_set_config.alignment_model_path}") success = False else: - self.alignment_model = YOLO(self.settings.alignment_model_path) - print(f"成功加载对齐检测模型: {self.settings.alignment_model_path}") + self.alignment_model = YOLO(app_set_config.alignment_model_path) + print(f"成功加载对齐检测模型: {app_set_config.alignment_model_path}") except Exception as e: print(f"加载对齐检测模型失败: {e}") success = False @@ -64,7 +65,7 @@ class VisionDetector: # image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image=cv2.flip(image, 0) return get_current_door_angle( - model=self.settings.angle_model_path, + model=app_set_config.angle_model_path, image=image ) @@ -72,21 +73,21 @@ class VisionDetector: """ 通过图像检测是否溢料 """ - # image_array=cv2.flip(image_array, 0) + image_array=cv2.flip(image_array, 0) # cv2.imwrite('test.jpg', image_array) - cv2.namedWindow("Alignment", cv2.WINDOW_NORMAL) - cv2.resizeWindow("Alignment", 640, 480) - cv2.imshow("Alignment", image_array) - cv2.waitKey(1) - print('path:', self.settings.overflow_model_path) - print('roi:', self.settings.roi_file_path) + # cv2.namedWindow("Alignment", cv2.WINDOW_NORMAL) + # cv2.resizeWindow("Alignment", 640, 480) + # cv2.imshow("Alignment", image_array) + # cv2.waitKey(1) + # print('path:', app_set_config.overflow_model_path) + # print('roi:', app_set_config.roi_file_path) return detect_overflow_from_image( - self.settings.overflow_model_path, - self.settings.roi_file_path, + app_set_config.overflow_model_path, + app_set_config.roi_file_path, image_array ) - def detect_vehicle_alignment(self, image_array): + def detect_vehicle_alignment(self, image_array)->bool: """ 通过图像检测模具车是否对齐 """ diff --git a/vision/roi_coordinates/1_rois.txt b/vision/roi_coordinates/1_rois.txt index bb8f71d..c9a20a1 100644 --- a/vision/roi_coordinates/1_rois.txt +++ b/vision/roi_coordinates/1_rois.txt @@ -1 +1 @@ -859,810,696,328 +644,608,522,246