vedirect/vedirect/phoenix.py
Michael Hope 66cb355392
All checks were successful
continuous-integration/drone/push Build is passing
vedirect: add the Phenix NVM group
2021-04-18 14:34:22 +02:00

430 lines
17 KiB
Python

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