""" powermon / protocols / pi18.py """ import logging from powermon.commands.command import CommandType from powermon.commands.command_definition import CommandDefinition from powermon.commands.reading_definition import ReadingType, ResponseType from powermon.commands.result import ResultType from powermon.libs.errors import CommandDefinitionMissing, InvalidCRC, InvalidResponse from powermon.ports import PortType from powermon.protocols.abstractprotocol import AbstractProtocol from powermon.protocols.helpers import crc_pi30 as crc from powermon.protocols.pi30 import BATTERY_TYPE_LIST, OUTPUT_MODE_LIST log = logging.getLogger("pi18") SETTER_COMMANDS = { "POP": { "name": "POP", "command_type": CommandType.PI18_SETTER, "description": "Set Device Output Source Priority", "help": " -- examples: POP0 (set utility first), POP01 (set solar first)", "regex": "POP([01])$", }, "PSP": { "name": "PSP", "command_type": CommandType.PI18_SETTER, "description": "Set Solar Power priority", "help": " -- examples: PSP0 (Battery-Load-Utility +AC Charge), PSP1 (Load-Battery-Utility)", "regex": "PSP([01])$", }, "PEI": { "name": "PEI", "command_type": CommandType.PI18_SETTER, "description": "Set Machine type, enable: Grid-Tie", "help": " -- examples: PEI (enable Grid-Tie)", }, "PDI": { "name": "PDI", "command_type": CommandType.PI18_SETTER, "description": "Set Machine type, disable: Grid-Tie", "help": " -- examples: PDI (disable Grid-Tie)", }, "PCP": { "name": "PCP", "command_type": CommandType.PI18_SETTER, "description": "Set Device Charger Priority", "help": " -- examples: PCP0,1 (set unit 0 [0-9] to Solar and Utility) PCP0,0 (set unit 0 to Solar first), PCP0,1 (set unit 0 to Solar and Utility), PCP0,2 (set unit 0 to solar only charging)", "regex": "PCP([0-9],[012])$", }, "MCHGC": { "name": "MCHGC", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Max Charging Current Solar + AC", "help": " -- examples: MCHGC0,040 (set unit 0 to max charging current of 40A), MCHGC1,060 (set unit 1 to max charging current of 60A) [010 020 030 040 050 060 070 080]", "regex": "MCHGC([0-9],0[1-8]0)$", }, "MUCHGC": { "name": "MUCHGC", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Max AC Charging Current", "help": " -- examples: MUCHGC0,040 (set unit 0 to max charging current of 40A), MUCHGC1,060 (set unit 1 to max charging current of 60A) [002 010 020 030 040 050 060 070 080]", "regex": "MUCHGC([0-9]),(002|0[1-8]0)$", }, "PBT": { "name": "PBT", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Type", "help": " -- examples: PBT0 (set battery as AGM), PBT1 (set battery as FLOODED), PBT2 (set battery as USER)", "regex": "PBT([012])$", }, "MCHGV": { "name": "MCHGV", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Bulk,Float Charging Voltages", "help": " -- example MCHGV552,540 - set battery charging voltage Bulk to 52.2V, float 54V (set Bulk Voltage [480~584] in 0.1V xxx, Float Voltage [480~584] in 0.1V yyy)", # Regex 48.0 - 58.4 Volt "regex": "MCHGV(4[8-9][0-9]|5[0-7][0-9]|58[0-5]),(4[8-9][0-9]|5[0-7][0-9]|58[0-4])$", }, "PSDV": { "name": "PSDV", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Cut-off Voltage", "help": " -- example PSDV400 - set battery cut-off voltage to 40V [400~480V] for 48V unit)", # Regex 40 to 48V "regex": "PSDV(4[0-7][0-9]|480)$", }, "BUCD": { "name": "BUCD", "command_type": CommandType.PI18_SETTER, "description": "Set Battery Stop dis,charging when Grid is available", "help": " -- example BUCD440,480 - set Stop discharge Voltage [440~510] in 0.1V xxx, Stop Charge Voltage [000(Full) or 480~580] in 0.1V yyy", # Regex 44 to 51V, Full|48V to 58V "regex": "BUCD((4[4-9]0|5[0-1]0),(000|4[8-9]0|5[0-8]0))$", }, } QUERY_COMMANDS = { "PI": { "name": "PI", "command_type": CommandType.PI18_QUERY, "description": "Protocol ID inquiry", "help": " -- queries the protocol ID", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "Protocol ID"}, ], "test_responses": [ b"^D00518m\xae\r" ] }, "ID": { "name": "ID", "aliases": ["default", "get_id"], "command_type": CommandType.PI18_QUERY, "description": "Device Serial Number inquiry", "help": " -- queries the device serial number", "result_type": ResultType.SINGLE, "reading_definitions": [{"description": "Serial Number"}], "test_responses": [ b"^D02514012345678901234567\r", ], }, "ET": { "name": "ET", "command_type": CommandType.PI18_QUERY, "description": "Total PV Generated Energy Inquiry", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "Total PV Generated Energy", "reading_type": ReadingType.WATT_HOURS, "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, ], "test_responses": [ b"" ], }, "EY": { "name": "EY", "command_type": CommandType.PI18_QUERY, "description": "Yearly PV Generated Energy Inquiry", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "PV Generated Energy for Year", "reading_type": ReadingType.WATT_HOURS, "response_type": ResponseType.INT, "icon": "mdi:counter", "device_class": "energy", "state_class": "total"}, {"description": "Year", "reading_type": ReadingType.YEAR, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:])"}, ], "test_responses": [ b"^D01105580051\x0b\x9f\r", ], "regex": "EY(\\d\\d\\d\\d)$", }, "EM": { "name": "EM", "command_type": CommandType.PI18_QUERY, "description": "Monthly PV Generated Energy Inquiry", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "PV Generated Energy for Month", "reading_type": ReadingType.WATT_HOURS, "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, {"description": "Year", "reading_type": ReadingType.YEAR, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:7])"}, {"description": "Month", "reading_type": ReadingType.MONTH, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "calendar.month_name[int(cn[7:])]"}, ], "test_responses": [ b"", ], "regex": "EM(\\d\\d\\d\\d\\d\\d)$", }, "ED": { "name": "ED", "command_type": CommandType.PI18_QUERY, "description": "Daily PV Generated Energy Inquiry", "help": " -- display daily generated energy, format is QEDyyyymmdd", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "PV Generated Energy for Day", "reading_type": ReadingType.WATT_HOURS, "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "energy", "state_class": "total"}, {"description": "Year", "reading_type": ReadingType.YEAR, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[3:7])"}, {"description": "Month", "reading_type": ReadingType.MONTH, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "calendar.month_name[int(cn[7:9])]"}, {"description": "Day", "reading_type": ReadingType.DAY, "response_type": ResponseType.INFO_FROM_COMMAND, "format_template": "int(cn[9:11])"}, ], "test_responses": [ b"(00238800!J\r", ], "regex": "ED(\\d\\d\\d\\d\\d\\d\\d\\d)$", }, "PIRI": { "name": "PIRI", "command_type": CommandType.PI18_QUERY, "description": "Current Settings inquiry", "help": " -- queries the current settings from the Inverter", "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "AC Input Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "AC Input Current", "reading_type": ReadingType.CURRENT, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "AC Output Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "AC Output Frequency", "reading_type": ReadingType.FREQUENCY, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "AC Output Current", "reading_type": ReadingType.CURRENT, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "AC Output Apparent Power", "reading_type": ReadingType.APPARENT_POWER}, {"description": "AC Output Active Power", "reading_type": ReadingType.WATTS}, {"description": "Battery Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery re-charge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery re-discharge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery Under Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery Bulk Charge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery Float Charge Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10"}, {"description": "Battery Type", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": BATTERY_TYPE_LIST}, {"description": "Max AC Charging Current", "reading_type": ReadingType.CURRENT}, {"description": "Max Charging Current", "reading_type": ReadingType.CURRENT}, {"description": "Input Voltage Range", "response_type": ResponseType.LIST, "options": ["Appliance", "UPS"]}, {"description": "Output Source Priority", "response_type": ResponseType.LIST, "options": ["Solar - Utility - Battery", "Solar - Battery - Utility"]}, {"description": "Charger Source Priority", "response_type": ResponseType.LIST, "options": ["Solar First", "Solar + Utility", "Only solar charging permitted"]}, {"description": "Max Parallel Units"}, {"description": "Machine Type", "response_type": ResponseType.LIST, "options": ["Off Grid", "Grid Tie"]}, {"description": "Topology", "response_type": ResponseType.LIST, "options": ["transformerless", "transformer"]}, {"description": "Output Mode", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": OUTPUT_MODE_LIST}, {"description": "Solar power priority", "response_type": ResponseType.LIST, "options": ["Battery-Load-Utiliy + AC Charger", "Load-Battery-Utiliy"]}, {"description": "MPPT strings"}, {"description": "Unknown flags?", "response_type": ResponseType.STRING}, ], "test_responses": [ b"^D0882300,217,2300,500,217,5000,5000,480,480,530,440,570,570,2,10,070,1,1,1,9,0,0,0,0,1,00\xe1k\r", ] }, "GS": { "name": "GS", "command_type": CommandType.PI18_QUERY, "description": "General Status Parameters inquiry", "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "AC Input Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:transmission-tower-export", "device_class": "voltage"}, {"description": "AC Input Frequency", "reading_type": ReadingType.FREQUENCY, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:current-ac", "device_class": "frequency"}, {"description": "AC Output Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:transmission-tower-export", "device_class": "voltage"}, {"description": "AC Output Frequency", "reading_type": ReadingType.FREQUENCY, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:current-ac", "device_class": "frequency"}, {"description": "AC Output Apparent Power", "reading_type": ReadingType.APPARENT_POWER, "response_type": ResponseType.INT, "icon": "mdi:power-plug", "device_class": "apparent_power"}, {"description": "AC Output Active Power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT, "icon": "mdi:power-plug", "device_class": "power", "state_class": "measurement"}, {"description": "AC Output Load", "reading_type": ReadingType.PERCENTAGE, "response_type": ResponseType.INT, "icon": "mdi:brightness-percent"}, {"description": "Battery Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, {"description": "Battery Voltage from SCC", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, {"description": "Battery Voltage from SCC2", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:battery-outline", "device_class": "voltage"}, {"description": "Battery Discharge Current", "reading_type": ReadingType.CURRENT, "response_type": ResponseType.INT, "icon": "mdi:battery-negative", "device_class": "current"}, {"description": "Battery Charging Current", "reading_type": ReadingType.CURRENT, "response_type": ResponseType.INT, "icon": "mdi:current-dc", "device_class": "current"}, {"description": "Battery Capacity", "reading_type": ReadingType.PERCENTAGE, "response_type": ResponseType.INT, "icon": "mdi:brightness-percent", "device_class": "battery"}, {"description": "Inverter heat sink temperature", "reading_type": ReadingType.TEMPERATURE, "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, {"description": "MPPT1 charger temperature", "reading_type": ReadingType.TEMPERATURE, "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, {"description": "MPPT2 charger temperature", "reading_type": ReadingType.TEMPERATURE, "response_type": ResponseType.INT, "icon": "mdi:details", "device_class": "temperature"}, {"description": "MPPT1 Input Power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "power", "state_class": "measurement"}, {"description": "MPPT2 Input Power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT, "icon": "mdi:solar-power", "device_class": "power", "state_class": "measurement"}, {"description": "MPPT1 Input Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:solar-power", "device_class": "voltage"}, {"description": "MPPT2 Input Voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "icon": "mdi:solar-power", "device_class": "voltage"}, {"description": "Setting value configuration state", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "Nothing changed", "1": "Something changed", }, }, {"description": "MPPT1 charger status", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "abnormal", "1": "normal but not charged", "2": "charging", }, }, {"description": "MPPT2 charger status", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "abnormal", "1": "normal but not charged", "2": "charging", }, }, {"description": "Load connection", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "disconnect", "1": "connect", }, }, {"description": "Battery power direction", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "donothing", "1": "charge", "2": "discharge", }, }, {"description": "DC-AC power direction", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "donothing", "1": "AC-DC", "2": "DC-AC", }, }, {"description": "Line power direction", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "0": "donothing", "1": "input", "2": "output", }, }, {"description": "Parallel instance number", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": ["Not valid", "valid"], }, ], "test_responses": [ b"D1062232,499,2232,499,0971,0710,019,008,000,000,000,000,000,044,000,000,0520,0000,1941,0000,0,2,0,1,0,2,1,0\x09\x7b\r", b"^D1062232,499,2232,499,1406,1376,028,549,000,000,000,010,095,060,000,000,0082,0000,1604,0000,0,2,0,1,1,1,1,0D\x12\r", ], }, "MOD": { "name": "MOD", "command_type": CommandType.PI18_QUERY, "description": "Mode inquiry", "result_type": ResultType.SINGLE, "reading_definitions": [ {"description": "Device Mode", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "00": "Power on", "01": "Standby", "02": "Bypass", "03": "Battery", "04": "Fault", "05": "Hybrid mode(Line mode, Grid mode)", } }, ], "test_responses": [ b"^D00505\xd9\x9f\r", ], }, "MCHGCR": { "name": "MCHGCR", "command_type": CommandType.PI18_QUERY, "description": "Max Charging Current Options inquiry", "help": " -- queries the maximum charging current setting of the Inverter", "result_type": ResultType.MULTIVALUED, "reading_definitions": [ {"description": "Max Charging Current Options", "reading_type": ReadingType.MESSAGE_AMPS, "response_type": ResponseType.STRING } ], "test_responses": [ b"^D034010,020,030,040,050,060,070,080\x161\r", ], }, "MUCHGCR": { "name": "MUCHGCR", "command_type": CommandType.PI18_QUERY, "description": "Max Utility Charging Current Options inquiry", "help": " -- queries the maximum utility charging current setting of the Inverter", "result_type": ResultType.MULTIVALUED, "reading_definitions": [ {"reading_type": ReadingType.MESSAGE_AMPS, "description": "Max Utility Charging Current", "response_type": ResponseType.STRING} ], "test_responses": [ b"^D038002,010,020,030,040,050,060,070,080\xd01\r" ], }, "FLAG": { "name": "FLAG", "command_type": CommandType.PI18_QUERY, "description": "Query enable/disable flag status", "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "Buzzer beep", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Overload bypass function", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Display back to default page", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Overload restart", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Over temperature restart", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Backlight on", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Alarm primary source interrupt", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Fault code record", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.ENABLED_BOOL}, {"description": "Reserved", "reading_type": ReadingType.MESSAGE}, ], "test_responses": [ b"^D0200,0,0,0,0,1,0,0,12\xc2\x39\r", ], }, "VFW": { "name": "VFW", "description": "Device CPU version inquiry", "command_type": CommandType.PI18_QUERY, "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "Main CPU Version", "reading_type": ReadingType.MESSAGE}, {"description": "Slave 1 CPU Version", "reading_type": ReadingType.MESSAGE}, {"description": "Slave 2 CPU Version", "reading_type": ReadingType.MESSAGE}, ], "test_responses": [ b"^D02005220,00000,00000\x3e\xf8\r", ], }, # Fault + warning bitmap. 2-digit fault code followed by ~32 0/1 warning bits. # Fault-code list cross-referenced with PI30 QPGS (same firmware family). "FWS": { "name": "FWS", "description": "Fault and warning status inquiry", "command_type": CommandType.PI18_QUERY, "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "Fault code", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.OPTION, "options": { "00": "No fault", "01": "Fan is locked", "02": "Over temperature", "03": "Battery voltage is too high", "04": "Battery voltage is too low", "05": "Output short circuited or Over temperature", "06": "Output voltage is too high", "07": "Over load time out", "08": "Bus voltage is too high", "09": "Bus soft start failed", "11": "Main relay failed", "51": "Over current inverter", "52": "Bus soft start failed", "53": "Inverter soft start failed", "54": "Self-test failed", "55": "Over DC voltage on output of inverter", "56": "Battery connection is open", "57": "Current sensor failed", "58": "Output voltage is too low", "60": "Inverter negative power", "71": "Parallel version different", "72": "Output circuit failed", "80": "CAN communication failed", "81": "Parallel host line lost", "82": "Parallel synchronized signal lost", "83": "Parallel battery voltage detect different", "84": "Parallel Line voltage or frequency detect different", "85": "Parallel Line input current unbalanced", "86": "Parallel output setting different", }}, {"description": "PV loss warning", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Inverter fault", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Bus over", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Bus under", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Bus soft fail", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Line fail", "response_type": ResponseType.ENABLED_BOOL}, {"description": "OPV short", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Inverter voltage too low", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Inverter voltage too high", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Over temperature", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Fan locked", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery voltage high", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery low alarm", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery under shutdown", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery derating", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Overload", "response_type": ResponseType.ENABLED_BOOL}, {"description": "EEPROM fault", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Inverter over current", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Inverter soft fail", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Self test fail", "response_type": ResponseType.ENABLED_BOOL}, {"description": "OP DC voltage over", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery open", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Current sensor fail", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery short", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Power limit", "response_type": ResponseType.ENABLED_BOOL}, {"description": "PV voltage high", "response_type": ResponseType.ENABLED_BOOL}, {"description": "MPPT overload fault", "response_type": ResponseType.ENABLED_BOOL}, {"description": "MPPT overload warning", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery too low to charge", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery weak", "response_type": ResponseType.ENABLED_BOOL}, {"description": "Battery equalization", "response_type": ResponseType.ENABLED_BOOL}, ], "test_responses": [ b"^D07100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\xaa\xaa\r", ], }, # Per-unit parallel view. PGS, n = 0..N-1 (0 is master). # LVX6048 emits a 30-field layout that differs from the PI30 QPGS layout; # only fields confirmed against live responses are semantically named here. # The rest are exposed as raw strings so the command doesn't error out, and # can be tightened later as more firmware rev responses are confirmed. # Observed unit-1 response (Not valid + fault 71 "Parallel version different"): # 0,4,71,2453,599,0000,000,0000,0000,00000,00000,000,211,005,000,000,000, # 000,0008,0000,2925,0000,1,0,0,0,0,0,016 "PGS": { "name": "PGS", "description": "Parallel general status inquiry", "help": " -- example: PGS0 queries parallel status for instance 0 (master)", "command_type": CommandType.PI18_QUERY, "result_type": ResultType.COMMA_DELIMITED, "reading_definitions": [ {"description": "Parallel instance number", "reading_type": ReadingType.MESSAGE, "response_type": ResponseType.LIST, "options": ["Not valid", "valid"]}, {"description": "Parallel unit count", "reading_type": ReadingType.MESSAGE}, {"description": "Fault code", "reading_type": ReadingType.MESSAGE}, {"description": "Field 4 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Grid frequency", "reading_type": ReadingType.FREQUENCY, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "device_class": "frequency"}, {"description": "AC output voltage", "reading_type": ReadingType.VOLTS, "response_type": ResponseType.TEMPLATE_INT, "format_template": "r/10", "device_class": "voltage"}, {"description": "AC output frequency (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "AC output apparent power", "reading_type": ReadingType.APPARENT_POWER, "response_type": ResponseType.INT, "device_class": "apparent_power"}, {"description": "AC output active power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT, "device_class": "power"}, {"description": "Total AC output apparent power", "reading_type": ReadingType.APPARENT_POWER, "response_type": ResponseType.INT}, {"description": "Total AC output active power", "reading_type": ReadingType.WATTS, "response_type": ResponseType.INT}, {"description": "Load percentage", "reading_type": ReadingType.PERCENTAGE, "response_type": ResponseType.INT}, {"description": "Field 13 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 14 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 15 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 16 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 17 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 18 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 19 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 20 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 21 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 22 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 23 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 24 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 25 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 26 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 27 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Flag 28 (raw)", "reading_type": ReadingType.MESSAGE}, {"description": "Field 30 (raw)", "reading_type": ReadingType.MESSAGE}, ], "test_responses": [ b"^D1130,4,71,2453,599,0000,000,0000,0000,00000,00000,000,211,005,000,000,000,000,0008,0000,2925,0000,1,0,0,0,0,0,016\x8f\xad\r", ], "regex": "PGS(\\d+)$", }, } COMMANDS_TO_REMOVE = [] class PI18(AbstractProtocol): """ pi18 protocol handler """ def __str__(self): return "PI18 protocol handler" def __init__(self) -> None: super().__init__() self.protocol_id = b"PI18" self.add_command_definitions(QUERY_COMMANDS) self.add_command_definitions(SETTER_COMMANDS, result_type=ResultType.PI18_ACK) self.remove_command_definitions(COMMANDS_TO_REMOVE) self.check_definitions_count(expected=26) # Count of all Commands self.add_supported_ports([PortType.SERIAL, PortType.USB]) def check_crc(self, response: str, command_definition: CommandDefinition = None): """ crc check, override for now """ log.debug("check crc for %s in pi18", response) if response.startswith(b"^D") or response.startswith(b"^1") or response.startswith(b"^0"): # get response CRC data_to_check = response[:-3] crc_high, crc_low = crc(data_to_check) # print(crc_high, crc_low) # print(response[-3], response[-2]) if (crc_high, crc_low) == (response[-3], response[-2]): return True else: log.info("PI18 response check_crc doesnt match calc (%x, %x), got (%x, %x)", crc_high, crc_low, response[-3], response[-2]) raise InvalidCRC(f"PI18 response check_crc doesnt match calc ({crc_high:02x}, {crc_low:02x}), got ({response[-3]:02x}, {response[-2]:02x})") else: log.info("PI18 response doesnt start with ^D - check_crc fails") raise InvalidResponse("PI18 response starts with invalid character - crc check fails") log.info("PI18 response check_crc fall through") return False def trim_response(self, response: str, command_definition: CommandDefinition = None) -> str: """ Remove extra characters from response """ log.debug("trim %s, definition: %s", response, command_definition) if response.startswith(b"^D"): # trim ^Dxxx where xxx is data length response = response[5:] if response.endswith(b'\r'): # has checksum, so trim last 3 chars response = response[:-3] if response.startswith(b'('): # pi30 style response response = response[1:] # if response.startswith(b'^1') or response.startswith(b'^0'): # # ACK / NACK response # response = response[1:] return response def get_full_command(self, command: str) -> bytes: """ generate the full command including prefix, crc and \n as needed """ log.info("Using protocol: %s with %i commands", self.protocol_id, len(self.command_definitions)) command_defn = self.get_command_definition(command) # raise exception if no command definition is found if command_defn is None: raise CommandDefinitionMissing(f"No definition found in PI18 for {command}") # full command is ^PlllCCCcrc\n or ^SlllCCCcrc\n # lll = length of all except ^Dlll # CCC = command # crc = 2 bytes length = len(command) + 3 # Determine prefix match command_defn.command_type: case CommandType.PI18_QUERY: prefix = "^P" case CommandType.PI18_SETTER: prefix = "^S" case _: # edge case / default PI30 command / maybe this should raise an error prefix = "(" full_command = bytes(f"{prefix}{length:#03d}{command}", "utf-8") crc_high, crc_low = crc(full_command) full_command += bytes([crc_high, crc_low, 13]) log.debug("full command: %s", full_command) return full_command