import array import asyncio import sys import time import keyboard import serial import struct import requests from queue import Queue import threading import argparse from pymodbus.exceptions import ModbusIOException from pymodbus.pdu import ModbusRequest from pymodbus.server import StartTcpServer, StartAsyncTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext from pymodbus.client import ModbusSerialClient as ModbusClient print_condition = False all_plc_recv = 0 serverUrl = 'http://106.52.71.204:5050' # 定义颜色代码 RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' BLUE = '\033[34m' MAGENTA = '\033[35m' CYAN = '\033[36m' WHITE = '\033[37m' RESET = '\033[m' def bcd_coder_array(value=0, lenght=4): return int_to_bcd(value, lenght) def int_to_bcd(number=0, length=5): bcd_array = [] try: int_number = int(round(number)) # 使用round函数四舍五入,然后转成整数 while int_number > 0: bcd = int_number % 100 bcd_array.insert(0, (bcd // 10) << 4 | (bcd % 10)) int_number = int_number // 100 except: pass while len(bcd_array) < length: bcd_array.insert(0, 0) return bcd_array def int_to_ascii_array(number, length): # 将整数转换为ASCII码字符串 ascii_str = str(number) # 获取ASCII码字符串的长度 ascii_str_length = len(ascii_str) # 如果ASCII码字符串的长度小于指定长度,则在前面补0 if ascii_str_length < length: ascii_str = '0' * (length - ascii_str_length) + ascii_str # 将ASCII码字符串转换为ASCII码数组 ascii_array = [ord(c) for c in ascii_str] return ascii_array def str_to_ascii_array(str, length): # 获取ASCII码字符串的长度 ascii_str_length = len(str) # 如果ASCII码字符串的长度小于指定长度,则在前面补0 if ascii_str_length < length: ascii_str = '0' * (length - ascii_str_length) + str # 将ASCII码字符串转换为ASCII码数组 ascii_array = [ord(c) for c in str] return ascii_array def hex_to_ascii_arr(hex_num, lenth): str_num = hex(hex_num)[2:].upper().rjust(lenth, '0') array = [ord(c) for c in str_num] return array def convert_ascii_to_num(ascii_val): if ord('0') <= ascii_val <= ord('9'): return ascii_val - ord('0') elif ord('A') <= ascii_val <= ord('F'): return ascii_val - ord('A') + 10 elif ord('a') <= ascii_val <= ord('f'): return ascii_val - ord('a') + 10 else: return 0 def ascii_array_to_word(ascii_array): if len(ascii_array) != 4: return 0 return convert_ascii_to_num(ascii_array[0]) * 0x1000 + \ convert_ascii_to_num(ascii_array[1]) * 0x0100 + \ convert_ascii_to_num(ascii_array[2]) * 0x010 + \ convert_ascii_to_num(ascii_array[3]) def date_to_bcd(date_str): # 0:06:20.585233 # condition_print("date=",date_str,type(date_str)) # 移除日期中的"-"字符 date_str = date_str.replace("-", "") # 按照两位数字进行分割 date_parts = [date_str[i:i + 2] for i in range(0, len(date_str), 2)] # 将每个部分转换为十六进制数 bcd_array = [int(part, 16) for part in date_parts] return bcd_array def time_to_bcd(time_str): # 10:06:20.585233 time_str = time_str.split('.')[0] # 去除掉后面的毫秒 time_parts = time_str.split(':') # 按":"分割 bcd = [int(part) for part in time_parts] # 分别转为整数 bcd = [int(str(i), 16) for i in bcd] # 分别转为16进制 return bcd def conv_to_plc_address(type_char, value): # 检查type_char是否为'M'或'D',value是否为整数 if type_char not in ('M', 'D') or not isinstance(value, int): raise ValueError("Type must be 'M' or 'D' and value must be an integer.") # 使用str.format()方法格式化字符串 formatted = "{type}{value:04d}".format(type=type_char, value=value) return formatted def condition_print(*args, **kwargs): global print_condition # 使用全局变量 message = ' '.join(str(arg) for arg in args) if kwargs: message += ', ' + ', '.join("{}={}".format(key, value) for key, value in kwargs.items()) if print_condition: print(message) class MitsubishiPLC: def __init__(self, plc_port, comm_config): global print_condition, api_condition, all_plc_recv port_opened = False print("DevicePLC init", plc_port, comm_config) # cyx self.event_format_long = False try: self.ser = serial.Serial(port=plc_port, baudrate=comm_config['baudrate'], parity=comm_config['parity'], bytesize=comm_config['bytesize'], stopbits=comm_config['stopbits'], timeout=1, rtscts=False) #condition_print(f"DevicePLC open {plc_port} success") # cyx print(f"DevicePLC open {plc_port} success") # cyx except: condition_print("\033[91m {}\033[00m".format("plc " + plc_port + " open error")) try: self.ser = serial.Serial(port="com1", baudrate=comm_config['baudrate'], parity=comm_config['parity'], bytesize=comm_config['bytesize'], stopbits=comm_config['stopbits'], timeout=2, rtscts=False) except: print("\033[91m {}\033[00m".format("plc " + "com1" + " open error")) else: port_opened = True print("\033[92m {}\033[00m".format("plc " + "com1" + " open success")) else: port_opened = True print("\033[92m {}\033[00m".format("plc " + plc_port + " open success--1")) finally: pass if port_opened: self.link_down_timer = threading.Timer(5.0, self.link_down_timeout) # 创建一个5秒后执行的定时器 # self.link_down_timer.start() self.link_down_event = threading.Event() if self.port_is_opened(): # self.recv_thread.start(); pass def port_is_opened(self): if hasattr(self, 'ser') and self.ser: return True else: return False return False def get_connect_state(self): return self.port_is_opened() def link_down_timeout(self): self.link_down_event.set() self.link_down_timer = threading.Timer(5.0, self.link_down_timeout) # 创建一个5秒后执行的定时器 self.link_down_timer.start() def reset_link_down_timer(self): if self.link_down_timer: self.link_down_timer.cancel() self.link_down_timer = threading.Timer(5.0, self.link_down_timeout) # 创建一个5秒后执行的定时器 self.link_down_timer.start() def send_heartBit(self): if self.port_is_opened(): pass def crc16_ccitt(self, data: bytearray): # CRC16/CCITT crcval = 0x0000 for c in data: q = (crcval ^ c) & 0x0F crcval = (crcval >> 4) ^ (q * 0o10201) q = (crcval ^ (c >> 4)) & 0x0F crcval = (crcval >> 4) ^ (q * 0o10201) return crcval def plc_read_frame(self, command, plc_no=0x01, pc_no=0xFF, address='D0160', len=0x02): frame = [] frame.append(0x05) frame.extend(hex_to_ascii_arr(plc_no, 2)) frame.extend(hex_to_ascii_arr(pc_no, 2)) frame.extend([ord(c) for c in command]) # word read command frame.append(0x30) frame.extend(str_to_ascii_array(address, 5)) # eg.D0160 frame.extend(int_to_ascii_array(len, 2)) checksum = sum(frame[1:]) & 0xFF frame.extend(hex_to_ascii_arr(checksum, 2)) # condition_print("frame=",frame,type(frame)) condition_print('frame intent to read plc: [', ', '.join('{:02x}'.format(x) for x in frame), ']') return frame def plc_write_frame(self, command, plc_no=0x01, pc_no=0xFF, address='D0160', date_len=0x02, data=[]): frame = [0x05] frame.extend(hex_to_ascii_arr(plc_no, 2)) frame.extend(hex_to_ascii_arr(pc_no, 2)) frame.extend([ord(c) for c in command]) # word write command bit write command frame.append(0x30) frame.extend(str_to_ascii_array(address, 5)) # eg.D0160 frame.extend(int_to_ascii_array(date_len, 2)) data_index = 0 while date_len > 0: value = data[data_index] data_index = data_index + 1 if command == 'BW': arr_len = 1 else: arr_len = 4 word_ascii_array = hex_to_ascii_arr(value, arr_len) # condition_print(word,'[', ', '.join('{:02x}'.format(x) for x in word_ascii_array), ']') frame.extend(word_ascii_array) date_len = date_len - 1 checksum = sum(frame[1:]) & 0xFF frame.extend(hex_to_ascii_arr(checksum, 2)) condition_print('frame intend to write plc: [', ', '.join('{:02x}'.format(x) for x in frame), ']') return frame # Symbol name Description Code (hexadecimal) # STX Start of Text 02H # ETX End of Text 03H # EOT End of Transmission 04H # ENQ Enquiry 05H # ACK Acknowledge 06H # LF Line Feed 0AH # CL Clear 0CH # CR Carriage Return ODH # NAK Negative Acknowledge 15H def frame_is_valid(self, frame): # 检查第一个字节是否为控制代码 control_code = frame[0] ETX: bool = False if control_code not in [0x05, 0x02, 0x03, 0x4, 0x06, 0x15]: condition_print("First byte is not a control code.") return False # 检查最后两个字节是否为校验和代码 hex_string = ''.join(chr(char) for char in frame[-2:]) # 然后,你可以使用int函数将这个16进制的字符串转换为一个整数,指定基数为16 checksum = int(hex_string, 16) if frame[0] == 0x02 and frame[-3] != 0x03: return False if frame[0] == 0x06 or frame[0] == 0x15: return True if frame[-3] == 0x03: ETX = True end_position = -3 if ETX else -2 checksum_calc = sum(frame[1:-2]) & 0xFF # 计算校验和并取第八位 if checksum != checksum_calc: condition_print("Last two bytes do not match checksum.", checksum, checksum_calc, frame[-2] - 0x30, frame[-1] - 0x30) return False # 创建一个包含所有有效字符的字符串 valid_chars = '0123456789abcdefABCDEF' # 定义一个函数来检查bytes对象中的每个元素 for i in frame[1:end_position]: char = chr(i) if char not in valid_chars: condition_print(f'{char} ({i}) is not a valid character.') # 在此处添加其他的逻辑处理代码,例如抛出异常 # raise ValueError(f'{char} ({i}) is not a valid character.') return False else: # condition_print(f'{char} ({i}) is a valid character.') pass return True def plc_write(self, command='WW', plc_no=0x01, pc_no=0xFF, address='D0160', len=0x02, data=0x00000000): frame = self.plc_write_frame(command, plc_no, pc_no, address, len, data) timeout = 2 # 设置超时时间为2秒 if hasattr(self, 'ser') and self.ser: self.ser.write(frame) frame = self.ser.read(1); time.sleep(0.06) start_time = time.time() # 记录开始时间 while self.ser.in_waiting: # 检查是否超时 if time.time() - start_time > timeout: condition_print("serial com read Time's out!--", time) return {"status": "error", "msg": "serial com read Time's out!"} break frame += self.ser.read(self.ser.in_waiting) # 读取剩余的数据 if self.frame_is_valid(frame) == False: condition_print("recv frame is not valid", '[', ', '.join('{:02x}'.format(x) for x in frame), ']') return {"status": "error", "msg": "recv frame is not valid"} else: condition_print("--write data to plc success", "resp", ' [', ', '.join('{:02x}'.format(x) for x in frame), ']') # condition_print('recv pld frame: [', ', '.join('{:02x}'.format(x) for x in frame), ']') return {"status": "success", "data": frame} else: return {"status": "error", "msg": "serial com is not open!"} pass def plc_read(self, command, plc_no=0x01, pc_no=0xFF, address='D0160', len=0x01): frame = self.plc_read_frame(command, plc_no, pc_no, address, len) timeout = 2 # 设置超时时间为2秒 if hasattr(self, 'ser') and self.ser: self.ser.write(frame) frame = self.ser.read(1); time.sleep(0.06) start_time = time.time() # 记录开始时间 while self.ser.in_waiting: # 检查是否超时 if time.time() - start_time > timeout: condition_print("serial com read Time's out!--", time) return {"status": "error", "msg": "serial com read Time's out!"} break frame += self.ser.read(self.ser.in_waiting) # 读取剩余的数据 if self.frame_is_valid(frame) == False: condition_print("recv frame is not valid") return {"status": "error", "msg": "recv frame is not valid"} else: condition_print('--read data return: [', ', '.join('{:02x}'.format(x) for x in frame), ']') return {"status": "success", "data": frame} else: return {"status": "error", "msg": "serial com is not open!"} def plc_read_words(self, plc_no=0x01, pc_no=0xFF, address='D0160', length=0x01): resp = {'status': 'error'} result = self.plc_read('WR', plc_no, pc_no, address, length) if result and 'status' in result and result['status'] == 'success' and 'data' in result: frame = result['data'] data_len = (len(frame) - 8) / 4 if data_len == length: value = [ascii_array_to_word(frame[5 + 4 * i:5 + 4 * (i + 1)]) for i in range(length)] resp = {'status': 'success', 'length': data_len, 'data': value} return resp def plc_read_bits(self, plc_no=0x01, pc_no=0xFF, address='D0160', length=0x01): resp = {'status': 'error'} result = self.plc_read('BR', plc_no, pc_no, address, length) condition_print('plc_read_bits result', result) # cyx if 'status' in result and result['status'] == 'success' and 'data' in result: frame = result['data'] data_len = (len(frame) - 8) / 1 if data_len == length: value = [(frame[5 + 1 * i] - 0x30) for i in range(length)] resp = {'status': 'success', 'length': data_len, 'data': value} return resp def plc_write_word(self, plc_no=0x01, pc_no=0xFF, address='D0160', data=0x0000): return self.plc_write('WW', plc_no, pc_no, address, 0x01, list([data])) def plc_write_dword(self, plc_no=0x01, pc_no=0xFF, address='D0160', data=0x00000000): return self.plc_write('WW', plc_no, pc_no, address, 0x02, list([data >> 16, data & 0xFFFF])) def plc_write_words(self, plc_no=0x01, pc_no=0xFF, start_addr='D0160', length=0x04, data=[]): return self.plc_write('WW', plc_no, pc_no, start_addr, length, data) def plc_write_bits(self, plc_no=0x01, pc_no=0xFF, address='D0160', length=0x01, data=[]): return self.plc_write('BW', plc_no, pc_no, address, length, data) from pymodbus.datastore import ModbusSequentialDataBlock class CustomDataBlock(ModbusSequentialDataBlock): def __init__(self, data_type, plc_devices=None, plc_address=0x01, *args, **kwargs): if plc_devices is None: plc_devices = [] self.plc_address = plc_address # PLC的 站地址 如:1 3 4等 self.data_type = data_type # 区分coil input_register holding_register self.address = 0 # 这个是client 进行读写的寄存器地址 self.plc_devices = plc_devices # plc 设备的列表,每一台机器对应一个 # plc_devices 中元素中的device 是 MitsubishiPLC的实例,用于和物理PLC的通讯 # 机器的COM口相同的话,会共享相同的MitsubishiPLC的实例 self.plc_comm_lock = threading.Lock() # 具体访问MitsubishiPLC的保护锁 super().__init__(*args, **kwargs) def getValues(self, address, count): plc_device_obj = [item for item in self.plc_devices if item["slave"] == self.plc_address] # 按照站地址进行匹配 if len(plc_device_obj) and plc_device_obj[0].get('device'): plc_device = plc_device_obj[0].get('device') # 得到访问物理PLC的MitsubishiPLC的实例 if self.data_type == 'coil': # 处理线圈数据类型的逻辑 print('read coils address=', address) # modbus client 要读的寄存器地址 if plc_device: with self.plc_comm_lock: # 保护访问资源,执行完缩进的代码块释放资源 resp = plc_device.plc_read_bits(self.plc_address, 0xFF, conv_to_plc_address('M', address), count) if resp and resp.get('status') == 'success' and resp.get('data') and resp.get('length'): return resp['data'] else: raise ModbusIOException("Modbus server read coil failed--PLC response error") else: if address == 18: return [True, False, True] else: raise ModbusIOException("Modbus server read coil failed--PLC not connected") pass elif self.data_type == 'input_register': print('read input register address=', address) # 处理输入寄存器数据类型的逻辑 if plc_device: with self.plc_comm_lock: # 保护访问资源,执行完缩进的代码块释放资源 resp = plc_device.plc_read_words(self.plc_address, 0xFF, conv_to_plc_address('D', address), count) if resp and resp.get('status') == 'success' and resp.get('data') and resp.get('length'): return resp['data'] else: raise ModbusIOException("Modbus server read input register failed--PLC response error") else: if address == 185: return [88, 89, 100] else: raise ModbusIOException("Modbus server read input register failed--PLC not connected") pass elif self.data_type == 'holding_register': # 处理保持寄存器数据类型的逻辑 #print('read holding register address=', address,count) if address == 10: # 10 作为特殊的寄存器,用来返回PLC(物理)的连接状态 if plc_device: return [plc_device.get_connect_state()] else: return [0] if plc_device: with self.plc_comm_lock: # 保护访问资源,执行完缩进的代码块释放资源 resp = plc_device.plc_read_words(self.plc_address, 0xFF, conv_to_plc_address('D', address), count) if resp and resp.get('status') == 'success' and resp.get('data') and resp.get('length'): return resp['data'] else: raise ModbusIOException("Modbus server read holding register failed--PLC response error") else: if address == 185: return [88, 89, 100] else: raise ModbusIOException("Modbus server read holding register failed--PLC not connected") pass def setValues(self, address, values): plc_device_obj = [item for item in self.plc_devices if item["slave"] == self.plc_address] if len(plc_device_obj) and plc_device_obj[0].get('device'): plc_device = plc_device_obj[0].get('device') if self.data_type == 'coil': print("modbus server set coil values", address, values) # cyx # 处理线圈数据类型的逻辑 if plc_device: with self.plc_comm_lock: # 保护访问资源,执行完缩进的代码块释放资源 resp = plc_device.plc_write_bits(self.plc_address, 0xFF, conv_to_plc_address('M', address), 0x01, values) if resp and resp.get('status') == 'success' and resp.get('data') and resp['data'][0] == 0x06: # ACK pass else: raise ModbusIOException("Modbus server set coil failed--PLC response error") else: # 如果读取不成功,抛出一个Modbus异常 raise ModbusIOException("Modbus server set coil failed--PLC not connected") elif self.data_type == 'input_register': # 处理输入寄存器数据类型的逻辑 pass elif self.data_type == 'holding_register': print("modbus server set holding register address=", address, values, self.plc_address) # cyx if plc_device: with self.plc_comm_lock: # 保护访问资源,执行完缩进的代码块释放资源 resp = plc_device.plc_write_word(self.plc_address, 0xFF, conv_to_plc_address('D', address), values[0]) if resp and resp.get('status') == 'success' and resp.get('data') and resp['data'][0] == 0x06: # ACK pass else: raise ModbusIOException("Modbus server set holding register failed--PLC response error") # 处理保持寄存器数据类型的逻辑 else: # 如果读取不成功,抛出一个Modbus异常 raise ModbusIOException("Modbus server set holding register failed--PLC not connected") exit_flag = False class ModbusPlcServer: def __init__(self, host='127.0.0.1', port=5020, machines_config=None, timeout=3): if machines_config is None: machines_config = [] self.server_task = None self.modbus_server = None self.host = host self.port = port self.is_running = True # 服务器运行状态的标志变量 self.plc_devices = [] self.machines_config = machines_config try: for config in machines_config: if config.get('type') == 'mhi' and config.get('com').startswith("com") and config.get('plcAddress'): # 使用列表推导式生成列表 # 按照com 串口进行区分的,如果1个RS485上有几个PLC, 实际因为只用一个COM口,所以也就创建1个MitsubishiPLC # modbus client 访问时会传入站地址由于区分是哪个PLC,如:1 3 7 8等 device_matched = [device_json['device'] for device_json in self.plc_devices if device_json["com"] == config.get('com')] # 判断列表是否包含元素,并据此取第一个元素或返回None device_have_created = device_matched[0] if device_matched else None if device_have_created is None: device_have_created = MitsubishiPLC(config.get('com'), config.get('comm_config')) self.plc_devices.append({'slave': int(config.get('plcAddress')), 'com': config.get('com'), 'device': device_have_created}) except Exception as error: pass async def run_server(self, ): global serverUrl """ block = ModbusSequentialDataBlock(0x00, [0] * 0xff) store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) context = ModbusServerContext(slaves=store, single=True) identity = ModbusDeviceIdentification() identity.VendorName = 'HYSZ Tech' identity.ProductCode = 'DTMGT-01' identity.VendorUrl = 'http://www.hysz.com' identity.ProductName = 'DTMGT PLC Server' identity.ModelName = 'DTMGT PLC Server' identity.MajorMinorRevision = '1.0' """ stores = {} # 创建 Modbus 数据块和上下文 context = None for config in self.machines_config: coil_block = CustomDataBlock('coil', self.plc_devices, int(config.get('plcAddress')), 0, [False] * 0x100) holding_register_block = CustomDataBlock('holding_register', self.plc_devices, int(config.get('plcAddress')), 0, [0] * 0x100) input_coil_block = CustomDataBlock('input_colid', self.plc_devices, int(config.get('plcAddress')), 0, [False] * 0x100) input_register_block = CustomDataBlock('input_register', self.plc_devices, int(config.get('plcAddress')), 0, [0] * 0x100) store = ModbusSlaveContext( di=input_coil_block, co=coil_block, hr=holding_register_block, ir=input_register_block, zero_mode=True ) stores[int(config.get('plcAddress'))] = store context = ModbusServerContext(slaves=stores, single=False) # 每个机器的modbus server 都有自己的上下文context (stores) """ coil_block = CustomDataBlock('coil', self.plc_devices, 1, 0, [False] * 0x100) holding_register_block = CustomDataBlock('holding_register', self.plc_devices, 1, 0, [0] * 0x100) input_coil_block = CustomDataBlock('input_colid', self.plc_devices, 1, 0, [False] * 0x100) input_register_block = CustomDataBlock('input_register', self.plc_devices, 1, 0, [0] * 0x100) store = ModbusSlaveContext( di=input_coil_block, co=coil_block, hr=holding_register_block, ir=input_register_block, zero_mode=True ) context = ModbusServerContext(slaves=store, single=True) """ # requests.post(serverUrl + '/modbusServerStarted', params={}, json={}) print(f"Modbus server starting on {self.host}:{self.port}") try: self.modbus_server = await StartAsyncTcpServer(context=context, address=(self.host, self.port)) print(f"Modbus server started on {self.host}:{self.port}") self.modbus_server.serve_forever() except Exception as error: print("Failed to start the server:", error) async def stop_server(self): global exit_flag if self.modbus_server: self.modbus_server.shutdown() self.modbus_server.serve_done.set_result(True) self.server_task.cancel() # 取消任务 try: await self.server_task # 等待任务结束 except: pass finally: await self.modbus_server.wait_closed() exit_flag = True def getPlcConfig(serverUrl, params): result = {} query_string = params response = requests.get(serverUrl + '/get_machine_config', params=query_string, json={}) if response.status_code == 200: result = response.json() else: print('请求失败') pass return result class ModbusServer: def __init__(self, machines_config): self.modbus_loop = None self.modbus_server = None self.machines_config = machines_config def run_modbus_server(self): print("mhi_plc create modbus server") self.modbus_loop = asyncio.new_event_loop() asyncio.set_event_loop(self.modbus_loop) self.modbus_server = ModbusPlcServer('127.0.0.1', 5020, self.machines_config) self.modbus_loop.run_until_complete(self.modbus_server.run_server()) print("mhi_plc create modbus over") def stop_modbus_server(self): print("stop_modbus_server") try: if self.modbus_server: self.modbus_server.stop_server() print("modbus server stop ") if hasattr(self, 'modbus_loop') and self.modbus_loop.is_running(): self.modbus_loop.call_soon_threadsafe(self.modbus_loop.stop) print("modbus server event loop exit") except Exception as error: print("stop_modbus_servererror:", error) modbus_server = None # 声明全局变量 exit_flag = False def get_machine_configs(serverUrl='127.0.0.1:5050'): global modbus_server, exit_flag config_got = False while not config_got: try: resp = getPlcConfig(serverUrl, {}) if resp.get('status') == 'success': config_got = True machine_config = resp.get('data', []) modbus_server = ModbusServer(machine_config) modbus_server.modbus_thread = threading.Thread(target=modbus_server.run_modbus_server) modbus_server.modbus_thread.start() except Exception as error: pass time.sleep(3) pass def main(): global modbus_server # 引用全局变量 global exit_flag get_machine_configs_thread = threading.Thread(target=get_machine_configs, args=(args.serverUrl,)) get_machine_configs_thread.start() def stop_server(): # 停止服务器的函数 global exit_flag if modbus_server: modbus_server.stop_modbus_server() if modbus_server and modbus_server.modbus_thread: modbus_server.modbus_thread.join() get_machine_configs_thread.join() exit_flag = True try: while not exit_flag: if keyboard.is_pressed('z'): #stop_server() pass time.sleep(0.1) except KeyboardInterrupt: print("用户强制退出(Ctrl+C)") stop_server() sys.exit(0) if __name__ == "__main__": # 创建解析器 parser = argparse.ArgumentParser(description="My modbus and plc Description") # 添加参数 parser.add_argument('--serverUrl', default="http://127.0.0.1:5050", help="Server URL") # 添加参数 parser.add_argument('--host', default="127.0.0.1", help="Seqrver URL") # 添加参数 parser.add_argument('--port', default=5020, help="Server URL") # 解析参数 args = parser.parse_args() serverUrl = args.serverUrl main()