From 16ca41bd5daa944c023785677572982abc33cbbe Mon Sep 17 00:00:00 2001 From: Yeongdo Park Date: Mon, 17 Feb 2020 12:40:35 -0800 Subject: [PATCH] Read power ports' status --- stabiliti.py | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 3 deletions(-) diff --git a/stabiliti.py b/stabiliti.py index 1005770..0bc71aa 100644 --- a/stabiliti.py +++ b/stabiliti.py @@ -2,11 +2,175 @@ import easymodbus.modbusClient from datetime import datetime as dt from functools import reduce +nRegsPortStatus = 32 + +addrControlAC1 = 64 +addrStatusAC1 = 96 + +addrControlDC2 = 128 +addrStatusDC2 = 160 + +addrControlDC3 = 192 +addrStatusDC3 = 224 + +addrControlSystem = 0x0100 +addrStatusSystem = 0x0120 + +addrStatusThermal = 0x0160 + +addrStatusDiagnostic = 0x0180 + +p1_port_status = {0: "AC1 port is real power soft-limiting", + 1: "AC1 port is current soft-limiting", + 2: "AC1 port is reactive power soft-limiting", + 3: "AC1 port is power derated (limited) due to high temperature", + 4: "Reserved", + 5: "AC1 port is throttling back on port DC2 due to soft-limiting", + 6: "Reserved", + 7: "AC1 port is throttling back on port DC3 due to soft-limiting", + 8: "AC1 port has the seamless transfer feature enabled when in FPWR control", + 9: "The PCS SEL-547 interface transfer switch HW is indicating the PCS is islanded (islanding contactor is commanded to open). If enabled, the PCS is able to form a microgrid in FPWR control.", + 10: "The PCS SEL-547 interface transfer switch HW is indicating the PCS is islanded (islanding contactor has successfully opened). If enabled, the PCS is able to form a microgrid in FPWR control.", + 11: "Reserved", + 12: "Reserved", + 13: "Reserved", + 14: "Reserved", + 15: "Reserved"} + +p2_port_status = {0: "DC2 port is power soft-limiting", + 1: "DC2 port is current soft-limiting", + 2: "Reserved", + 3: "DC2 port is power derated (limited) due to high temperature", + 4: "Reserved", + 5: "DC2 port is throttling back on port AC1 due to soft-limiting", + 6: "Reserved", + 7: "DC2 port is throttling back on port DC3 due to soft-limiting", + 8: "Reserved", + 9: "Reserved", + 10: "Reserved", + 11: "Reserved", + 12: "Reserved", + 13: "Reserved", + 14: "Reserved", + 15: "Reserved"} + +p3_port_status = {0: "DC3 port is power soft-limiting", + 1: "DC3 port is current soft-limiting", + 2: "Reserved", + 3: "DC3 port is power derated (limited) due to high temperature", + 4: "Reserved", + 5: "DC3 port is throttling back on port AC1 due to soft-limiting", + 6: "Reserved", + 7: "DC3 port is throttling back on port DC2 due to soft-limiting", + 8: "Reserved", + 9: "Reserved", + 10: "Reserved", + 11: "Reserved", + 12: "Reserved", + 13: "Reserved", + 14: "Reserved", + 15: "Reserved"} + + +def toPower(x): + return x * 10 + +def toVar(x): + return x * 10 + +def toVa(x): + return x * 10 + +def toPwrFactor(x): + return x * 0.01 + +def toCurrent(x): + return x * 0.1 + +def toVoltage(x): + return x + +def toFreq(x): + return x * 0.001 + +def toTemperature(x): + return x * 0.1 + +def toRpm(x): + return x + +def toMinutes(x): + return x + +def toTime(x): + return x // 60, x % 60 + +def toBaud(x): + return x * 100 + +def toString(r): + ms = r >> 8 + ls = (r & 0xff) + return chr(ms) + chr(ls) + +def toBitString(r): + return bin(r)[2:].zfill(16) + +def selectMessages (bits, msgDict): + msgs = [] + for i, bit in enumerate(bits[::-1]): + if bit == '1': + msgs.append(msgDict[i]) + return msgs + +def parse_address (addr_string): + import re + m = re.findall(r'0x([0-9A-F]+)', addr_string) + + if len(m) > 1: + start, end = m + elif len(m) > 0: + start = m[0] + end = start + else: + raise ValueError() + + nregisters = int(end, 16) - int(start, 16) + 1 + + return int(start, 16), nregisters + + +class StabilitiRegister(object): + def __init__(self, name, proplist): + self.name = name + self.index = proplist[0] + self.address, self.size = parse_address(proplist[1]) + self.access = proplist[2] + self.dtype = proplist[3] + self.vrange = proplist[4] + self.vdefault = proplist[5] + + def isreadonly(self): + return self.is_readable() and (not self.is_writable()) + + def is_readable(self): + return 'R' in self.access + + def is_writable(self): + return 'W' in self.access + + def getdefaultvalue(self): + if self.vdefault = 'NA': + return None + else: + return self.vdefault + class StabilitiController(object): '''''' def __init__(self, ipaddr, port=502, uid=240): self.client = easymodbus.modbusClient.ModbusClient(ipaddr, port) self.client.unitidentifier = uid + self.reg_info = {} def checkConnect(self): if not self.client.is_connected(): @@ -17,9 +181,7 @@ class StabilitiController(object): self.client.close() def reg2str(self, r): - ms = (r & 0xff00) >> 8 - ls = (r & 0xff) - return chr(ms) + chr(ls) + return toString(r) def getNetworkConfigs(self, config=None): self.checkConnect() @@ -128,5 +290,104 @@ b3-b4: The status of the fault. pass def getSystemStatus(self): + '''Get PCS Status''' + '''\ +system_status[0] – PCS in self-test mode +system_status[1] – PCS reconnect timer 0 counting down from last disconnect due to ABORT-0 fault +system_status[2] – PCS reconnect timer 1 counting down from last disconnect due to ABORT-1 fault +system_status[3] – PCS reconnect timer 2 counting down from last disconnect due to ABORT-2 fault +system_status[4] – PCS DC2 port pre-charge operation is active +system_status[5] – Invalid control method programmed in PCS +system_status[6] – AC rotation indication ( 0 – AC1 port wired as A-B-C | 1 – AC1 port wired as C-B-A ) +system_status[7] – PV/MPPT low-voltage indication ( 0 – PV/MPPT is disabled due to low voltage at port | 1 – PV/MPPT is enabled with voltage above minimum PV/MPPT limit set for port ) +system_status[8] – PV/MPPT time-of-day indication ( 0 – System time-of-day outside PV/MPPT operational range | 1 – System time-of-day satisfies start-time and stop-time limits set for PV/MPPT port, PV/MPPT port allowed to convert power ) +system_status[9] – PCS power conversion is active +system_status[10] – PCS hardware shutdown function is active (power conversion disabled) +system_status[11] – PCS lockdown active due to GFDI fault, IMI fault, or fan fault +system_status[12] – PCS fault of severity level ABORT-0 active +system_status[13] – PCS fault of severity level ABORT-1 active +system_status[14] – PCS fault of severity level ABORT-2 active +system_status[15] – PCS GFDI fault or IMI fault detected + ''' + regs = self.client.read_holdingregisters(addrStatusSystem, nRegsPortStatus) pass + def getPortStatusAC1(self): + '''Get Status of AC1 Power Port''' + + def relAddr(x): + return x - addrStatusAC1 + + regs = self.client.read_holdingregisters(addrStatusAC1, nRegsPortStatus) + + p1_all_status = {"p1_port_status": toBitString(regs[relAddr(96)]), + "p1_real_pwr_ramped": toPower(regs[relAddr(100)]), + "p1_reactive_pwr_ramped": toVar(regs[relAddr(101)]), + "p1_frequency": toFreq(regs[relAddr(105)]), + "p1_v_ab_rms": toVoltage(regs[relAddr(109)]), + "p1_v_bc_rms": toVoltage(regs[relAddr(110)]), + "p1_v_ca_rms": toVoltage(regs[relAddr(111)]), + "p1_v_an_rms": toVoltage(regs[relAddr(112)]), + "p1_v_bn_rms": toVoltage(regs[relAddr(113)]), + "p1_v_cn_rms": toVoltage(regs[relAddr(114)]), + "p1_power_factor": toPwrFactor(regs[relAddr(118)]), + "p1_real_power": toPower(regs[relAddr(119)]), + "p1_reactive_power": toVar(regs[relAddr(120)]), + "p1_apparent_power": toVa(regs[relAddr(121)]), + "p1_i_a_int_rms": toCurrent(regs[relAddr(122)]), + "p1_i_b_int_rms": toCurrent(regs[relAddr(123)]), + "p1_i_c_int_rms": toCurrent(regs[relAddr(124)]), + "p1_i_a_ext_rms": toCurrent(regs[relAddr(125)]), + "p1_i_b_ext_rms": toCurrent(regs[relAddr(126)]), + "p1_i_c_ext_rms": toCurrent(regs[relAddr(127)])} + + status = p1_all_status['p1_port_status'] + + return selectMessages(status, p1_port_status), p1_all_status + + def getPortStatusDC2(self): + '''Get Status of DC2 Power Port''' + + def relAddr(x): + return x - addrStatusDC2 + + regs = self.client.read_holdingregisters(addrStatusDC2, nRegsPortStatus) + + p2_all_status = {'p2_port_status': toBitString(regs[relAddr(160)]), + 'p2_current_ramped': toCurrent(regs[relAddr(164)]), + 'p2_power_ramped': toPower(regs[relAddr(165)]), + 'p2_voltage_ramped': toVoltage(regs[relAddr(166)]), + 'pv_tod_stat': toMinutes(regs[relAddr(167)]), + 'p2_pv_restart_stat': regs[relAddr(168)], + 'p2_v_pn': toVoltage(regs[relAddr(173)]), + 'p2_v_pg': toVoltage(regs[relAddr(176)]), + 'dc_com_voltage': toVoltage(regs[relAddr(177)]), + 'p2_power': toPower(regs[relAddr(185)]), + 'p2_current': toCurrent(regs[relAddr(186)])} + + status = p2_all_status['p2_port_status'] + + return selectMessages(status, p2_port_status), p2_all_status + + def getPortStatusDC3(self): + '''Get Status of DC3 Power Port''' + + def relAddr(x): + return x - addrStatusDC3 + + regs = self.client.read_holdingregisters(addrStatusDC3, nRegsPortStatus) + + p3_all_status = {'p3_port_status': toBitString(regs[relAddr(224)]), + 'p3_current_ramped': toCurrent(regs[relAddr(228)]), + 'p3_power_ramped': toPower(regs[relAddr(229)]), + 'p3_voltage_ramped': toVoltage(regs[relAddr(230)]), + 'p3_pv_restart_stat': regs[relAddr(232)], + 'p3_v_pn': toVoltage(regs[relAddr(237)]), + 'p3_v_pg': toVoltage(regs[relAddr(240)]), + 'p3_power': toPower(regs[relAddr(249)]), + 'p3_current': toCurrent(regs[relAddr(250)])} + + status = p3_all_status['p3_port_status'] + + return selectMessages(status, p3_port_status), p3_all_status +