billet_counter.py 11 KB

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