空开有效,需要调灯

main
COT001\DEV 2026-03-31 15:39:35 +08:00
parent 8c57551006
commit acca3cfaf7
2 changed files with 154 additions and 96 deletions

View File

@ -7,6 +7,11 @@ gps:
port: /dev/ttyLP4 port: /dev/ttyLP4
baudrate: 9600 baudrate: 9600
timeout: 50 timeout: 50
# indicator指示灯和蜂鸣器配置
indicator:
port: /dev/ttyUSB1
baudrate: 9600
timeout: 50
# breaker断路器配置 # breaker断路器配置
breaker: breaker:
port: /dev/ttyUSB0 port: /dev/ttyUSB0

View File

@ -723,6 +723,110 @@ class LSDAQ:
self.close() self.close()
self.logger.info("Modbus Serial TCP Client stopped.") 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: class Breaker:
def __init__(self, config:dict, logger): def __init__(self, config:dict, logger):
# 加载配置参数 # 加载配置参数
@ -744,13 +848,21 @@ class Breaker:
self.task_start_threshold = config.get('task_start_threshold', 2000) self.task_start_threshold = config.get('task_start_threshold', 2000)
self.task_stop_threshold = config.get('task_stop_threshold', 2000) self.task_stop_threshold = config.get('task_stop_threshold', 2000)
self.locked = 0 self.locked = 0
self.closed = 0x0F # 0x0F分闸 0xF0合闸 self.closed = 0x0F
self.reasonForLastOpen = 15 # F无 1过流 2漏电 3过温 4过载 5过压 6欠压 7远程 8模组 9失压 A锁扣 B限电 0: 本地 self.reasonForLastOpen = 15
self.active_powers = [] self.active_powers = []
self.duration = config.get('duration', 5) 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) OVV = config.get('OVV', 275)
UVV = config.get('UVV', 150) UVV = config.get('UVV', 150)
@ -773,22 +885,15 @@ class Breaker:
'active_power': 0, 'active_power': 0,
'load_status': 0 'load_status': 0
} }
# 构建指令集 # 构建指令集(仅断路器指令)
self.cmdList = { self.cmdList = {
# 指令格式:指令描述,指令字符串,回复长度,超时时间,发送校验标志,接收校验标志,重试次数
'readAllDatas': ['', f"0204 0000 0027", 83, 300, 1, 1, 3], 'readAllDatas': ['', f"0204 0000 0027", 83, 300, 1, 1, 3],
'readOverLimitValues': ['', f"0203 0002 0006", 17, 200, 1, 1, 3], 'readOverLimitValues': ['', f"0203 0002 0006", 17, 200, 1, 1, 3],
'readOverLimitActionTime': ['', f"0203 0010 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], '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], '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], 'closeBreaker': ['', f"0205 0001 ff00", 8, 100, 1, 1, 3],
'openBreaker': ['', f"0205 0001 0000", 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]
} }
self.optFlag = 0 self.optFlag = 0
@ -797,51 +902,12 @@ class Breaker:
def update_config(self): def update_config(self):
pass pass
def exeCmd(self, cmdName) -> list: # type: ignore def exeCmd(self, cmdName) -> list:
try:
info = ''
self.logger.info("==-==")
self.logger.info(cmdName)
cmd = self.cmdList.get(cmdName, None) cmd = self.cmdList.get(cmdName, None)
if cmd is None: if cmd is None:
return [False, None, f"Command {cmdName} not found in cmdList."] return [False, None, f"Command {cmdName} not found in cmdList."]
retry = 0 self.logger.info(f"==-=={cmdName}")
data = bytearray().fromhex(cmd[1]) return self.client.exeCmd(cmd)
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 parseData(self, cmdName, rawData): def parseData(self, cmdName, rawData):
try: try:
@ -865,7 +931,7 @@ class Breaker:
self.reg_values['locked'] = self.locked self.reg_values['locked'] = self.locked
self.reg_values['closed'] = self.closed self.reg_values['closed'] = self.closed
self.reg_values['reasonForLastOpen'] = self.reasonForLastOpen 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['active_power'] = self.active_power
self.reg_values['load_status'] = self.load_status self.reg_values['load_status'] = self.load_status
@ -898,61 +964,44 @@ class Breaker:
def alarming(self): def alarming(self):
"""报警时:红灯亮+蜂鸣器响,绿灯灭""" """报警时:红灯亮+蜂鸣器响,绿灯灭"""
if not self.alarm and self.closed & 0xFF == 0xF0: if self.indicator:
self.exeCmd('turnOffGreen') self.indicator.alarming(self.closed & 0xFF)
self.exeCmd('turnOnRed')
self.exeCmd('turnOnAlarm')
self.alarm = 1 # 设置报警标志
def unalarming(self): def unalarming(self):
"""解除报警:根据合闸状态控制指示灯""" """解除报警:根据合闸状态控制指示灯"""
if self.alarm: if self.indicator:
self.exeCmd('turnOffRed') self.indicator.unalarming(self.closed & 0xFF)
self.exeCmd('turnOffAlarm')
# 如果是合闸状态,恢复绿灯
if self.closed & 0xFF == 0xF0:
self.exeCmd('turnOnGreen')
else:
self.exeCmd('turnOffGreen')
self.alarm = 0 # 清除报警标志
def open(self): def open(self):
"""打开串口连接""" """打开串口连接"""
self.serial = serial.Serial(self.port, self.baudrate, timeout=self.timeout) if self.client.open():
if not self.serial.is_open:
self.errorCode = 0x0001
return -1
else:
self.errorCode = 0 self.errorCode = 0
return 0 return 0
else:
self.errorCode = 0x0001
return -1
def close(self): def close(self):
if self.serial.is_open: self.client.close()
self.serial.close()
def run(self): def run(self):
"""主运行循环""" """主运行循环"""
try: try:
while True: while True:
# self.logger.info(f"optFlag={self.optFlag}")
match self.optFlag: match self.optFlag:
case 0: case 0:
if self.open() == 0: if self.open() == 0:
ret0 = self.exeCmd('openBreaker') ret0 = self.exeCmd('openBreaker')
# 初始化时关闭所有指示灯 # 初始化时关闭所有指示灯
self.exeCmd('turnOffGreen') if self.indicator:
self.exeCmd('turnOffRed') self.indicator.turnOffAll()
self.exeCmd('turnOffAlarm')
# self.logger.info(f"setOverLimitValues ret: {ret0}")
ret1 = self.exeCmd('setOverLimitValues') ret1 = self.exeCmd('setOverLimitValues')
# self.logger.info(f"setOverLimitValues ret: {ret1}")
ret2 = self.exeCmd('readOverLimitValues') ret2 = self.exeCmd('readOverLimitValues')
self.logger.info(f"readOverLimitValues ret: {ret2}") self.logger.info(f"readOverLimitValues ret: {ret2}")
ret3 = self.exeCmd('setOverLimitActionTime') ret3 = self.exeCmd('setOverLimitActionTime')
# self.logger.info(f"setOverLimitValues ret: {ret3}")
ret4 = self.exeCmd('readOverLimitActionTime') ret4 = self.exeCmd('readOverLimitActionTime')
self.logger.info(f"readOverLimitActionTime ret: {ret4}") 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 self.optFlag = 1
continue continue
self.optFlag = -1 self.optFlag = -1
@ -970,10 +1019,8 @@ class Breaker:
ret = self.exeCmd('openBreaker') ret = self.exeCmd('openBreaker')
if ret[0]: if ret[0]:
# 分闸成功后,关闭所有指示灯 # 分闸成功后,关闭所有指示灯
self.exeCmd('turnOffGreen') if self.indicator:
self.exeCmd('turnOffRed') self.indicator.turnOffAll()
self.exeCmd('turnOffAlarm')
self.alarm = 0 # 清除报警标志
self.optFlag = 1 self.optFlag = 1
continue continue
self.optFlag = -1 self.optFlag = -1
@ -981,9 +1028,9 @@ class Breaker:
case 3: case 3:
ret = self.exeCmd('closeBreaker') ret = self.exeCmd('closeBreaker')
if ret[0]: if ret[0]:
# 合闸成功后,点亮绿灯(如果没有报警) # 合闸成功后,点亮绿灯
# if not self.alarm: if self.indicator:
self.exeCmd('turnOnGreen') self.indicator.turnOnGreen()
self.optFlag = 1 self.optFlag = 1
continue continue
self.optFlag = -1 self.optFlag = -1
@ -1700,7 +1747,13 @@ class ModbusGateway:
self.logger.info(f"Local modbusTCP service starts, IP={self.host}, port={self.port}") 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.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.lsdaq = LSDAQ(self.config_manager.config['lsdaq'], self.logger)
self.hsdaq = HSDAQ(self.config_manager.config['hsdaq'], self.logger) self.hsdaq = HSDAQ(self.config_manager.config['hsdaq'], self.logger)