Browse Source

7.15暂存版本

oldwine 5 months ago
parent
commit
bbd7710d59

+ 23 - 3
conf/5#nodes.csv

@@ -45,9 +45,9 @@ L5
 L6坯头位置,int,101,4,0,2,TRUE,FALSE,500,3
 L7坯头位置,int,101,6,0,2,TRUE,FALSE,500,3
 L8坯头位置,int,101,8,0,2,TRUE,FALSE,500,3
-天车A1位置,dint,360,100,0,4,TRUE,FALSE,500,3
-天车A3位置,dint,360,104,0,4,TRUE,FALSE,500,3
-天车A2位置,dint,360,108,0,4,TRUE,FALSE,500,3
+天车A1位置,dint,360,104,0,4,TRUE,FALSE,500,3
+天车A3位置,dint,360,108,0,4,TRUE,FALSE,500,3
+天车A2位置,dint,360,112,0,4,TRUE,FALSE,500,3
 车1自动触发信号,bool,310,46,0,1,TRUE,FALSE,500,3
 车1手动触发信号,bool,310,46,1,1,TRUE,FALSE,500,3
 车1自动触发结果,string,310,2,0,22,TRUE,FALSE,500,3
@@ -94,3 +94,23 @@ L8
 车位2车牌看门狗,int,311,0,0,2,TRUE,FALSE,500,5
 车位3车牌看门狗,int,312,0,0,2,TRUE,FALSE,500,5
 车位4车牌看门狗,int,313,0,0,2,TRUE,FALSE,500,5
+501堆垛高度,int,301,2,0,2,TRUE,FALSE,500,6
+501堆垛组数,int,301,4,0,2,TRUE,FALSE,500,6
+501堆垛方向,int,301,30,0,2,TRUE,FALSE,500,6
+601堆垛高度,int,302,2,0,2,TRUE,FALSE,500,6
+601堆垛组数,int,302,4,0,2,TRUE,FALSE,500,6
+601堆垛方向,int,302,30,0,2,TRUE,FALSE,500,6
+当前班别,dint,360,88,0,4,TRUE,FALSE,500,6
+车牌车1存在,bool,310,46,2,1,TRUE,FALSE,500,6
+车牌车2存在,bool,311,46,2,1,TRUE,FALSE,500,6
+车牌车3存在,bool,312,46,2,1,TRUE,FALSE,500,6
+车牌车4存在,bool,313,46,2,1,TRUE,FALSE,500,6
+L1短尺,int,102,2,0,2,TRUE,FALSE,500,6
+L2短尺,int,102,4,0,2,TRUE,FALSE,500,6
+L3短尺,int,102,6,0,2,TRUE,FALSE,500,6
+L4短尺,int,102,8,0,2,TRUE,FALSE,500,6
+L5短尺,int,102,10,0,2,TRUE,FALSE,500,6
+L6短尺,int,102,12,0,2,TRUE,FALSE,500,6
+L7短尺,int,102,14,0,2,TRUE,FALSE,500,6
+L8短尺,int,102,16,0,2,TRUE,FALSE,500,6
+车1车厢夹数,int,306,28,0,2,TRUE,FALSE,500,7

+ 10 - 0
conf/6#nodes.csv

@@ -46,3 +46,13 @@ L7
 L8坯头位置,int,201,8,0,2,TRUE,FALSE,500,3
 定尺看门狗1,int,200,0,0,2,TRUE,FALSE,500,3
 定尺看门狗2,int,201,0,0,2,TRUE,FALSE,500,3
+热送辊道状态,boollist,360,93,0,1,TRUE,FALSE,500,3
+当前班别,dint,360,88,0,4,TRUE,FALSE,500,3
+L1短尺,int,200,20,0,2,TRUE,FALSE,500,3
+L2短尺,int,200,22,0,2,TRUE,FALSE,500,3
+L3短尺,int,200,24,0,2,TRUE,FALSE,500,3
+L4短尺,int,200,26,0,2,TRUE,FALSE,500,3
+L5短尺,int,201,20,0,2,TRUE,FALSE,500,3
+L6短尺,int,201,22,0,2,TRUE,FALSE,500,3
+L7短尺,int,201,24,0,2,TRUE,FALSE,500,3
+L8短尺,int,201,26,0,2,TRUE,FALSE,500,3

+ 40 - 13
main.py

@@ -7,25 +7,28 @@ from utils.logger import Logger
 from models.parking import Parking
 from models.overhead_crane import Crane
 from models.data_checker import Checker
+from models.billet_stacks import Stack_manager, Billet_stack
+from models.jiaoban import Banci
+import pymysql
 
 ##############################################################
 # 日志配置
 
 logger_5 = Logger('5#')
-logger_5.file_on('logs/5#log.log')
-logger_5.screen_on()
+logger_5.file_on_with_rotation('logs/5#log.log')
+# logger_5.screen_on()
 
 logger_6 = Logger('6#')
-logger_6.file_on('logs/6#log.log')
-logger_6.screen_on()
+logger_6.file_on_with_rotation('logs/6#log.log')
+# logger_6.screen_on()
 
 logger_sender = Logger('sender')
-logger_sender.file_on('logs/sender_log.log')
-logger_sender.screen_on()
+logger_sender.file_on_with_rotation('logs/sender_log.log')
+# logger_sender.screen_on()
 
 logger_trace = Logger('trace')
-logger_trace.file_on('logs/trace_log.log')
-logger_trace.screen_on()
+logger_trace.file_on_with_rotation('logs/trace_log.log')
+# logger_trace.screen_on()
 
 
 ##############################################################
@@ -67,29 +70,45 @@ data_web.set_mqtt_client(mqtt_web)
 logger_sender.info('[PREPARE]与WEB业务平台使用MQTT连接成功')
 
 
+##############################################################
+# MYSQL数据库连接
+
+db = pymysql.connect(host='localhost', port=3306, user='root', password='1qaz2wsx@..', database='steel_production_db')
+
+
 ##############################################################
 # 数据发送服务
 
 sender = Sender(logger_sender)
 #sender.set_mqtt_client(mqtt_web)
+#sender.set_mysql_client(db)
 
 # debug设置
 sender.http_flag = False
+sender.mysql_flag = True
 
 
 ##############################################################
 # 分炉分坯服务
 
 position_5 = [10650, 11600, 12830, 13924, 15237, 16440, 17757, 18935]
-position_6 = [8230, 9655, 10658, 11708, 12988, 14066, 15380, 16750]
+position_6 = [8084, 9303, 10618, 11998, 13040, 14409, 15584, 16853]
 
-pusher_5 = Trace_pusher(data_5, logger_5, sender, position_5, True)
-pusher_6 = Trace_pusher(data_6, logger_6, sender, position_6)
+pusher_5 = Trace_pusher(data_5, logger_5, sender, position_5, data_web.make_point('5#手动换炉'), True)
+pusher_6 = Trace_pusher(data_6, logger_6, sender, position_6, data_web.make_point('6#手动换炉'), hostmove_flag=True)
 
 flfp_5 = Counter(data_mes, data_5, 5, logger_5, sender, pusher_5)
 flfp_6 = Counter(data_mes, data_6, 6, logger_6, sender, pusher_6)
 
 
+##############################################################
+# 堆垛管理服务
+
+stack_manager = Stack_manager()
+stack_manager.add_stack('501', '5', data_5)
+stack_manager.add_stack('601', '6', data_5, -170)
+
+
 ##############################################################
 # 停车位检测服务
 
@@ -99,10 +118,18 @@ parking = Parking(data_5, sender)
 ##############################################################
 # 天车跟踪服务
 
-crane = Crane(data_5, pusher_5, pusher_6, parking, sender, logger_trace)
+crane = Crane(data_5, pusher_5, pusher_6, parking, stack_manager, sender, logger_trace)
+
 
 ##############################################################
 # 数据警报服务
 
 checker = Checker(data_5, data_6, logger_sender)
-checker.async_start_check()
+checker.async_start_check()
+
+
+##############################################################
+# 自动交班服务
+
+banci_5 = Banci(data_5, sender, logger_5, '5')
+banci_6 = Banci(data_6, sender, logger_6, '6')

+ 4 - 4
models/billet_counter.py

@@ -170,7 +170,7 @@ class Counter:
         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.logger.warning(f"[Counter]请注意,定尺{sizing}/拉速{speed}数据持续异常,已按照异常数据发送")
 
         self.strand_add(i+1, sizing, speed)
         self.end_cutting[i].allow_update()
@@ -206,7 +206,7 @@ class Counter:
 
             tmp = self.send_list[i]
             self.send_list[i] = []
-            tmp.append(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
+            tmp[5] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
             self.send_dst.data_from_casting(i, tmp, True)
 
             # 检查自己是否是本炉最后一根钢坯
@@ -251,7 +251,7 @@ class Counter:
             # self.logger.info(f"[Counter]{sno}流:{heatData['heatNo']}炉第{heatIndex}根计入系统,坯号:{billetNo}")
 
             # [坯号, 炉次信息, 定尺, 拉速, 开浇时间, 停浇时间]
-            self.send_list[sno-1] = [billetNo, heatData, sizing, speed, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())]
+            self.send_list[sno-1] = [billetNo, heatData, sizing, speed, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '']
             self.send_dst.data_from_casting(sno-1, self.send_list[sno-1])
             # self.sender.begin_cut(heatData, billetNo, heatIndex, sizing, speed)
             # 使用sender发送炉次首次开切信号
@@ -259,7 +259,7 @@ class Counter:
                 pass
                 # self.sender.heat_first(heatData)
         else:
-            self.send_dst.data_from_casting(sno-1, [f'000000000{sno}00', {}, sizing, speed, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())])
+            self.send_dst.data_from_casting(sno-1, [f'000000000{sno}00', {}, sizing, speed, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), ''])
             # self.logger.info(f"[Counter]{sno}流:未知炉第{heatIndex}根,本炉无法计入系统,下一炉开始正常")
 
 

+ 71 - 11
models/billet_stacks.py

@@ -1,16 +1,76 @@
-from utils.s7data import *
+from utils.s7data import S7data
+import time
 
 class Billet_stack:
-    def __init__(self, name, data_s7: S7data):
+    def __init__(self, name, ccmNo, data_s7: S7data, height_init_offset = 0):
         self.name = name
+        self.ccmNo = ccmNo
+        self.height_init_offset = height_init_offset
         self.height = data_s7.make_point(f"{name}堆垛高度")
         self.toptotal = data_s7.make_point(f"{name}堆垛组数")
-        self.groups = []
-        self.vertical = data_s7.make_point(f"{name}堆垛摆放方式")
-        self.crane = data_s7.make_point(f"{name}堆垛行车")
-        self.update_sig = data_s7.make_point(f"{name}堆垛更新信号")
-        for i in range(12):
-            self.groups.append(data_s7.make_point(f"{name}堆垛第{i+1}组"))
-
-    def sda():
-        pass
+        self.fangx = data_s7.make_point(f"{name}堆垛方向")
+        self.last_toptotal = self.toptotal.data
+        self.changed_time = time.time()
+        self.changed_count = 0
+        self.jinwei = 0
+        # self.groups = []
+        # self.vertical = data_s7.make_point(f"{name}堆垛摆放方式")
+        # self.crane = data_s7.make_point(f"{name}堆垛行车")
+        # self.update_sig = data_s7.make_point(f"{name}堆垛更新信号")
+        # for i in range(12):
+        #     self.groups.append(data_s7.make_point(f"{name}堆垛第{i+1}组"))
+
+        self.height.set_convertor(self.layerNum_cal)
+        self.toptotal.set_convertor(self.do_update)
+        self.fangx.set_convertor(self.height.allow_update)
+
+    def layerNum_cal(self, data):
+        res = (data + self.height_init_offset) // 170
+        if self.fangx.data == 1:
+            if res % 2 == 0:
+                res += 1
+        elif self.fangx.data == 2:
+            if res % 2:
+                res += 1
+        return res
+
+    def do_update(self, data):
+        if data != self.last_toptotal:
+            self.jinwei = 0
+            self.changed_count = data - self.last_toptotal
+            self.last_toptotal = data
+            self.changed_time = time.time()
+
+            if self.changed_count > 2:
+                self.changed_count = -1
+                self.jinwei = -1
+            elif self.changed_count < -2:
+                self.changed_count = 1
+                self.jinwei = 1
+
+    def get_changed(self):
+        if time.time() - self.changed_time <= 20:
+            return self.changed_count
+        return 0
+
+class Stack_manager:
+    def __init__(self):
+        self.stacks = {}
+
+    def add_stack(self, name, ccmNo, data_s7: S7data, height_init_offset = 0):
+        self.stacks[name] = Billet_stack(name, ccmNo, data_s7, height_init_offset)
+
+    def wait_signal(self, name):
+        stack = self.stacks[name]
+        count = 4
+        while count:
+            time.sleep(5)
+            count -= 1
+            changed = stack.get_changed()
+            if changed > 0:
+                return [changed, stack.height.state, stack.toptotal.data]
+            elif changed < 0:
+                return [changed, stack.height.state-stack.jinwei, 1 if stack.jinwei else stack.toptotal.data+1]
+            else:
+                continue
+        return [0, 0, 0]

+ 116 - 34
models/billet_trace_pusher.py

@@ -4,10 +4,11 @@ from utils.s7data import *
 from models.data_sender import *
 
 class Trace_pusher:
-    def __init__(self, data_s7: S7data, logger: logging.Logger, sender: Sender, strand_position: list, hostsend_flag=False, hostmove_flag=False):
+    def __init__(self, data_s7: S7data, logger: logging.Logger, sender: Sender, strand_position: list, manual_change_heat_sig: Statepoint, hostsend_flag=False, hostmove_flag=False):
         self.data_s7 = data_s7
         self.logger = logger
         self.sender = sender
+        self.manual_change_heat_sig = manual_change_heat_sig
         self.strands_cutting = [[], [], [], [], [], [], [], []]
         self.strands_buffer = [[], [], [], [], [], [], [], []]
         self.locks = [threading.Lock() for i in range(8)]
@@ -39,6 +40,8 @@ class Trace_pusher:
         self.pusher_left.hmd_add(0)
         self.pusher_right.hmd_add(0)
 
+        self.manual_change_heat_sig.set_excite_action(self.manual_change_heat)
+
         self.pusher_left.set_convertor(lambda data: data < min(self.strand_position))
         self.pusher_right.set_convertor(lambda data: data > max(self.strand_position))
         self.pusher_left.set_excite_action(lambda: self.arrive_cooling_bed('left'))
@@ -59,6 +62,7 @@ class Trace_pusher:
         self.integration_total = [0] * 8
         self.integration_lock = threading.Lock()
         self.billet_out_sig = [self.data_s7.make_point(f"L{i+1}拉速", Integration_speed_mpmin) for i in range(8)]
+        self.speed_to_zero_sig = [self.data_s7.make_point(f"L{i+1}拉速") for i in range(8)]
         self.billet_position = [
             self.data_s7.make_point('L1切割信号[1]'),
             self.data_s7.make_point('L2切割信号[1]'),
@@ -96,18 +100,21 @@ class Trace_pusher:
             ]
 
         if self.hostmove_flag:
-            self.hostmove_sig = data_s7.make_point('推钢机激光')
-            self.hostmove_sig.hmd_add(0)
-            self.hostmove_sig.set_convertor(lambda data: data > 19000)
+            self.hostmove_sig = data_s7.make_point('热送辊道状态[1]')
             self.hostmove_sig.set_excite_action(lambda: self.billet_to_stack("步进冷床", self.get_billet('right')))
 
         for i in range(8):
             self.billet_position[i].allow_update(False)
             self.billet_position[i].set_state(False)
+            self.billet_out_sig[i].allow_update(False)
+            self.billet_out_sig[i].set_state(False)
             # 6号机 + 5号机
             # 拉速补充钢坯
             self.billet_out_sig[i].set_convertor(lambda data, i=i: round(data*1000) >= self.integration_total[i] + self.length_cutting[i].data)
             self.billet_out_sig[i].set_excite_action(lambda i=i: self.billet_out_action(i))
+            # 拉速为0时表示此时拉速补偿已经不可用
+            self.speed_to_zero_sig[i].set_convertor(lambda data: data == 0)
+            self.speed_to_zero_sig[i].set_excite_action(lambda i=i: self.billet_out_sig[i].allow_update(False))
             # 正常情况进入跟踪
             self.billet_position[i].set_excite_action(lambda i=i: self.billet_in_buffer_action(i))
             self.billet_position[i].allow_update()
@@ -127,15 +134,21 @@ class Trace_pusher:
                 self.hostsend_barrier[i].set_reset_action(lambda i=i: self.logger.debug(f"{i+1}流热送挡板关闭"))
                 self.hostsend_barrier[i].allow_update()
 
+    def integration_start(self, i):
+        if self.speed_to_zero_sig[i].data == 0:
+            return None
+        self.billet_out_sig[i].data = 0
+        self.billet_out_sig[i].state = False
+        self.integration_total[i] = 0
+        self.billet_out_sig[i].allow_update()
+
     def billet_out_action(self, i):
         billetNo = self.current_heatNo + '0' + str(i+1) + '99'
         sizing = self.length_cutting[i].data
         speed = self.drawing_speed[i].data
-        # 积分的钢坯长度的累计
-        with self.integration_lock:
-            if self.strands_cutting[i]:
-                return None
-            self.integration_total[i] += sizing
+        
+        if self.strands_cutting[i]:
+            return None
         
         # [坯号, 炉次信息, 定尺, 拉速, 开切时间, 停切时间]
         self.billet_out[i] = [billetNo, self.current_heatData, sizing, speed, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '']
@@ -146,6 +159,10 @@ class Trace_pusher:
             if self.strands_cutting[i]:
                 return None
             time.sleep(0.5)
+
+        # 漏钢时,积分数据不会被清零,需要累加
+        with self.integration_lock:
+            self.integration_total[i] += sizing
         self.billet_in_buffer_action(i)
 
     def billet_in_buffer_action(self, i):
@@ -170,14 +187,14 @@ class Trace_pusher:
         with self.locks[i]:
             if self.strands_buffer[i]:
                 self.barrier_checker[i] = True
-                time.sleep(12)
+                time.sleep(10)
                 billetData = self.strands_buffer[i]
                 self.strands_buffer[i] = []
                 if self.strand_position[i] <= self.pusher_left.data:
-                    self.logger.info(f"[TRACE]{i+1}流钢坯通过挡板进入推钢区域,在推钢机侧")
+                    self.logger.info(f"[TRACE]{i+1}流钢坯通过挡板进入推钢区域,在推钢机侧")
                     self.pusher_left_list.append(billetData)
                 else:
-                    self.logger.info(f"[TRACE]{i+1}流钢坯通过挡板进入推钢区域,在推钢机侧")
+                    self.logger.info(f"[TRACE]{i+1}流钢坯通过挡板进入推钢区域,在推钢机侧")
                     self.pusher_right_list.append(billetData)
         
         if self.hostsend_flag and self.hostsend_barrier[i].state:
@@ -202,7 +219,7 @@ class Trace_pusher:
                         break
                 if index == -1:
                     pass
-                    #self.logger.warning(f"[TRACE]推钢机侧未找到{i+1}流的热送钢坯")
+                    #self.logger.warning(f"[TRACE]推钢机侧未找到{i+1}流的热送钢坯")
                 else:
                     gp_tmp = self.pusher_left_list[index]
                     self.pusher_left_list = self.pusher_left_list[:index] + self.pusher_left_list[index+1:]
@@ -214,16 +231,16 @@ class Trace_pusher:
                         break
                 if index == -1:
                     pass
-                    #self.logger.warning(f"[TRACE]推钢机侧未找到{i+1}流的热送钢坯")
+                    #self.logger.warning(f"[TRACE]推钢机侧未找到{i+1}流的热送钢坯")
                 else:
                     gp_tmp = self.pusher_right_list[index]
                     self.pusher_right_list = self.pusher_right_list[:index] + self.pusher_right_list[index+1:]
 
             if gp_tmp:
-                self.logger.info(f"[TRACE]{i+1}流钢坯热送")
-                if gp_tmp[0][:8] not in self.heat_filter:
+                # self.logger.info(f"[TRACE]{i+1}流钢坯热送")
+                if not gp_tmp[0].startswith('0') and gp_tmp[0][:8] not in self.heat_filter:
                     self.change_heat(gp_tmp)
-                if not gp_tmp[0].startswith('0'):
+                if not self.current_heatNo.startswith('0'):
                     self.hostsend(gp_tmp)
             
     def change_heat(self, data):
@@ -238,20 +255,33 @@ class Trace_pusher:
         self.total = 0
         self.strand = [0, 0, 0, 0, 0, 0, 0, 0]
 
+    def manual_change_heat(self):
+        with self.count_lock:
+            tmp = self.manual_change_heat_sig.data
+            if tmp['heatNo'] == self.current_heatNo:
+                return None
+            
+            self.logger.info(f"手动换炉触发:{self.current_heatNo}->{tmp['heatNo']}")
+
+            tmp['sendTime'] = tmp['startPourTime']
+            self.sender.begin_pour(tmp, max(self.data_s7.get_value('大包重量1'), self.data_s7.get_value('大包重量2')))
+
+            self.change_heat([tmp['heatNo'], tmp])
+
     def arrive_cooling_bed(self, direc):
         with self.count_lock:
             if direc == 'left':
-                self.logger.debug(f"左侧冷床上推入{len(self.pusher_left_list)}根钢坯")
+                self.logger.debug(f"侧冷床上推入{len(self.pusher_left_list)}根钢坯")
                 tmp = self.pusher_left_list
                 self.pusher_left_list = []
 
             elif direc == 'right':
-                self.logger.debug(f"右侧冷床上推入{len(self.pusher_right_list)}根钢坯")
+                self.logger.debug(f"侧冷床上推入{len(self.pusher_right_list)}根钢坯")
                 tmp = self.pusher_right_list
                 self.pusher_right_list = []
 
             for i in tmp:
-                if not (i[0].startswith(self.current_heatNo) or i[0].startswith(self.old_heatNo)):
+                if not i[0].startswith('0') and i[0][:8] not in self.heat_filter:
                     self.change_heat(i)
                     break
 
@@ -261,11 +291,11 @@ class Trace_pusher:
         if direc == 'left':
             if len(billets):
                 self.billet_to_bed_impl(billets, self.bed_left)
-            self.logger.debug(f"侧冷床目前情况:{len(self.bed_left[0])}根|{len(self.bed_left[1])}根|{len(self.bed_left[2])}根")
+            self.logger.debug(f"侧冷床目前情况:{len(self.bed_left[0])}根|{len(self.bed_left[1])}根|{len(self.bed_left[2])}根")
         elif direc == 'right':
             if len(billets):
                 self.billet_to_bed_impl(billets, self.bed_right)
-            self.logger.debug(f"侧冷床目前情况:{len(self.bed_right[2])}根|{len(self.bed_right[1])}根|{len(self.bed_right[0])}根")
+            self.logger.debug(f"侧冷床目前情况:{len(self.bed_right[2])}根|{len(self.bed_right[1])}根|{len(self.bed_right[0])}根")
 
 
     def billet_to_bed_impl(self, billets: list, dst: list):
@@ -279,6 +309,12 @@ class Trace_pusher:
             dst[0].extend(billets)
         elif count == 3 and (len(billets) >= 4 or len(dst[count-1]) >= 4 or len(dst[count-1]) + len(billets) > 4):
             self.logger.error(f"组坯异常!")
+
+            # test 把无法组坯的不正常钢坯 直接上传服务器
+            tmp = dst[0]
+            if len(tmp) < 4:
+                self.billet_union(tmp)
+
             dst.remove(dst[0])
             dst.append(billets)
             count -= 1
@@ -293,18 +329,36 @@ class Trace_pusher:
 
 
     def billet_union(self, billets):
+        ccmNo = self.current_heatData['ccmNo'] if self.current_heatData else '0'
+
+        if len(billets) < 4:
+            for i in billets:
+                strandNo = i[0][9]
+                billetNo = self.current_heatNo + ccmNo + strandNo + '{:0>2}'.format(99)
+                i[0] = billetNo
+                i[1] = self.current_heatData
+                if self.current_heatData:
+                    self.sender.billet_upload(self.current_heatData, billetNo, self.total, i[2], i[3], i[4], i[5], '', error=True)
+            self.logger.warning(f"{self.current_heatNo}炉发送异常钢坯{len(billets)}根")
+            return None
+
         if self.sizing_count_heatNo != self.current_heatNo:
             self.sizing_count_heatNo = self.current_heatNo
             self.sizing_count = {}
 
-        if billets[0][2] not in self.sizing_count:
-            self.sizing_count[billets[0][2]] = 0
+        # 组坯时,定尺为此组内定尺最小值
+        sizing = billets[0][2]
+        for i in billets:
+            if i[2] < sizing:
+                sizing = i[2]
+
+        if sizing not in self.sizing_count:
+            self.sizing_count[sizing] = 0
 
-        self.sizing_count[billets[0][2]] += 1
-        billet_unionNo = self.current_heatNo + '{:0>5}'.format(int(billets[0][2])) + '{:0>2}'.format(self.sizing_count[billets[0][2]])
+        self.sizing_count[sizing] += 1
+        billet_unionNo = self.current_heatNo + '{:0>5}'.format(int(sizing)) + '{:0>2}'.format(self.sizing_count[sizing])
 
         billetsNo = []
-        ccmNo = self.current_heatData['ccmNo'] if self.current_heatData else '0'
         for i in billets:
             strandNo = i[0][9]
             self.total += 1
@@ -313,11 +367,12 @@ class Trace_pusher:
             billetsNo.append(billetNo)
             i[0] = billetNo
             i[1] = self.current_heatData
+            i[2] = sizing
             if self.current_heatData:
                 self.sender.billet_upload(self.current_heatData, billetNo, self.total, i[2], i[3], i[4], i[5], billet_unionNo)
         
         if self.current_heatData:
-            self.sender.billet_union(self.current_heatData, billet_unionNo, billetsNo, int(billets[0][2]))
+            self.sender.billet_union(self.current_heatData, billet_unionNo, billetsNo, sizing)
 
         self.logger.info(f"{self.current_heatNo}炉组号{billet_unionNo}钢坯{len(billets)}根:\n    {'、'.join(billetsNo)}")
 
@@ -336,17 +391,25 @@ class Trace_pusher:
 
             else:
                 with self.integration_lock:
-                    self.billet_out_sig[i].data = 0
-                    self.integration_total[i] = 0
+                    # 验证现在拉速积分是否有效
+                    if self.billet_out_sig[i].permitted_update:
+                        # 有效:检测并把所有短尺的钢坯定尺都改为定尺软件给的
+                        if round(self.billet_out_sig[i].data * 1000) - self.integration_total[i] < 10000:
+                            data[2] = self.data_s7.get_value(f"L{i+1}短尺")
+                        
+                        # 对拉速积分进行切割信号校准
+                        self.billet_out_sig[i].data = 0
+                        self.integration_total[i] = 0
+                    else:
+                        # 无效:开启拉速积分
+                        self.integration_start(i)
 
                 if self.strands_cutting[i]:
                     self.logger.warning(f"{i+1}流有钢坯开切冲突")
                     self.strands_cutting[i] = data
-                    self.strands_cutting[i].append('')
                 else:
                     self.logger.info(f"{i+1}流钢坯开切")
                     self.strands_cutting[i] = data
-                    self.strands_cutting[i].append('')
 
     def hostsend(self, i):
         ccmNo = self.current_heatData['ccmNo']
@@ -358,9 +421,13 @@ class Trace_pusher:
         i[1] = self.current_heatData
         self.sender.billet_upload(self.current_heatData, billetNo, self.total, i[2], i[3], i[4], i[5], '')
 
-        self.logger.info(f"{self.current_heatNo}炉钢坯热送:{billetNo}")
+        self.logger.info(f"{self.current_heatNo}炉{strandNo}流钢坯热送:{billetNo}")
         self.sender.host_send(ccmNo, billetNo, "棒一")
 
+    def show_coolbed(self):
+        print(f"北侧冷床目前情况:{len(self.bed_left[0])}根|{len(self.bed_left[1])}根|{len(self.bed_left[2])}根")
+        print(f"南侧冷床目前情况:{len(self.bed_right[2])}根|{len(self.bed_right[1])}根|{len(self.bed_right[0])}根")
+
     def clean_status(self):
         self.logger.debug(f"[TRACE]小冷床状态清空")
         with self.count_lock:
@@ -369,6 +436,21 @@ class Trace_pusher:
             self.bed_left = [[], [], []]
             self.bed_right = [[], [], []]
 
+    def init_coolbed(self):
+        left_count = input(f"输入北侧冷床钢坯根数序列(由外到内):")
+        right_count = input(f"输入南侧冷床钢坯根数序列(由外到内):")
+        if not (left_count.isdigit() and len(left_count) == 3 and right_count.isdigit() and len(right_count) == 3):
+            warnings.warn("冷床格式化错误,请输入正确格式")
+            return None
+        
+        left_count = [int(i) for i in left_count]
+        right_count = [int(i) for i in right_count]
+        temp = ['000000000000', {}, 0, 0, '', '']
+        with self.count_lock:
+            self.bed_left = [[temp.copy() for i in range(left_count[j])] for j in range(3)]
+            self.bed_right = [[temp.copy() for i in range(right_count[j])] for j in range(3)]
+
+
     def get_billet(self, direc):
         with self.count_lock:
             if direc == "left":
@@ -390,6 +472,6 @@ class Trace_pusher:
     def billet_to_stack(self, stackNo, billets):
         if billets:
             self.logger.info(f"有钢坯放入{stackNo}堆垛")
-            self.sender.stack_add('6', billets, "", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), "6#小冷床(右)", '501堆垛')
+            self.sender.stack_add('6', billets, "", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), "6#小冷床(南)", '步进冷床堆垛')
 
 # [坯号, 炉次信息, 定尺, 拉速, 开切时间, 停切时间]

+ 53 - 27
models/data_checker.py

@@ -1,6 +1,6 @@
 from utils.s7data import S7data
 from utils.logger import Logger
-import requests, time, threading
+import requests, time, threading, warnings
 
 class Checker:
     def __init__(self, data_5: S7data, data_6: S7data, logger: Logger):
@@ -11,26 +11,29 @@ class Checker:
         self.already = set()
 
         self.policy_5 = {
-            '定尺看门狗1': [10, '5#机1-4流定尺数据'],
-            '定尺看门狗2': [10, '5#机5-8流定尺数据'],
-            '推钢机激光': [1800, '5#机组坯、装运等除直轧外所有数据'],
-            '天车A1位置': [1800, '5#机堆垛、装车数据'],
-            '天车A2位置': [1800, '5#、6#机堆垛、装车、高线数据'],
-            '天车A3位置': [1800, '6#机堆垛、装车、高线数据'],
-            '车位1视觉看门狗': [10, '5#机车位1到站离站信号'],
-            '车位2视觉看门狗': [10, '6#机车位2到站离站信号'],
-            '车位3视觉看门狗': [10, '6#机车位3到站离站信号'],
-            '车位4视觉看门狗': [10, '6#机车位4到站离站信号'],
-            '车位1车牌看门狗': [10, '5#机车位1车牌识别'],
-            '车位2车牌看门狗': [10, '6#机车位2车牌识别'],
-            '车位3车牌看门狗': [10, '6#机车位3车牌识别'],
-            '车位4车牌看门狗': [10, '6#机车位4车牌识别']
+            # 信号: [无响应报警间隔, 信号描述, 信号名称, 处理方法]
+            '定尺看门狗1': [60, '5#机1-4流定尺数据', '定尺信号', '立刻排查通信'],
+            '定尺看门狗2': [60, '5#机5-8流定尺数据', '定尺信号', '立刻排查通信'],
+            '推钢机激光': [1800, '5#机组坯、装运等除直轧外所有数据', '推钢机信号', '排查停机、plc内信号响应'],
+            ('天车A1位置', '天车A2位置', '天车A3位置'): [600, '所有天车吊运钢坯数据', '天车位置信号', '立刻排查plc内信号,小概率是停机导致'],
+            '车位1视觉看门狗': [60, '5#机车位1到站离站信号、车厢数据', '车位1信号', '立刻排查通信、识别软件是否启动'],
+            '车位2视觉看门狗': [60, '5#机车位2到站离站信号、车厢数据', '车位2信号', '立刻排查通信、识别软件是否启动'],
+            '车位3视觉看门狗': [60, '5#机车位3到站离站信号、车厢数据', '车位3信号', '立刻排查通信、识别软件是否启动'],
+            '车位4视觉看门狗': [60, '5#机车位4到站离站信号、车厢数据', '车位4信号', '立刻排查通信、识别软件是否启动'],
+            '车位1车牌看门狗': [60, '5#机车位1车牌识别', '车位1车牌识别', '立刻排查通信、识别软件是否启动'],
+            '车位2车牌看门狗': [60, '6#机车位2车牌识别', '车位2车牌识别', '立刻排查通信、识别软件是否启动'],
+            '车位3车牌看门狗': [60, '6#机车位3车牌识别', '车位3车牌识别', '立刻排查通信、识别软件是否启动'],
+            '车位4车牌看门狗': [60, '6#机车位4车牌识别', '车位4车牌识别', '立刻排查通信、识别软件是否启动'],
+            ('L1切割信号[1]', 'L2切割信号[1]', 'L3切割信号[1]', 'L4切割信号[1]'): [600, '5#机1-4流切割数据', '液压剪信号', '立刻排查停机/通信异常'],
+            ('L5切割信号[1]', 'L6切割信号[1]', 'L7切割信号[1]', 'L8切割信号[1]'): [600, '5#机5-8流切割数据', '液压剪信号', '立刻排查停机/通信异常']
         }
 
         self.policy_6 = {
-            '定尺看门狗1': [10, '6#机1-4流定尺数据'],
-            '定尺看门狗2': [10, '6#机5-8流定尺数据'],
-            '推钢机激光': [300, '6#机钢坯基础信息、组坯等所有数据']
+            '定尺看门狗1': [60, '6#机1-4流定尺数据', '定尺信号', '立刻排查通信'],
+            '定尺看门狗2': [60, '6#机5-8流定尺数据', '定尺信号', '立刻排查通信'],
+            '推钢机激光': [300, '6#机钢坯基础信息、组坯等所有数据', '推钢机信号', '排查停机、plc内信号响应'],
+            ('L1切割信号[1]', 'L2切割信号[1]', 'L3切割信号[1]', 'L4切割信号[1]'): [600, '6#机1-4流切割数据', '液压剪信号', '立刻排查停机/通信异常'],
+            ('L5切割信号[1]', 'L6切割信号[1]', 'L7切割信号[1]', 'L8切割信号[1]'): [600, '6#机5-8流切割数据', '液压剪信号', '立刻排查停机/通信异常']
         }
 
         self.time_5 = {}
@@ -58,39 +61,62 @@ class Checker:
         tmp['markdown']['text'] = f'''#### 警告:钢坯数字化项目数据异常!
 
 **异常详情:**
-- **异常信号**:{name}
+- **异常信号**:{self.policy_5[name][2] if ccmNo == '5' else self.policy_6[name][2]}
 - **异常描述**:{self.policy_5[name][0] if ccmNo == '5' else self.policy_6[name][0]}秒无响应
 - **发生时间**:{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}
 - **影响范围**:{self.policy_5[name][1] if ccmNo == '5' else self.policy_6[name][1]}
+- **处理方法**:{self.policy_5[name][3] if ccmNo == '5' else self.policy_6[name][3]}
 
 ---
 
 > 看门狗信号异常请立即检查,其他异常请先检查是否设备停机或短暂停止'''
         response = requests.post(url, json=tmp)
         if response.ok and response.json()['errcode'] == 0:
-            self.logger.warning(f"[WARNING]{name}信号异常,已经提交了报告")
+            self.logger.warning(f"[WARNING]{str(name)}信号异常,已经提交了报告")
         else:
-            self.logger.warning(f"[WARNING]{name}信号异常,但提交报告失败")
+            self.logger.warning(f"[WARNING]{str(name)}信号异常,但提交报告失败")
 
     def start_check(self):
         while self.threading_sig:
             for i, j in self.policy_5.items():
-                newdata = self.data_5.get_value(i)
+                if isinstance(i, tuple):
+                    newdata = []
+                    for k in i:
+                        newdata.append(self.data_5.get_value(k))
+                    newdata = tuple(newdata)
+                elif isinstance(i, str):
+                    newdata = self.data_5.get_value(i)
+                else:
+                    warnings.warn('checker模块Error:使用不支持的信号变量类型')
+                    self.logger.error('checker模块Error:使用不支持的信号变量类型')
+                    return None
                 if newdata != self.value_5[i]:
                     self.value_5[i] = newdata
                     self.time_5[i] = time.time()
-                if '5#'+i not in self.already and time.time() - self.time_5[i] > j[0]:
+                    self.already.discard('5#'+str(i))
+                if '5#'+str(i) not in self.already and time.time() - self.time_5[i] > j[0]:
                     self.push(i, '5')
-                    self.already.add('5#'+i)
+                    self.already.add('5#'+str(i))
 
             for i, j in self.policy_6.items():
-                newdata = self.data_6.get_value(i)
+                if isinstance(i, tuple):
+                    newdata = []
+                    for k in i:
+                        newdata.append(self.data_6.get_value(k))
+                    newdata = tuple(newdata)
+                elif isinstance(i, str):
+                    newdata = self.data_6.get_value(i)
+                else:
+                    warnings.warn('checker模块Error:使用不支持的信号变量类型')
+                    self.logger.error('checker模块Error:使用不支持的信号变量类型')
+                    return None
                 if newdata != self.value_6[i]:
                     self.value_6[i] = newdata
                     self.time_6[i] = time.time()
-                if '6#'+i not in self.already and time.time() - self.time_6[i] > j[0]:
+                    self.already.discard('6#'+str(i))
+                if '6#'+str(i) not in self.already and time.time() - self.time_6[i] > j[0]:
                     self.push(i, '6')
-                    self.already.add('6#'+i)
+                    self.already.add('6#'+str(i))
 
             time.sleep(5)
 

+ 119 - 25
models/data_sender.py

@@ -1,5 +1,5 @@
 import paho.mqtt.client as mqtt
-import json, time, requests
+import json, time, requests, pymysql, threading
 
 
 class Sender:
@@ -15,7 +15,11 @@ class Sender:
             'car_save': 'syn/billetHotsendBase/shipp/save',
             'car_go': 'syn/billetHotsendBase/shipp/depart',
             'stack_add': 'syn/billet/addStacking',
-            'plate_update': 'syn/storageBill/update'
+            'plate_update': 'syn/storageBill/update',
+            'stack_car_save': 'syn/billet/stackingAndLoadingVehicles/loading',
+            'jiaoban': 'syn/billet/changeShift',
+            'jiaoban_6': 'syn/billet/changeSixShift',
+            'plate_zhagang': 'trace/performance/car/arrive'
         }
 
         self.url = {
@@ -27,7 +31,11 @@ class Sender:
             'car_save': 'http://192.168.0.119:8181/billetHotsendBase/billetHotsendBase/add',
             'car_go': 'http://192.168.0.119:8181/billetHotsendBase/billetHotsendBase/rodLineDepart',
             'stack_add': 'http://192.168.0.119:8181/billet/stackingAndLoadingVehicles/addStacking',
-            'plate_update': ''
+            'plate_update': '',
+            'stack_car_save': '',
+            'jiaoban': '',
+            'jiaoban_6': '',
+            'plate_zhagang': ''
         }
 
         self.heat_tangible_temp = {
@@ -67,7 +75,8 @@ class Sender:
             'weight'            : 0.0,
             'cutStartTime'      : '',
             'cutStopTime'       : '',
-            'assemblyNumber'    : ''
+            'assemblyNumber'    : '',
+            'sign': "1"
         }
 
         self.billet_union_temp = {
@@ -129,29 +138,46 @@ class Sender:
             "billetNoList": []
         }
 
+        self.stack_car_save_temp = {
+            "ccmNo": "",
+            "billetHotsendTypeConfigId": "",
+            "address": "",
+            "layer": "",
+            "vehicleNumber": "",
+            "liftingTime": "",
+            "location": "",
+            "positionNum":"",
+            "plateOrStack":""
+        }
+
         self.stack_dict = {
             "601堆垛": '6',
             "602堆垛": '7',
             "604堆垛": '8',
-            "步进冷床": '9',
+            "步进冷床堆垛": '9',
             "501堆垛": '10',
         }
 
         self.mqtt_cli = None
-        self.http_flag = True
+        self.http_flag = False
+        self.mysql_flag = False
         self._cache = {}
         self._billet = {}
+        self.mysql_lock = threading.Lock()
 
     def set_mqtt_client(self, cli):
         self.mqtt_cli = cli
 
+    def set_mysql_client(self, cli: pymysql.connections.Connection):
+        self.mysql_cli = cli
+
     def send(self, purpose, payload, qos=2):
         if not isinstance(payload, dict):
             self.logger.error('[SENDER]发送数据非dict类型')
             raise TypeError(f"Need a dict type but {type(payload)} given.")
             return None
         
-        self.logger.info(f"[SENDER]使用 {'MQTT'if self.mqtt_cli else ''} {'HTTP'if self.http_flag else ''} 发送数据")
+        self.logger.info(f"[SENDER]使用 {'MQTT'if self.mqtt_cli else ''} {'HTTP'if self.http_flag else ''} {'MYSQL'if self.mysql_flag else ''} 发送数据")
         self.logger.debug(f"[SENDER]{purpose}:{payload}")
         
         if self.mqtt_cli and purpose in self.topic:
@@ -169,20 +195,57 @@ class Sender:
             except:
                 self.logger.error('[SENDER]HTTP:请求超时')
 
+        if self.mysql_flag and hasattr(self, 'mysql_cli') and self.mysql_cli:
+            if self.save_mysql(purpose, payload):
+                self.logger.info('[SENDER]MYSQL:数据保存成功')
+            else:
+                self.logger.error('[SENDER]MYSQL:数据保存失败')
+
+    def save_mysql(self, table, args: dict):
+        if not hasattr(self, 'mysql_cli') or self.mysql_cli == None:
+            return None
+        
+        keys = []
+        solved_values = []
+
+        for k, v in args.items():
+            keys.append(k)
+            if 'time' in k.lower():
+                if v:
+                    solved_values.append(v)
+                else:
+                    solved_values.append(None)
+            elif isinstance(v, int) or isinstance(v, float) or isinstance(v, str):
+                solved_values.append(v)
+            else:
+                solved_values.append(json.dumps(v))
+
+        sql = f"INSERT INTO {table}({', '.join(keys)}) VALUES({', '.join(['%s' for i in range(len(keys))])})"
+        with self.mysql_lock:
+            try:
+                self.mysql_cli.ping()
+                with self.mysql_cli.cursor() as cursor:
+                    cursor.execute(sql, tuple(solved_values))
+                    self.mysql_cli.commit()
+                    return True
+            except pymysql.Error as e:
+                self.logger.error(f"[SENDER]MYSQL:{e}")
+
+        return False
+
     def begin_pour(self, heat_data, bigbagweight):
         tmp = self.heat_tangible_temp.copy()
         tmp['optype'] = 1
         tmp['casterCode'] = heat_data.get('ccmNo', '')
-        tmp['emptyLadleWeight'] = bigbagweight - heat_data.get('netWeight', 0)
+        tmp['emptyLadleWeight'] = bigbagweight - float(heat_data.get('netWeight', 0))
         tmp['fullLadleWeight'] = bigbagweight
         tmp['grade'] = heat_data.get('grade', '')
         tmp['spec'] = heat_data.get('spec', '')
         tmp['heatsCode'] = heat_data.get('heatNo', '')
         tmp['ladleCode'] = heat_data.get('ladleNo', '')
-        tmp['moltenSteelWeight'] = heat_data.get('netWeight', 0)
+        tmp['moltenSteelWeight'] = float(heat_data.get('netWeight', 0))
         tmp['startPourTime'] = heat_data.get('sendTime', '')
         
-        #此处应存储进数据库,暂时使用缓存处理
         self._cache[heat_data.get('heatNo', 'error')] = tmp
         self.send('heat_add', tmp)
 
@@ -194,7 +257,6 @@ class Sender:
             tmp['spec'] = heat_data.get('spec', '')
             tmp['stopPourTime'] = heat_data.get('sendTime', '')
 
-            #此处应存储进数据库,暂时使用缓存处理
             self._cache[heat_data.get('heatNo', 'error')] = tmp
             self.send('heat_add', tmp)
         else:
@@ -207,7 +269,6 @@ class Sender:
         tmp['optype'] = 2
         tmp['firstCutTime'] = cuttime
 
-        #此处应存储进数据库,暂时使用缓存处理
         self._cache[heat_data.get('heatNo', 'error')] = tmp
         self.send('heat_add', tmp)
 
@@ -218,11 +279,10 @@ class Sender:
         tmp['optype'] = 2
         tmp['lastCutTime'] = cuttime
 
-        #此处应存储进数据库,暂时使用缓存处理
         self._cache[heat_data.get('heatNo', 'error')] = tmp
         self.send('heat_add', tmp)
 
-    def begin_cut(self, heat_data, billetNo, heatnoIndex, sizing, speed):
+    def begin_cut(self, heat_data, billetNo, heatnoIndex, sizing, speed, error=False):
         cuttime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
         tmp = self.billet_tangible_temp.copy()
         tmp['optype'] = 1
@@ -239,8 +299,8 @@ class Sender:
         tmp['castingSpeed'] = speed
         tmp['weight'] = sizing / 1000 * 0.2265
         tmp['cutStartTime'] = cuttime
+        tmp['sign'] = "2" if error else "1"
 
-        #此处应存储进数据库,暂时使用缓存处理
         self._billet[heat_data.get('heatNo', '')+str(heatnoIndex)] = tmp
         self.send('billet_add', tmp)
 
@@ -259,11 +319,10 @@ class Sender:
         tmp['optype'] = 2
         tmp['cutStopTime'] = cuttime
 
-        #此处应存储进数据库,暂时使用缓存处理
         self._billet[heat_data.get('heatNo', '')+str(heatnoIndex)] = tmp
         self.send('billet_add', tmp)
 
-    def billet_upload(self, heat_data, billetNo, heatnoIndex, sizing, speed, starttime, stoptime, unionNo):
+    def billet_upload(self, heat_data, billetNo, heatnoIndex, sizing, speed, starttime, stoptime, unionNo, error=False):
         tmp = self.billet_tangible_temp.copy()
         tmp['optype'] = 1
         tmp['heatNo'] = heat_data.get('heatNo', '')
@@ -281,8 +340,8 @@ class Sender:
         tmp['cutStartTime'] = starttime
         tmp['cutStopTime'] = stoptime
         tmp['assemblyNumber'] = unionNo
+        tmp['sign'] = "2" if error else "1"
 
-        #此处应存储进数据库
         self.send('billet_add', tmp)
 
     def billet_union(self, heat_data, unionNo, billetNos, sizing):
@@ -297,7 +356,6 @@ class Sender:
         tmp['billetsNum'] = len(billetNos)
         tmp['billetWeight'] = sizing / 1000 * 0.2265 * tmp['billetsNum']
 
-        #此处应存储进数据库
         self.send('billet_union', tmp)
 
     def host_send(self, ccmNo, billetNos, dst_str, craneNo = "", fromaddr = ""):
@@ -311,7 +369,6 @@ class Sender:
         tmp['location'] = fromaddr
         tmp['destination'] = dst_str
 
-        #此处应存储进数据库
         self.send('host_send', tmp)
 
     def car_add(self, ccmNo, carNo, plate: str):
@@ -322,7 +379,6 @@ class Sender:
         tmp['licensePlate'] = plate
         tmp['positionNum'] = carNo
 
-        #此处应存储进数据库
         self.send('car_add', tmp)
 
     def car_save(self, ccmNo, billetNos, plate, craneNo, Ltime, fromaddr, toaddr, layer=0, address=0):
@@ -341,7 +397,6 @@ class Sender:
         tmp['layer'] = str(layer)
         tmp['address'] = str(address)
 
-        #此处应存储进数据库
         self.send('car_save', tmp)
 
     def car_go(self, ccmNo, carNo, plate = ''):
@@ -351,7 +406,6 @@ class Sender:
         tmp['positionNum'] = carNo
         tmp['licensePlate'] = plate
 
-        #此处应存储进数据库
         self.send('car_go', tmp)
 
     def stack_add(self, ccmNo, billetNos, craneNo, Ltime, fromaddr, toaddr, layer=0, address=0):
@@ -372,7 +426,6 @@ class Sender:
             'address' : str(address)
         })
 
-        #此处应存储进数据库
         self.send('stack_add', tmp)
 
     def plate_update(self, ccmNo, carNo, plate):
@@ -382,9 +435,50 @@ class Sender:
         tmp['positionNum'] = carNo
         tmp['licensePlate'] = plate
 
-        #此处应存储进数据库
         self.send('plate_update', tmp)
 
+    def stack_car_save(self, ccmNo, craneNo, Ltime, fromaddr, toaddr, layer, address):
+        tmp = self.stack_car_save_temp.copy()
+
+        tmp['ccmNo'] = ccmNo
+        tmp['billetHotsendTypeConfigId'] = self.stack_dict[fromaddr]
+        tmp['vehicleNumber'] = craneNo
+        tmp['liftingTime'] = Ltime
+        tmp['location'] = fromaddr
+        tmp['positionNum'] = toaddr[-1] if len(toaddr) else ""
+        tmp['plateOrStack'] = fromaddr
+        tmp['layer'] = str(layer)
+        tmp['address'] = str(address)
+
+        self.send('stack_car_save', tmp)
+
+    def jiaoban(self, ccmNo, ban):
+        tmp = {}
+
+        tmp['changeShiftTime'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
+        tmp['ban'] = ban
+
+        self.send('jiaoban', tmp)
+
+    def jiaoban_6(self, ccmNo, ban):
+        tmp = {}
+
+        tmp['changeShiftTime'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
+        tmp['ban'] = ban
+
+        self.send('jiaoban_6', tmp)
+
+    def plate_zhagang(self, carNo, plate):
+        tmp = {}
+
+        tmp['type'] = 'car_arrive'
+        tmp['data'] = {
+            'car': plate,
+            'position': carNo
+        }
+
+        self.send('plate_zhagang', tmp)
+
 
 if __name__ == '__main__':
     import logging

+ 30 - 0
models/jiaoban.py

@@ -0,0 +1,30 @@
+from utils.s7data import S7data
+from models.data_sender import Sender
+from utils.logger import Logger
+import time, threading
+
+class Banci:
+    def __init__(self, s7_data: S7data, sender: Sender, logger: Logger, ccmNo):
+        self.s7_data = s7_data
+        self.sender = sender
+        self.logger = logger
+        self.ccmNo = ccmNo
+
+        self.ban_5 = s7_data.make_point('当前班别')
+        self.ban_now_5 = self.ban_5.data
+
+        self.thread = threading.Thread(target=self.jiaoban_action)
+        self.thread.start()
+
+    def jiaoban_action(self):
+        time.sleep(2)
+        self.ban_now_5 = self.ban_5.data
+        while True:
+            if self.ban_5.data != self.ban_now_5:
+                self.ban_now_5 = self.ban_5.data
+                self.logger.info(f'{self.ccmNo}#机触发交班信号')
+                if self.ccmNo == '6':
+                    self.sender.jiaoban_6(self.ccmNo, self.ban_now_5)
+                elif self.ccmNo == '5':
+                    self.sender.jiaoban(self.ccmNo, self.ban_now_5)
+            time.sleep(1)

+ 137 - 41
models/overhead_crane.py

@@ -4,13 +4,37 @@ from utils.logger import Logger
 from models.billet_trace_pusher import Trace_pusher
 from models.parking import Parking
 from models.data_sender import Sender
+from models.billet_stacks import Stack_manager
+from utils.statepoint import Statepoint
+
+class Changed_recorder:
+    def __init__(self, point: Statepoint):
+        self.point = point
+        self.point.set_convertor(self.do_update)
+        self.old_data = self.point.data
+        self.changed_count = 0
+        self.changed_time = time.time()
+    
+    def do_update(self, data):
+        if data != self.old_data:
+            self.changed_count = data - self.old_data
+            self.changed_time = time.time()
+        self.old_data = data
+
+    def get_changed(self):
+        if time.time() - self.changed_time <= 20:
+            return self.changed_count
+        
+        return 0
+
 
 class Crane:
-    def __init__(self, data_s7: S7data, tracer_5: Trace_pusher, tracer_6: Trace_pusher, parking: Parking, sender: Sender, logger: Logger):
+    def __init__(self, data_s7: S7data, tracer_5: Trace_pusher, tracer_6: Trace_pusher, parking: Parking, stack_manager: Stack_manager, sender: Sender, logger: Logger):
         self.data = data_s7
         self.tracer_5 = tracer_5
         self.tracer_6 = tracer_6
         self.parking = parking
+        self.stack_manager = stack_manager
         self.sender = sender
         self.logger = logger
 
@@ -26,36 +50,50 @@ class Crane:
             "A3": ""
         }
 
+        self.billet_layer_addr = {
+            "A1": [0, 0, 0],
+            "A2": [0, 0, 0],
+            "A3": [0, 0, 0]
+        }
+
         self.billets_from = {
             "A1": "",
             "A2": "",
             "A3": ""
         }
 
+        self.billets_from_stack = {
+            "A1": "",
+            "A2": "",
+            "A3": ""
+        }
+
         self.areas = {
-            "6#小冷床(左)": [34000, 39700],#确认
-            "6#小冷床(右)": [22000, 24300],#确认
+            "6#小冷床()": [34000, 39700],#确认
+            "6#小冷床(南)": [20000, 24300],
             "车位3": [1300, 4500],#确认
             "高线辊道": [60300, 62300],#确认
             "601堆垛": [6000, 17000],#确认
-            "5#小冷床()": [86000, 95000],
-            "5#小冷床()": [72140, 76140],
+            "5#小冷床()": [86000, 95000],
+            "5#小冷床()": [72140, 76140],
             "501堆垛": [105520, 117725],#确认
             "车位1": [119370, 122395],#确认
             "车位2": [43000, 48000]#确认
         }
 
+        self.car1_checker = Changed_recorder(self.data.make_point("车1车厢夹数"))
+
         self.threads_poor = [
-            threading.Thread(target=self.area_producer, args=("6#小冷床(左)",)),
-            threading.Thread(target=self.area_producer, args=("6#小冷床()",)),
-            threading.Thread(target=self.area_producer, args=("5#小冷床()",)),
-            threading.Thread(target=self.area_producer, args=("5#小冷床()",)),
-            threading.Thread(target=self.area_consumer, args=("车位1",)),
+            threading.Thread(target=self.area_producer, args=("6#小冷床()",)),
+            threading.Thread(target=self.area_producer, args=("6#小冷床()",)),
+            threading.Thread(target=self.area_producer, args=("5#小冷床()",)),
+            threading.Thread(target=self.area_producer, args=("5#小冷床()",)),
+            threading.Thread(target=self.area_consumer, args=("车位1", self.car1_checker)),
             threading.Thread(target=self.area_consumer, args=("车位2",)),
             threading.Thread(target=self.area_consumer, args=("车位3",)),
             threading.Thread(target=self.area_consumer, args=("高线辊道",)),
-            threading.Thread(target=self.area_consumer, args=("601堆垛",)),
-            threading.Thread(target=self.area_consumer, args=("501堆垛",))
+            threading.Thread(target=self.area_stack, args=("601堆垛",)),
+            threading.Thread(target=self.area_stack, args=("501堆垛",))
         ]
 
         for i in self.threads_poor:
@@ -77,7 +115,7 @@ class Crane:
         while True:
             if self.cranes[craneNo].data < start or self.cranes[craneNo].data > end:
                 return True
-            if wait_time >= 18:
+            if wait_time >= 16:
                 return False
             time.sleep(0.5)
             wait_time += 0.5
@@ -92,19 +130,24 @@ class Crane:
                 continue
 
             if self.billetsNo[craneNo] == "":
-                self.logger.debug(f"天车{craneNo}正在等待或尝试夹起钢坯")
+                self.logger.debug(f"天车{craneNo}区域{areaNo}:正在等待或尝试夹起钢坯")
                 if self.get_billet(craneNo, areaNo):
-                    self.logger.debug(f"天车{craneNo}获取到钢坯")
+                    self.logger.info(f"天车{craneNo}区域{areaNo}:获取到钢坯{self.billetsNo[craneNo]}")
                     #test
                     if '5#' in areaNo:
-                        if '' in areaNo:
-                            self.logger.debug(f"侧冷床目前情况:{len(self.tracer_5.bed_left[0])}根|{len(self.tracer_5.bed_left[1])}根|{len(self.tracer_5.bed_left[2])}根")
+                        if '' in areaNo:
+                            self.logger.debug(f"5#北侧冷床目前情况:{len(self.tracer_5.bed_left[0])}根|{len(self.tracer_5.bed_left[1])}根|{len(self.tracer_5.bed_left[2])}根")
                         else:
-                            self.logger.debug(f"右侧冷床目前情况:{len(self.tracer_5.bed_right[2])}根|{len(self.tracer_5.bed_right[1])}根|{len(self.tracer_5.bed_right[0])}根")
+                            self.logger.debug(f"5#南侧冷床目前情况:{len(self.tracer_5.bed_right[2])}根|{len(self.tracer_5.bed_right[1])}根|{len(self.tracer_5.bed_right[0])}根")
+                    elif '6#' in areaNo:
+                        if '北' in areaNo:
+                            self.logger.debug(f"6#北侧冷床目前情况:{len(self.tracer_6.bed_left[0])}根|{len(self.tracer_6.bed_left[1])}根|{len(self.tracer_6.bed_left[2])}根")
+                        else:
+                            self.logger.debug(f"6#南侧冷床目前情况:{len(self.tracer_6.bed_right[2])}根|{len(self.tracer_6.bed_right[1])}根|{len(self.tracer_6.bed_right[0])}根")
                 else:
-                    self.logger.debug(f"天车{craneNo}未获取到钢坯直接离开")
+                    self.logger.debug(f"天车{craneNo}区域{areaNo}:未获取到钢坯直接离开")
             else:
-                self.logger.debug(f"天车{craneNo}有未卸下的钢坯")
+                self.logger.debug(f"天车{craneNo}区域{areaNo}:有未卸下的钢坯")
 
             while not self.crane_leave(craneNo, areaNo):
                 pass
@@ -112,7 +155,7 @@ class Crane:
             self.logger.debug(f"天车{craneNo}离开区域{areaNo}")
             time.sleep(1)
 
-    def area_consumer(self, areaNo):
+    def area_consumer(self, areaNo, checker: Changed_recorder = None):
         while True:
             craneNo = self.scan_crane(areaNo)
             self.logger.debug(f"天车{craneNo}进入区域{areaNo}")
@@ -121,18 +164,58 @@ class Crane:
                 self.logger.debug(f"天车{craneNo}离开区域{areaNo}")
                 continue
 
-            if self.billetsNo[craneNo] == "":
-                self.logger.debug(f"天车{craneNo}未携带钢坯")
+            if self.billetsNo[craneNo] == "" and self.billet_layer_addr[craneNo][0] == 0:
+                self.logger.debug(f"天车{craneNo}区域{areaNo}:未携带钢坯")
             else:
-                self.logger.debug(f"天车{craneNo}正在等待或尝试卸下钢坯")
-                self.unload_billet(craneNo, areaNo)
+                self.logger.debug(f"天车{craneNo}区域{areaNo}:正在等待或尝试卸下钢坯")
+                if checker == None:
+                    self.unload_billet(craneNo, areaNo)
 
             while not self.crane_leave(craneNo, areaNo):
                 pass
+
+            if checker != None:
+                count = 4
+                while count:
+                    time.sleep(5)
+                    count -= 1
+                    changed = checker.get_changed()
+                    if changed > 0:
+                        self.unload_billet(craneNo, areaNo)
+                    elif changed < 0:
+                        self.logger.warning(f"区域{areaNo}产生意料之外的钢坯数量减少{-changed}根")
+                    else:
+                        continue
             
             self.logger.debug(f"天车{craneNo}离开区域{areaNo}")
             time.sleep(1)
 
+    def area_stack(self, areaNo):
+        while True:
+            craneNo = self.scan_crane(areaNo)
+            self.logger.debug(f"天车{craneNo}进入区域{areaNo}")
+
+            if self.crane_leave(craneNo, areaNo):
+                self.logger.debug(f"天车{craneNo}离开区域{areaNo}")
+                continue
+            
+            while not self.crane_leave(craneNo, areaNo):
+                pass
+
+            self.logger.debug(f"天车{craneNo}离开区域{areaNo}")
+            if areaNo[-2:] == '堆垛':
+                self.logger.debug(f"天车{craneNo}区域{areaNo}:等待识别信号")
+                stack_change = self.stack_manager.wait_signal(areaNo[:-2])
+                if stack_change[0] > 0:
+                    if self.billetsNo[craneNo] == "":
+                        self.logger.debug(f"天车{craneNo}区域{areaNo}:未携带钢坯")
+                    else:
+                        self.unload_billet(craneNo, areaNo, stack_change[1], stack_change[2])
+                elif stack_change[0] < 0:
+                    self.logger.info(f"天车{craneNo}区域{areaNo}:获取到钢坯({stack_change[1]}, {stack_change[2]})")
+                    self.billet_layer_addr[craneNo] = stack_change
+                    self.billets_from_stack[craneNo] = areaNo
+
     def get_billet(self, craneNo, areaNo):
         # 阻塞获取
         start = self.areas[areaNo][0]
@@ -141,13 +224,13 @@ class Crane:
         while not bool(tmp):
             if self.cranes[craneNo].data < start or self.cranes[craneNo].data > end:
                 return False
-            if areaNo == "6#小冷床()":
+            if areaNo == "6#小冷床()":
                 tmp = self.tracer_6.get_billet("left")
-            elif areaNo == "6#小冷床()":
+            elif areaNo == "6#小冷床()":
                 tmp = self.tracer_6.get_billet("right")
-            elif areaNo == "5#小冷床()":
+            elif areaNo == "5#小冷床()":
                 tmp = self.tracer_5.get_billet("left")
-            elif areaNo == "5#小冷床()":
+            elif areaNo == "5#小冷床()":
                 tmp = self.tracer_5.get_billet("right")
             time.sleep(0.5)
         
@@ -155,31 +238,44 @@ class Crane:
         self.billets_from[craneNo] = areaNo
         return tmp
 
-    def unload_billet(self, craneNo, areaNo):
+    def unload_billet(self, craneNo, areaNo, layer=0, addr=0):
         # 直接放下
         tmp = self.billetsNo[craneNo]
         self.billetsNo[craneNo] = ""
+        tmp_stack_addr = self.billet_layer_addr[craneNo]
+        self.billet_layer_addr[craneNo] = [0, 0, 0]
         tmp_from = self.billets_from[craneNo]
         self.billets_from[craneNo] = ""
+        tmp_from_stack = self.billets_from_stack[craneNo]
+        self.billets_from_stack[craneNo] = ""
         if areaNo == "车位1":
-            self.logger.info(f"有一夹子钢坯放置进了车位1")
-            if not tmp.startswith("00"):
+            if tmp_stack_addr[0]:
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯({tmp_stack_addr[1]}, {tmp_stack_addr[2]})")
+                self.sender.stack_car_save(self.parking.ccmNo_list[0], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from_stack, areaNo, tmp_stack_addr[1], tmp_stack_addr[2])
+            elif tmp and not tmp.startswith("00"):
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
                 self.sender.car_save(self.parking.ccmNo_list[0], tmp, self.parking.current_car[0], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, areaNo)
         if areaNo == "车位2":
-            self.logger.info(f"有一夹子钢坯放置进了车位2")
-            if not tmp.startswith("00"):
+            if tmp_stack_addr[0]:
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯({tmp_stack_addr[1]}, {tmp_stack_addr[2]})")
+                self.sender.stack_car_save(self.parking.ccmNo_list[1], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from_stack, areaNo, tmp_stack_addr[1], tmp_stack_addr[2])
+            elif tmp and not tmp.startswith("00"):
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
                 self.sender.car_save(self.parking.ccmNo_list[1], tmp, self.parking.current_car[1], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, areaNo)
         if areaNo == "车位3":
-            self.logger.info(f"有一夹子钢坯放置进了车位3")
-            if not tmp.startswith("00"):
+            if tmp_stack_addr[0]:
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯({tmp_stack_addr[1]}, {tmp_stack_addr[2]})")
+                self.sender.stack_car_save(self.parking.ccmNo_list[2], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from_stack, areaNo, tmp_stack_addr[1], tmp_stack_addr[2])
+            elif tmp and not tmp.startswith("00"):
+                self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
                 self.sender.car_save(self.parking.ccmNo_list[2], tmp, self.parking.current_car[2], craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, areaNo)
         elif areaNo == "高线辊道":
-            self.logger.info(f"有一夹子钢坯放置进了高线辊道")
+            self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
             if not tmp.startswith("00"):
                 self.sender.host_send("6", tmp, "高线", craneNo, tmp_from)
         elif areaNo == "601堆垛":
-            self.logger.info(f"有一夹子钢坯放置进了601堆垛")
-            self.sender.stack_add('6', tmp, craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, '601堆垛')
+            self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
+            self.sender.stack_add('6', tmp, craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, '601堆垛', layer, addr)
         elif areaNo == "501堆垛":
-            self.logger.info(f"有一夹子钢坯放置进了501堆垛")
-            self.sender.stack_add('5', tmp, craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, '501堆垛')
+            self.logger.info(f"天车{craneNo}区域{areaNo}:放入钢坯{tmp}")
+            self.sender.stack_add('5', tmp, craneNo, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), tmp_from, '501堆垛', layer, addr)

+ 18 - 1
models/parking.py

@@ -1,9 +1,10 @@
-import threading, time
+import threading, time, snap7
 from utils.s7data import S7data
 from models.data_sender import Sender
 
 class Parking:
     def __init__(self, data_s7: S7data, sender: Sender):
+        self.data_s7 = data_s7
         self.sender = sender
 
         self.ccmNo_list = ["5", "6", "6", "6"]
@@ -21,6 +22,10 @@ class Parking:
         self.car_be_send_state = [False] * 4
         self.car_be_lock = [threading.Lock() for i in range(4)]
 
+        self.send_lock = threading.Lock()
+        self.s7_sender = snap7.client.Client()
+        self.s7_sender.connect('192.168.1.215', 0, 1)
+
         self.plate_set = {'厂内00664', '厂内00415', '厂内00687', '厂内00701', '厂内00700', '厂内00901', '厂内00699', '厂内00695', '厂内00694', '厂内00693', '厂内00692', '厂内00902', '厂内00690', '厂内00689', '陕E08582D', '陕E08515D', '陕E08000D', '陕E00298D'}
 
         for name in range(4):
@@ -62,6 +67,18 @@ class Parking:
         with self.car_be_lock[i]:
             if self.car_be_send_state[i]:
                 return None
+            
+            # 临时测试
+            with self.send_lock:
+                byte_value = self.s7_sender.db_read(306, 38, 1)
+                snap7.util.set_bool(byte_value, 0, 2, True)
+                self.s7_sender.db_write(306, 38, byte_value)
+            time.sleep(3)
+            with self.send_lock:
+                byte_value = self.s7_sender.db_read(306, 38, 1)
+                snap7.util.set_bool(byte_value, 0, 2, False)
+                self.s7_sender.db_write(306, 38, byte_value)
+
             start_time = time.time()
             now = time.time()
             while self.car_be[i].state and now - start_time < 10:

+ 57 - 0
models/zhagang.py

@@ -0,0 +1,57 @@
+from utils.tcp_data import *
+from models.data_sender import Sender
+import time, threading
+
+class car_plate:
+    def __init__(self, sender: Sender):
+        self.sender = sender
+
+        self.server_car = Tcp_server('192.168.12.211', 5200, encoding='gbk')
+        self.server_car.set_convertor(self.car_solve)
+
+        self.server_plate = Tcp_server('192.168.12.211', 5201, encoding='')
+        self.server_plate.set_convertor(self.plate_solve)
+
+        self.c2 = Statepoint(0)
+        self.c2.set_excite_action(lambda: self.server_plate.send_to_all_clients(b'\x02'))
+
+        self.c3 = Statepoint(0)
+        self.c2.set_excite_action(lambda: self.server_plate.send_to_all_clients(b'\x03'))
+
+        self.plates = {2:'', 3:''}
+
+        self.server_plate.start()
+        self.server_car.start()
+
+        self.thread = threading.Thread(target=self.send_plate)
+        self.thread.start()
+
+    def car_solve(self, data):
+        data = data.split('C')
+        data.reverse()
+        c2flag = False
+        c3flag = False
+        for i in data:
+            if i == '':
+                continue
+            tmp = i.split('|')
+            if not c2flag and tmp[0] == '2':
+                c2flag = True
+                self.c2.inject(int(tmp[1]))
+            elif not c3flag and tmp[0] == '3':
+                c3flag = True
+                self.c3.inject(int(tmp[1]))
+            if c2flag and c3flag:
+                break
+
+    def plate_solve(self, data):
+        carNo = data[0]
+        length = data[2]
+        plate = data[3:3+length].decode('gbk')
+        self.plates[carNo] = plate
+
+    def send_plate(self):
+        while True:
+            self.sender.plate_zhagang('2', self.plates[2])
+            self.sender.plate_zhagang('2', self.plates[3])
+            time.sleep(30)

+ 19 - 0
utils/logger.py

@@ -1,4 +1,5 @@
 import logging
+from logging.handlers import RotatingFileHandler
 
 class Logger(logging.Logger):
     def __init__(self, name):
@@ -36,4 +37,22 @@ class Logger(logging.Logger):
         self.handler = logging.FileHandler(path)
         self.handler.setLevel(level)
         self.handler.setFormatter(formatter)
+        self.addHandler(self.handler)
+
+    def file_on_with_rotation(self, path='log.txt', level=logging.DEBUG, format=None, maxBytes=8*1024*1024, backupCount=3):
+        if self.handler:
+            return None
+        
+        if format == None:
+            formatter = logging.Formatter(self.format_default)
+        else:
+            formatter = logging.Formatter(format)
+
+        self.handler = RotatingFileHandler(
+            path, 
+            maxBytes=maxBytes,
+            backupCount=backupCount
+        )
+        self.handler.setLevel(level)
+        self.handler.setFormatter(formatter)
         self.addHandler(self.handler)

+ 48 - 25
utils/mqttdata.py

@@ -14,7 +14,7 @@ class Mqttdata:
         self.logger = None
 
         self.thread = None
-        self.node_data = {'5#开浇信号': {}, '5#停浇信号': {}, '6#开浇信号': {}, '6#停浇信号': {}}
+        self.node_data = {'5#开浇信号': {}, '5#停浇信号': {}, '6#开浇信号': {}, '6#停浇信号': {}, '5#手动换炉': {}, '6#手动换炉': {}}
         self.target_from_name = {}
 
     def set_logger(self, logger):
@@ -25,6 +25,7 @@ class Mqttdata:
         self.cli.on_connect = self.on_connect
         self.cli.on_message = self.on_message
         self.cli.subscribe('data/service/cast/info', qos=2)
+        self.cli.subscribe('syn/pushbillethotsend/nexthosend', qos=2)
 
     def start_auto_update(self):
         if self.thread == None:
@@ -49,30 +50,51 @@ class Mqttdata:
 
     def on_message(self, client, userdata, message):
         # logger.debug(message.payload.decode())
-        data = json.loads(message.payload.decode())
-        if 'ccmNo' not in data or 'castState' not in data:
-            warnings.warn('[MES]MQTT报文格式错误')
-            if self.logger:
-                self.logger.error('[MES]MQTT报文格式错误')
-            return None
-        if int(data['ccmNo']) == 6:
-            if data['castState'] and data != self.node_data['6#开浇信号']:
-                self.node_data['6#开浇信号'] = data
-                self.send('6#开浇信号')
-            elif not data['castState'] and data != self.node_data['6#停浇信号']:
-                self.node_data['6#停浇信号'] = data
-                self.send('6#停浇信号')
-        elif int(data['ccmNo']) == 5:
-            if data['castState'] and data != self.node_data['5#开浇信号']:
-                self.node_data['5#开浇信号'] = data
-                self.send('5#开浇信号')
-            elif not data['castState'] and data != self.node_data['5#停浇信号']:
-                self.node_data['5#停浇信号'] = data
-                self.send('5#停浇信号')
-        else:
-            warnings.warn('[MES]MQTT收到未知铸机号')
-            if self.logger:
-                self.logger.error('[MES]MQTT收到未知铸机号')
+        topic = message.topic
+        if topic == 'syn/pushbillethotsend/nexthosend':
+            data = json.loads(message.payload.decode())
+            if 'ccmNo' not in data:
+                warnings.warn('[MES]MQTT报文格式错误')
+                if self.logger:
+                    self.logger.error('[MES]MQTT报文格式错误')
+                return None
+            if int(data['ccmNo']) == 6:
+                if data != self.node_data['6#手动换炉']:
+                    self.node_data['6#手动换炉'] = data
+                    self.send('6#手动换炉')
+            elif int(data['ccmNo']) == 5:
+                if data != self.node_data['5#手动换炉']:
+                    self.node_data['5#手动换炉'] = data
+                    self.send('5#手动换炉')
+            else:
+                warnings.warn('[MES]MQTT收到未知铸机号')
+                if self.logger:
+                    self.logger.error('[MES]MQTT收到未知铸机号')
+        elif topic == 'data/service/cast/info':
+            data = json.loads(message.payload.decode())
+            if 'ccmNo' not in data or 'castState' not in data:
+                warnings.warn('[MES]MQTT报文格式错误')
+                if self.logger:
+                    self.logger.error('[MES]MQTT报文格式错误')
+                return None
+            if int(data['ccmNo']) == 6:
+                if data['castState'] and data != self.node_data['6#开浇信号']:
+                    self.node_data['6#开浇信号'] = data
+                    self.send('6#开浇信号')
+                elif not data['castState'] and data != self.node_data['6#停浇信号']:
+                    self.node_data['6#停浇信号'] = data
+                    self.send('6#停浇信号')
+            elif int(data['ccmNo']) == 5:
+                if data['castState'] and data != self.node_data['5#开浇信号']:
+                    self.node_data['5#开浇信号'] = data
+                    self.send('5#开浇信号')
+                elif not data['castState'] and data != self.node_data['5#停浇信号']:
+                    self.node_data['5#停浇信号'] = data
+                    self.send('5#停浇信号')
+            else:
+                warnings.warn('[MES]MQTT收到未知铸机号')
+                if self.logger:
+                    self.logger.error('[MES]MQTT收到未知铸机号')
 
     def on_connect(self, client, userdata, flags, reason_code, properties):
         if reason_code.is_failure:
@@ -81,6 +103,7 @@ class Mqttdata:
                 self.logger.error(f"Failed to connect: {reason_code}. loop_forever() will retry connection")
         else:
             client.subscribe('data/service/cast/info', qos=2)
+            client.subscribe('syn/pushbillethotsend/nexthosend', qos=2)
             if self.logger:
                 self.logger.info("MQTT connection succeeded")
 

+ 9 - 5
utils/s7data.py

@@ -62,7 +62,11 @@ class S7data:
         return self.S7Client
     
     def get_value(self, name):
-        if self.nodes[name]['type'] == 'int':
+        if len(name) > 3 and name[-3] == '[' and name[-1] == ']' and name[-2].isdigit() and 0 <= int(name[-2]) < 8:
+            index = int(name[-2])
+            name = name[:-3]
+            data = (self.node_data[name][0] >> index) & 1
+        elif self.nodes[name]['type'] == 'int':
             data = snap7.util.get_int(self.node_data[name], 0)
         elif self.nodes[name]['type'] == 'dint':
             data = snap7.util.get_dint(self.node_data[name], 0)
@@ -73,9 +77,9 @@ class S7data:
         elif self.nodes[name]['type'] == 'real':
             data = snap7.util.get_real(self.node_data[name], 0)
         elif self.nodes[name]['type'] == 'string':
-            data = self.node_data[name][2:2+int.from_bytes(self.node_data[name][1:2])].decode('gbk')
+            data = self.node_data[name][2:2+int.from_bytes(self.node_data[name][1:2])].decode('gbk', errors='replace')
         elif self.nodes[name]['type'] == 'wstring':
-            data = self.node_data[name][4:].decode(encoding='utf-16be')
+            data = self.node_data[name][4:].decode(encoding='utf-16be', errors='replace')
         else:
             warnings.warn('暂不支持的类型:' + self.nodes[name]['type'])
             if self.logger:
@@ -96,9 +100,9 @@ class S7data:
         elif self.nodes[name]['type'] == 'real':
             data = snap7.util.get_real(self.node_data[name], 0)
         elif self.nodes[name]['type'] == 'string':
-            data = self.node_data[name][2:2+int.from_bytes(self.node_data[name][1:2])].decode('gbk')
+            data = self.node_data[name][2:2+int.from_bytes(self.node_data[name][1:2])].decode('gbk', errors='replace')
         elif self.nodes[name]['type'] == 'wstring':
-            data = self.node_data[name][4:].decode(encoding='utf-16be')
+            data = self.node_data[name][4:].decode(encoding='utf-16be', errors='replace')
         else:
             warnings.warn('暂不支持的类型:' + self.nodes[name]['type'])
             if self.logger:

+ 117 - 0
utils/tcp_data.py

@@ -0,0 +1,117 @@
+import socket
+from utils.statepoint import *
+
+class Tcp_server(socket.socket):
+    def __init__(self, ip, port, backlog = 5, cli_max = 5, encoding = 'utf-8', point_t = Statepoint):
+        super().__init__()
+
+        self.backlog = backlog
+        self.cli_max = cli_max
+        self.encoding = encoding
+        self.point_t = point_t
+
+        self.run_flag = False
+        self.main_thread = None
+
+        self.convertor = lambda data: bool(data)
+        self.excite_action = lambda: None
+
+        self.clients = {}
+        self.datas = {}
+        self.points = {}
+        self.threads = {}
+
+        self.bind((ip, port))
+        self.listen(backlog)
+
+    def accept_action(self):
+        while self.run_flag:
+            if len(self.clients) < self.cli_max:
+                try:
+                    cli, addr = self.accept()
+                except OSError as e:
+                    print('客户端等待连接服务被打断:', str(e))
+                    self.run_flag = False
+                    return None
+                tmp_point = self.point_t()
+                tmp_point.set_convertor(self.convertor)
+                tmp_point.set_excite_action(lambda t=tmp_point: (t.set_state(False), self.excite_action()))
+                self.clients[addr] = cli
+                self.datas[addr] = b''
+                self.points[addr] = [tmp_point]
+                self.threads[addr] = threading.Thread(target=self.update_action, args=(addr,))
+                self.threads[addr].start()
+                print(f'{addr}客户端已连接')
+            else:
+                time.sleep(1)
+
+    def update_action(self, addr):
+        cli = self.clients[addr]
+        noerror = True
+        while self.run_flag:
+            try:
+                tmp = cli.recv(1024)
+            except Exception as e:
+                noerror = False
+                tmp = b''
+                print(f'{addr}异常断连:{e}')
+            if tmp:
+                self.datas[addr] = tmp
+                self.send(addr)
+            else:
+                break
+        if noerror:
+            print(f'{addr}正常断连')
+        self.clients.pop(addr, None)
+        self.datas.pop(addr, None)
+        self.points.pop(addr, None)
+        self.threads.pop(addr, None)
+
+    def send(self, addr):
+        if self.encoding:
+            data = self.datas[addr].decode(encoding=self.encoding, errors='replace')
+        else:
+            data = self.datas[addr]
+
+        for i in self.points[addr]:
+            i.inject(data)
+
+    def start(self):
+        if self.main_thread and self.main_thread.is_alive():
+            return None
+        
+        self.getsockname()
+        self.main_thread = threading.Thread(target=self.accept_action)
+        self.run_flag = True
+        self.main_thread.start()
+
+    def stop(self):
+        if self.main_thread == None:
+            return None
+        
+        self.run_flag = False
+        try:
+            addr = self.getsockname()
+            cli_tmp = socket.socket()
+            cli_tmp.connect(addr)
+            cli_tmp.close()
+        finally:
+            self.main_thread.join()
+            self.main_thread = None
+
+    def set_convertor(self, func):
+        self.convertor = func
+
+    def set_excite_action(self, func):
+        self.excite_action = func
+
+    def close(self):
+        self.stop()
+        return super().close()
+
+    def send_to_all_clients(self, bytes_data: bytes):
+        for i, j in self.clients.items():
+            try:
+                j.send(bytes_data)
+            except Exception as e:
+                print(f'{i}发送数据时产生异常:{e}')