From acca3cfaf70527530436eb65ae83c8e8e08d8ee1 Mon Sep 17 00:00:00 2001 From: "COT001\\DEV" <871066422@qq.com> Date: Tue, 31 Mar 2026 15:39:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A9=BA=E5=BC=80=E6=9C=89=E6=95=88=EF=BC=8C?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E8=B0=83=E7=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-1.2-debug.yaml | 5 + pcm_influxdb/pcm-influxdb-debug.py | 245 ++++++++++++++++++----------- 2 files changed, 154 insertions(+), 96 deletions(-) diff --git a/config-1.2-debug.yaml b/config-1.2-debug.yaml index 8507a21..97deb65 100644 --- a/config-1.2-debug.yaml +++ b/config-1.2-debug.yaml @@ -7,6 +7,11 @@ gps: port: /dev/ttyLP4 baudrate: 9600 timeout: 50 +# indicator(指示灯和蜂鸣器)配置 +indicator: + port: /dev/ttyUSB1 + baudrate: 9600 + timeout: 50 # breaker(断路器)配置 breaker: port: /dev/ttyUSB0 diff --git a/pcm_influxdb/pcm-influxdb-debug.py b/pcm_influxdb/pcm-influxdb-debug.py index e36f7b2..61cb73c 100644 --- a/pcm_influxdb/pcm-influxdb-debug.py +++ b/pcm_influxdb/pcm-influxdb-debug.py @@ -723,6 +723,110 @@ class LSDAQ: self.close() self.logger.info("Modbus Serial TCP Client stopped.") +class SerialClient: + """串口通信基础类""" + def __init__(self, port, baudrate, timeout, logger): + self.port = port + self.baudrate = baudrate + self.timeout = timeout + self.logger = logger + self.serial = None + + def open(self): + self.serial = serial.Serial(self.port, self.baudrate, timeout=self.timeout) + return self.serial.is_open + + def close(self): + if self.serial and self.serial.is_open: + self.serial.close() + + def exeCmd(self, cmd): + try: + info = '' + data = bytearray().fromhex(cmd[1]) + if cmd[4] == 1: + data += bytearray(checkValue(data).to_bytes(2, 'big')) + + retry = 0 + RETRYTIMES = int(cmd[6]) if len(cmd) >= 7 else 1 + + while retry < RETRYTIMES: + info += f"[{nowStr()}] Sent:{wordData2HexStr(data)}\n" + self.serial.write(data) + time.sleep(int(cmd[3])/1000.0) + recvData = self.serial.read(int(cmd[2])) + info += f"[{nowStr()}] Echo:{wordData2HexStr(recvData)}\n" + + rspLen = int(cmd[2]) + if len(recvData) >= rspLen: + if recvData[0:2] == bytearray().fromhex(cmd[1][0:4]): + recvData = recvData[0:rspLen] + if cmd[5] == 1: + crc = int.from_bytes(recvData[rspLen-2:rspLen], byteorder='big') + calc_value = checkValue(recvData[0:rspLen-2]) + if crc == calc_value: + return [True, recvData, info] + else: + return [True, recvData, info] + retry += 1 + return [False, None, info] + except Exception as e: + info += f"[{nowStr()}] Error in exeCmd: {str(e)}\n" + return [False, None, info] + +class IndicatorController: + """指示灯和蜂鸣器控制器(独立串口)""" + def __init__(self, config, logger): + self.logger = logger + self.client = SerialClient( + config.get('port', '/dev/ttyUSB1'), + config.get('baudrate', 9600), + config.get('timeout', 50)/1000.0, + logger + ) + self.cmdList = { + 'turnOnGreen': ['', "0105 0002 FF00", 8, 100, 1, 1, 3], + 'turnOffGreen': ['', "0105 0002 0000", 8, 100, 1, 1, 3], + 'turnOnRed': ['', "0105 0008 FF00", 8, 100, 1, 1, 3], + 'turnOffRed': ['', "0105 0000 0000", 8, 100, 1, 1, 3], + 'turnOnAlarm': ['', "0105 00A1 FF00", 8, 100, 1, 1, 3], + 'turnOffAlarm': ['', "0105 00A1 0000", 8, 100, 1, 1, 3], + } + self.alarm = 0 + self.client.open() + + def exe(self, name): + return self.client.exeCmd(self.cmdList[name]) + + def alarming(self, closed): + """报警时:红灯亮+蜂鸣器响,绿灯灭""" + if not self.alarm and closed == 0xF0: + self.exe('turnOffGreen') + self.exe('turnOnRed') + self.exe('turnOnAlarm') + self.alarm = 1 + + def unalarming(self, closed): + """解除报警:根据合闸状态控制指示灯""" + if self.alarm: + self.exe('turnOffRed') + self.exe('turnOffAlarm') + if closed == 0xF0: + self.exe('turnOnGreen') + else: + self.exe('turnOffGreen') + self.alarm = 0 + + def turnOffAll(self): + """关闭所有指示灯和蜂鸣器""" + self.exe('turnOffGreen') + self.exe('turnOffRed') + self.exe('turnOffAlarm') + self.alarm = 0 + + def turnOnGreen(self): + self.exe('turnOnGreen') + class Breaker: def __init__(self, config:dict, logger): # 加载配置参数 @@ -744,13 +848,21 @@ class Breaker: self.task_start_threshold = config.get('task_start_threshold', 2000) self.task_stop_threshold = config.get('task_stop_threshold', 2000) self.locked = 0 - self.closed = 0x0F # 0x0F:分闸 0xF0:合闸 - self.reasonForLastOpen = 15 # F:无 1:过流 2:漏电 3:过温 4:过载 5:过压 6:欠压 7:远程 8:模组 9:失压 A:锁扣 B:限电 0: 本地 + self.closed = 0x0F + self.reasonForLastOpen = 15 self.active_powers = [] self.duration = config.get('duration', 5) - self.alarm = 0 + self.active_power = 0 - self.active_power = 0 # 有功功率,单位:W + # 创建独立的串口客户端 + self.client = SerialClient(self.port, self.baudrate, self.timeout, logger) + + # 从配置中创建指示灯控制器(如果配置存在) + indicator_config = config.get('indicator', None) + if indicator_config: + self.indicator = IndicatorController(indicator_config, logger) + else: + self.indicator = None OVV = config.get('OVV', 275) UVV = config.get('UVV', 150) @@ -773,22 +885,15 @@ class Breaker: 'active_power': 0, 'load_status': 0 } - # 构建指令集 + # 构建指令集(仅断路器指令) self.cmdList = { - # 指令格式:指令描述,指令字符串,回复长度,超时时间,发送校验标志,接收校验标志,重试次数 'readAllDatas': ['', f"0204 0000 0027", 83, 300, 1, 1, 3], 'readOverLimitValues': ['', f"0203 0002 0006", 17, 200, 1, 1, 3], 'readOverLimitActionTime': ['', f"0203 0010 0006", 17, 200, 1, 1, 3], 'setOverLimitValues': ['', f"0210 0002 0006 0C {OVV:04X} {UVV:04X} {OCV:04X} {LCV:04X} {OTV:04X} {OPV:04X}", 8, 100, 1, 1, 3], 'setOverLimitActionTime': ['', f"0210 0010 0006 0C {OVT:04X} {UVT:04X} {OCT:04X} {LCT:04X} {OTT:04X} {OPT:04X}", 8, 100, 1, 1, 3], 'closeBreaker': ['', f"0205 0001 ff00", 8, 100, 1, 1, 3], - 'openBreaker': ['', f"0205 0001 0000", 8, 100, 1, 1, 3], - 'turnOnGreen': ['', f"0105 0002 FF00", 8, 100, 1, 1, 3], - 'turnOffGreen': ['', f"0105 0002 0000", 8, 100, 1, 1, 3], - 'turnOnRed': ['', f"0105 0008 FF00", 8, 100, 1, 1, 3], - 'turnOffRed': ['', f"0105 0000 0000", 8, 100, 1, 1, 3], - 'turnOnAlarm': ['', f"0105 00A1 FF00", 8, 100, 1, 1, 3], - 'turnOffAlarm': ['', f"0105 00A1 0000", 8, 100, 1, 1, 3] + 'openBreaker': ['', f"0205 0001 0000", 8, 100, 1, 1, 3] } self.optFlag = 0 @@ -797,51 +902,12 @@ class Breaker: def update_config(self): pass - def exeCmd(self, cmdName) -> list: # type: ignore - try: - info = '' - self.logger.info("==-==") - self.logger.info(cmdName) - cmd = self.cmdList.get(cmdName, None) - if cmd is None: - return [False, None, f"Command {cmdName} not found in cmdList."] - retry = 0 - data = bytearray().fromhex(cmd[1]) - if (cmd[4] == 1): - data += bytearray(checkValue(data[0:]).to_bytes(2, 'big')) - if len(cmd) >= 7: - RETRYTIMES = int(cmd[6]) - else: - RETRYTIMES = 1 - while (retry < RETRYTIMES): - info += f"[{nowStr()}] Sent:{wordData2HexStr(data)}\n" - recvData = bytearray() - self.serial.write(data) - time.sleep(int(cmd[3])/1000.0) - recvData = self.serial.read(int(cmd[2])) - info += (f"[{nowStr()}] Echo:{wordData2HexStr(recvData)}\n") - rspLen = int(cmd[2]) - if len(recvData) >= rspLen: - if recvData[0:2] == bytearray().fromhex(cmd[1][0:4]): - # info += f"[{nowStr()}] Echo:{wordData2HexStr(recvData[0:rspLen])}\n" - recvData = recvData[0:rspLen] - if (cmd[5] == 1): - crc = int.from_bytes(recvData[rspLen-2:rspLen], byteorder='big') - calc_value = checkValue(recvData[0:rspLen-2]) - # info += f"{crc:04X}, {calc_value:04X}\n" - if crc == calc_value: - # self.logger.info(info) - return [True, recvData, info] - else: - # self.logger.info(info) - return [True, recvData, info] - retry += 1 - # self.logger.info(info) - return [False, None, info] - except Exception as e: - info += f"[{nowStr()}] Error in exeCmd({cmd}): {str(e)}\n" # type: ignore - # self.logger.info(info) - return [False, None, info] + def exeCmd(self, cmdName) -> list: + cmd = self.cmdList.get(cmdName, None) + if cmd is None: + return [False, None, f"Command {cmdName} not found in cmdList."] + self.logger.info(f"==-=={cmdName}") + return self.client.exeCmd(cmd) def parseData(self, cmdName, rawData): try: @@ -865,7 +931,7 @@ class Breaker: self.reg_values['locked'] = self.locked self.reg_values['closed'] = self.closed self.reg_values['reasonForLastOpen'] = self.reasonForLastOpen - self.reg_values['alarm'] = self.alarm + self.reg_values['alarm'] = self.indicator.alarm if self.indicator else 0 self.reg_values['active_power'] = self.active_power self.reg_values['load_status'] = self.load_status @@ -898,61 +964,44 @@ class Breaker: def alarming(self): """报警时:红灯亮+蜂鸣器响,绿灯灭""" - if not self.alarm and self.closed & 0xFF == 0xF0: - self.exeCmd('turnOffGreen') - self.exeCmd('turnOnRed') - self.exeCmd('turnOnAlarm') - self.alarm = 1 # 设置报警标志 + if self.indicator: + self.indicator.alarming(self.closed & 0xFF) def unalarming(self): """解除报警:根据合闸状态控制指示灯""" - if self.alarm: - self.exeCmd('turnOffRed') - self.exeCmd('turnOffAlarm') - # 如果是合闸状态,恢复绿灯 - if self.closed & 0xFF == 0xF0: - self.exeCmd('turnOnGreen') - else: - self.exeCmd('turnOffGreen') - self.alarm = 0 # 清除报警标志 + if self.indicator: + self.indicator.unalarming(self.closed & 0xFF) def open(self): """打开串口连接""" - self.serial = serial.Serial(self.port, self.baudrate, timeout=self.timeout) - if not self.serial.is_open: - self.errorCode = 0x0001 - return -1 - else: + if self.client.open(): self.errorCode = 0 return 0 + else: + self.errorCode = 0x0001 + return -1 def close(self): - if self.serial.is_open: - self.serial.close() + self.client.close() def run(self): """主运行循环""" try: while True: - # self.logger.info(f"optFlag={self.optFlag}") match self.optFlag: case 0: if self.open() == 0: ret0 = self.exeCmd('openBreaker') # 初始化时关闭所有指示灯 - self.exeCmd('turnOffGreen') - self.exeCmd('turnOffRed') - self.exeCmd('turnOffAlarm') - # self.logger.info(f"setOverLimitValues ret: {ret0}") + if self.indicator: + self.indicator.turnOffAll() ret1 = self.exeCmd('setOverLimitValues') - # self.logger.info(f"setOverLimitValues ret: {ret1}") ret2 = self.exeCmd('readOverLimitValues') self.logger.info(f"readOverLimitValues ret: {ret2}") ret3 = self.exeCmd('setOverLimitActionTime') - # self.logger.info(f"setOverLimitValues ret: {ret3}") ret4 = self.exeCmd('readOverLimitActionTime') self.logger.info(f"readOverLimitActionTime ret: {ret4}") - if ret0[0] and ret1[0]:# and ret3[0] + if ret0[0] and ret1[0]: self.optFlag = 1 continue self.optFlag = -1 @@ -970,10 +1019,8 @@ class Breaker: ret = self.exeCmd('openBreaker') if ret[0]: # 分闸成功后,关闭所有指示灯 - self.exeCmd('turnOffGreen') - self.exeCmd('turnOffRed') - self.exeCmd('turnOffAlarm') - self.alarm = 0 # 清除报警标志 + if self.indicator: + self.indicator.turnOffAll() self.optFlag = 1 continue self.optFlag = -1 @@ -981,9 +1028,9 @@ class Breaker: case 3: ret = self.exeCmd('closeBreaker') if ret[0]: - # 合闸成功后,点亮绿灯(如果没有报警) - # if not self.alarm: - self.exeCmd('turnOnGreen') + # 合闸成功后,点亮绿灯 + if self.indicator: + self.indicator.turnOnGreen() self.optFlag = 1 continue self.optFlag = -1 @@ -1700,7 +1747,13 @@ class ModbusGateway: self.logger.info(f"Local modbusTCP service starts, IP={self.host}, port={self.port}") self.gps = GPS(self.config_manager.config['gps'], self.logger) - self.breaker = Breaker(self.config_manager.config['breaker'], self.logger) + + # 将 indicator 配置添加到 breaker 配置中 + breaker_config = self.config_manager.config['breaker'].copy() + if 'indicator' in self.config_manager.config: + breaker_config['indicator'] = self.config_manager.config['indicator'] + self.breaker = Breaker(breaker_config, self.logger) + self.lsdaq = LSDAQ(self.config_manager.config['lsdaq'], self.logger) self.hsdaq = HSDAQ(self.config_manager.config['hsdaq'], self.logger)