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 = {}