From 4613b0f6e5683d910e524405363e0b3a828eee00 Mon Sep 17 00:00:00 2001
From: cdeyw <827523911@qq.com>
Date: Sat, 13 Sep 2025 11:00:17 +0800
Subject: [PATCH] first commit
---
.idea/.gitignore | 3 +
.idea/Feeding_control_system.iml | 10 +
.../inspectionProfiles/profiles_settings.xml | 6 +
.idea/misc.xml | 7 +
.idea/modules.xml | 8 +
.idea/vcs.xml | 6 +
config/config.yaml | 44 +++
setup.py | 0
src/__init__.py | 5 +
src/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 427 bytes
src/control/__init__.py | 4 +
.../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 331 bytes
.../feeding_controller.cpython-39.pyc | Bin 0 -> 7769 bytes
.../__pycache__/state_machine.cpython-39.pyc | Bin 0 -> 4025 bytes
src/control/feeding_controller.py | 325 ++++++++++++++++++
src/control/state_machine.py | 105 ++++++
src/divices/__init__.py | 5 +
.../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 344 bytes
.../__pycache__/inverter.cpython-39.pyc | Bin 0 -> 2790 bytes
src/divices/__pycache__/relay.cpython-39.pyc | Bin 0 -> 3007 bytes
.../__pycache__/transmitter.cpython-39.pyc | Bin 0 -> 2785 bytes
src/divices/inverter.py | 96 ++++++
src/divices/relay.py | 85 +++++
src/divices/transmitter.py | 82 +++++
src/logs/app.log | 120 +++++++
src/main.py | 84 +++++
src/utils/__init__.py | 4 +
src/utils/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 262 bytes
src/utils/__pycache__/config.cpython-39.pyc | Bin 0 -> 1559 bytes
src/utils/__pycache__/logger.cpython-39.pyc | Bin 0 -> 795 bytes
src/utils/config.py | 39 +++
src/utils/logger.py | 33 ++
.../__pycache__/test_feeding.cpython-39.pyc | Bin 0 -> 2679 bytes
tests/test_control/logs/app.log | 3 +
tests/test_control/test_feeding.py | 75 ++++
.../__pycache__/test_relay.cpython-39.pyc | Bin 0 -> 2065 bytes
tests/test_devices/logs/app.log | 1 +
tests/test_devices/test_relay.py | 48 +++
38 files changed, 1198 insertions(+)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/Feeding_control_system.iml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
create mode 100644 config/config.yaml
create mode 100644 setup.py
create mode 100644 src/__init__.py
create mode 100644 src/__pycache__/__init__.cpython-39.pyc
create mode 100644 src/control/__init__.py
create mode 100644 src/control/__pycache__/__init__.cpython-39.pyc
create mode 100644 src/control/__pycache__/feeding_controller.cpython-39.pyc
create mode 100644 src/control/__pycache__/state_machine.cpython-39.pyc
create mode 100644 src/control/feeding_controller.py
create mode 100644 src/control/state_machine.py
create mode 100644 src/divices/__init__.py
create mode 100644 src/divices/__pycache__/__init__.cpython-39.pyc
create mode 100644 src/divices/__pycache__/inverter.cpython-39.pyc
create mode 100644 src/divices/__pycache__/relay.cpython-39.pyc
create mode 100644 src/divices/__pycache__/transmitter.cpython-39.pyc
create mode 100644 src/divices/inverter.py
create mode 100644 src/divices/relay.py
create mode 100644 src/divices/transmitter.py
create mode 100644 src/logs/app.log
create mode 100644 src/main.py
create mode 100644 src/utils/__init__.py
create mode 100644 src/utils/__pycache__/__init__.cpython-39.pyc
create mode 100644 src/utils/__pycache__/config.cpython-39.pyc
create mode 100644 src/utils/__pycache__/logger.cpython-39.pyc
create mode 100644 src/utils/config.py
create mode 100644 src/utils/logger.py
create mode 100644 tests/test_control/__pycache__/test_feeding.cpython-39.pyc
create mode 100644 tests/test_control/logs/app.log
create mode 100644 tests/test_control/test_feeding.py
create mode 100644 tests/test_devices/__pycache__/test_relay.cpython-39.pyc
create mode 100644 tests/test_devices/logs/app.log
create mode 100644 tests/test_devices/test_relay.py
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/Feeding_control_system.iml b/.idea/Feeding_control_system.iml
new file mode 100644
index 0000000..7377413
--- /dev/null
+++ b/.idea/Feeding_control_system.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..60c2417
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d6d8525
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/config.yaml b/config/config.yaml
new file mode 100644
index 0000000..ba5cc0b
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,44 @@
+# 网络配置
+network:
+ relay:
+ host: "192.168.0.18"
+ port: 50000
+ inverter:
+ host: "192.168.0.20"
+ port: 502
+ slave_id: 1
+ transmitters:
+ upper:
+ host: "192.168.0.21"
+ port: 502
+ slave_id: 1
+ weight_register: 0
+ register_count: 2
+ lower:
+ host: "192.168.0.22"
+ port: 502
+ slave_id: 2
+ weight_register: 0
+ register_count: 2
+
+# 系统参数
+parameters:
+ min_required_weight: 50
+ max_error_count: 3
+ monitoring_interval: 1
+ timeout: 30
+
+# 下料参数,频率
+feeding:
+ stage1_frequency: 30.0
+ stage2_frequency: 40.0
+ stage3_frequency: 50.0
+ target_weight_per_stage: 33.3
+
+# 继电器DO端口映射
+relay:
+ door_upper: 0 # DO0 - 上料斗滑动门
+ door_lower_1: 1 # DO1 - 出砼门控制1
+ door_lower_2: 2 # DO2 - 出砼门控制2
+ break_arch_upper: 3 # DO3 - 上料斗破拱
+ break_arch_lower: 4 # DO4 - 下料斗破拱
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..5da0451
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,5 @@
+from .control import feeding_controller,state_machine
+from .divices import inverter,relay,transmitter
+from .utils import config,logger
+
+__all__ = ['FeedingController', 'FeedingStateMachine', 'FeedingState']
\ No newline at end of file
diff --git a/src/__pycache__/__init__.cpython-39.pyc b/src/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a51ebd8795c9990bc7c0b9af2ea2cd56ffab1226
GIT binary patch
literal 427
zcmYk2%}N6?6or%dZ|gw)d5PJ$77-B?ce?2=41_RAXClc=xk(H2I__Ng5;Y+d;Z
zuA10UdLbvAFXY_Zu(F&ZU+>9B_v!bqV2&&D=FTq@gd>g>(wJZ!kq8GLsaPi@(J4uF
zMzUWPt6UeP&@(c_s6{+^#bnOY6{@mt>|?6Povnr7vTtcK^cDuC1h~>zW(C!(>0~de
z*cGzh39$Ys1)>HJ0Q#pu@{naxs#1BkOY(l$-KC9Yo#)f2T%l%Q_7U0^bJ87V341yhwFMXHXQ@K
uf#F3oww`+ajZDpGtkAWArlwT((o%Z48C`Ls2fkI}Hhh%{EtmKLFQYvKQ+YiA
literal 0
HcmV?d00001
diff --git a/src/control/__init__.py b/src/control/__init__.py
new file mode 100644
index 0000000..2f0d072
--- /dev/null
+++ b/src/control/__init__.py
@@ -0,0 +1,4 @@
+from .feeding_controller import FeedingController
+from .state_machine import FeedingStateMachine, FeedingState
+
+__all__ = ['FeedingController', 'FeedingStateMachine', 'FeedingState']
\ No newline at end of file
diff --git a/src/control/__pycache__/__init__.cpython-39.pyc b/src/control/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5fc684c88c95709da8716eb48b1f606eaff00f11
GIT binary patch
literal 331
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
literal 0
HcmV?d00001
diff --git a/src/control/__pycache__/state_machine.cpython-39.pyc b/src/control/__pycache__/state_machine.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dab7010e1ba60a41345fae4d89da0b88c3c69e38
GIT binary patch
literal 4025
zcmcIn>u(!H5Z}FreSXGC+CocP0z!a!1o(mwAe1(BOQqI9G!oJ&(#iE+Vh2CM-kG+M
zk%|(brZ0++%ARCItv%8*~`OWO?
zY$_575_oR={>YpR5%MQ8t&axG5H#mUAdE1YBYA4lykaU8c_pXjHB%FPHK*r&rjL?i
zglSBlB}})q8P)Uy?_+-8{Wb;O0N!8$-~+Y+d;s_$3jrSzd=U6BivS-HdjAz;
z@L}L%tQYuR!AF4aWBtJQ&nRY;?K(x`yEi~z5?4I+(BWY(nmX;cJe79ywwo!kIQ6td
zp_GSrFf}!0<%$y%Hc#U}S|19`5H#m0AT}{6Bc{TrsWQdXm}=@wGkr`q{mf??%nx5R
zyzo)mX4%5TDL3WXr9SxM+VZ99^{;9d?^e%$T>bXzwdMO91#bwVU{6JYKqun?=N#A0
zk2yR&Rv#L3&^9ZdN@ubKdvNL;_klocP8f(mwZ>1(bl{5i4&m}nT$wscOw9&lF@9z!
z;Ohfi{hlrX#Scg*E4Nl^Kd+_{#mxu%D0>*169eLsdCKSlEfY&&1mq}5Gb{w1l*-3d
zuERRdn6Ta9Og6_npNxe?h!DrljW@C4yTwAw$`-P&Wua#s^d6zpQ6X}0b1E%{aS9&n
zqh3NM^f9BThlq^I_p`NYpGb2y&abZD|12rKx2W)au+sA*;PzQ}s}jCUs=#tKDkN1<
zr=?Ycav~Y`bJ)v-BU(IyB#PuQBnS{ch@=q)vWMdM9w0r?90Le=QZZD8iqSL-v^{9q
z5H#meAnm(DdXZ^hsqO`igRU*!TU^=r;umRj2U4ha4&U2)tN
zy~jZkf-GbrdK^a{7`X_bHertx>gWl}@7!5iKELth-RjbBt%+rOBr%d&ku1}BWdTCc
zRVUFuN>U(No%>;Z;c~4*dR!NX@&S#
zL8o`M8MTQte*$GRT_k$KPfx-G=7}bzIPyO+9X(T&5xI%!%H@ZNX}k1)!_+-fk6YXT
z+hY0(;?aTWrcpa%ig*s942*pdr#SM!nDz*lj%K*MHB#puCaTMA(%Vq&Z;wl=tk3$A;%7`8LAPlJTlP)Y+?9C=`5qr=7gRL*v7Cc_mIdS&Ty^}@BC66ns`
z9%acCA|d<3-XX&h3JW3EH(qX~Q4ErI_aUUL?&FE3zw|
zRwK|vxkMm8*MEQe{E(~ncR{IpnS)+I=L={~KnIT;X!JwE-^Mrcd^~hH~s9rK}+T
zzAEO_d5wwr8*rT{D>ukD+5)8n*I0z%v9FadSY^ns-@8-2@?8o2AP3e~uMU*3+!>Hl
zl?y)&Bq5kzfFb@o5^-J&2nK+Knh!SzKLErG7NEYW!_^Lr7laE#!O6PWV!?!^4t|j+
zVcJK7bRV5Q*bSTJU*Kw?3omq>%oyGRCag*j2CPxb6wFqH*|J(yz-(1iiR(-EYxkBa
z_dc)8U6B@-uu7}WU8-EXEhpL@^n(p{OxO1Lt0
ztHsGdE|hPxw2
zKYB5^7Y&x&Y|a^!7YDiN5C~BG7=WOq}>Gv1W9zPAuo2bGM`Fb%7*
LKr9R`D1ZL|uAG&+
literal 0
HcmV?d00001
diff --git a/src/control/feeding_controller.py b/src/control/feeding_controller.py
new file mode 100644
index 0000000..0b2f7a4
--- /dev/null
+++ b/src/control/feeding_controller.py
@@ -0,0 +1,325 @@
+import time
+from src.utils.config import config
+from src.utils.logger import app_logger
+from src.divices.relay import RelayController
+from src.divices.inverter import InverterController
+from src.divices.transmitter import TransmitterController
+from .state_machine import FeedingStateMachine, FeedingState
+
+
+class FeedingController:
+ """下料控制器"""
+
+ def __init__(self):
+ self.logger = app_logger.getChild('FeedingController')
+ self.state_machine = FeedingStateMachine()
+
+ # 初始化设备
+ self._initialize_devices()
+
+ # 系统参数
+ self.min_required_weight = config.get('parameters.min_required_weight', 50)
+ self.max_error_count = config.get('parameters.max_error_count', 3)
+ self.timeout = config.get('parameters.timeout', 30)
+
+ # 下料参数
+ self.stage_frequencies = {
+ 1: config.get('feeding.stage1_frequency', 30.0),
+ 2: config.get('feeding.stage2_frequency', 40.0),
+ 3: config.get('feeding.stage3_frequency', 50.0)
+ }
+ self.target_weight_per_stage = config.get('feeding.target_weight_per_stage', 33.3)
+
+ # 状态
+ self.error_count = 0
+ self.last_upper_weight = 0
+ self.last_lower_weight = 0
+ self.last_weight_time = time.time()
+
+ def _initialize_devices(self):
+ """初始化设备"""
+ try:
+ # 网络继电器
+ relay_config = config.get('network.relay')
+ self.relay = RelayController(
+ relay_config['host'],
+ relay_config['port']
+ )
+
+ # 设置继电器设备映射
+ relay_mapping = config.get('relay')
+ self.relay.set_device_mapping(relay_mapping)
+
+ # 变频器
+ inverter_config = config.get('network.inverter')
+ self.inverter = InverterController(
+ inverter_config['host'],
+ inverter_config['port'],
+ inverter_config['slave_id']
+ )
+
+ # 变送器
+ transmitters_config = config.get('network.transmitters')
+
+ self.upper_transmitter = TransmitterController(
+ 'upper',
+ transmitters_config['upper']['host'],
+ transmitters_config['upper']['port'],
+ transmitters_config['upper']['slave_id']
+ )
+ self.upper_transmitter.set_config(
+ transmitters_config['upper']['weight_register'],
+ transmitters_config['upper']['register_count']
+ )
+
+ self.lower_transmitter = TransmitterController(
+ 'lower',
+ transmitters_config['lower']['host'],
+ transmitters_config['lower']['port'],
+ transmitters_config['lower']['slave_id']
+ )
+ self.lower_transmitter.set_config(
+ transmitters_config['lower']['weight_register'],
+ transmitters_config['lower']['register_count']
+ )
+
+ self.logger.info("设备初始化完成")
+ except Exception as e:
+ self.logger.error(f"设备初始化失败: {e}")
+ raise
+
+ def check_material_request(self):
+ """检查是否需要向上料斗要料"""
+ current_weight = self.upper_transmitter.read_weight()
+
+ # 如果读取失败,增加错误计数
+ if current_weight is None:
+ self.error_count += 1
+ self.logger.warning(f"上料斗重量读取失败,错误计数: {self.error_count}")
+
+ # 如果连续错误次数超过阈值,触发报警
+ if self.error_count >= self.max_error_count:
+ self.logger.error("警告:上料斗重量传感器连续读取失败,请检查设备连接")
+ return False # 读取失败时不触发要料
+
+ # 重置错误计数
+ self.error_count = 0
+
+ if current_weight < self.min_required_weight:
+ self.logger.info("上料斗重量不足,通知搅拌楼要料")
+ self.move_upper_door_to_mixer()
+ return True
+ return False
+
+ def move_upper_door_to_mixer(self):
+ """移动上料斗到搅拌楼出砼口"""
+ self.logger.info("移动上料斗到搅拌楼出砼口")
+ self.relay.control('door_upper', 'open')
+
+ def return_upper_door(self):
+ """返回上料斗"""
+ self.logger.info("上料斗返回原位")
+ self.relay.control('door_upper', 'close')
+
+ def start_feeding(self):
+ """开始下料过程"""
+ if self.state_machine.get_state() != FeedingState.IDLE:
+ self.logger.warning("下料已在进行中")
+ return False
+
+ self.logger.info("开始三阶段下料过程")
+ self.state_machine.set_state(FeedingState.STAGE_ONE)
+ return True
+
+ def execute_stage_one(self):
+ """执行第一阶段下料"""
+ self.logger.info("开始第一阶段下料 (1/3)")
+
+ # 设置变频器频率
+ frequency = self.stage_frequencies[1]
+ if not self.inverter.set_frequency(frequency):
+ self.logger.error("设置变频器频率失败")
+ self.finish_feeding()
+ return
+
+ # 启动变频器
+ if not self.inverter.control('start'):
+ self.logger.error("启动变频器失败")
+ self.finish_feeding()
+ return
+
+ # 控制出砼门
+ self.relay.control('door_lower_1', 'open')
+ self.relay.control('door_lower_2', 'open')
+
+ # 监控下料过程
+ start_time = time.time()
+ initial_weight = self.lower_transmitter.read_weight()
+
+ # 如果初始重量读取失败,取消操作
+ if initial_weight is None:
+ self.logger.error("无法获取初始重量,取消下料操作")
+ self.finish_feeding()
+ return
+
+ target_weight = initial_weight + self.target_weight_per_stage
+
+ while self.state_machine.get_state() == FeedingState.STAGE_ONE:
+ current_weight = self.lower_transmitter.read_weight()
+
+ # 检查重量读取是否成功
+ if current_weight is None:
+ self.error_count += 1
+ if self.error_count >= self.max_error_count:
+ self.logger.error("下料斗重量传感器连续读取失败,停止下料")
+ self.finish_feeding()
+ return
+ else:
+ self.error_count = 0 # 重置错误计数
+
+ # 检查是否达到目标重量或超时
+ if (current_weight is not None and current_weight >= target_weight) or \
+ (time.time() - start_time) > self.timeout:
+ self.state_machine.set_state(FeedingState.STAGE_TWO)
+ break
+
+ time.sleep(2) # 每2秒读取一次
+
+ def execute_stage_two(self):
+ """执行第二阶段下料"""
+ self.logger.info("开始第二阶段下料 (2/3)")
+
+ # 调整变频器频率
+ frequency = self.stage_frequencies[2]
+ if not self.inverter.set_frequency(frequency):
+ self.logger.error("设置变频器频率失败")
+ self.finish_feeding()
+ return
+
+ start_time = time.time()
+ initial_weight = self.lower_transmitter.read_weight()
+
+ if initial_weight is None:
+ self.logger.error("无法获取初始重量,取消下料操作")
+ self.finish_feeding()
+ return
+
+ target_weight = initial_weight + self.target_weight_per_stage
+
+ while self.state_machine.get_state() == FeedingState.STAGE_TWO:
+ current_weight = self.lower_transmitter.read_weight()
+
+ if current_weight is None:
+ self.error_count += 1
+ if self.error_count >= self.max_error_count:
+ self.logger.error("下料斗重量传感器连续读取失败,停止下料")
+ self.finish_feeding()
+ return
+ else:
+ self.error_count = 0
+
+ if (current_weight is not None and current_weight >= target_weight) or \
+ (time.time() - start_time) > self.timeout:
+ self.state_machine.set_state(FeedingState.STAGE_THREE)
+ break
+
+ time.sleep(2) # 每2秒读取一次
+
+ def execute_stage_three(self):
+ """执行第三阶段下料"""
+ self.logger.info("开始第三阶段下料 (3/3)")
+
+ # 调整变频器频率
+ frequency = self.stage_frequencies[3]
+ if not self.inverter.set_frequency(frequency):
+ self.logger.error("设置变频器频率失败")
+ self.finish_feeding()
+ return
+
+ start_time = time.time()
+ initial_weight = self.lower_transmitter.read_weight()
+
+ if initial_weight is None:
+ self.logger.error("无法获取初始重量,取消下料操作")
+ self.finish_feeding()
+ return
+
+ target_weight = initial_weight + self.target_weight_per_stage
+
+ while self.state_machine.get_state() == FeedingState.STAGE_THREE:
+ current_weight = self.lower_transmitter.read_weight()
+
+ if current_weight is None:
+ self.error_count += 1
+ if self.error_count >= self.max_error_count:
+ self.logger.error("下料斗重量传感器连续读取失败,停止下料")
+ self.finish_feeding()
+ return
+ else:
+ self.error_count = 0
+
+ if (current_weight is not None and current_weight >= target_weight) or \
+ (time.time() - start_time) > self.timeout:
+ self.state_machine.set_state(FeedingState.COMPLETED)
+ break
+
+ time.sleep(2) # 每2秒读取一次
+
+ def finish_feeding(self):
+ """完成下料"""
+ self.logger.info("下料完成,关闭出砼门")
+ self.inverter.control('stop')
+ self.relay.control('door_lower_1', 'close')
+ self.relay.control('door_lower_2', 'close')
+ self.state_machine.set_state(FeedingState.IDLE)
+ self.error_count = 0
+
+ def check_arch_blocking(self):
+ """检查是否需要破拱"""
+ current_time = time.time()
+
+ # 检查上料斗
+ upper_weight = self.upper_transmitter.read_weight()
+ if upper_weight is not None:
+ if (abs(upper_weight - self.last_upper_weight) < 0.1) and \
+ (current_time - self.last_weight_time) > 10:
+ self.logger.info("上料斗可能堵塞,启动破拱")
+ self.relay.control('break_arch_upper', 'open')
+ time.sleep(2)
+ self.relay.control('break_arch_upper', 'close')
+
+ # 检查下料斗
+ lower_weight = self.lower_transmitter.read_weight()
+ if lower_weight is not None:
+ if (abs(lower_weight - self.last_lower_weight) < 0.1) and \
+ (current_time - self.last_weight_time) > 10:
+ self.logger.info("下料斗可能堵塞,启动破拱")
+ self.relay.control('break_arch_lower', 'open')
+ time.sleep(2)
+ self.relay.control('break_arch_lower', 'close')
+
+ # 更新重量记录
+ if upper_weight is not None:
+ self.last_upper_weight = upper_weight
+ if lower_weight is not None:
+ self.last_lower_weight = lower_weight
+
+ if upper_weight is not None or lower_weight is not None:
+ self.last_weight_time = current_time
+
+ def run_cycle(self):
+ """运行一个控制周期"""
+ try:
+ # 检查是否需要要料
+ self.check_material_request()
+
+ # 检查破拱
+ self.check_arch_blocking()
+
+ # 执行状态机
+ self.state_machine.transition(self)
+
+ return True
+ except Exception as e:
+ self.logger.error(f"控制周期执行错误: {e}")
+ return False
\ No newline at end of file
diff --git a/src/control/state_machine.py b/src/control/state_machine.py
new file mode 100644
index 0000000..eb22c58
--- /dev/null
+++ b/src/control/state_machine.py
@@ -0,0 +1,105 @@
+from abc import ABC, abstractmethod
+from enum import Enum
+from src.utils.logger import app_logger
+
+
+class FeedingState(Enum):
+ """下料状态枚举"""
+ IDLE = 0
+ STAGE_ONE = 1
+ STAGE_TWO = 2
+ STAGE_THREE = 3
+ COMPLETED = 4
+
+
+class State(ABC):
+ """状态基类"""
+
+ def __init__(self):
+ self.logger = app_logger.getChild('StateMachine')
+
+ @abstractmethod
+ def handle(self, context):
+ """处理状态逻辑"""
+ pass
+
+
+class IdleState(State):
+ """空闲状态"""
+
+ def handle(self, context):
+ self.logger.info("系统处于空闲状态")
+ # 等待开始下料命令
+ return FeedingState.IDLE
+
+
+class StageOneState(State):
+ """第一阶段下料状态"""
+
+ def handle(self, context):
+ self.logger.info("执行第一阶段下料")
+ # 第一阶段下料逻辑
+ context.execute_stage_one()
+ return FeedingState.STAGE_TWO
+
+
+class StageTwoState(State):
+ """第二阶段下料状态"""
+
+ def handle(self, context):
+ self.logger.info("执行第二阶段下料")
+ # 第二阶段下料逻辑
+ context.execute_stage_two()
+ return FeedingState.STAGE_THREE
+
+
+class StageThreeState(State):
+ """第三阶段下料状态"""
+
+ def handle(self, context):
+ self.logger.info("执行第三阶段下料")
+ # 第三阶段下料逻辑
+ context.execute_stage_three()
+ return FeedingState.COMPLETED
+
+
+class CompletedState(State):
+ """完成状态"""
+
+ def handle(self, context):
+ self.logger.info("下料完成")
+ # 完成逻辑
+ context.finish_feeding()
+ return FeedingState.IDLE
+
+
+class FeedingStateMachine:
+ """下料状态机"""
+
+ def __init__(self):
+ self.states = {
+ FeedingState.IDLE: IdleState(),
+ FeedingState.STAGE_ONE: StageOneState(),
+ FeedingState.STAGE_TWO: StageTwoState(),
+ FeedingState.STAGE_THREE: StageThreeState(),
+ FeedingState.COMPLETED: CompletedState()
+ }
+ self.current_state = FeedingState.IDLE
+ self.logger = app_logger.getChild('FeedingStateMachine')
+
+ def transition(self, context):
+ """状态转换"""
+ if self.current_state in self.states:
+ next_state = self.states[self.current_state].handle(context)
+ if next_state != self.current_state:
+ self.logger.info(f"状态从 {self.current_state} 转换到 {next_state}")
+ self.current_state = next_state
+
+ def set_state(self, state: FeedingState):
+ """设置当前状态"""
+ self.logger.info(f"手动设置状态为 {state}")
+ self.current_state = state
+
+ def get_state(self):
+ """获取当前状态"""
+ return self.current_state
\ No newline at end of file
diff --git a/src/divices/__init__.py b/src/divices/__init__.py
new file mode 100644
index 0000000..82f5c8e
--- /dev/null
+++ b/src/divices/__init__.py
@@ -0,0 +1,5 @@
+from .relay import RelayController
+from .inverter import InverterController
+from .transmitter import TransmitterController
+
+__all__ = ['RelayController', 'InverterController', 'TransmitterController']
\ No newline at end of file
diff --git a/src/divices/__pycache__/__init__.cpython-39.pyc b/src/divices/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..98188386820240440f9fbb5145e552ed58a8ae03
GIT binary patch
literal 344
zcmY+9yGjHx6ozw|nO!f7Aovi|h_#3;D1yxvy44T}Aql&YBr}}Eg?Sx2-^0=;NNeRQ
zSecB1?g{yFu0JPYu{cE8R^)B_p5KoW{5O(=WxnAeLm`DNa=62|_{0)NJ39H)R<7=9
zj5bJBFSu(|{fOG;6X&rOr;`V5#dy>E2)(r$a_aQn?=(aWd)c!!2p^n@`vnSR5ugQC
z#;TpA8j9vrNOSWmHnpUq_@^@Ury1wMTFze)92Aj)ujTC}8%DVm;0EZQw2bUdYi0a~
r%b!(w7(>*K1&~acosl{)&W$&b^YdX$^DEb@XREKvKSPsojPoJi$HiRn
literal 0
HcmV?d00001
diff --git a/src/divices/__pycache__/inverter.cpython-39.pyc b/src/divices/__pycache__/inverter.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..14bedebd410fb425de23e483c1a66a6b6cbc2809
GIT binary patch
literal 2790
zcmZ`*TW=dh6rP#Ac)f9)HVx2=TEb1jFHxWRP$3j4B@_e^QY%WWh?UuyBpWun?(Di?
z%c)v55*1Plh-*uVoQjI}p{bA%LWt5Ae#O4hxCu`@BUR#@S$plK&aU?C%$&KL`R1E%
zrZPNi6L<>dUkf)1g#3+}{zrq%Bz(~kFr0AelOxApv
zSRvfF$jT-+Pmoe>3)+$rO-9}h`3JS=q+6ZxJrTscqSrolMK$)q0E+BNwd(lc{JfAZ
zHtc^4$V|c)y#q#&GUcSqI4x^@x6p+l46dzeDgXlq=eZS9&T)(9;ctmNw^u12;)NBx
zYzv(aL$1h2AUDK!L7=0_=o`UVA!8w@!XTER?+aNQg`qY+`)=#T7aLb^wXgiNap_k`
zY*8pKX^FlNMscE7Lm4Ml==Owpw==DsrhR!$sl7
zv(rLwFPL}S6f!5OLq|)qk#uLdch+-7H0yOxov79m%W=HGiya5!im+2c&CXlU8HM*z
zaFwt%2;@rCrg9I{IfjbL{#-SL%Tm%poff$z&WPor{Yw2s`6u_vH*r>lt$ue-b
zF%BNHmE){QzbDt(8kC(PrwP}V={d^v6P?syau`y$1ho+uV)OnF?JGAkN|dO=x%SMH
zu8kjm-u&g}(easSSP=}h57lAFCOH5t01%UWwgV&%kr+bCP~K*8H|D5N=%;26_VW}N
z958}SO9*3hlon_aY-9fb=sKh9j2vz!z=FuP8b$oK=oRf+K(ztH0U7>rjd$r`zyi
zh*w~9^;c*yKufD(l}SpsT&Cozt+NG^G~6h6NZ>3$VHV=fivJ*#dx$J-qVC
zw3_V}&h9oX8%ns;q71CvU6k5Bs6VjAgWrB1FSFx~ld6+!uvGB3&R=_Qe{JiltDAT4
z!e%HH)XFLjC&iDX7YnDWKXMn0kZ2yLuhK2si@X4oEpWF}Akn>GE|hpcm2|aD)RYoa
zim2wt5<}h2kl{$gP9~WzLyhQpFa$_qQJpdMx2gWfG;P>RCL04`QoCw=0C^tk3=S@{
z;^GmuGll+zpX=$VIMMi41GN+anvn6r4XcymdOnyMsOVJn@~UQt8eZDg?gfX
zPSxM5TEj8eziW{$u?57;12NY-HS*huc?u|{NNCsp=_8>5nmM$MgoTWRDy2wR01^(T
zDv+L=-D7m)^Sc`tR;T3%Aj6o6wE_Ga3ZQr?(IcodC(f+p8{`%6x
zYb#lC`^(D@?yVjjS907rt+P=(a(q%bdreeqJ$erH=qV3jfXGQj$5eox!>m%FaZD*?
z>Zjq0Sc<$F$U&5Ryjn~`1$7^v?btK{C1V>dz5^krlqLsI-;$C%anJMMUBh@;D05`yi&TRz6w!9}R72R84q_%ak
n3+@mTwb=8ciS(YLt}p2=Xg~ys^-4O!SMi1rVJrnW6${$`y@v2x
literal 0
HcmV?d00001
diff --git a/src/divices/__pycache__/relay.cpython-39.pyc b/src/divices/__pycache__/relay.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..71dba421da8ab4d84dda9a741b95e79652647d45
GIT binary patch
literal 3007
zcmZ`*-ESPX5huCay$_vEvf?&!8U#XsqPad8C)){JA^k{gt7wZVKy1LxLCxm0q@|Vb
z_AcCA*E*~U8CFmuNMaaO)7nvi6Y3%<3PkCb+r)AElBfO&e68=~d(Tt)(9Ce}r1&gx
z!Qqgc8P1Zw8L~P$nkP`U`KL>t6$tq+cD`IJ=zI@q_#6;H1g()u+R_R&eO6)6vYJ)r
z6;80oCF0VK;BkWBo9FbY)m8|3<3%#7kLoX*rL}8s&@}R@TDCiW$7>tjIaq#1i9(zgF
zsw<}&L8KbBnpE*5thICR#?IZ%oxAVtZ2Y}*^ZoAI?{%)-0j9(>UuuMr=FNtRJoIl+
zkk3Fp1vUIBkc1}0B?$vcBi6PA-NF&>6QJNd`VQXM;5D`xku@!NZCdc!w4<9g$UCA4
zG$RQ3JT|9rvC3q5uRK?tEIaLus1P)+*QnR4fe7_uN>+s%M%5?|2csoR^HA0<>{ZVPqWM`V1sDchgF`o52_spb50y7Bd|W!5H%lIEuIQ}m`hg$0
zE{19NRUm{q@D(hFXKDZYUpCj89D?@Ifg*%mrvED*{_&t7kJVrpt*00^5zAuq{lN$S
z+PU`u(9Z2&c7NOMe7FuVtcoyGV(Jhy>MKZ4G$oG#rzb)gxessQQ!pNywOQ-wVLu-g
z2aiYkyX`K?%MW?&fGKp=c>Nz>T5m?AO%sT>RR)m
zWH|#hdx7;wa>;!*UE&N
z?`5FpNpO;rsJQYeC95_ittn$|OxTG%uydZg3|3a{U)X}5?Q>;P1`cA~iE*Pk*KhRJ
zu5@nPyMOm~=h}xu*{O%f^}!1gKMq`X;||IUa#!b9Z*@N2n3e
z`Cxl{W~wxMv=cVGi!#!g7iQfb&Yd}@N6x-@>c{Tcb1$8F
z;U_u|=M?#M*@!^`kQ)G!_t1{$4}|7QdY9BFIHqkO7ve>o8CcJqx$MbiBpkRwC(&YE%VF;D*jB88#b1C^ePC2rJ-PgUEXt)I&_;gfW_@6aAW}lk^!{
zgnsMEM-jKD?qT{E6in%u9t^mez$By%bs51$eX`0Ds$PwNUZsLwwAHH#73>H>mxt2vN49#
zgSY?Dx&F@X@$I~Er@MBghp_MEfK;sV$%VPz|Dk9)9e)!ww(mZw(YgKS2Y>rR_u4xt
zEV{pGcYl4Ylu;#6P~$+fRg-}UXl?tDg2MFZGC!}X01_04nkfGUt~t_Ac*6Ih-432w
zCEuR`OzDw+;0M*Z)VT#nTz(+r<>3>|L(=VM&}lFpVhSOwKsg(SoHk)i(8H|2T2Bn0
z+@2a_Ak@@O)k91@bUR~+WTBd4{0lIc*_O#AsxTsH8(tO3Ze$DCcAQOUdXT_dAo-)d
zBpmG^c8w(5@XIiE1g!;q8J@6*WN-}t0%9Kg_zC!>^e}gBt?y2zd3t7QHpW}x{y*R9ZftkoyaFPp
zU)=fAcIW!bE?Iro@uet)Z(q1SU&ph99*J!2^g@buOl}G
zL~}p50AX!d%oN%VYf?7ho;E1L<5DM(977QY0z9ro!^ptYf{(NBgG~4&5W+C1Im`E3
z3~A1uqK6^STaOO|q^~_!D&WnDD0E$&cinnJ#5Lp#uKQYCt@URzt}7ZI3|nEOwB^In
zhtWBXC|9_TX|Nne?psLqE<3~Fjb|Qcm^wmuK$9$j_;8UI$Fhga4CJ}5q0|v1_=?u!
laMK@+Bfl0NO`ja*C1Qm4v#+zyr|y3Tb5)4QPVfDK^#ubHK#u?b
literal 0
HcmV?d00001
diff --git a/src/divices/__pycache__/transmitter.cpython-39.pyc b/src/divices/__pycache__/transmitter.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4f037629f8a76112662132a41449637da08e9c29
GIT binary patch
literal 2785
zcmai0TW=dh6rS0e*N)@3X&XqOmAMFEgrYtm9wHP~3Y5N(2U<~TMXa2ijkE6h!pv^L
zmQ%HAP=RY3_dz^4#OvAfiS{oKuR=6De_vNmGqphcs($328%5kWG5r|H#@et3$x=D?QH3o=;<#I#3w*B}?hJkKrR!Bgzfzuuu|&RF
zb%IKv;Bgl<2OkSMhv5_RKs=J8jN~*%b2>|V2E3+cGX1u}3}&vHIZO4e+Zu~8d({AY
z;w-UBayA=bNoeD2l%=3euraW6kKBKp=gXqxhoQ&kD&>$@g23a^i}0`Zg|+sD_2&BZ
z_StjI8{fAs-EV&V6EJNG!mJ^Ua=zqAW4R(iX;dpbl(q=+r#;7Kt_f3m5Ap_lbMT47
zKpM0`9MaH$(uNLHYZyRvMt~ZjxnVKdptr~!Z4+n47|ygA&WtkxW*hVMiJ^8+bF{6k
z0m&wK3>t1Di6g<|mzJx}fuljvE_mVGvL7&Mb-
z*Aolj&N)n1YtnWczwC#OgI0?)5JIi~JFZ)_esDMhJ;eta9|J9px-L3*??E0BQs;rv
z!Si4g@YSOIFueWf_h-L7>Yso6t2OA(a};HDz6Y9}AE=#o$X*c0gz!S#?UG-ZfWAPu
zi3Ubzh8w*z-Nh5^J!mEZpiE`J)DMQ>Bt>wGBE-NNItI8!QQt5&$q8@`gM?=7GfGw<
zZtHJ?#M)5qQ?zdpa3FLlV-sX2$VrIc6?%p;^H`TVL=FK5+qWVFZu`mI)}?#>?Psa-
zq6h)oCCWtTUzz5&KWzVaZzeN;Bpc^w`GD^wp(|qmNEzHg#{0p~Qz$S!&MRE`6r36&
zE2*~&QvL!AzXYGawTPzCahjwlIz{UThGW|O+w%ea6xIQV2tQs=VY@*$2|`nz1sJv3
z$6XlT=l~2cNs1=GsO^GLM;OZqrdkCera(jaQ0v;m=C5b_Aa;OL819l}6y}*OvOOfT
z7FWobZcq^($E>6f-3XG=4E0*+??bD~__*Bm^h4_9WTVS2U2MSiGTaLUx}dD|-1IrDcQGci~u1<2(h)
zRQG=goY6_8c(mO=*&As#0ur<&+0cQG-Z3#Q8hSU1_FSg5SHS^{ZXBWQJ&jZYSKNss
zx?-F$aAl^rLl}iI&|iFx{@DNNLmXlnN=JCE+grah*ROSoQS0jC*4m@_tS)1D#<(Yh
zG*w=frYb$1=u`@QpX+*7N3|ED9Bu%luFoDlswxJ@B3svHm!bo}JpFmCb!Dq{@!YeU
ztIe&|=ELh){;ocTYB-aL&=+m_Rcf
zbGGk))4F@PC&`X0ZnC%Bhq&T=JCg`-!6#6ZeLLuA4iep!V1vO6?9q$NTgLCP_z
zdH0lz_t%kDR8~XgQc%f<(q!IZRNz<}vSud*S%7a8gt=t5CN9=OPvoplk7Fv@9d{$j
z=FWt-K`!0}f@$BThNeMl;QNndB_M5CdP19mB$m+9+CFM&FTt#AHt+|y
zIH_~Xj$pOK%ZTI1gyWPdEDDfMI?m@&KIlqfj>9T04C?@i)P1PL`*06fpA_UA&z`@9
zWG9~}4oe!xJb)QTn+a
literal 0
HcmV?d00001
diff --git a/src/divices/inverter.py b/src/divices/inverter.py
new file mode 100644
index 0000000..ca8e729
--- /dev/null
+++ b/src/divices/inverter.py
@@ -0,0 +1,96 @@
+from pymodbus.client import ModbusTcpClient
+from pymodbus.exceptions import ModbusException
+from src.utils.logger import app_logger
+
+
+class InverterController:
+ """变频器控制器"""
+
+ def __init__(self, host: str, port: int, slave_id: int = 1):
+ self.host = host
+ self.port = port
+ self.slave_id = slave_id
+ self.client = None
+ self.logger = app_logger.getChild('InverterController')
+
+ # 变频器寄存器地址
+ self.registers = {
+ 'frequency': 0x01, # 频率设置寄存器
+ 'start': 0x00, # 启动命令寄存器
+ 'stop': 0x01 # 停止命令寄存器
+ }
+
+ def connect(self):
+ """连接变频器"""
+ try:
+ self.client = ModbusTcpClient(self.host, port=self.port)
+ return self.client.connect()
+ except Exception as e:
+ self.logger.error(f"连接变频器失败: {e}")
+ return False
+
+ def disconnect(self):
+ """断开变频器连接"""
+ if self.client:
+ try:
+ self.client.close()
+ except Exception as e:
+ self.logger.error(f"断开变频器连接失败: {e}")
+
+ def set_frequency(self, frequency: float):
+ """设置变频器频率"""
+ if not self.client:
+ if not self.connect():
+ return False
+
+ try:
+ # 写入频率值 (假设频率值需要转换为Hz*10)
+ result = self.client.write_register(
+ self.registers['frequency'],
+ int(frequency * 10),
+ slave=self.slave_id
+ )
+
+ if isinstance(result, Exception):
+ self.logger.error(f"设置变频器频率失败: {result}")
+ return False
+
+ self.logger.info(f"设置变频器频率为 {frequency}Hz")
+ return True
+ except ModbusException as e:
+ self.logger.error(f"变频器Modbus通信错误: {e}")
+ return False
+
+ def control(self, action: str):
+ """控制变频器启停"""
+ if not self.client:
+ if not self.connect():
+ return False
+
+ try:
+ if action == 'start':
+ result = self.client.write_register(
+ self.registers['start'],
+ 1,
+ slave=self.slave_id
+ )
+ self.logger.info("启动变频器")
+ elif action == 'stop':
+ result = self.client.write_register(
+ self.registers['stop'],
+ 1,
+ slave=self.slave_id
+ )
+ self.logger.info("停止变频器")
+ else:
+ self.logger.error(f"无效的变频器操作: {action}")
+ return False
+
+ if isinstance(result, Exception):
+ self.logger.error(f"控制变频器失败: {result}")
+ return False
+
+ return True
+ except ModbusException as e:
+ self.logger.error(f"变频器控制错误: {e}")
+ return False
\ No newline at end of file
diff --git a/src/divices/relay.py b/src/divices/relay.py
new file mode 100644
index 0000000..39e736c
--- /dev/null
+++ b/src/divices/relay.py
@@ -0,0 +1,85 @@
+import socket
+import binascii
+import time
+from src.utils.logger import app_logger
+
+
+class RelayController:
+ """网络继电器控制器"""
+
+ def __init__(self, host: str, port: int):
+ self.host = host
+ self.port = port
+ self.logger = app_logger.getChild('RelayController')
+
+ # 设备DO端口映射 (将在初始化时从配置加载)
+ self.device_mapping = {}
+
+ # 继电器控制命令
+ self.relay_commands = {
+ 0: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'},
+ 1: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'},
+ 2: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'},
+ 3: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'},
+ 4: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'}
+ }
+
+ # 读取状态命令
+ self.read_status_command = '000000000006010100000008'
+
+ def set_device_mapping(self, mapping: dict):
+ """设置设备映射"""
+ self.device_mapping = mapping
+
+ def send_command(self, command_hex: str):
+ """发送命令到网络继电器"""
+ try:
+ byte_data = binascii.unhexlify(command_hex)
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+ sock.settimeout(5) # 设置超时
+ sock.connect((self.host, self.port))
+ sock.send(byte_data)
+ response = sock.recv(1024)
+ self.logger.debug(f"收到继电器响应: {binascii.hexlify(response)}")
+ return response
+ except Exception as e:
+ self.logger.error(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 device_name, bit_index in self.device_mapping.items():
+ status_dict[device_name] = status_bin[bit_index] == '1'
+ else:
+ self.logger.warning("读取继电器状态失败或响应无效")
+
+ return status_dict
+
+ def control(self, device_name: str, action: str):
+ """控制继电器开关"""
+ if device_name not in self.device_mapping:
+ self.logger.error(f"无效的继电器设备: {device_name}")
+ return False
+
+ bit_index = self.device_mapping[device_name]
+
+ if bit_index not in self.relay_commands:
+ self.logger.error(f"不支持的DO端口: {bit_index}")
+ return False
+
+ if action not in self.relay_commands[bit_index]:
+ self.logger.error(f"无效的继电器动作: {action}")
+ return False
+
+ self.logger.info(f"控制继电器 {device_name} ({bit_index}) {action}")
+ result = self.send_command(self.relay_commands[bit_index][action])
+ time.sleep(0.1) # 短暂延迟确保命令执行
+ return result is not None
\ No newline at end of file
diff --git a/src/divices/transmitter.py b/src/divices/transmitter.py
new file mode 100644
index 0000000..2c4943b
--- /dev/null
+++ b/src/divices/transmitter.py
@@ -0,0 +1,82 @@
+from pymodbus.client import ModbusTcpClient
+from pymodbus.exceptions import ModbusException
+import struct
+from src.utils.logger import app_logger
+
+
+class TransmitterController:
+ """重量变送器控制器"""
+
+ def __init__(self, name: str, host: str, port: int, slave_id: int = 1):
+ self.name = name
+ self.host = host
+ self.port = port
+ self.slave_id = slave_id
+ self.client = None
+ self.logger = app_logger.getChild(f'TransmitterController.{name}')
+
+ # 默认配置
+ self.weight_register = 0
+ self.register_count = 2
+
+ def set_config(self, weight_register: int, register_count: int):
+ """设置变送器配置"""
+ self.weight_register = weight_register
+ self.register_count = register_count
+
+ def connect(self):
+ """连接变送器"""
+ try:
+ self.client = ModbusTcpClient(self.host, port=self.port)
+ return self.client.connect()
+ except Exception as e:
+ self.logger.error(f"连接变送器 {self.name} 失败: {e}")
+ return False
+
+ def disconnect(self):
+ """断开变送器连接"""
+ if self.client:
+ try:
+ self.client.close()
+ except Exception as e:
+ self.logger.error(f"断开变送器 {self.name} 连接失败: {e}")
+
+ def read_weight(self):
+ """读取重量数据"""
+ if not self.client:
+ if not self.connect():
+ return None
+
+ try:
+ # 读取保持寄存器
+ result = self.client.read_holding_registers(
+ address=self.weight_register,
+ count=self.register_count,
+ slave=self.slave_id
+ )
+
+ if isinstance(result, Exception):
+ self.logger.error(f"读取变送器 {self.name} 数据失败: {result}")
+ return None
+
+ # 解析重量数据
+ if self.register_count == 2:
+ # 组合两个寄存器为32位浮点数 (大端序)
+ weight_bytes = struct.pack('>HH', result.registers[0], result.registers[1])
+ weight = struct.unpack('>f', weight_bytes)[0]
+ elif self.register_count == 1:
+ # 单个寄存器直接作为整数
+ weight = float(result.registers[0])
+ else:
+ self.logger.error(f"不支持的寄存器数量: {self.register_count}")
+ return None
+
+ self.logger.debug(f"变送器 {self.name} 读取重量: {weight}kg")
+ return weight
+
+ except ModbusException as e:
+ self.logger.error(f"变送器 {self.name} Modbus通信错误: {e}")
+ return None
+ except Exception as e:
+ self.logger.error(f"变送器 {self.name} 数据解析错误: {e}")
+ return None
\ No newline at end of file
diff --git a/src/logs/app.log b/src/logs/app.log
new file mode 100644
index 0000000..01045bd
--- /dev/null
+++ b/src/logs/app.log
@@ -0,0 +1,120 @@
+2025-09-12 19:40:44,426 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:40:44,426 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:40:44,426 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-12 19:40:44,426 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-12 19:40:44,426 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-12 19:40:44,426 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-12 19:40:47,438 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-12 19:40:47,438 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-12 19:40:50,453 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:40:50,453 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:40:53,476 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:40:53,476 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:40:57,499 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:40:57,499 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:40:57,499 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2
+2025-09-12 19:40:57,499 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2
+2025-09-12 19:41:00,500 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:00,500 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:03,500 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-12 19:41:03,500 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-12 19:41:03,500 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:41:03,500 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:41:07,507 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:07,507 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:07,507 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3
+2025-09-12 19:41:07,507 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3
+2025-09-12 19:41:07,507 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-12 19:41:07,507 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-12 19:41:10,516 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:10,516 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:13,529 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-12 19:41:13,529 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-12 19:41:13,529 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:41:13,529 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-12 19:41:17,535 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:17,535 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:41:17,535 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4
+2025-09-12 19:41:17,535 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4
+2025-09-12 19:41:17,535 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-12 19:41:17,535 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-12 19:42:04,120 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:42:04,120 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:42:04,120 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-12 19:42:04,120 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-12 19:42:04,120 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-12 19:42:04,120 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-12 19:42:07,124 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-12 19:42:07,124 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-12 19:42:10,125 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-12 19:42:10,125 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:35:50,544 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-13 10:35:50,544 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-13 10:35:50,546 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-13 10:35:50,546 - FeedingControl.MainSystem - INFO - ʼ
+2025-09-13 10:35:50,546 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-13 10:35:50,546 - FeedingControl.MainSystem - INFO - ϵͳ
+2025-09-13 10:35:53,550 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-13 10:35:53,550 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 1
+2025-09-13 10:35:56,552 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:35:56,552 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:35:59,555 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:35:59,555 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:03,566 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:03,566 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:03,566 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2
+2025-09-13 10:36:03,566 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 2
+2025-09-13 10:36:06,573 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:06,573 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:09,582 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:09,582 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:09,582 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:09,582 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:13,601 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:13,601 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:13,601 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3
+2025-09-13 10:36:13,601 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 3
+2025-09-13 10:36:13,601 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:13,601 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:16,602 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:16,602 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:19,617 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:19,617 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:19,617 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:19,617 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:23,626 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:23,626 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:23,626 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4
+2025-09-13 10:36:23,626 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 4
+2025-09-13 10:36:23,626 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:23,626 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:26,638 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:26,638 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:29,640 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:29,640 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:29,640 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:29,640 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:33,662 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:33,662 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:33,663 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 5
+2025-09-13 10:36:33,663 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 5
+2025-09-13 10:36:33,663 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:33,663 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:36,672 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:36,672 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:39,685 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:39,685 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:39,685 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:39,685 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:43,711 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:43,711 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:43,711 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 6
+2025-09-13 10:36:43,711 - FeedingControl.FeedingController - WARNING - ϶ȡʧܣ: 6
+2025-09-13 10:36:43,711 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:43,711 - FeedingControl.FeedingController - ERROR - 棺϶ȡʧܣ豸
+2025-09-13 10:36:46,715 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:46,715 - FeedingControl.TransmitterController.upper - ERROR - upper ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.21:502]
+2025-09-13 10:36:49,725 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:49,725 - FeedingControl.TransmitterController.lower - ERROR - lower ModbusͨŴ: Modbus Error: [Connection] Failed to connect[ModbusTcpClient 192.168.0.22:502]
+2025-09-13 10:36:49,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
+2025-09-13 10:36:49,725 - FeedingControl.StateMachine - INFO - ϵͳڿ״̬
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..ebc8f52
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,84 @@
+import time
+import signal
+import sys
+import threading
+from utils.config import config
+from utils.logger import app_logger
+from control.feeding_controller import FeedingController
+
+
+class FeedingControlSystem:
+ """主控制系统"""
+
+ def __init__(self):
+ self.logger = app_logger.getChild('MainSystem')
+ self.running = False
+ self.controller = None
+ self._setup_signal_handlers()
+ self._initialize_controller()
+
+ def _setup_signal_handlers(self):
+ """设置信号处理器"""
+ signal.signal(signal.SIGINT, self._signal_handler)
+ signal.signal(signal.SIGTERM, self._signal_handler)
+
+ def _signal_handler(self, signum, frame):
+ """信号处理函数"""
+ self.logger.info(f"收到信号 {signum},正在停止系统...")
+ self.stop()
+ sys.exit(0)
+
+ def _initialize_controller(self):
+ """初始化控制器"""
+ try:
+ self.controller = FeedingController()
+ self.logger.info("控制器初始化完成")
+ except Exception as e:
+ self.logger.error(f"控制器初始化失败: {e}")
+ raise
+
+ def start(self):
+ """启动系统"""
+ if self.running:
+ self.logger.warning("系统已在运行")
+ return
+
+ self.logger.info("启动控制系统")
+ self.running = True
+
+ # 主循环
+ monitoring_interval = config.get('parameters.monitoring_interval', 1)
+
+ while self.running:
+ try:
+ if self.controller:
+ self.controller.run_cycle()
+ time.sleep(monitoring_interval)
+ except Exception as e:
+ self.logger.error(f"系统运行错误: {e}")
+ time.sleep(monitoring_interval)
+
+ def stop(self):
+ """停止系统"""
+ if not self.running:
+ self.logger.warning("系统未在运行")
+ return
+
+ self.logger.info("停止控制系统")
+ self.running = False
+
+ self.logger.info("控制系统已停止")
+
+
+def main():
+ """主函数"""
+ try:
+ system = FeedingControlSystem()
+ system.start()
+ except Exception as e:
+ app_logger.error(f"系统启动失败: {e}")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/utils/__init__.py b/src/utils/__init__.py
new file mode 100644
index 0000000..b1ea832
--- /dev/null
+++ b/src/utils/__init__.py
@@ -0,0 +1,4 @@
+from .config import Config
+from .logger import setup_logger
+
+__all__ = ['Config', 'setup_logger']
\ No newline at end of file
diff --git a/src/utils/__pycache__/__init__.cpython-39.pyc b/src/utils/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..253f18e93e2b6c535e4eb17edea3421ad8ae33f0
GIT binary patch
literal 262
zcmYjLF>b>!43uQYDGKKo8RDf6D2kv!i;h{k83G8yQe+j9Wk~YiC*>b`h1N{{LZ(nb
zh7jQKNFERDHk((1v18{!V7=t#IT$vdm|=?`h@gg8+ESW1Gt3rkfxHl*+>2(rO=}SG>YCC2uq04^5AKR
literal 0
HcmV?d00001
diff --git a/src/utils/__pycache__/config.cpython-39.pyc b/src/utils/__pycache__/config.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7bd6d3764a32f6d3fee9ac50baab507e8670c8cc
GIT binary patch
literal 1559
zcma)6&2Jk;6rY)$U9TM{aSIg60kuaT8kJjBDg-11BqU!zs=6{-tetVfV61uj-kX{C@q6#NvA9?w
zu>P=r-F%M|@*4{C%L3sQ*zqMGf(RNCFaJ`Hg3LnJwmhrNJx<9RL|B3kiQwDJv!%UF
zJV(&CN!31v*Q8cuhOe~(ZLC*2y#Q773*6_fYQRW><~=5;XNhIWg>{b#F6^NVh8@Xe
zp~=n?;efUv+#&Z|QGr_}kQYQ5ZWYC%SQ-+qB#IEgDdVh0on~tzSq9_BpX^Kz?oaQJ
zr(fKh{_yb0OKX?w-CnTSQ0=aYwq&5|>r#qVXTuMoj#g3V$Gup~b{#}8GW)Fb8tt%3
z%|fpAyA8b=u(_0w$Gr+Wei}$hQ{t19jOZAs&jf+kXsv1~2To1d52J=apWwt4W(=Yh
z%dnZ<`Iaaw*pn;&JLFge^+dPA_$2CX&$zzd>a?`)7cnvfo=}JOmsc~-D<`Lro1>&o
zR41T#XSOgU7j~9b9T-iv=!m6c%No=812Gur7f?;p$Si8>;sCGt$
zl#c0DKu6e|aI$StfZ|0G*Dey8{jLEOHo)T~r@_VKv(eGPL0-7Sy<3kT{+K)q>UqWE
zpLQnSeL1=J&E<23s=l4*=7md;f-5^ggvG2?UBjZ-a4eK@plx_HuxV@ASGdW2;UEio%MRf{@Kd
z5^BRCzu4FxG{Qt03xt2cWvmUUpMfWF1qfj!>M%xK3SU~G{n89Jxb%0`x`irwL@)LMx
zgBoXc0jH?bXiMKWCEssHA_}(X)q+C!6vAg5-RnY+&soPS#tE{K{x93cy^;t0GD4d5
MtPAj0tfljR0`}yUcK`qY
literal 0
HcmV?d00001
diff --git a/src/utils/__pycache__/logger.cpython-39.pyc b/src/utils/__pycache__/logger.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f034ee94e38a5d4b0d40e9374f672fed9d22885f
GIT binary patch
literal 795
zcmYjPL2uJA6t*2FO|y0&0XKwpnY2nUIB-G}LJU5yvSC>m7$!pkp$?oN(to4jtie%H4M)q#Pfij`s&D&|zddtjq^s<9%4_EjW#)
znBy5fbu>A4rnIz}mQ+5dVz;C-tN>3vi?HQ4Dvn#aQ@?bUx#kR=dZo9P2}%T=8f9a;
zQ4-4n)GFOe|&wZQa}2
z-Wjn=n2OgTU5hiJ6pMk_X`4ox$8j=>O|zTJjA<>TA-OWNV0vg8Jdw80c;Zc>v@(9i
zUI|Dlv(Zgb@tlo#3KcCS^i_TPNK3)8)yrlq^Z`r(o)5eOtHuP3^Ch9<8)}DyK4|F=
zpsjH$$)-72X$q~*J`4_9me3#8gTrf4K2rHr!g%&PLViK=2He%(y=RYl#UvUsnH4fW
z7Lo3C1sq@$_n}hDJngHA(jx0A8TH0GNmZ}zuv<*3V~vY`op9}+a0eVB6qA3{ZQ<)U
zZ~|C94hZb_A<}-MaC13jzaDbZf*$HVD~eqJ9aj<{Nea%;7l+-KRT11tu^LvXUvoDt
W)!)l Dict[str, Any]:
+ """加载配置文件"""
+ if not os.path.exists(self.config_path):
+ raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
+
+ with open(self.config_path, 'r', encoding='utf-8') as file:
+ return yaml.safe_load(file)
+
+ def get(self, key_path: str, default=None):
+ """获取配置值,支持点号分隔的路径"""
+ keys = key_path.split('.')
+ value = self._config
+
+ try:
+ for key in keys:
+ value = value[key]
+ return value
+ except (KeyError, TypeError):
+ return default
+
+ def reload(self):
+ """重新加载配置"""
+ self._config = self._load_config()
+
+
+# 全局配置实例
+config = Config()
\ No newline at end of file
diff --git a/src/utils/logger.py b/src/utils/logger.py
new file mode 100644
index 0000000..0955c1c
--- /dev/null
+++ b/src/utils/logger.py
@@ -0,0 +1,33 @@
+import logging
+import os
+
+
+def setup_logger(name, log_file, level=logging.INFO):
+ """设置日志记录器"""
+ formatter = logging.Formatter(
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+ )
+
+ # 确保日志目录存在
+ log_dir = os.path.dirname(log_file)
+ if log_dir and not os.path.exists(log_dir):
+ os.makedirs(log_dir)
+
+ # 文件处理器
+ file_handler = logging.FileHandler(log_file)
+ file_handler.setFormatter(formatter)
+
+ # 控制台处理器
+ console_handler = logging.StreamHandler()
+ console_handler.setFormatter(formatter)
+
+ logger = logging.getLogger(name)
+ logger.setLevel(level)
+ logger.addHandler(file_handler)
+ logger.addHandler(console_handler)
+
+ return logger
+
+
+# 创建全局日志记录器
+app_logger = setup_logger('FeedingControl', 'logs/app.log')
\ No newline at end of file
diff --git a/tests/test_control/__pycache__/test_feeding.cpython-39.pyc b/tests/test_control/__pycache__/test_feeding.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e4935d12d287afbb9cfd9b308e37b0f2cf1135ea
GIT binary patch
literal 2679
zcma)8&2Jk;6rb6hwbzc5I7z?KLd8c9xDr;9imK2OMWjHhsurR_6|17v)-$#@S?{{D
z;}%=Cgp`(|3MdyqLJ?{XAoYT{ATBKzxbQFR6$R=O2P6(aDevvtjiW>nvzoVWX5PN{
z<~Q%XS!Z-KOQ8L%|5RH7?iU;kw+svxpoh1BaKfoaEPQJot!tK6*DakU^UPxLx?vgc
z*1c3cZKWv@Y}ve%;Y1kl&c<{^KSf23>Kh=MIeG$loLzilxx?Cr3=RORrKAsPF5-G
zG!uJEtrSn8SJB=j5vpz0%M^4C8uAG1BJ{8Z1eJ4rO+&Rj#nWp#H)AftvwURD;5is&
z!BjFD1<5gx8RvPBp5T*fj8E}Bd@tX}_XD4S*(|Ib;M4H!!8O()HXISScc|3P&AvES
zoIQS`I8&TG;lA-BApjw(1)+*qBamuXU~b0!6P)d#!v$TM@RI0wWQLw|RoE`yB0Vcn
zu&1H4u_-M_V8R-|CfsUG*-}*9Pzh;`4LG|TH2tl1=kQpYjfRks;RV-(Ea;ITh3B*^
z)Ag?ki3)N`I(}Gp6%4}O9Gb?X>m5e>%$lmZK3Kcbbfw^SV%-|AJMY>;%0MPoK`QWF
z6-bB`+x0=yRmY3c%B_o_sj8p}+WbPFIXz&`E>tVnzFt@WOZiYaRWWN<(3)h#=
zE;U-^nj`BC87zyky3~(y`~Z{84qI@r^-Bs8AdKl?B^mUM6&tO{2!(p9F#&qR<3I>A
zX;w3-_J}3)A$!P1G#Wquu=F;LYJam{`_PW5R*ZGGpYD5A?3*3<0;Qms?68c1I%p|i
zun=Mdgh0oj2x2?)uS3enEjWao2+TuQL1>e_iOstX6<-!(~3Iw%8Bqc%_jXPlomF8?%3REfZMIj{*;Nm_YRyIzRq$G{T
z{NuWmkB39W$;$SfgV;;Zjd6}4p4tJqV_+cM1B7UkT9&3BVf?55)ij{AUHIQ8P%2|I
z4;n`J<6Gwp3_A1<+&t^Ft9QsU?XvcPj((cFlj|BCh~a6{Wmoof*hRQE7*U$iD_ZN7
zB!W9y3ZnP|nStK`2rs>P1k^qF;l{?TyBjy}|N7~-zu)`f!N=ck-2QCiyAL@&h!#r)2I=4MTonTunde07RV>I+W~tQ>G22JlG9fr^xZP_KYkI_B7)AzE
z=%$cQ!b}1tmZvyg??!ZlD7p5Y!
z2^oX|VT}&BA_h}p0uK}ZU;qLw2(82LD}#j$EAI|9E(u|V;6*9d$3TwQa^U)q?5;%j
z(Q?JbOghTwh3xn#e@t79CK6F3Y1h$I$DYHVaiFL2DlW2Es`oCctk}
PQ_JZ`^qf9<(xm?ZR)6Q`
literal 0
HcmV?d00001
diff --git a/tests/test_control/logs/app.log b/tests/test_control/logs/app.log
new file mode 100644
index 0000000..561a8a9
--- /dev/null
+++ b/tests/test_control/logs/app.log
@@ -0,0 +1,3 @@
+2025-09-12 19:40:24,636 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:40:27,379 - FeedingControl.FeedingController - INFO - 豸ʼ
+2025-09-12 19:40:31,216 - FeedingControl.FeedingController - INFO - 豸ʼ
diff --git a/tests/test_control/test_feeding.py b/tests/test_control/test_feeding.py
new file mode 100644
index 0000000..81fb8a5
--- /dev/null
+++ b/tests/test_control/test_feeding.py
@@ -0,0 +1,75 @@
+import unittest
+from unittest.mock import patch, MagicMock
+import sys
+import os
+
+from src.control.feeding_controller import FeedingController
+
+# 添加src目录到Python路径
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))
+
+
+from src.control.state_machine import FeedingState
+
+
+class TestFeedingController(unittest.TestCase):
+
+ def setUp(self):
+ # Mock配置
+ self.mock_config = {
+ 'network': {
+ 'relay': {'host': '192.168.0.18', 'port': 50000},
+ 'inverter': {'host': '192.168.0.20', 'port': 502, 'slave_id': 1},
+ 'transmitters': {
+ 'upper': {'host': '192.168.0.21', 'port': 502, 'slave_id': 1, 'weight_register': 0,
+ 'register_count': 2},
+ 'lower': {'host': '192.168.0.22', 'port': 502, 'slave_id': 2, 'weight_register': 0,
+ 'register_count': 2}
+ }
+ },
+ 'parameters': {
+ 'min_required_weight': 50,
+ 'max_error_count': 3,
+ 'monitoring_interval': 1,
+ 'timeout': 30
+ },
+ 'feeding': {
+ 'stage1_frequency': 30.0,
+ 'stage2_frequency': 40.0,
+ 'stage3_frequency': 50.0,
+ 'target_weight_per_stage': 33.3
+ },
+ 'relay': {
+ 'door_upper': 0,
+ 'door_lower_1': 1,
+ 'door_lower_2': 2,
+ 'break_arch_upper': 3,
+ 'break_arch_lower': 4
+ }
+ }
+
+ @patch('control.feeding_controller.config')
+ def test_initialization(self, mock_config):
+ mock_config.get.side_effect = lambda key, default=None: self._get_nested_config(key, default)
+
+ with patch('control.feeding_controller.RelayController') as mock_relay, \
+ patch('control.feeding_controller.InverterController') as mock_inverter, \
+ patch('control.feeding_controller.TransmitterController') as mock_transmitter:
+ controller = FeedingController()
+ self.assertIsNotNone(controller)
+
+ def _get_nested_config(self, key_path, default=None):
+ """辅助函数:获取嵌套配置值"""
+ keys = key_path.split('.')
+ value = self.mock_config
+
+ try:
+ for key in keys:
+ value = value[key]
+ return value
+ except (KeyError, TypeError):
+ return default
+
+
+if __name__ == '__main__':
+ unittest.main()
\ No newline at end of file
diff --git a/tests/test_devices/__pycache__/test_relay.cpython-39.pyc b/tests/test_devices/__pycache__/test_relay.cpython-39.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72c7faed6efa78e38f4b86c2a8192e01fa46b8a2
GIT binary patch
literal 2065
zcmai#TW=dh6o6-DFJ3#&t>w}v2qfqOeqga9r~*Q$B2`cyYE`AEVijq$-I+G)?2Vaq
zP%MWRN8*t`z=QqR-vK`Y%_~p&1xWA!=j__4(PAbcK25Ax_}#C+daT16dd)*s0vJlV^$Qsqe^RfMaSn+1craO=-NND@#%
z0w$wXxBeJJ
z35O7D!p4NtF&WS)Ll~kmAiUq48AT{~%>Jm|`(nrM-P!fKes4E^_&XsA^_^FOVGqx6
zZiDDJrX})R@nKO&6|}2Y8-_OgBx0w
zT*%`%k~|HIBF>%zU|Y-N*$bl9VCFyf?|petoJ5DAN(+?_WKk8(6wPv(kZ&LfUG7TXiOTT_Z
z2yD?18M3R~vICuxOvou4voRge0pnEAF&kKwi|nZ**o>Z9V={He)`f$tzHTq{xfFQ)
zM}k}3m+els*Zmxa`}^H)r=ifEX)9R{RmP9QWGGFObD5RkBOcgFM#l;jnzoji0BmU*
zX2Pt8T1!>l*ZsWg=b7x-Y87S$D=D0g*JlgfS{m>;(`A@N(zr_MVNzBes>M3$-$EDg
zWg}b-C5ICi
zc?Q5%;El2KLS`LTq3eyCKgGDL7^_Wu)0l^*Y1ND$tD$^h)p(mVQSgL%8|J>s@ODLD
z5!&F*SS&IGoW{ebjPvY6Sf~*Ip&JmxYjm`GEzCElx}NJJP=ASQ_q1X>wFH!wIZ?YN
zI;K+x>*#A_@4ZIWuE_>#akhLq^)8s36IB>4HO?*QnU$L2ewb((Y}NywO!s?dyvPOX^2?i22ts=7`Gj4!oSTIF;?Pvk*f=}
shhX+XSd6<