vedirect/vedirect/schema.py

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()