|
@@ -0,0 +1,285 @@
|
|
|
+from utils.statepoint import *
|
|
|
+from utils.mqttdata import *
|
|
|
+from utils.s7data import *
|
|
|
+from models.data_sender import *
|
|
|
+import logging
|
|
|
+
|
|
|
+_Debug = 0
|
|
|
+
|
|
|
+class Counter:
|
|
|
+ @property
|
|
|
+ def data_mqtt(self):
|
|
|
+ if self._data_mqtt.cli == None:
|
|
|
+ raise ValueError('The MQTT connection to MES has not been initialized.')
|
|
|
+ return self._data_mqtt
|
|
|
+
|
|
|
+ @property
|
|
|
+ def data_s7(self):
|
|
|
+ if self._data_s7 == None:
|
|
|
+ raise ValueError('The S7 connection to the casting machine PLC has not been initialized.')
|
|
|
+ return self._data_s7
|
|
|
+
|
|
|
+ @property
|
|
|
+ def sender(self):
|
|
|
+ if self._sender == None:
|
|
|
+ raise ValueError('The sender has not been set.')
|
|
|
+ return self._sender
|
|
|
+
|
|
|
+ # 数据点定义
|
|
|
+ def create_data_point(self, ccmNo):
|
|
|
+ self.ladle_weight_1 = self.data_s7.make_point('大包重量1')
|
|
|
+ self.ladle_weight_2 = self.data_s7.make_point('大包重量2')
|
|
|
+
|
|
|
+ self.begin_pour = self.data_mqtt.make_point(f'{ccmNo}#开浇信号')
|
|
|
+ self.end_pour = self.data_mqtt.make_point(f'{ccmNo}#停浇信号')
|
|
|
+
|
|
|
+ if _Debug:
|
|
|
+ self.begin_pour_ring = self.data_s7.make_point('浇铸信号[2]')
|
|
|
+
|
|
|
+ self.begin_cutting = []
|
|
|
+ for i in range(8):
|
|
|
+ self.begin_cutting.append(self.data_s7.make_point(f'L{i+1}切割信号[0]'))
|
|
|
+
|
|
|
+ self.end_cutting = []
|
|
|
+ for i in range(8):
|
|
|
+ self.end_cutting.append(self.data_s7.make_point(f'L{i+1}切割信号[1]'))
|
|
|
+
|
|
|
+ self.length_cutting = []
|
|
|
+ for i in range(8):
|
|
|
+ self.length_cutting.append(self.data_s7.make_point(f'L{i+1}定尺'))
|
|
|
+
|
|
|
+ self.drawing_speed = []
|
|
|
+ for i in range(8):
|
|
|
+ self.drawing_speed.append(self.data_s7.make_point(f'L{i+1}拉速'))
|
|
|
+
|
|
|
+
|
|
|
+ def init_data_point(self):
|
|
|
+ # 统一初始化数据点
|
|
|
+ self.begin_pour.allow_update(False)
|
|
|
+ self.begin_pour.set_state(False)
|
|
|
+ self.end_pour.allow_update(False)
|
|
|
+ self.end_pour.set_state(False)
|
|
|
+ for i in range(8):
|
|
|
+ self.begin_cutting[i].allow_update(False)
|
|
|
+ self.begin_cutting[i].set_state(False)
|
|
|
+ self.begin_cutting[i].data = 0
|
|
|
+ self.end_cutting[i].allow_update(False)
|
|
|
+ self.end_cutting[i].set_state(False)
|
|
|
+ self.end_cutting[i].data = 0
|
|
|
+
|
|
|
+ # 数据点逻辑设置
|
|
|
+ self.begin_pour.set_excite_action(self.begin_pour_action)
|
|
|
+ self.end_pour.set_excite_action(self.end_pour_action)
|
|
|
+
|
|
|
+ for i in range(8):
|
|
|
+ self.begin_cutting[i].set_excite_action(lambda i=i: self.begin_cutting_action(i))
|
|
|
+ self.begin_cutting[i].set_keep_time(3000)
|
|
|
+ self.end_cutting[i].set_excite_action(lambda i=i: self.end_cutting_action(i))
|
|
|
+ self.end_cutting[i].set_reset_action(lambda i=i: self.end_cutting[i].allow_update(False))
|
|
|
+
|
|
|
+ # 统一开启数据点
|
|
|
+ self.begin_pour.allow_update()
|
|
|
+ self.end_pour.allow_update()
|
|
|
+ for i in range(8):
|
|
|
+ self.begin_cutting[i].allow_update()
|
|
|
+
|
|
|
+
|
|
|
+ def __init__(self, data_mqtt: Mqttdata, data_s7: S7data, ccmNo, logger: logging.Logger, sender: Sender):
|
|
|
+ # 模块入口、出口、日志定义
|
|
|
+ self._data_mqtt = data_mqtt
|
|
|
+ self._data_s7 = data_s7
|
|
|
+ self._sender = sender
|
|
|
+ self.logger = logger
|
|
|
+
|
|
|
+ self.logger.info(f"[Counter]分炉分坯模块:{ccmNo}号机模块启动")
|
|
|
+
|
|
|
+ # 配置必须的数据点
|
|
|
+ self.create_data_point(ccmNo)
|
|
|
+ self.init_data_point()
|
|
|
+
|
|
|
+ #分炉分坯功能
|
|
|
+ self.last_cutting_timestamp = 0
|
|
|
+
|
|
|
+ self.strand = [0, 0, 0, 0, 0, 0, 0, 0]
|
|
|
+ self.cutting_state_heat = [{}, {}, {}, {}, {}, {}, {}, {}]
|
|
|
+ self.cutting_state_heat_index = [0, 0, 0, 0, 0, 0, 0, 0]
|
|
|
+ self.total = 0
|
|
|
+ self.limit_count = 0
|
|
|
+ self.limit_target = 24
|
|
|
+ self.limit_flag = False
|
|
|
+ self.lock = threading.Lock()
|
|
|
+ self.old_heat = {}
|
|
|
+ self.new_heat = {}
|
|
|
+ self.last_cutting_strand = 0
|
|
|
+
|
|
|
+
|
|
|
+ def begin_pour_action(self):
|
|
|
+ # 标志是否为铸机开机的第一次开浇
|
|
|
+ flag = (time.time() - self.last_cutting_timestamp) > 1800
|
|
|
+
|
|
|
+ # 大包重量选择算法
|
|
|
+ ladle_weight = max(self.ladle_weight_1.data, self.ladle_weight_2.data)
|
|
|
+
|
|
|
+ # 写入日志
|
|
|
+ if flag:
|
|
|
+ self.logger.info(f'[Counter]首次开浇:{self.begin_pour.data['heatNo']}')
|
|
|
+ elif self.new_heat == {}:
|
|
|
+ self.logger.info(f'[Counter]炉次开浇:{self.begin_pour.data['heatNo']},当前出坯炉次:{"未知" if self.old_heat == {} else self.old_heat["heatNo"]}')
|
|
|
+ else:
|
|
|
+ self.logger.warning(f'[Counter]炉次开浇:{self.begin_pour.data['heatNo']},炉次{self.new_heat['heatNo']}被覆盖')
|
|
|
+
|
|
|
+ # 维护换炉操作
|
|
|
+ if flag:
|
|
|
+ self.old_heat = self.begin_pour.data
|
|
|
+ self.new_heat = {}
|
|
|
+ else:
|
|
|
+ self.new_heat = self.begin_pour.data
|
|
|
+ self.start_limit()
|
|
|
+
|
|
|
+ # 使用sender向外发送信号
|
|
|
+ self.sender.begin_pour(self.begin_pour.data, ladle_weight)
|
|
|
+
|
|
|
+
|
|
|
+ def end_pour_action(self):
|
|
|
+ # 写入日志
|
|
|
+ self.logger.info(f'[Counter]炉次停浇:{self.end_pour.data["heatNo"]}')
|
|
|
+
|
|
|
+ # 使用sender向外发送信号
|
|
|
+ if self.old_heat:
|
|
|
+ self.sender.end_pour(self.end_pour.data)
|
|
|
+
|
|
|
+
|
|
|
+ def cutting_data_comple(self, i):
|
|
|
+ count = 3
|
|
|
+
|
|
|
+ # 补充计入模块
|
|
|
+ while count:
|
|
|
+ time.sleep(0.5)
|
|
|
+ sizing = self.length_cutting[i].data
|
|
|
+ speed = self.drawing_speed[i].data
|
|
|
+ if 0 < sizing < 30000 and 0 < speed < 10:
|
|
|
+ self.strand_add(i+1, sizing, speed)
|
|
|
+ return None
|
|
|
+ count -= 1
|
|
|
+
|
|
|
+ time.sleep(0.5)
|
|
|
+ sizing = self.length_cutting[i].data
|
|
|
+ speed = self.drawing_speed[i].data
|
|
|
+ if not (0 < sizing < 30000 and 0 < speed < 10):
|
|
|
+ self.logger.warning("[Counter]请注意,定尺/拉速数据持续异常,已按照异常数据发送")
|
|
|
+
|
|
|
+ self.strand_add(i+1, sizing, speed)
|
|
|
+ self.end_cutting[i].allow_update()
|
|
|
+
|
|
|
+ def begin_cutting_action(self, i):
|
|
|
+ # 写入日志
|
|
|
+ self.logger.info(f'[Counter]{i+1}流:开始切割')
|
|
|
+
|
|
|
+ # 计入模块
|
|
|
+ sizing = self.length_cutting[i].data
|
|
|
+ speed = self.drawing_speed[i].data
|
|
|
+ if 0 < sizing < 30000 and 0 < speed < 10:
|
|
|
+ self.strand_add(i+1, sizing, speed)
|
|
|
+ self.end_cutting[i].allow_update()
|
|
|
+ else:
|
|
|
+ threading.Thread(target=self.cutting_data_comple, args=(i,)).start()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def end_cutting_action(self, i):
|
|
|
+ # 写入日志
|
|
|
+ self.logger.info(f'[Counter]{i+1}流:完成切割')
|
|
|
+
|
|
|
+ # 复位流状态
|
|
|
+ cutting_state_heat = self.cutting_state_heat[i]
|
|
|
+ cutting_state_heat_index = self.cutting_state_heat_index[i]
|
|
|
+ self.cutting_state_heat[i] = {}
|
|
|
+ self.cutting_state_heat_index[i] = 0
|
|
|
+
|
|
|
+ if cutting_state_heat:
|
|
|
+ # 使用sender向外发送信号
|
|
|
+ self.sender.end_cut(cutting_state_heat, cutting_state_heat_index)
|
|
|
+ # 检查自己是否是本炉最后一根钢坯
|
|
|
+ if self.last_cutting_strand == i+1:
|
|
|
+ self.last_cutting_strand = 0
|
|
|
+ self.sender.heat_last(cutting_state_heat)
|
|
|
+
|
|
|
+
|
|
|
+ def strand_add(self, sno, sizing, speed):
|
|
|
+ with self.lock:
|
|
|
+ # 维护辅助时间戳
|
|
|
+ self.last_cutting_timestamp = time.time()
|
|
|
+
|
|
|
+ #维护内部正确性的主要算法
|
|
|
+ if self.limit_flag:
|
|
|
+ self.limit_count += 1
|
|
|
+ if self.limit_count == self.limit_target:
|
|
|
+ # 此处已经在切割本炉最后一根
|
|
|
+ if self.old_heat:
|
|
|
+ self.last_cutting_strand = sno
|
|
|
+ if self.limit_count > self.limit_target:
|
|
|
+ # 此处已经在切割新炉第一根
|
|
|
+ self.change_heat()
|
|
|
+ self.total = 0
|
|
|
+ self.strand = [0, 0, 0, 0, 0, 0, 0, 0]
|
|
|
+ self.limit_count = 0
|
|
|
+ self.limit_flag = False
|
|
|
+ self.strand[sno-1] += 1
|
|
|
+ self.total += 1
|
|
|
+
|
|
|
+ heatData = self.old_heat
|
|
|
+ heatIndex = self.total
|
|
|
+ strandIndex = self.strand[sno-1]
|
|
|
+
|
|
|
+ if heatData:
|
|
|
+ # 记录当前流状态,帮助停切信号判断钢坯信息
|
|
|
+ self.cutting_state_heat[sno-1] = heatData
|
|
|
+ self.cutting_state_heat_index[sno-1] = heatIndex
|
|
|
+ # 生成坯号,使用sender向外发送信号
|
|
|
+ ccmNo = heatData['ccmNo']
|
|
|
+ billetNo = heatData['heatNo'] + ccmNo + str(sno) + '{:0>2}'.format(strandIndex)
|
|
|
+ self.logger.info(f"[Counter]{sno}流:{heatData['heatNo']}炉第{heatIndex}根计入系统,坯号:{billetNo}")
|
|
|
+ self.sender.begin_cut(heatData, billetNo, heatIndex, sizing, speed)
|
|
|
+ # 使用sender发送炉次首次开切信号
|
|
|
+ if heatIndex == 1:
|
|
|
+ self.sender.heat_first(heatData)
|
|
|
+ else:
|
|
|
+ self.logger.info(f"[Counter]{sno}流:未知炉第{heatIndex}根,本炉无法计入系统,下一炉开始正常")
|
|
|
+
|
|
|
+
|
|
|
+ def start_limit(self):
|
|
|
+ # 根据实际情况设置本炉总支数
|
|
|
+ with self.lock:
|
|
|
+ if self.old_heat:
|
|
|
+ if self.total % 4 == 1:
|
|
|
+ self.limit_target = 23
|
|
|
+ elif self.total % 4 == 2:
|
|
|
+ if self.total >= 28:
|
|
|
+ self.limit_target = 22
|
|
|
+ else:
|
|
|
+ self.limit_target = 26
|
|
|
+ elif self.total % 4 == 3:
|
|
|
+ self.limit_target = 25
|
|
|
+ else:
|
|
|
+ self.limit_target = 24
|
|
|
+ else:
|
|
|
+ self.limit_target = 24
|
|
|
+
|
|
|
+ self.limit_flag = True
|
|
|
+
|
|
|
+
|
|
|
+ def change_heat(self):
|
|
|
+ # 异常情况
|
|
|
+ if not self.new_heat:
|
|
|
+ self.logger.error('[Counter]换炉:失败,无新炉次信息')
|
|
|
+ return None
|
|
|
+
|
|
|
+ # 写入日志
|
|
|
+ if self.old_heat:
|
|
|
+ self.logger.info(f'[Counter]换炉:{self.old_heat["heatNo"]}->{self.new_heat["heatNo"]}')
|
|
|
+ else:
|
|
|
+ self.logger.info(f'[Counter]换炉:未知炉次->{self.new_heat["heatNo"]}')
|
|
|
+
|
|
|
+ #真正换炉过程
|
|
|
+ self.old_heat = self.new_heat
|
|
|
+ self.new_heat = {}
|