billet_counter.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. from utils.statepoint import *
  2. from utils.mqttdata import *
  3. from utils.s7data import *
  4. from models.data_sender import *
  5. import logging
  6. _Debug = 0
  7. class Counter:
  8. @property
  9. def data_mqtt(self):
  10. if self._data_mqtt.cli == None:
  11. raise ValueError('The MQTT connection to MES has not been initialized.')
  12. return self._data_mqtt
  13. @property
  14. def data_s7(self):
  15. if self._data_s7 == None:
  16. raise ValueError('The S7 connection to the casting machine PLC has not been initialized.')
  17. return self._data_s7
  18. @property
  19. def sender(self):
  20. if self._sender == None:
  21. raise ValueError('The sender has not been set.')
  22. return self._sender
  23. # 数据点定义
  24. def create_data_point(self, ccmNo):
  25. self.ladle_weight_1 = self.data_s7.make_point('大包重量1')
  26. self.ladle_weight_2 = self.data_s7.make_point('大包重量2')
  27. self.begin_pour = self.data_mqtt.make_point(f'{ccmNo}#开浇信号')
  28. self.end_pour = self.data_mqtt.make_point(f'{ccmNo}#停浇信号')
  29. if _Debug:
  30. self.begin_pour_ring = self.data_s7.make_point('浇铸信号[2]')
  31. self.begin_cutting = []
  32. for i in range(8):
  33. self.begin_cutting.append(self.data_s7.make_point(f'L{i+1}切割信号[0]'))
  34. self.end_cutting = []
  35. for i in range(8):
  36. self.end_cutting.append(self.data_s7.make_point(f'L{i+1}切割信号[1]'))
  37. self.length_cutting = []
  38. for i in range(8):
  39. self.length_cutting.append(self.data_s7.make_point(f'L{i+1}定尺'))
  40. self.drawing_speed = []
  41. for i in range(8):
  42. self.drawing_speed.append(self.data_s7.make_point(f'L{i+1}拉速'))
  43. def init_data_point(self):
  44. # 统一初始化数据点
  45. self.begin_pour.allow_update(False)
  46. self.begin_pour.set_state(False)
  47. self.end_pour.allow_update(False)
  48. self.end_pour.set_state(False)
  49. for i in range(8):
  50. self.begin_cutting[i].allow_update(False)
  51. self.begin_cutting[i].set_state(False)
  52. self.begin_cutting[i].data = 0
  53. self.end_cutting[i].allow_update(False)
  54. self.end_cutting[i].set_state(False)
  55. self.end_cutting[i].data = 0
  56. # 数据点逻辑设置
  57. self.begin_pour.set_excite_action(self.begin_pour_action)
  58. self.end_pour.set_excite_action(self.end_pour_action)
  59. for i in range(8):
  60. self.begin_cutting[i].set_excite_action(lambda i=i: self.begin_cutting_action(i))
  61. self.begin_cutting[i].set_keep_time(3000)
  62. self.end_cutting[i].set_excite_action(lambda i=i: self.end_cutting_action(i))
  63. self.end_cutting[i].set_reset_action(lambda i=i: self.end_cutting[i].allow_update(False))
  64. # 统一开启数据点
  65. self.begin_pour.allow_update()
  66. self.end_pour.allow_update()
  67. for i in range(8):
  68. self.begin_cutting[i].allow_update()
  69. def __init__(self, data_mqtt: Mqttdata, data_s7: S7data, ccmNo, logger: logging.Logger, sender: Sender):
  70. # 模块入口、出口、日志定义
  71. self._data_mqtt = data_mqtt
  72. self._data_s7 = data_s7
  73. self._sender = sender
  74. self.logger = logger
  75. self.logger.info(f"[Counter]分炉分坯模块:{ccmNo}号机模块启动")
  76. # 配置必须的数据点
  77. self.create_data_point(ccmNo)
  78. self.init_data_point()
  79. #分炉分坯功能
  80. self.last_cutting_timestamp = 0
  81. self.strand = [0, 0, 0, 0, 0, 0, 0, 0]
  82. self.cutting_state_heat = [{}, {}, {}, {}, {}, {}, {}, {}]
  83. self.cutting_state_heat_index = [0, 0, 0, 0, 0, 0, 0, 0]
  84. self.total = 0
  85. self.limit_count = 0
  86. self.limit_target = 24
  87. self.limit_flag = False
  88. self.lock = threading.Lock()
  89. self.old_heat = {}
  90. self.new_heat = {}
  91. self.last_cutting_strand = 0
  92. def begin_pour_action(self):
  93. # 标志是否为铸机开机的第一次开浇
  94. flag = (time.time() - self.last_cutting_timestamp) > 1800
  95. # 大包重量选择算法
  96. ladle_weight = max(self.ladle_weight_1.data, self.ladle_weight_2.data)
  97. # 写入日志
  98. if flag:
  99. self.logger.info(f'[Counter]首次开浇:{self.begin_pour.data['heatNo']}')
  100. elif self.new_heat == {}:
  101. self.logger.info(f'[Counter]炉次开浇:{self.begin_pour.data['heatNo']},当前出坯炉次:{"未知" if self.old_heat == {} else self.old_heat["heatNo"]}')
  102. else:
  103. self.logger.warning(f'[Counter]炉次开浇:{self.begin_pour.data['heatNo']},炉次{self.new_heat['heatNo']}被覆盖')
  104. # 维护换炉操作
  105. if flag:
  106. self.old_heat = self.begin_pour.data
  107. self.new_heat = {}
  108. else:
  109. self.new_heat = self.begin_pour.data
  110. self.start_limit()
  111. # 使用sender向外发送信号
  112. self.sender.begin_pour(self.begin_pour.data, ladle_weight)
  113. def end_pour_action(self):
  114. # 写入日志
  115. self.logger.info(f'[Counter]炉次停浇:{self.end_pour.data["heatNo"]}')
  116. # 使用sender向外发送信号
  117. if self.old_heat:
  118. self.sender.end_pour(self.end_pour.data)
  119. def cutting_data_comple(self, i):
  120. count = 3
  121. # 补充计入模块
  122. while count:
  123. time.sleep(0.5)
  124. sizing = self.length_cutting[i].data
  125. speed = self.drawing_speed[i].data
  126. if 0 < sizing < 30000 and 0 < speed < 10:
  127. self.strand_add(i+1, sizing, speed)
  128. return None
  129. count -= 1
  130. time.sleep(0.5)
  131. sizing = self.length_cutting[i].data
  132. speed = self.drawing_speed[i].data
  133. if not (0 < sizing < 30000 and 0 < speed < 10):
  134. self.logger.warning("[Counter]请注意,定尺/拉速数据持续异常,已按照异常数据发送")
  135. self.strand_add(i+1, sizing, speed)
  136. self.end_cutting[i].allow_update()
  137. def begin_cutting_action(self, i):
  138. # 写入日志
  139. self.logger.info(f'[Counter]{i+1}流:开始切割')
  140. # 计入模块
  141. sizing = self.length_cutting[i].data
  142. speed = self.drawing_speed[i].data
  143. if 0 < sizing < 30000 and 0 < speed < 10:
  144. self.strand_add(i+1, sizing, speed)
  145. self.end_cutting[i].allow_update()
  146. else:
  147. threading.Thread(target=self.cutting_data_comple, args=(i,)).start()
  148. def end_cutting_action(self, i):
  149. # 写入日志
  150. self.logger.info(f'[Counter]{i+1}流:完成切割')
  151. # 复位流状态
  152. cutting_state_heat = self.cutting_state_heat[i]
  153. cutting_state_heat_index = self.cutting_state_heat_index[i]
  154. self.cutting_state_heat[i] = {}
  155. self.cutting_state_heat_index[i] = 0
  156. if cutting_state_heat:
  157. # 使用sender向外发送信号
  158. self.sender.end_cut(cutting_state_heat, cutting_state_heat_index)
  159. # 检查自己是否是本炉最后一根钢坯
  160. if self.last_cutting_strand == i+1:
  161. self.last_cutting_strand = 0
  162. self.sender.heat_last(cutting_state_heat)
  163. def strand_add(self, sno, sizing, speed):
  164. with self.lock:
  165. # 维护辅助时间戳
  166. self.last_cutting_timestamp = time.time()
  167. #维护内部正确性的主要算法
  168. if self.limit_flag:
  169. self.limit_count += 1
  170. if self.limit_count == self.limit_target:
  171. # 此处已经在切割本炉最后一根
  172. if self.old_heat:
  173. self.last_cutting_strand = sno
  174. if self.limit_count > self.limit_target:
  175. # 此处已经在切割新炉第一根
  176. self.change_heat()
  177. self.total = 0
  178. self.strand = [0, 0, 0, 0, 0, 0, 0, 0]
  179. self.limit_count = 0
  180. self.limit_flag = False
  181. self.strand[sno-1] += 1
  182. self.total += 1
  183. heatData = self.old_heat
  184. heatIndex = self.total
  185. strandIndex = self.strand[sno-1]
  186. if heatData:
  187. # 记录当前流状态,帮助停切信号判断钢坯信息
  188. self.cutting_state_heat[sno-1] = heatData
  189. self.cutting_state_heat_index[sno-1] = heatIndex
  190. # 生成坯号,使用sender向外发送信号
  191. ccmNo = heatData['ccmNo']
  192. billetNo = heatData['heatNo'] + ccmNo + str(sno) + '{:0>2}'.format(strandIndex)
  193. self.logger.info(f"[Counter]{sno}流:{heatData['heatNo']}炉第{heatIndex}根计入系统,坯号:{billetNo}")
  194. self.sender.begin_cut(heatData, billetNo, heatIndex, sizing, speed)
  195. # 使用sender发送炉次首次开切信号
  196. if heatIndex == 1:
  197. self.sender.heat_first(heatData)
  198. else:
  199. self.logger.info(f"[Counter]{sno}流:未知炉第{heatIndex}根,本炉无法计入系统,下一炉开始正常")
  200. def start_limit(self):
  201. # 根据实际情况设置本炉总支数
  202. with self.lock:
  203. if self.old_heat:
  204. if self.total % 4 == 1:
  205. self.limit_target = 23
  206. elif self.total % 4 == 2:
  207. if self.total >= 28:
  208. self.limit_target = 22
  209. else:
  210. self.limit_target = 26
  211. elif self.total % 4 == 3:
  212. self.limit_target = 25
  213. else:
  214. self.limit_target = 24
  215. else:
  216. self.limit_target = 24
  217. self.limit_flag = True
  218. def change_heat(self):
  219. # 异常情况
  220. if not self.new_heat:
  221. self.logger.error('[Counter]换炉:失败,无新炉次信息')
  222. return None
  223. # 写入日志
  224. if self.old_heat:
  225. self.logger.info(f'[Counter]换炉:{self.old_heat["heatNo"]}->{self.new_heat["heatNo"]}')
  226. else:
  227. self.logger.info(f'[Counter]换炉:未知炉次->{self.new_heat["heatNo"]}')
  228. #真正换炉过程
  229. self.old_heat = self.new_heat
  230. self.new_heat = {}