325 lines
9.7 KiB
Python
325 lines
9.7 KiB
Python
from dataclasses import dataclass
|
|
import enum
|
|
from typing import Callable, Union
|
|
|
|
import pint
|
|
|
|
_ureg = pint.UnitRegistry()
|
|
|
|
W = _ureg.watt
|
|
V = _ureg.volt
|
|
A = _ureg.amp
|
|
C = float # _ureg.degree_Celsius
|
|
K = _ureg.kelvin
|
|
Hours = _ureg.hour
|
|
mVK = float # _ureg.mvolt * 1e-3 / _ureg.kelvin
|
|
Percent = float
|
|
kWh = float
|
|
Minute = _ureg.minute
|
|
Unknown = int
|
|
|
|
|
|
class State(enum.IntEnum):
|
|
"""State is the state of operation. Sent in the `CS` field."""
|
|
# Off
|
|
OFF = 0
|
|
# Low power
|
|
LOW_POWER = 1
|
|
# Fault
|
|
FAULT = 2
|
|
# Bulk
|
|
BULK = 3
|
|
# Absorption
|
|
ABSORPTION = 4
|
|
# Float
|
|
FLOAT = 5
|
|
# Storage
|
|
STORAGE = 6
|
|
# Equalize (manual)
|
|
EQUALIZE_MANUAL = 7
|
|
# Inverting
|
|
INVERTING = 9
|
|
# Power supply
|
|
POWER_SUPPLY = 11
|
|
# Starting-up
|
|
STARTING_UP = 245
|
|
# Repeated absorption
|
|
REPEATED_ABSORPTION = 246
|
|
# Auto equalize / Recondition
|
|
AUTO_EQUALIZE_RECONDITION = 247
|
|
# BatterySafe
|
|
BATTERYSAFE = 248
|
|
# External Control
|
|
EXTERNAL_CONTROL = 252
|
|
|
|
|
|
class Capabilities(enum.IntFlag):
|
|
LOAD_OUTPUT_PRESENT = 1 << 0
|
|
ROTARY_ENCODER_PRESENT = 1 << 1
|
|
HISTORY_SUPPORT = 1 << 2
|
|
BATTERYSAFE_MODE = 1 << 3
|
|
ADAPTIVE_MODE = 1 << 4
|
|
MANUAL_EQUALISE = 1 << 5
|
|
AUTOMATIC_EQUALISE = 1 << 6
|
|
STORAGE_MODE = 1 << 7
|
|
REMOTE_ON_OFF_VIA_RX_PIN = 1 << 8
|
|
SOLAR_TIMER_STREETLIGHTING = 1 << 9
|
|
ALTERNATIVE_VEDIRECT_TX_PIN_FUNCTION = 1 << 10
|
|
USER_DEFINED_LOAD_SWITCH = 1 << 11
|
|
LOAD_CURRENT_IN_TEXT_PROTOCOL = 1 << 12
|
|
PANEL_CURRENT = 1 << 13
|
|
BMS_SUPPORT = 1 << 14
|
|
EXTERNAL_CONTROL_SUPPORT = 1 << 15
|
|
PARALLEL_CHARGING_SUPPORT = 1 << 16
|
|
ALARM_RELAY = 1 << 17
|
|
ALTERNATIVE_VEDIRECT_RX_PIN_FUNCTION = 1 << 18
|
|
VIRTUAL_LOAD_OUTPUT = 1 << 19
|
|
VIRTUAL_RELAY = 1 << 20
|
|
PLUGIN_DISPLAY_SUPPORT = 1 << 21
|
|
UNDEFINED_24 = 1 << 24
|
|
LOAD_AUTOMATIC_ENERGY_SELECTOR = 1 << 25
|
|
BATTERY_TEST = 1 << 26
|
|
PAYGO_SUPPORT = 1 << 27
|
|
|
|
|
|
class Err(enum.IntEnum):
|
|
"""Err is the error code of the device. Sent in the `ERR` field."""
|
|
# No error
|
|
NO_ERROR = 0
|
|
# Battery voltage too high
|
|
BATTERY_VOLTAGE_TOO_HIGH = 2
|
|
# Charger temperature too high
|
|
CHARGER_TEMPERATURE_TOO_HIGH = 17
|
|
# Charger over current
|
|
CHARGER_OVER_CURRENT = 18
|
|
# Charger current reversed
|
|
CHARGER_CURRENT_REVERSED = 19
|
|
# Bulk time limit exceeded
|
|
BULK_TIME_LIMIT_EXCEEDED = 20
|
|
# Current sensor issue (sensor bias/sensor broken)
|
|
CURRENT_SENSOR_ISSUE = 21
|
|
# Terminals overheated
|
|
TERMINALS_OVERHEATED = 26
|
|
# Input voltage too high (solar panel)
|
|
INPUT_VOLTAGE_TOO_HIGH_SOLAR_PANEL = 33
|
|
# Input current too high (solar panel)
|
|
INPUT_CURRENT_TOO_HIGH_SOLAR_PANEL = 34
|
|
# Input shutdown (due to excessive battery voltage)
|
|
INPUT_SHUTDOWN = 38
|
|
# Factory calibration data lost
|
|
FACTORY_CALIBRATION_DATA_LOST = 116
|
|
# Invalid/incompatible firmware
|
|
INVALID_OR_INCOMPATIBLE_FIRMWARE = 117
|
|
# User settings invalid
|
|
USER_SETTINGS_INVALID = 119
|
|
|
|
|
|
class OffReason(enum.IntFlag):
|
|
NO_INPUT_POWER = 1 << 0
|
|
RESERVED = 1 << 1
|
|
SOFT_POWER_SWITCH = 1 << 2
|
|
REMOTE_INPUT = 1 << 3
|
|
INTERNAL_REASOn = 1 << 4
|
|
PAY_AS_YOU_GO_OUT_OF_CREDIT = 1 << 5
|
|
BMS_SHUTDOWN = 1 << 6
|
|
RESERVED_2 = 1 << 7
|
|
|
|
|
|
class AdditionalChargerState(enum.IntFlag):
|
|
SAFE_MODE_ACTIVE = 1 << 0
|
|
AUTOMATIC_EQUALISATION_ACTIVE = 1 << 1
|
|
TEMPERATURE_DIMMING_ACTIVeE = 1 << 4
|
|
INPUT_CURRENT_DIMMING_ACTIVE = 1 << 6
|
|
|
|
|
|
class LoadOutputOffReason(enum.IntFlag):
|
|
BATTERY_LOW = 1 << 0
|
|
SHORT_CIRCUIT = 1 << 1
|
|
TIMER_PROGRAM = 1 << 2
|
|
REMOTE_INPUT = 1 << 3
|
|
PAY_AS_YOU_GO_OUT_OF_CREDIT = 1 << 4
|
|
RESERVED = 1 << 5
|
|
RESERVED_2 = 1 << 6
|
|
DEVICE_STARTING_UP = 1 << 7
|
|
|
|
|
|
class LoadOutputControl(enum.IntEnum):
|
|
LOAD_OUTPUT_OFF = 0
|
|
AUTOMATIC_CONTROL_BATTERYLIFE = 1
|
|
ALTERNATIVE_CONTROL_1 = 2
|
|
ALTERNATIVE_CONTROL_2 = 3
|
|
LOAD_OUTPUT_ON = 4
|
|
USER_DEFINED_SETTINGS_1 = 5
|
|
USER_DEFINED_SETTINGS_2 = 6
|
|
AUTOMATIC_ENERGY_SELECTOR = 7
|
|
|
|
|
|
class RelayMode(enum.IntEnum):
|
|
RELAY_ALWAYS_OFF = 0
|
|
PANEL_VOLTAGE_HIGH = 1
|
|
INTERNAL_TEMPERATURE_HIGH = 2
|
|
BATTERY_VOLTAGE_TOO_LOW = 3
|
|
EQUALISATION_ACTIVE = 4
|
|
ERROR_CONDITION_PRESENT = 5
|
|
INTERNAL_TEMPERATURE_LOW = 6
|
|
BATTERY_VOLTAGE_TOO_HIGH = 7
|
|
CHARGER_IN_FLOAT_OR_STORAGE = 8
|
|
DAY_DETECTION = 9
|
|
LOAD_CONTROL = 10
|
|
|
|
|
|
class TXPortMode(enum.IntEnum):
|
|
NORMAL_VEDIRECT_COMMUNICATION = 0
|
|
PULSE_ON_HARVEST = 1
|
|
LIGHTING_CONTROL_PWM_NORMAL = 2
|
|
LIGHTING_CONTROL_PWM_INVERTED = 3
|
|
VIRTUAL_LOAD_OUTPUT = 4
|
|
|
|
|
|
class RXPortMode(enum.IntEnum):
|
|
REMOTE_ON_OFF = 0
|
|
LOAD_OUTPUT_CONFIGURATION = 1
|
|
LOAD_OUTPUT_ON_OFF_REMOTE_CONTROL_INVERTED = 2
|
|
LOAD_OUTPUT_ON_OFF_REMOTE_CONTROL = 3
|
|
|
|
|
|
class TrackerMode(enum.IntEnum):
|
|
OFF = 0
|
|
LIMITED = 1
|
|
MPPT = 2
|
|
|
|
|
|
# Schema
|
|
@dataclass
|
|
class Register:
|
|
"""Defines a single register on the controller."""
|
|
command: int
|
|
scale: Union[float, Callable, None]
|
|
size: Union[str, Callable]
|
|
|
|
|
|
class Group:
|
|
"""Tags a group of registers."""
|
|
pass
|
|
|
|
|
|
@dataclass
|
|
class Product(Group):
|
|
id: int = Register(0x0100, None, 'I')
|
|
group_id: int = Register(0x0104, None, 'B')
|
|
serial_number: str = Register(0x010A, None, str)
|
|
model_name: str = Register(0x010B, None, str)
|
|
capabilities: Capabilities = Register(0x0140, Capabilities, 'I')
|
|
|
|
|
|
@dataclass
|
|
class Device(Group):
|
|
mode: int = Register(0x200, None, 'B')
|
|
state: State = Register(0x201, State, 'B')
|
|
remote_control_used: Unknown = Register(0x202, None, 'I')
|
|
off_reason: OffReason = Register(0x0207, OffReason, 'I')
|
|
|
|
|
|
@dataclass
|
|
class Load(Group):
|
|
current: A = Register(0xEDAD, 0.1, 'H')
|
|
offset_voltage: V = Register(0xEDAC, 0.01, 'B') # Spec says H.
|
|
output_control: LoadOutputControl = Register(0xEDAB, LoadOutputControl,
|
|
'B')
|
|
output_voltage: V = Register(0xEDA9, 0.01, 'H')
|
|
output_state: Unknown = Register(0xEDA8, None, 'B')
|
|
switch_high_level: V = Register(0xED9D, 0.01, 'H')
|
|
switch_low_level: V = Register(0xED9C, 0.01, 'H')
|
|
output_off_reason: LoadOutputOffReason = Register(0xED91,
|
|
LoadOutputOffReason, 'B')
|
|
aes_timer: Minute = Register(0xED90, 1, 'H')
|
|
|
|
|
|
@dataclass
|
|
class Relay(Group):
|
|
relay_operation_mode: RelayMode = Register(0xEDD9, RelayMode, 'B')
|
|
battery_low_voltage_set: V = Register(0x0350, 0.01, 'H')
|
|
battery_low_voltage_clear: V = Register(0x0351, 0.01, 'H')
|
|
battery_high_voltage_set: V = Register(0x0352, 0.01, 'H')
|
|
battery_high_voltage_clear: V = Register(0x0353, 0.01, 'H')
|
|
panel_high_voltage_set: V = Register(0xEDBA, 0.01, 'H')
|
|
panel_high_voltage_clear: V = Register(0xEDB9, 0.01, 'H')
|
|
minimum_enabled_time: Minute = Register(0x100A, 1, 'H')
|
|
|
|
|
|
@dataclass
|
|
class Charger(Group):
|
|
battery_temperature: K = Register(0xEDEC, 0.01, 'H')
|
|
maximum_current: A = Register(0xEDDF, 0.01, 'H')
|
|
system_yield: kWh = Register(0xEDDD, 0.01, 'I')
|
|
user_yield: kWh = Register(0xEDDC, 0.01, 'I')
|
|
internal_temperature: C = Register(0xEDDB, 0.01, 'h')
|
|
error_code: Err = Register(0xEDDA, Err, 'B')
|
|
current: A = Register(0xEDD7, 0.1, 'H')
|
|
voltage: V = Register(0xEDD5, 0.01, 'H')
|
|
additional_state_info: AdditionalChargerState = Register(
|
|
0xEDD4, AdditionalChargerState, 'B')
|
|
yield_today: kWh = Register(0xEDD3, 0.01, 'H')
|
|
maximum_power_today: W = Register(0xEDD2, 1, 'H')
|
|
yield_yesterday: kWh = Register(0xEDD1, 0.01, 'H')
|
|
maximum_power_yesterday: W = Register(0xEDD0, 1, 'H')
|
|
voltage_settings: Unknown = Register(0xEDCE, None, 'H')
|
|
history_version: Unknown = Register(0xEDCD, None, 'B')
|
|
streetlight_version: Unknown = Register(0xEDCC, None, 'B')
|
|
adjustable_voltage_minimum: V = Register(0x2211, 0.01, 'H')
|
|
|
|
|
|
@dataclass
|
|
class Panel(Group):
|
|
power: W = Register(0xEDBC, 0.01, 'I')
|
|
voltage: V = Register(0xEDBB, 0.01, 'H')
|
|
current: A = Register(0xEDBD, 0.1, 'H')
|
|
maximum_voltage: V = Register(0xEDB8, 0.01, 'H')
|
|
tracker_mode: Unknown = Register(0xEDB3, None, 'B')
|
|
|
|
|
|
@dataclass
|
|
class Battery(Group):
|
|
batterysafe_mode: bool = Register(0xEDFF, None, 'B')
|
|
adaptive_mode: bool = Register(0xEDFE, None, 'B')
|
|
automatic_equalisation_mode: int = Register(0xEDFD, None, 'B')
|
|
bulk_time_limit: Hours = Register(0xEDFC, 0.01, 'H')
|
|
absorption_time_limit: Hours = Register(0xEDFB, 0.01, 'H')
|
|
absorption_voltage: V = Register(0xEDF7, 0.01, 'H')
|
|
float_voltage: V = Register(0xEDF6, 0.01, 'H')
|
|
equalisation_voltage: V = Register(0xEDF4, 0.01, 'H')
|
|
temp_compensation: mVK = Register(0xEDF2, 0.01, 'h')
|
|
type: int = Register(0xedf1, 1, 'b')
|
|
maximum_current: A = Register(0xEDF0, 0.1, 'H')
|
|
voltage: V = Register(0xEDEF, 1, 'B')
|
|
temperature: K = Register(0xEDEC, 0.01, 'H')
|
|
voltage_setting: V = Register(0xEDEA, 1, 'B')
|
|
bms_present: bool = Register(0xEDE8, None, 'B')
|
|
tail_current: A = Register(0xEDE7, 0.1, 'H')
|
|
low_temperature_charge_current: A = Register(0xEDE6, 0.1, 'H')
|
|
auto_equalise_stop_on_voltage: bool = Register(0xEDE5, None, 'B')
|
|
equalisation_current_level: Percent = Register(0xEDE4, 1, 'B')
|
|
equalisation_duration: Hours = Register(0xEDE3, 0.01, 'H')
|
|
re_bulk_voltage_offset: V = Register(0xED2E, 0.01, 'H')
|
|
low_temperature_level: C = Register(0xEDE0, 0.01, 'h')
|
|
voltage_compensation: V = Register(0xEDCA, 0.01, 'H')
|
|
|
|
|
|
@dataclass
|
|
class VEDirectPort(Group):
|
|
tx_port_mode: TXPortMode = Register(0xED9E, TXPortMode, 'B')
|
|
rx_port_mode: RXPortMode = Register(0xED98, RXPortMode, 'B')
|
|
|
|
|
|
@dataclass
|
|
class Registers:
|
|
"""The registers on the device."""
|
|
product: Product = Product()
|
|
device: Device = Device()
|
|
charger: Charger = Charger()
|
|
load: Load = Load()
|
|
panel: Panel = Panel()
|
|
battery: Battery = Battery()
|
|
relay: Relay = Relay()
|
|
vedirect_port: VEDirectPort = VEDirectPort()
|