from dataclasses import dataclass import dataclasses from typing import Optional import enum import pint from .hex import Register, Mode, Field _ureg = pint.get_application_registry() @dataclass class Product: id: int name: str remark: Optional[str] = None PRODUCT_IDS = ( Product(0xA201, 'Phoenix Inverter 12V 250VA 230Vac', 'obsolete (32k)'), Product(0xA202, 'Phoenix Inverter 24V 250VA 230Vac', 'obsolete (32k)'), Product(0xA204, 'Phoenix Inverter 48V 250VA 230Vac', 'obsolete (32k)'), Product(0xA211, 'Phoenix Inverter 12V 375VA 230Vac', 'obsolete (32k)'), Product(0xA212, 'Phoenix Inverter 24V 375VA 230Vac', 'obsolete (32k)'), Product(0xA214, 'Phoenix Inverter 48V 375VA 230Vac', 'obsolete (32k)'), Product(0xA221, 'Phoenix Inverter 12V 500VA 230Vac', 'obsolete (32k)'), Product(0xA222, 'Phoenix Inverter 24V 500VA 230Vac', 'obsolete (32k)'), Product(0xA224, 'Phoenix Inverter 48V 500VA 230Vac', 'obsolete (32k)'), Product(0xA231, 'Phoenix Inverter 12V 250VA 230Vac 64k'), Product(0xA232, 'Phoenix Inverter 24V 250VA 230Vac 64k'), Product(0xA234, 'Phoenix Inverter 48V 250VA 230Vac 64k'), Product(0xA239, 'Phoenix Inverter 12V 250VA 120Vac 64k'), Product(0xA23A, 'Phoenix Inverter 24V 250VA 120Vac 64k'), Product(0xA23C, 'Phoenix Inverter 48V 250VA 120Vac 64k'), Product(0xA241, 'Phoenix Inverter 12V 375VA 230Vac 64k'), Product(0xA242, 'Phoenix Inverter 24V 375VA 230Vac 64k'), Product(0xA244, 'Phoenix Inverter 48V 375VA 230Vac 64k'), Product(0xA249, 'Phoenix Inverter 12V 375VA 120Vac 64k'), Product(0xA24A, 'Phoenix Inverter 24V 375VA 120Vac 64k'), Product(0xA24C, 'Phoenix Inverter 48V 375VA 120Vac 64k'), Product(0xA251, 'Phoenix Inverter 12V 500VA 230Vac 64k'), Product(0xA252, 'Phoenix Inverter 24V 500VA 230Vac 64k'), Product(0xA254, 'Phoenix Inverter 48V 500VA 230Vac 64k'), Product(0xA259, 'Phoenix Inverter 12V 500VA 120Vac 64k'), Product(0xA25A, 'Phoenix Inverter 24V 500VA 120Vac 64k'), Product(0xA25C, 'Phoenix Inverter 48V 500VA 120Vac 64k'), Product(0xA261, 'Phoenix Inverter 12V 800VA 230Vac 64k'), Product(0xA262, 'Phoenix Inverter 24V 800VA 230Vac 64k'), Product(0xA264, 'Phoenix Inverter 48V 800VA 230Vac 64k'), Product(0xA269, 'Phoenix Inverter 12V 800VA 120Vac 64k'), Product(0xA26A, 'Phoenix Inverter 24V 800VA 120Vac 64k'), Product(0xA26C, 'Phoenix Inverter 48V 800VA 120Vac 64k'), Product(0xA271, 'Phoenix Inverter 12V 1200VA 230Vac 64k'), Product(0xA272, 'Phoenix Inverter 24V 1200VA 230Vac 64k'), Product(0xA274, 'Phoenix Inverter 48V 1200VA 230Vac 64k'), Product(0xA279, 'Phoenix Inverter 12V 1200VA 120Vac 64k'), Product(0xA27A, 'Phoenix Inverter 24V 1200VA 120Vac 64k'), Product(0xA27C, 'Phoenix Inverter 48V 1200VA 120Vac 64k'), Product(0xA281, 'Phoenix Inverter Smart 12V 1600VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA282, 'Phoenix Inverter Smart 24V 1600VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA284, 'Phoenix Inverter Smart 48V 1600VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA291, 'Phoenix Inverter Smart 12V 2000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA292, 'Phoenix Inverter Smart 24V 2000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA294, 'Phoenix Inverter Smart 48V 2000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA2A1, 'Phoenix Inverter Smart 12V 3000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA2A2, 'Phoenix Inverter Smart 24V 3000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA2A4, 'Phoenix Inverter Smart 48V 3000VA 230Vac 64k', 'Smart, integrated Bluetooth'), Product(0xA2E1, 'Phoenix Inverter 12V 800VA 230Vac 64k HS', 'redesign (replaces A261)'), Product(0xA2E2, 'Phoenix Inverter 24V 800VA 230Vac 64k HS', 'redesign (replaces A262)'), Product(0xA2E4, 'Phoenix Inverter 48V 800VA 230Vac 64k HS', 'redesign (replaces A264)'), Product(0xA2E9, 'Phoenix Inverter 12V 800VA 120Vac 64k HS', 'redesign (replaces A269)'), Product(0xA2EA, 'Phoenix Inverter 24V 800VA 120Vac 64k HS', 'redesign (replaces A26A)'), Product(0xA2EC, 'Phoenix Inverter 48V 800VA 120Vac 64k HS', 'redesign (replaces A26C)'), Product(0xA2F1, 'Phoenix Inverter 12V 1200VA 230Vac 64k HS', 'redesign (replaces A271)'), Product(0xA2F2, 'Phoenix Inverter 24V 1200VA 230Vac 64k HS', 'redesign (replaces A272)'), Product(0xA2F4, 'Phoenix Inverter 48V 1200VA 230Vac 64k HS', 'redesign (replaces A274)'), Product(0xA2F9, 'Phoenix Inverter 12V 1200VA 120Vac 64k HS', 'redesign (replaces A279)'), Product(0xA2FA, 'Phoenix Inverter 24V 1200VA 120Vac 64k HS', 'redesign (replaces A27A)'), Product(0xA2FC, 'Phoenix Inverter 48V 1200VA 120Vac 64k HS', 'redesign (replaces A27C)'), ) # NVM registers INV_NVM_COMMAND = Register(0xEB99, 'INV_NVM_COMMAND', 'B', Mode.W) RESTORE_DEFAULT = Register(0x0004, 'RESTORE_DEFAULT', '-', Mode.W) # class NVMCommand(enum.IntEnum): # # 1,NvmSave,Save current user settings to NVM,,,, # # 2,NvmRevert,Cancel modified settings.,,,, # # ,,Load most recent saved user settings.,,,, # # 3,NvmBackup,Undo last save. Load second last time saved settings.,,,, # # 4,NvmDefault,Load the factory default values.,,,, # Product information registers class Capabilities(enum.IntFlag): # 8,Remote input available REMOTE_INPUT_AVAILABLE = 1 << 8 # 17,Build in user-relay available # TYPO BUILD_IN_USER_RELAY_AVAILABLE = 1 << 17 # 28,Support of device hibernation SUPPORT_OF_DEVICE_HIBERNATION = 1 << 28 # 29,Improved load current measurement IMPROVED_LOAD_CURRENT_MEASUREMENT = 1 << 29 PRODUCT_ID = Register(0x0100, 'PRODUCT_ID', 'xHx', Mode.R) PRODUCT_REVISION = Register(0x0101, 'PRODUCT_REVISION', 'xH', Mode.R, None, None) APP_VER = Register(0x0102, 'APP_VER', 'xH', Mode.R) SERIAL_NUMBER = Register(0x010A, 'SERIAL_NUMBER', 'S', Mode.R) MODEL_NAME = Register(0x010B, 'MODEL_NAME', 'S', Mode.R) AC_OUT_RATED_POWER = Register(0x2203, 'AC_OUT_RATED_POWER', 'h', Mode.R, 1, 'VA') # Typo. CAPABILITIES = Register(0x0140, 'CAPABILITIES', 'I', Mode.R, None, Capabilities) CAPABILITIES_BLE = Register(0x0150, 'CAPABILITIES_BLE', 'I', Mode.RW) AC_OUT_NOM_VOLTAGE = Register(0x2202, 'AC_OUT_NOM_VOLTAGE', 'B', Mode.R, 1, 'V') BAT_VOLTAGE = Register(0xEDEF, 'BAT_VOLTAGE', 'B', Mode.R, 1, 'V') # # Generic device status registers class DeviceState(enum.IntEnum): # Off,Not inverting. When due to a protection the inverter will # automatically start again when the cause is solved. OFF = 0 # Low Power,Eco load search active LOW_POWER = 1 # Fault,Not inverting due to a fatal active protection. A turn OFF-ON cycle # is required to enable the device again. FAULT = 2 # Inverting,Normal operating INVERTING = 9 class DeviceOffReason(enum.IntFlag): NONE = 0 # 0,No input power (will also cause a battery alarm), NO_INPUT_POWER = 1 << 0 # 2,Soft power button or SW controlled (VE.Direct or Bluetooth), SOFT_POWER_BUTTON_OR_SW_CONTROLLED = 1 << 2 # 3,HW remote input connector, HW_REMOTE_INPUT_CONNECTOR = 1 << 3 # 4,Internal reason (see alarm reason for more info), INTERNAL_REASON = 1 << 4 # 5,"PayGo, out of credit, need token", PAYGO_OUT_OF_CREDIT_NEED_TOKEN = 1 << 5 class WarningReason(enum.IntFlag): NONE = 0 # 0,Low battery voltage, LOW_BATTERY_VOLTAGE = 1 << 0 # 1,High battery voltage, HIGH_BATTERY_VOLTAGE = 1 << 1 # 5,Low temperature, LOW_TEMPERATURE = 1 << 5 # 6,High temperature, HIGH_TEMPERATURE = 1 << 6 # 8,Overload, OVERLOAD = 1 << 8 # 9,Poor DC connection, POOR_DC_CONNECTION = 1 << 9 # 10,Low AC-output voltage, LOW_AC_OUTPUT_VOLTAGE = 1 << 10 # 11,High AC-output voltage, HIGH_AC_OUTPUT_VOLTAGE = 1 << 11 DEVICE_STATE = Register(0x0201, 'DEVICE_STATE', 'B', Mode.R, None, DeviceState) DEVICE_OFF_REASON = Register(0x0207, 'DEVICE_OFF_REASON', 'I', Mode.R, None, DeviceOffReason) WARNING_REASON = Register(0x031C, 'WARNING_REASON', 'H', Mode.R, None, WarningReason) ALARM_REASON = Register(0x031E, 'ALARM_REASON', 'H', Mode.R, None, WarningReason) # Generic device control registers class DeviceMode(enum.IntEnum): # 2,Inverter On,,,,, INVERTER_ON = 2 # 3,Device On (multi compliant),1),,,, DEVICE_ON = 3 # 4,Device Off,VE.Direct is still enabled,,,, DEVICE_OFF = 4 # 5,Eco mode,,,,, ECO_MODE = 5 # 0xFD,Hibernate,VE.Direct is affected 2),,,, HIBERNATE = 0xFD BLE_MODE = Register(0x0090, 'BLE_MODE', 'B', Mode.RW) DEVICE_MODE = Register(0x0200, 'DEVICE_MODE', 'B', Mode.RW, None, DeviceMode) SETTINGS_CHANGED = Register(0xEC41, 'SETTINGS_CHANGED', 'I', Mode.RW) # Inverter operation registers HISTORY_TIME = Register(0x1040, 'HISTORY_TIME', 'I', Mode.R, 1, _ureg.second) HISTORY_ENERGY = Register(0x1041, 'HISTORY_ENERGY', 'I', Mode.R, 0.01 * 1000, _ureg.watthour) AC_OUT_CURRENT = Register(0x2201, 'AC_OUT_CURRENT', 'h', Mode.R, 0.1, _ureg.amp) AC_OUT_VOLTAGE = Register(0x2200, 'AC_OUT_VOLTAGE', 'h', Mode.R, 0.01, _ureg.volt) AC_OUT_APPARENT_POWER = Register(0x2205, 'AC_OUT_APPARENT_POWER', 'i', Mode.R, 1, _ureg.watt) INV_LOOP_GET_IINV = Register(0xEB4E, 'INV_LOOP_GET_IINV', 'h', Mode.R, 0.001, _ureg.amp) DC_CHANNEL1_VOLTAGE = Register(0xED8D, 'DC_CHANNEL1_VOLTAGE', 'h', Mode.R, 0.01, _ureg.volt) # User AC-out control registers AC_OUT_VOLTAGE_SETPOINT = Register(0x0230, 'AC_OUT_VOLTAGE_SETPOINT', 'H', Mode.W, 0.01, _ureg.volt) AC_OUT_VOLTAGE_SETPOINT_MIN = Register(0x0231, 'AC_OUT_VOLTAGE_SETPOINT_MIN', 'H', Mode.R, 0.01, _ureg.volt) AC_OUT_VOLTAGE_SETPOINT_MAX = Register(0x0232, 'AC_OUT_VOLTAGE_SETPOINT_MAX', 'H', Mode.R, 0.01, _ureg.volt) AC_LOAD_SENSE_POWER_THRESHOLD = Register(0x2206, 'AC_LOAD_SENSE_POWER_THRESHOLD', 'H', Mode.W, None, _ureg.watt) AC_LOAD_SENSE_POWER_CLEAR = Register(0x2207, 'AC_LOAD_SENSE_POWER_CLEAR', 'H', Mode.W, None, _ureg.watt) INV_WAVE_SET50HZ_NOT60HZ = Register(0xEB03, 'INV_WAVE_SET50HZ_NOT60HZ', 'B', Mode.W) INV_OPER_ECO_MODE_INV_MIN = Register(0xEB04, 'INV_OPER_ECO_MODE_INV_MIN', 'h', Mode.W, 0.001, _ureg.A) INV_OPER_ECO_MODE_RETRY_TIME = Register(0xEB06, 'INV_OPER_ECO_MODE_RETRY_TIME', 'B', Mode.W, 0.25, _ureg.second) INV_OPER_ECO_LOAD_DETECT_PERIODS = Register( 0xEB10, 'INV_OPER_ECO_LOAD_DETECT_PERIODS', 'B', Mode.W, 0.02, _ureg.hertz) # User battery control registers SHUTDOWN_LOW_VOLTAGE_SET = Register(0x2210, 'SHUTDOWN_LOW_VOLTAGE_SET', 'H', Mode.W, 0.01, _ureg.volt) ALARM_LOW_VOLTAGE_SET = Register(0x0320, 'ALARM_LOW_VOLTAGE_SET', 'H', Mode.W, 0.01, _ureg.volt) ALARM_LOW_VOLTAGE_CLEAR = Register(0x0321, 'ALARM_LOW_VOLTAGE_CLEAR', 'H', Mode.W, 0.01, _ureg.volt) VOLTAGE_RANGE_MIN = Register(0x2211, 'VOLTAGE_RANGE_MIN', 'H', Mode.R, 0.01, _ureg.volt) VOLTAGE_RANGE_MAX = Register(0x2212, 'VOLTAGE_RANGE_MAX', 'H', Mode.R, 0.01, _ureg.volt) # Datasheet says H. INV_PROT_UBAT_DYN_CUTOFF_ENABLE = Register(0xEBBA, 'INV_PROT_UBAT_DYN_CUTOFF_ENABLE', 'B', Mode.W) INV_PROT_UBAT_DYN_CUTOFF_FACTOR = Register(0xEBB7, 'INV_PROT_UBAT_DYN_CUTOFF_FACTOR', 'H', Mode.W) INV_PROT_UBAT_DYN_CUTOFF_FACTOR2000 = Register( 0xEBB5, 'INV_PROT_UBAT_DYN_CUTOFF_FACTOR2000', 'H', Mode.W) INV_PROT_UBAT_DYN_CUTOFF_FACTOR250 = Register( 0xEBB3, 'INV_PROT_UBAT_DYN_CUTOFF_FACTOR250', 'H', Mode.W) INV_PROT_UBAT_DYN_CUTOFF_FACTOR5 = Register( 0xEBB2, 'INV_PROT_UBAT_DYN_CUTOFF_FACTOR5', 'H', Mode.W) INV_PROT_UBAT_DYN_CUTOFF_VOLTAGE = Register( 0xEBB1, 'INV_PROT_UBAT_DYN_CUTOFF_VOLTAGE', 'H', Mode.R, 0.001, _ureg.volt) # Relay control registers class RelayMode(enum.IntEnum): # Normal operation. On during normal operation (warnings are ignored). NORMAL_OPERATION = 4 # Warnings and alarms. Off when a warning or alarm is active (inverter on). WARNINGS_AND_ALARMS = 0 # Battery low. Off when a low battery warning or alarm is active. BATTERY_LOW = 5 # External fan. On when the internal fan is on. EXTERNAL_FAN = 6 # Disabled relay. Always Off. DISABLED_RELAY = 3 # Remote. Controlled by writing to RELAY_CONTROL (0x034E). REMOTE = 2 RELAY_CONTROL = Register(0x034E, 'RELAY_CONTROL', 'B', Mode.RW) RELAY_MODE = Register(0x034F, 'RELAY_MODE', 'B', Mode.W, None, RelayMode) class NVMCommand(enum.IntEnum): NONE = 0 SAVE = 1 REVERT = 2 LOAD_BACKUP = 3 LOAD_DEFAULTS = 4 INV_NVM_COMMAND = Register(0xEB99, 'INV_NVM_COMMAND', 'B', Mode.W, None, NVMCommand) ID = int REGISTERS = { ID(r.id): r for r in globals().values() if isinstance(r, Register) } @dataclass class NVM: inv_nvm_command: int = Field(INV_NVM_COMMAND) @dataclass class Information: product_id: int = Field(PRODUCT_ID) product_revision: int = Field(PRODUCT_REVISION) app_ver: int = Field(APP_VER) serial_number: str = Field(SERIAL_NUMBER) model_name: str = Field(MODEL_NAME) ac_out_rated_power: float = Field(AC_OUT_RATED_POWER) capabilities: Capabilities = Field(CAPABILITIES) # capabilities_ble: float = Field(CAPABILITIES_BLE) ac_out_nom_voltage: float = Field(AC_OUT_NOM_VOLTAGE) bat_voltage: float = Field(BAT_VOLTAGE) @dataclass class Status: device_state: DeviceState = Field(DEVICE_STATE) device_off_reason: DeviceOffReason = Field(DEVICE_OFF_REASON) warning_reason: WarningReason = Field(WARNING_REASON) alarm_reason: int = Field(ALARM_REASON) @dataclass class DeviceControl: # ble_mode: float = Field(BLE_MODE) device_mode: DeviceMode = Field(DEVICE_MODE) settings_changed: int = Field(SETTINGS_CHANGED) @dataclass class Inverter: history_time: float = Field(HISTORY_TIME) history_energy: float = Field(HISTORY_ENERGY) ac_out_current: float = Field(AC_OUT_CURRENT) ac_out_voltage: float = Field(AC_OUT_VOLTAGE) # ac_out_apparent_power: float = Field(AC_OUT_APPARENT_POWER) inv_loop_get_iinv: float = Field(INV_LOOP_GET_IINV) dc_channel1_voltage: float = Field(DC_CHANNEL1_VOLTAGE) @dataclass class ACControl: ac_out_voltage_setpoint: float = Field(AC_OUT_VOLTAGE_SETPOINT) ac_out_voltage_setpoint_min: float = Field(AC_OUT_VOLTAGE_SETPOINT_MIN) ac_out_voltage_setpoint_max: float = Field(AC_OUT_VOLTAGE_SETPOINT_MAX) # ac_load_sense_power_threshold: float = Field( # AC_LOAD_SENSE_POWER_THRESHOLD) # ac_load_sense_power_clear: float = Field( # AC_LOAD_SENSE_POWER_CLEAR) inv_wave_set50hz_not60hz: int = Field(INV_WAVE_SET50HZ_NOT60HZ) inv_oper_eco_mode_inv_min: float = Field(INV_OPER_ECO_MODE_INV_MIN) inv_oper_eco_mode_retry_time: float = Field(INV_OPER_ECO_MODE_RETRY_TIME) inv_oper_eco_load_detect_periods: float = Field( INV_OPER_ECO_LOAD_DETECT_PERIODS) @dataclass class BatteryControl: shutdown_low_voltage_set: float = Field(SHUTDOWN_LOW_VOLTAGE_SET) alarm_low_voltage_set: float = Field(ALARM_LOW_VOLTAGE_SET) alarm_low_voltage_clear: float = Field(ALARM_LOW_VOLTAGE_CLEAR) voltage_range_min: float = Field(VOLTAGE_RANGE_MIN) voltage_range_max: float = Field(VOLTAGE_RANGE_MAX) inv_prot_ubat_dyn_cutoff_enable: int = Field( INV_PROT_UBAT_DYN_CUTOFF_ENABLE) inv_prot_ubat_dyn_cutoff_factor: int = Field( INV_PROT_UBAT_DYN_CUTOFF_FACTOR) inv_prot_ubat_dyn_cutoff_factor2000: int = Field( INV_PROT_UBAT_DYN_CUTOFF_FACTOR2000) inv_prot_ubat_dyn_cutoff_factor250: int = Field( INV_PROT_UBAT_DYN_CUTOFF_FACTOR250) inv_prot_ubat_dyn_cutoff_factor5: int = Field( INV_PROT_UBAT_DYN_CUTOFF_FACTOR5) inv_prot_ubat_dyn_cutoff_voltage: float = Field( INV_PROT_UBAT_DYN_CUTOFF_VOLTAGE) @dataclass class RelayControl: relay_control: float = Field(RELAY_CONTROL) relay_mode: float = Field(RELAY_MODE) GROUPS = ( Information, Status, DeviceControl, Inverter, ACControl, BatteryControl, NVM, ) for group in GROUPS: for field in dataclasses.fields(group): r = field.metadata.get('vedirect.Register', None) assert r is not None if field.type == int: assert r.scale is None, r elif field.type == float: assert r.scale is not None, r