From 4cd13438495aac994885790aa3b7d3e089505bc9 Mon Sep 17 00:00:00 2001 From: David Rapan Date: Sun, 14 Jul 2024 16:00:08 +0200 Subject: [PATCH] Fix/Feat: Register reading/parsing and timings... Due to security reasons: - Temporarily removed some custom sensor *templating (Battery State, Battery SOH..) - Reworked some simple custom sensors like additions and subtracting to work directly with registers (PV Power, Power losses, Total Losses and Today Losses..) Extended signed integer reading: - Added Solarman Smart Meter profile Updated some inverter profiles according to the changes, etc. Asynchronous file open using aiofiles Asynchronous directory listing using asyncio Sorting inverter profiles Discovery attempts from global value --- custom_components/solarman/api.py | 91 +- custom_components/solarman/common.py | 6 + custom_components/solarman/config_flow.py | 13 +- custom_components/solarman/const.py | 27 +- custom_components/solarman/coordinator.py | 4 +- custom_components/solarman/discovery.py | 9 +- .../afore_BNTxxxKTL-2mppt.yaml | 10 +- .../inverter_definitions/deye_2mppt.yaml | 758 ++++---- .../inverter_definitions/deye_4mppt.yaml | 495 ++--- .../inverter_definitions/deye_hybrid.yaml | 1639 +++++++++-------- .../inverter_definitions/deye_sg01hp3.yaml | 103 +- .../inverter_definitions/deye_sg04lp3.yaml | 124 ++ .../deye_sun-12k-sg04lp3.yaml | 139 +- .../solarman_dtsd422-d3.yaml | 456 +++++ custom_components/solarman/parser.py | 193 +- custom_components/solarman/sensor.py | 188 +- custom_components/solarman/services.py | 16 +- 17 files changed, 2511 insertions(+), 1760 deletions(-) create mode 100644 custom_components/solarman/inverter_definitions/solarman_dtsd422-d3.yaml diff --git a/custom_components/solarman/api.py b/custom_components/solarman/api.py index 63827e1..e7d8fc3 100644 --- a/custom_components/solarman/api.py +++ b/custom_components/solarman/api.py @@ -1,7 +1,7 @@ -import socket import time import yaml import struct +import socket import logging import asyncio import aiofiles @@ -18,16 +18,11 @@ _LOGGER = logging.getLogger(__name__) -def read_file(filepath): - with open(filepath) as file: - return file.read() - class InverterApi(PySolarmanV5Async): def __init__(self, address, serial, port, mb_slave_id): - super().__init__(address, serial, port = port, mb_slave_id = mb_slave_id, logger = _LOGGER, auto_reconnect = True, socket_timeout = COORDINATOR_TIMEOUT) - self._last_frame: bytes = b"" - self.status = -1 + super().__init__(address, serial, port = port, mb_slave_id = mb_slave_id, logger = _LOGGER, auto_reconnect = True, socket_timeout = TIMINGS_SOCKET_TIMEOUT) self.status_lastUpdate = "N/A" + self.status = -1 def is_connecting(self): return self.status == 0 @@ -53,7 +48,7 @@ async def reconnect(self) -> None: self.writer.write(self._last_frame) await self.writer.drain() except Exception as e: - self.log.exception(format_exception(e)) + self.log.exception(f"Cannot open connection to {self.address}. [{format_exception(e)}]") async def _send_receive_v5_frame(self, data_logging_stick_frame): """ @@ -70,14 +65,14 @@ async def _send_receive_v5_frame(self, data_logging_stick_frame): v5_response = await asyncio.wait_for(self.data_queue.get(), self.socket_timeout) if v5_response == b"": raise NoSocketAvailableError("Connection closed on read. Retry if auto-reconnect is enabled") - except AttributeError as exc: - raise NoSocketAvailableError("Connection already closed") from exc + except AttributeError as e: + raise NoSocketAvailableError("Connection already closed") from e except NoSocketAvailableError: raise except TimeoutError: raise - except Exception as exc: - self.log.exception("[%s] Send/Receive error: %s", self.serial, exc) + except Exception as e: + self.log.exception("[%s] Send/Receive error: %s", self.serial, e) raise finally: self.data_wanted_ev.clear() @@ -88,11 +83,10 @@ async def _send_receive_v5_frame(self, data_logging_stick_frame): async def async_connect(self) -> None: if self.reader_task: _LOGGER.debug(f"Reader Task done: {self.reader_task.done()}, cancelled: {self.reader_task.cancelled()}.") - #if not self.reader_task or self.reader_task.done() or self.reader_task.cancelled(): - if not self.reader_task: + if not self.reader_task: #if not self.reader_task or self.reader_task.done() or self.reader_task.cancelled(): _LOGGER.info(f"Connecting to {self.address}:{self.port}") await self.connect() - elif not self.is_connected(): + elif not self.status > 0: await self.reconnect() async def async_disconnect(self, loud = True) -> None: @@ -112,14 +106,11 @@ async def async_disconnect(self, loud = True) -> None: self.writer.close() await self.writer.wait_closed() - async def async_reconnect(self) -> None: - await self.async_disconnect(False) - loop = asyncio.get_running_loop() - loop.create_task(self.reconnect()) - - async def _read_registers(self, code, params, start, end) -> None: + async def async_read(self, params, code, start, end) -> None: length = end - start + 1 + await self.async_connect() + match code: case 3: response = await self.read_holding_registers(register_addr = start, quantity = length) @@ -128,24 +119,17 @@ async def _read_registers(self, code, params, start, end) -> None: params.parse(response, start, length) - async def async_read(self, code, params, start, end) -> None: - await self.async_connect() - await self._read_registers(code, params, start, end) - class Inverter(InverterApi): - def __init__(self, address, mac, serial, port, mb_slave_id, lookup_path, lookup_file): + def __init__(self, address, serial, port, mb_slave_id, name, mac, lookup_path, lookup_file): super().__init__(address, serial, port, mb_slave_id) + self.name = name self.mac = mac self.lookup_path = lookup_path self.lookup_file = lookup_file if lookup_file and not lookup_file == "parameters.yaml" else "deye_hybrid.yaml" - async def async_load(self): - loop = asyncio.get_running_loop() - self.parameter_definition = await loop.run_in_executor(None, lambda: yaml.safe_load(read_file(self.path + self.lookup_file))) - async def get_sensors(self): async with aiofiles.open(self.lookup_path + self.lookup_file) as f: - self.parameter_definition = await f.read() + self.parameter_definition = yaml.safe_load(await f.read()) if self.parameter_definition: params = ParameterParser(self.parameter_definition) return params.get_sensors() @@ -162,9 +146,7 @@ def get_result(self, middleware = None): self.status_lastUpdate = datetime.now().strftime("%m/%d/%Y, %H:%M:%S") self.status = 1 - result = middleware.get_result() if middleware else {} - result["Connection Status"] = self.get_connection_status() - return result + return middleware.get_result() if middleware else {} async def async_get_failed(self, message): _LOGGER.debug(f"Request failed. [Previous Status: {self.get_connection_status()}]") @@ -181,30 +163,30 @@ async def async_get(self, runtime = 0): requests_count = len(requests) result = 0 - _LOGGER.debug(f"Scheduling {requests_count} query requests. #{runtime}") + _LOGGER.debug(f"Scheduling {requests_count} query requests. #{runtime}") try: for request in requests: - code = request['mb_functioncode'] - start = request['start'] - end = request['end'] + code = request["mb_functioncode"] + start = request["start"] + end = request["end"] _LOGGER.debug(f"Querying ({start} - {end}) ...") - attempts_left = COORDINATOR_QUERY_RETRY_ATTEMPTS + attempts_left = ACTION_RETRY_ATTEMPTS while attempts_left > 0: attempts_left -= 1 try: - await self.async_read(code, params, start, end) + await self.async_read(params, code, start, end) result = 1 except (V5FrameError, TimeoutError, Exception) as e: result = 0 - if not isinstance(e, TimeoutError) or not attempts_left > 0 or _LOGGER.isEnabledFor(logging.DEBUG): + if not isinstance(e, TimeoutError) or not attempts_left >= 1 or _LOGGER.isEnabledFor(logging.DEBUG): _LOGGER.warning(f"Querying ({start} - {end}) failed. #{runtime} [{format_exception(e)}]") - await asyncio.sleep(COORDINATOR_ERROR_SLEEP) + await asyncio.sleep(TIMINGS_QUERY_EXCEPT_SLEEP) _LOGGER.debug(f"Querying {'succeeded.' if result == 1 else f'attempts left: {attempts_left}{'' if attempts_left > 0 else ', aborting.'}'}") @@ -222,27 +204,28 @@ async def async_get(self, runtime = 0): except UpdateFailed: raise except Exception as e: - await self.async_get_failed(f"Querying {self.serial} at {self.address}:{self.port} failed during connection start. [{format_exception(e)}]") + await self.async_get_failed(f"Querying {self.serial} at {self.address}:{self.port} failed. [{format_exception(e)}]") return self.get_result() -# Service calls - async def service_write_holding_register(self, register, value): - _LOGGER.debug(f'Service Call: write_holding_register : [{register}], value : [{value}]') + async def service_write_holding_register(self, register, value) -> bool: + _LOGGER.debug(f"service_write_holding_register: {register}, value: {value}") try: await self.async_connect() await self.write_holding_register(register, value) except Exception as e: - _LOGGER.warning(f"Service Call: write_holding_register : [{register}], value : [{value}] failed. [{format_exception(e)}]") + _LOGGER.warning(f"service_write_holding_register: {register}, value: {value} failed. [{format_exception(e)}]") await self.async_disconnect() - return + return False + return True - async def service_write_multiple_holding_registers(self, register, values): - _LOGGER.debug(f'Service Call: write_multiple_holding_registers: [{register}], values : [{values}]') + async def service_write_multiple_holding_registers(self, registers, values) -> bool: + _LOGGER.debug(f"service_write_multiple_holding_registers: {registers}, values: {values}") try: await self.async_connect() - await self.write_multiple_holding_registers(register, values) + await self.write_multiple_holding_registers(registers, values) except Exception as e: - _LOGGER.warning(f"Service Call: write_multiple_holding_registers: [{register}], values : [{values}] failed. [{format_exception(e)}]") + _LOGGER.warning(f"service_write_multiple_holding_registers: {registers}, values: {values} failed. [{format_exception(e)}]") await self.async_disconnect() - return + return False + return True diff --git a/custom_components/solarman/common.py b/custom_components/solarman/common.py index 1d3dd49..8c71d1b 100644 --- a/custom_components/solarman/common.py +++ b/custom_components/solarman/common.py @@ -1,3 +1,9 @@ +import asyncio + +async def async_execute(x): + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, x) + def group_when(iterable, predicate): i, x, size = 0, 0, len(iterable) while i < size - 1: diff --git a/custom_components/solarman/config_flow.py b/custom_components/solarman/config_flow.py index a2fe960..c2f8585 100644 --- a/custom_components/solarman/config_flow.py +++ b/custom_components/solarman/config_flow.py @@ -17,6 +17,7 @@ from homeassistant.exceptions import HomeAssistantError from .const import * +from .common import * from .discovery import InverterDiscovery _LOGGER = logging.getLogger(__name__) @@ -31,8 +32,8 @@ async def step_user_data_process(discovery): _LOGGER.debug(f"step_user_data_process: discovery: {discovery}") return { CONF_NAME: DEFAULT_NAME, CONF_INVERTER_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_HOST: await discovery.get_ip(), CONF_INVERTER_SERIAL: await discovery.get_serial(), CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING, CONF_DISABLE_TEMPLATING: DEFAULT_DISABLE_TEMPLATING } -def step_user_data_schema(hass: HomeAssistant, data: dict[str, Any] = { CONF_NAME: DEFAULT_NAME, CONF_INVERTER_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING, CONF_DISABLE_TEMPLATING: DEFAULT_DISABLE_TEMPLATING }) -> Schema: - lookup_files = [f for f in os.listdir(hass.config.path(LOOKUP_DIRECTORY_PATH)) if os.path.isfile(LOOKUP_DIRECTORY_PATH + f)] +async def step_user_data_schema(hass: HomeAssistant, data: dict[str, Any] = { CONF_NAME: DEFAULT_NAME, CONF_INVERTER_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING, CONF_DISABLE_TEMPLATING: DEFAULT_DISABLE_TEMPLATING }) -> Schema: + lookup_files = sorted([f for f in await async_execute(lambda: os.listdir(hass.config.path(LOOKUP_DIRECTORY_PATH))) if os.path.isfile(LOOKUP_DIRECTORY_PATH + f)]) _LOGGER.debug(f"step_user_data_schema: data: {data}, {LOOKUP_DIRECTORY_PATH}: {lookup_files}") STEP_USER_DATA_SCHEMA = vol.Schema( { @@ -90,7 +91,7 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con _LOGGER.debug(f"ConfigFlowHandler.async_step_user: {user_input}") if user_input is None: discovery_options = await step_user_data_process(InverterDiscovery(self.hass)) - return self.async_show_form(step_id = "user", data_schema = step_user_data_schema(self.hass, discovery_options)) + return self.async_show_form(step_id = "user", data_schema = await step_user_data_schema(self.hass, discovery_options)) errors = {} @@ -112,7 +113,7 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con _LOGGER.debug(f"ConfigFlowHandler.async_step_user: validation failed: {user_input}") - return self.async_show_form(step_id = "user", data_schema = step_user_data_schema(self.hass, user_input), errors = errors) + return self.async_show_form(step_id = "user", data_schema = await step_user_data_schema(self.hass, user_input), errors = errors) @staticmethod @callback @@ -133,7 +134,7 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Con """Handle options flow.""" _LOGGER.debug(f"OptionsFlowHandler.async_step_init: {user_input}") if user_input is None: - return self.async_show_form(step_id = "init", data_schema = step_user_data_schema(self.hass, self.entry.options)) + return self.async_show_form(step_id = "init", data_schema = await step_user_data_schema(self.hass, self.entry.options)) errors = {} @@ -149,7 +150,7 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Con else: return self.async_create_entry(title = info["title"], data = user_input) - return self.async_show_form(step_id = "init", data_schema = step_user_data_schema(self.hass, user_input), errors = errors) + return self.async_show_form(step_id = "init", data_schema = await step_user_data_schema(self.hass, user_input), errors = errors) class InvalidHost(HomeAssistantError): """Error to indicate there is invalid hostname or IP address.""" diff --git a/custom_components/solarman/const.py b/custom_components/solarman/const.py index 50ec447..9186070 100644 --- a/custom_components/solarman/const.py +++ b/custom_components/solarman/const.py @@ -4,12 +4,14 @@ PLATFORMS: list[str] = ["sensor"] SENSOR_PREFIX = "Solarman" -DISCOVERY_MESSAGE = "WIFIKIT-214028-READ" DISCOVERY_PORT = 48899 +DISCOVERY_MESSAGE = "WIFIKIT-214028-READ" DISCOVERY_RECV_MESSAGE_SIZE = 1024 +COMPONENTS_DIRECTORY = "custom_components" + LOOKUP_DIRECTORY = "inverter_definitions" -LOOKUP_DIRECTORY_PATH = f"custom_components/{DOMAIN}/{LOOKUP_DIRECTORY}/" +LOOKUP_DIRECTORY_PATH = f"{COMPONENTS_DIRECTORY}/{DOMAIN}/{LOOKUP_DIRECTORY}/" CONF_INVERTER_DISCOVERY = "inverter_discovery" CONF_INVERTER_HOST = "inverter_host" @@ -25,21 +27,18 @@ DEFAULT_DISCOVERY = True DEFAULT_PORT_INVERTER = 8899 DEFAULT_INVERTER_MB_SLAVE_ID = 1 -DEFAULT_LOOKUP_FILE = "deye_sg04lp3.yaml" +DEFAULT_LOOKUP_FILE = "deye_hybrid.yaml" DEFAULT_BATTERY_NOMINAL_VOLTAGE = 48 DEFAULT_BATTERY_LIFE_CYCLE_RATING = 6000 DEFAULT_DISABLE_TEMPLATING = False -COORDINATOR_INTERVAL = 5 -COORDINATOR_INTERVAL_DEFAULT = 60 -COORDINATOR_UPDATE_INTERVAL = td(seconds = COORDINATOR_INTERVAL) -COORDINATOR_TIMEOUT = 15 -COORDINATOR_TIMEOUT2 = 30 -COORDINATOR_ERROR_SLEEP = 4 +ACTION_RETRY_ATTEMPTS = 5 -COORDINATOR_SOCKET_TIMEOUT = 30 / 2 -COORDINATOR_QUERY_INTERVAL_DEFAULT = 60 -COORDINATOR_QUERY_RETRY_ATTEMPTS = 4 -COORDINATOR_QUERY_ERROR_SLEEP = 4 +TIMINGS_INTERVAL = 5 +TIMINGS_COORDINATOR = td(seconds = TIMINGS_INTERVAL) +TIMINGS_COORDINATOR_TIMEOUT = TIMINGS_INTERVAL * 6 +TIMINGS_SOCKET_TIMEOUT = TIMINGS_INTERVAL * 3 - 1 +TIMINGS_QUERY_INTERVAL_DEFAULT = 60 +TIMINGS_QUERY_EXCEPT_SLEEP = 4 -FLOAT_ROUND_TO = 6 \ No newline at end of file +DIGITS_DEFAULT = 6 \ No newline at end of file diff --git a/custom_components/solarman/coordinator.py b/custom_components/solarman/coordinator.py index 22a3928..68f8fbd 100644 --- a/custom_components/solarman/coordinator.py +++ b/custom_components/solarman/coordinator.py @@ -14,7 +14,7 @@ class InverterCoordinator(DataUpdateCoordinator[dict[str, Any]]): def __init__(self, hass: HomeAssistant, inverter): - super().__init__(hass, _LOGGER, name = SENSOR_PREFIX, update_interval = COORDINATOR_UPDATE_INTERVAL, always_update = False) + super().__init__(hass, _LOGGER, name = SENSOR_PREFIX, update_interval = TIMINGS_COORDINATOR, always_update = False) self.inverter = inverter self.counter = -1 @@ -29,7 +29,7 @@ def _accounting(self): return int(self.counter * self._update_interval_seconds) async def _async_update_data(self) -> dict[str, Any]: - async with asyncio.timeout(COORDINATOR_TIMEOUT2): + async with asyncio.timeout(TIMINGS_COORDINATOR_TIMEOUT): return await self.inverter.async_get(self._accounting()) #async def _reload(self): diff --git a/custom_components/solarman/discovery.py b/custom_components/solarman/discovery.py index 79febcf..3c92d41 100644 --- a/custom_components/solarman/discovery.py +++ b/custom_components/solarman/discovery.py @@ -15,6 +15,7 @@ _LOGGER = logging.getLogger(__name__) class InverterDiscovery: + _port = DISCOVERY_PORT _message = DISCOVERY_MESSAGE.encode() def __init__(self, hass: HomeAssistant, address = None): @@ -24,7 +25,7 @@ def __init__(self, hass: HomeAssistant, address = None): self._mac = None self._serial = None - async def _discover(self, address = ""): + async def _discover(self, address = "", source = "0.0.0.0"): loop = asyncio.get_running_loop() try: @@ -33,8 +34,9 @@ async def _discover(self, address = ""): sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.setblocking(False) sock.settimeout(1.0) + #sock.bind((source, 0)) - await loop.sock_sendto(sock, self._message, (address, DISCOVERY_PORT)) + await loop.sock_sendto(sock, self._message, (address, self._port)) while True: try: @@ -62,6 +64,7 @@ async def _discover_all(self): _LOGGER.debug(f"_discover_all: Broadcasting on {net.with_prefixlen}") await self._discover(str(IPv4Network(net, False).broadcast_address)) + #await self._discover("", ipv4["address"]) if self._ip is not None: return None @@ -70,7 +73,7 @@ async def discover(self): if self._address: await self._discover(self._address) - attempts_left = COORDINATOR_QUERY_RETRY_ATTEMPTS + attempts_left = ACTION_RETRY_ATTEMPTS while self._ip is None and attempts_left > 0: attempts_left -= 1 diff --git a/custom_components/solarman/inverter_definitions/afore_BNTxxxKTL-2mppt.yaml b/custom_components/solarman/inverter_definitions/afore_BNTxxxKTL-2mppt.yaml index 627761f..1589d74 100644 --- a/custom_components/solarman/inverter_definitions/afore_BNTxxxKTL-2mppt.yaml +++ b/custom_components/solarman/inverter_definitions/afore_BNTxxxKTL-2mppt.yaml @@ -1,5 +1,9 @@ # To use modbus function in Afore BNTxxxKTL inverters, You first need to change protocol from RS485 to MODBUS in inverter menu +default: + update_interval: 60 + digits: 6 + requests: - start: 0x0000 end: 0x001A @@ -8,7 +12,7 @@ requests: - start: 0x0000 end: 0x000F mb_functioncode: 0x03 - + parameters: - group: solar items: @@ -165,6 +169,6 @@ parameters: uom: "°C" scale: 0.1 rule: 1 - + registers: [0x000D] - icon: "mdi:thermometer" \ No newline at end of file + icon: "mdi:thermometer" diff --git a/custom_components/solarman/inverter_definitions/deye_2mppt.yaml b/custom_components/solarman/inverter_definitions/deye_2mppt.yaml index afa5980..5b67fe2 100644 --- a/custom_components/solarman/inverter_definitions/deye_2mppt.yaml +++ b/custom_components/solarman/inverter_definitions/deye_2mppt.yaml @@ -2,393 +2,397 @@ # Latest update: 08.09.2023 # Microinverter SUN600G3 (DEYE/VESDAS) # 2x MPPT, 2x inverter -# 1x Logger, 2x Module, +# 1x Logger, 2x Module, + +default: + update_interval: 60 + digits: 6 requests: - start: 0x0001 - end: 0x007D + end: 0x007D mb_functioncode: 0x03 parameters: - group: solar - items: - - name: "PV1 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006D] - icon: 'mdi:solar-power' - - - name: "PV2 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006F] - icon: 'mdi:solar-power' - - - name: "PV1 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x006E] - icon: 'mdi:solar-power' - - - name: "PV2 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x0070] - icon: 'mdi:solar-power' - - - name: "Daily Production" - class: "energy" - state_class: "total" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x003C] - icon: 'mdi:solar-power' - - - name: "Daily Production 1" - class: "energy" - state_class: "total" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x0041] - icon: 'mdi:solar-power' - - - name: "Daily Production 2" - class: "energy" - state_class: "total" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x0042] - icon: 'mdi:solar-power' - - - name: "Total Production" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x003F,0x0040] - icon: 'mdi:solar-power' - validation: - min: 0.1 - - - name: "Total Production 1" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0045] - icon: 'mdi:solar-power' - - - name: "Total Production 2" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0047] - icon: 'mdi:solar-power' - - - name: "Active Power Regulations" - class: "" - state_class: "" - uom: "%" - scale: 1 - rule: 1 - registers: [0x0028] - icon: 'mdi:solar-power' + items: + - name: "PV1 Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006D] + icon: "mdi:solar-power" + + - name: "PV2 Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006F] + icon: "mdi:solar-power" + + - name: "PV1 Current" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x006E] + icon: "mdi:solar-power" + + - name: "PV2 Current" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x0070] + icon: "mdi:solar-power" + + - name: "Daily Production" + class: "energy" + state_class: "total" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x003C] + icon: "mdi:solar-power" + + - name: "Daily Production 1" + class: "energy" + state_class: "total" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x0041] + icon: "mdi:solar-power" + + - name: "Daily Production 2" + class: "energy" + state_class: "total" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x0042] + icon: "mdi:solar-power" + + - name: "Total Production" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x003F, 0x0040] + icon: "mdi:solar-power" + validation: + min: 0.1 + + - name: "Total Production 1" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0045] + icon: "mdi:solar-power" + + - name: "Total Production 2" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0047] + icon: "mdi:solar-power" + + - name: "Active Power Regulations" + class: "" + state_class: "" + uom: "%" + scale: 1 + rule: 1 + registers: [0x0028] + icon: "mdi:solar-power" - group: Grid items: - - name: "AC Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0049] - icon: 'mdi:transmission-tower' - - - name: "Grid Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 2 - registers: [0x004C] - icon: 'mdi:home-lightning-bolt' - - - name: "AC Output Frequency" - class: "frequency" - state_class: "measurement" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x004F] - icon: 'mdi:home-lightning-bolt' - - - name: "Grid Voltage Upp Limit" - class: "voltage" - state_class: "" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x001B] - icon: 'mdi:transmission-tower' - - - name: "Grid Voltage Lower Limit" - class: "voltage" - state_class: "" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x001C] - icon: 'mdi:transmission-tower' - - - name: "Grid Frequency Upper Limit" - class: "frequency" - state_class: "" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x001D] - icon: 'mdi:home-lightning-bolt' - - - name: "Grid Frequency Lower Limit" - class: "frequency" - state_class: "" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x001E] - icon: 'mdi:home-lightning-bolt' - - - name: "Overfrequency And Load Reduction Starting Point" - class: "frequency" - state_class: "" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x0022] - icon: 'mdi:home-lightning-bolt' - - - name: "Overfrequency And Load Reduction Percentage" - class: "" - state_class: "" - uom: "%" - scale: 1 - rule: 1 - registers: [0x0023] - icon: '' - - - name: "ON-OFF Enable" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x002B] - isstr: true - lookup: - - key: 0 - value: "OFF" - - key: 1 - value: "ON" - icon: 'mdi:toggle-switch' - - - name: "Island Protection Enable" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x002E] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:island' - - - name: "Overfrequency&Load-shedding Enable" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0031] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:toggle-switch' + - name: "AC Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0049] + icon: "mdi:transmission-tower" + + - name: "Grid Current" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 2 + registers: [0x004C] + icon: "mdi:home-lightning-bolt" + + - name: "AC Output Frequency" + class: "frequency" + state_class: "measurement" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x004F] + icon: "mdi:home-lightning-bolt" + + - name: "Grid Voltage Upp Limit" + class: "voltage" + state_class: "" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x001B] + icon: "mdi:transmission-tower" + + - name: "Grid Voltage Lower Limit" + class: "voltage" + state_class: "" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x001C] + icon: "mdi:transmission-tower" + + - name: "Grid Frequency Upper Limit" + class: "frequency" + state_class: "" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x001D] + icon: "mdi:home-lightning-bolt" + + - name: "Grid Frequency Lower Limit" + class: "frequency" + state_class: "" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x001E] + icon: "mdi:home-lightning-bolt" + + - name: "Overfrequency And Load Reduction Starting Point" + class: "frequency" + state_class: "" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x0022] + icon: "mdi:home-lightning-bolt" + + - name: "Overfrequency And Load Reduction Percentage" + class: "" + state_class: "" + uom: "%" + scale: 1 + rule: 1 + registers: [0x0023] + icon: "" + + - name: "ON-OFF Enable" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x002B] + isstr: true + lookup: + - key: 0 + value: "OFF" + - key: 1 + value: "ON" + icon: "mdi:toggle-switch" + + - name: "Island Protection Enable" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x002E] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:island" + + - name: "Overfrequency&Load-shedding Enable" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0031] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:toggle-switch" - group: Inverter items: - - name: "Running Status" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x003B] - isstr: true - lookup: - - key: 0 - value: "Stand-by" - - key: 1 - value: "Self-check" - - key: 2 - value: "Normal" - - key: 3 - value: "Warning" - - key: 4 - value: "Fault" - icon: 'mdi:home-lightning-bolt' - - - name: "Total AC Output Power (Active)" - class: "power" - state_class: "measurement" - uom: "W" - scale: 0.1 - rule: 3 - registers: [0x0056, 0x0057] - icon: 'mdi:home-lightning-bolt' - - - name: "Radiator Temperature" - class: "temperature" - uom: "°C" - state_class: "measurement" - scale: 0.01 - rule: 1 - offset: 1000 - registers: [0x005a] - - - name: "Inverter ID" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 5 - registers: [0x0003,0x0004,0x0005,0x0006,0x0007] - isstr: true - - - name: "Hardware Version" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 7 - registers: [0x000C] - isstr: true - - - name: "DC Master Firmware Version" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 7 - registers: [0x000D] - isstr: true - - - name: "AC Version. Number" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 7 - registers: [0x000E] - isstr: true - - - name: "Rated Power" - class: "energy" - state_class: "" - uom: "W" - scale: 0.1 - rule: 1 - registers: [0x0010] - icon: 'mdi:solar-power' - - - name: "Communication Protocol Version" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 7 - registers: [0x0012] - isstr: true - - - name: "Start-up Self-checking Time " - class: "" - state_class: "" - uom: "s" - scale: 1 - rule: 1 - registers: [0x0015] - icon: 'mdi:solar-power' - - - name: "Update Time" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 8 - registers: [0x0016,0x0017,0x0018] - isstr: true - - - name: "Soft Start Enable" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x002F] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:toggle-switch' - - - name: "Power Factor Regulation" - class: "" - state_class: "" - uom: "" - scale: 0.1 - rule: 2 - registers: [0x0032] - icon: '' - - - name: "Restore Factory Settings" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0036] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:factory' + - name: "Running Status" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x003B] + isstr: true + lookup: + - key: 0 + value: "Stand-by" + - key: 1 + value: "Self-check" + - key: 2 + value: "Normal" + - key: 3 + value: "Warning" + - key: 4 + value: "Fault" + icon: "mdi:home-lightning-bolt" + + - name: "Total AC Output Power (Active)" + class: "power" + state_class: "measurement" + uom: "W" + scale: 0.1 + rule: 3 + registers: [0x0056, 0x0057] + icon: "mdi:home-lightning-bolt" + + - name: "Radiator Temperature" + class: "temperature" + uom: "°C" + state_class: "measurement" + scale: 0.01 + rule: 1 + offset: 1000 + registers: [0x005a] + + - name: "Inverter ID" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 5 + registers: [0x0003, 0x0004, 0x0005, 0x0006, 0x0007] + isstr: true + + - name: "Hardware Version" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 7 + registers: [0x000C] + isstr: true + + - name: "DC Master Firmware Version" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 7 + registers: [0x000D] + isstr: true + + - name: "AC Version. Number" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 7 + registers: [0x000E] + isstr: true + + - name: "Rated Power" + class: "energy" + state_class: "" + uom: "W" + scale: 0.1 + rule: 1 + registers: [0x0010] + icon: "mdi:solar-power" + + - name: "Communication Protocol Version" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 7 + registers: [0x0012] + isstr: true + + - name: "Start-up Self-checking Time " + class: "" + state_class: "" + uom: "s" + scale: 1 + rule: 1 + registers: [0x0015] + icon: "mdi:solar-power" + + - name: "Update Time" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 8 + registers: [0x0016, 0x0017, 0x0018] + isstr: true + + - name: "Soft Start Enable" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x002F] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:toggle-switch" + + - name: "Power Factor Regulation" + class: "" + state_class: "" + uom: "" + scale: 0.1 + rule: 2 + registers: [0x0032] + icon: "" + + - name: "Restore Factory Settings" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0036] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:factory" diff --git a/custom_components/solarman/inverter_definitions/deye_4mppt.yaml b/custom_components/solarman/inverter_definitions/deye_4mppt.yaml index ff5b4b9..1e947c2 100644 --- a/custom_components/solarman/inverter_definitions/deye_4mppt.yaml +++ b/custom_components/solarman/inverter_definitions/deye_4mppt.yaml @@ -2,250 +2,271 @@ # Latest update: 08.09.2023 # Microinverter SUN2000G3 (DEYE/VESDAS) # 4x MPPT, 4x inverter -# 1x Logger, 4x Module, +# 1x Logger, 4x Module, -requests: - - start: 0x0001 - end: 0x007D - mb_functioncode: 0x03 +default: + update_interval: 60 + digits: 6 parameters: - group: solar - items: - - name: "PV1 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006D] - icon: 'mdi:solar-power' - - - name: "PV2 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006F] - icon: 'mdi:solar-power' - - - name: "PV3 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0071] - icon: 'mdi:solar-power' - - - name: "PV4 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0073] - icon: 'mdi:solar-power' - - - name: "PV1 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x006E] - icon: 'mdi:solar-power' - - - name: "PV2 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x0070] - icon: 'mdi:solar-power' - - - name: "PV3 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x0072] - icon: 'mdi:solar-power' - - - name: "PV4 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x0074] - icon: 'mdi:solar-power' - - - name: "Daily Production" - class: "energy" - state_class: "total" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x003C] - icon: 'mdi:solar-power' - - - name: "Total Production" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x003F,0x0040] - icon: 'mdi:solar-power' - validation: - min: 0.1 - invalidate_all: - - - name: "Active Power Regulations" - class: "" - state_class: "" - uom: "%" - scale: 1 - rule: 1 - registers: [0x0028] - icon: 'mdi:solar-power' + items: + - name: "PV1 Voltage" + realtime: + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006D] + icon: "mdi:solar-power" + + - name: "PV2 Voltage" + realtime: + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006F] + icon: "mdi:solar-power" + + - name: "PV3 Voltage" + realtime: + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0071] + icon: "mdi:solar-power" + + - name: "PV4 Voltage" + realtime: + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0073] + icon: "mdi:solar-power" + + - name: "PV1 Current" + realtime: + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x006E] + icon: "mdi:solar-power" + + - name: "PV2 Current" + realtime: + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x0070] + icon: "mdi:solar-power" + + - name: "PV3 Current" + realtime: + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x0072] + icon: "mdi:solar-power" + + - name: "PV4 Current" + realtime: + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x0074] + icon: "mdi:solar-power" + + - name: "Daily Production" + realtime: + class: "energy" + state_class: "total" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x003C] + icon: "mdi:solar-power" + + - name: "Total Production" + realtime: + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x003F, 0x0040] + icon: "mdi:solar-power" + validation: + min: 0.1 + invalidate_all: + + - name: "Active Power Regulations" + realtime: + class: "" + state_class: "" + uom: "%" + scale: 1 + rule: 1 + registers: [0x0028] + icon: "mdi:solar-power" - group: Grid items: - - name: "AC Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0049] - icon: 'mdi:transmission-tower' - - - name: "Grid Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 2 - registers: [0x004C] - icon: 'mdi:home-lightning-bolt' - - - name: "AC Output Frequency" - class: "frequency" - state_class: "measurement" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x004F] - icon: 'mdi:home-lightning-bolt' + - name: "AC Voltage" + realtime: + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0049] + icon: "mdi:transmission-tower" + + - name: "Grid Current" + realtime: + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 2 + registers: [0x004C] + icon: "mdi:home-lightning-bolt" + + - name: "AC Output Frequency" + realtime: + class: "frequency" + state_class: "measurement" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x004F] + icon: "mdi:home-lightning-bolt" - group: Inverter items: - - name: "Running Status" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x003B] - isstr: true - lookup: - - key: 0 - value: "Stand-by" - - key: 1 - value: "Self-check" - - key: 2 - value: "Normal" - - key: 3 - value: "Warning" - - key: 4 - value: "Fault" - icon: 'mdi:home-lightning-bolt' - - - name: "Total AC Output Power (Active)" - class: "power" - state_class: "measurement" - uom: "W" - scale: 0.1 - rule: 3 - registers: [0x0056, 0x0057] - icon: 'mdi:home-lightning-bolt' - - - name: "Radiator Temperature" - class: "temperature" - uom: "°C" - state_class: "measurement" - scale: 0.01 - rule: 1 - offset: 1000 - registers: [0x005a] - - - name: "Inverter ID" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 5 - registers: [0x0003,0x0004,0x0005,0x0006,0x0007] - isstr: true - - - name: "Rated Power" - class: "energy" - state_class: "" - uom: "W" - scale: 0.1 - rule: 1 - registers: [0x0010] - icon: 'mdi:solar-power' - - - name: "Start-up Self-checking Time " - class: "" - state_class: "" - uom: "s" - scale: 1 - rule: 1 - registers: [0x0015] - icon: 'mdi:solar-power' - - - name: "Soft Start Enable" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x002F] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:toggle-switch' - - - name: "Power Factor Regulation" - class: "" - state_class: "" - uom: "" - scale: 0.1 - rule: 2 - registers: [0x0032] - icon: '' - - - name: "Restore Factory Settings" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0036] - isstr: true - lookup: - - key: 0 - value: "Disabled" - - key: 1 - value: "Enabled" - icon: 'mdi:factory' + - name: "Running Status" + realtime: + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x003B] + isstr: true + lookup: + - key: 0 + value: "Stand-by" + - key: 1 + value: "Self-check" + - key: 2 + value: "Normal" + - key: 3 + value: "Warning" + - key: 4 + value: "Fault" + icon: "mdi:home-lightning-bolt" + + - name: "Total AC Output Power (Active)" + realtime: + class: "power" + state_class: "measurement" + uom: "W" + scale: 0.1 + rule: 3 + registers: [0x0056, 0x0057] + icon: "mdi:home-lightning-bolt" + + - name: "Radiator Temperature" + realtime: + class: "temperature" + uom: "°C" + state_class: "measurement" + scale: 0.01 + rule: 1 + offset: 1000 + registers: [0x005a] + + - name: "Inverter ID" + realtime: + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 5 + registers: [0x0003, 0x0004, 0x0005, 0x0006, 0x0007] + isstr: true + + - name: "Rated Power" + realtime: + class: "energy" + state_class: "" + uom: "W" + scale: 0.1 + rule: 1 + registers: [0x0010] + icon: "mdi:solar-power" + + - name: "Start-up Self-checking Time " + class: "" + state_class: "" + uom: "s" + scale: 1 + rule: 1 + registers: [0x0015] + icon: "mdi:solar-power" + + - name: "Soft Start Enable" + realtime: + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x002F] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:toggle-switch" + + - name: "Power Factor Regulation" + realtime: + class: "" + state_class: "" + uom: "" + scale: 0.1 + rule: 2 + registers: [0x0032] + icon: "" + + - name: "Restore Factory Settings" + realtime: + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0036] + isstr: true + lookup: + - key: 0 + value: "Disabled" + - key: 1 + value: "Enabled" + icon: "mdi:factory" diff --git a/custom_components/solarman/inverter_definitions/deye_hybrid.yaml b/custom_components/solarman/inverter_definitions/deye_hybrid.yaml index bb88f1a..684251b 100644 --- a/custom_components/solarman/inverter_definitions/deye_hybrid.yaml +++ b/custom_components/solarman/inverter_definitions/deye_hybrid.yaml @@ -1,3 +1,7 @@ +default: + update_interval: 60 + digits: 6 + requests: - start: 0x0003 end: 0x0070 @@ -10,821 +14,820 @@ requests: mb_functioncode: 0x03 parameters: - - group: solar - items: - - name: "PV1 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00BA] - icon: 'mdi:solar-power' - - - name: "PV2 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00BB] - icon: 'mdi:solar-power' - - - name: "PV1 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006D] - icon: 'mdi:solar-power' - - - name: "PV2 Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x006F] - icon: 'mdi:solar-power' - - - name: "PV1 Current" - class: "current" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x006E] - icon: 'mdi:solar-power' - - - name: "PV2 Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.1 - rule: 1 - registers: [0x0070] - icon: 'mdi:solar-power' - - - name: "Daily Production" - class: "energy" - state_class: "measurement" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x006C] - icon: 'mdi:solar-power' - - - name: "Total Production" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0060,0x0061] - icon: 'mdi:solar-power' - - - name: "Micro-inverter Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00A6] - icon: 'mdi:solar-power' - - - group: Battery - items: - - name: "Total Battery Charge" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0048,0x0049] - icon: 'mdi:battery-plus' - - - name: "Total Battery Discharge" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x004A,0x004B] - icon: 'mdi:battery-minus' - - - name: "Daily Battery Charge" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x0046] - icon: 'mdi:battery-plus' - - - name: "Daily Battery Discharge" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x0047] - icon: 'mdi:battery-minus' - - - name: "Battery Status" - class: "" - state_class: "measurement" - uom: "" - scale: 1 - rule: 1 - registers: [0x00BD] - isstr: true - lookup: - - key: 0 - value: "Charge" - - key: 1 - value: "Stand-by" - - key: 2 - value: "Discharge" - icon: 'mdi:battery' - - - name: "Battery Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00BE] - icon: 'mdi:battery' - - - name: "Battery Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.01 - rule: 1 - registers: [0x00B7] - icon: 'mdi:battery' - - - name: "Battery SOC" - class: "battery" - state_class: "measurement" - uom: "%" - scale: 1 - rule: 1 - registers: [0x00B8] - icon: 'mdi:battery' - - - name: "Battery Current" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.01 - rule: 2 - registers: [0x00BF] - icon: 'mdi:battery' - - - name: "Battery Temperature" - class: "temperature" - state_class: "measurement" - uom: "°C" - scale: 0.1 - rule: 1 - offset: 1000 - registers: [0x00B6] - icon: 'mdi:battery' - - - group: Grid - items: - - name: "Total Grid Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00A9] - icon: 'mdi:transmission-tower' - - - name: "Grid Voltage L1" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0096] - icon: 'mdi:transmission-tower' - - - name: "Grid Current L1" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.01 - rule: 1 - registers: [0x00A0] - icon: 'mdi:current-ac' - - name: "Grid Voltage L2" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x0097] - icon: 'mdi:transmission-tower' - - - name: "Grid Current L2" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.01 - rule: 1 - registers: [0x00A1] - icon: 'mdi:current-ac' - - - name: "Internal CT L1 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00A7] - icon: 'mdi:transmission-tower' - - - name: "Internal CT L2 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00A8] - icon: 'mdi:transmission-tower' - - - name: "External CT L1 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00AA] - icon: 'mdi:transmission-tower' - - - name: "External CT L2 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00AB] - icon: 'mdi:transmission-tower' - - - name: "Daily Energy Bought" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x004C] - icon: 'mdi:transmission-tower-export' - - - name: "Total Energy Bought" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x004E,0x0050] - icon: 'mdi:transmission-tower-export' - - - name: "Daily Energy Sold" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x004D] - icon: 'mdi:transmission-tower-import' - - - name: "Total Energy Sold" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0051,0x0052] - icon: 'mdi:transmission-tower-import' - - - - name: "Total Grid Production" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 4 - registers: [0x003F,0x0040] - icon: 'mdi:transmission-tower' - - - group: Upload - items: - - name: "Total Load Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00B2] - icon: 'mdi:lightning-bolt-outline' - - - name: "Load L1 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00B0] - icon: 'mdi:lightning-bolt-outline' - - - name: "Load L2 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00B1] - icon: 'mdi:lightning-bolt-outline' - - - name: "Load Voltage" - class: "voltage" - state_class: "measurement" - uom: "V" - scale: 0.1 - rule: 1 - registers: [0x009D] - icon: 'mdi:lightning-bolt-outline' - - - name: "Daily Load Consumption" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 1 - registers: [0x0054] - icon: 'mdi:lightning-bolt-outline' - - - name: "Total Load Consumption" - class: "energy" - state_class: "total_increasing" - uom: "kWh" - scale: 0.1 - rule: 3 - registers: [0x0055,0x0056] - icon: 'mdi:lightning-bolt-outline' - - - name: "SmartLoad Enable Status" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x00C3] - isstr: true - lookup: - - key: 0 - value: "OFF" - - key: 1 - value: "ON" - icon: 'mdi:lightning-bolt-outline' - - - group: Inverter - items: - - name: "Running Status" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x003B] - isstr: true - lookup: - - key: 0 - value: "Stand-by" - - key: 1 - value: "Self-checking" - - key: 2 - value: "Normal" - - key: 3 - value: "FAULT" - icon: 'mdi:home-lightning-bolt' - - - name: "Total Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00AF] - icon: 'mdi:home-lightning-bolt' - - - name: "Grid Frequency" - class: "frequency" - state_class: "measurement" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x004F] - icon: 'mdi:sine-wave' - - - name: "Current L1" - class: "current" - state_class: "measurement" - uom: "A" - scale: 0.01 - rule: 2 - registers: [0x00A4] - icon: 'mdi:home-lightning-bolt' - - - name: "Current L2" - class: "current" - uom: "A" - scale: 0.01 - rule: 2 - registers: [0x00A5] - icon: 'mdi:home-lightning-bolt' - - - name: "Inverter L1 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00AD] - icon: 'mdi:home-lightning-bolt' - - - name: "Inverter L2 Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 2 - registers: [0x00AE] - icon: 'mdi:home-lightning-bolt' - - - name: "Load Frequency" - class: "" - state_class: "measurement" - uom: "Hz" - scale: 0.01 - rule: 1 - registers: [0x00C0] - icon: 'mdi:sine-wave' - - - name: "DC Temperature" - class: "temperature" - state_class: "measurement" - uom: "°C" - scale: 0.1 - rule: 2 - offset: 1000 - registers: [0x005A] - icon: 'mdi:thermometer' - - - name: "AC Temperature" - class: "temperature" - state_class: "measurement" - uom: "°C" - scale: 0.1 - rule: 2 - offset: 1000 - registers: [0x005B] - icon: 'mdi:thermometer' - - - name: "Inverter ID" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 5 - registers: [0x0003,0x0004,0x0005,0x0006,0x0007] - isstr: true - - - name: "Communication Board Version No." - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x000E] - isstr: true - - - name: "Control Board Version No." - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x000D] - isstr: true - - - name: "Grid-connected Status" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x00C2] - isstr: true - lookup: - - key: 0 - value: "Off-Grid" - - key: 1 - value: "On-Grid" - - - name: "Gen-connected Status" - class: "" - uom: "" - state_class: "" - scale: 1 - rule: 1 - registers: [0x00A6] - isstr: true - lookup: - - key: 0 - value: "none" - - key: 1 - value: "On" - - - name: "Gen Power" - class: "power" - state_class: "measurement" - uom: "W" - scale: 1 - rule: 1 - registers: [0x00A6] - - - name: "Work Mode" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 3 - registers: [0x00F4,0x00F7] - isstr: true - lookup: - - key: 0 - value: "Selling First" - - key: 1 - value: "Zero-Export to Load&Solar Sell" - - key: 2 - value: "Zero-Export to Home&Solar Sell" - - key: 3 - value: "Zero-Export to Load" - - key: 4 - value: "Zero-Export to Home" - icon: 'mdi:home-lightning-bolt' - - - group: Alert - items: - - name: "Alert" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 6 - registers: [0x0065,0x0066,0x0067,0x0068,0x0069,0x006A] - - - group: Time of Use - items: - - name: "Time of use Time 1" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FA] - icon: 'mdi:timelapse' - - - name: "Time of use Time 2" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FB] - icon: "mdi:timelapse" - - - name: "Time of Use Time 3" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FC] - icon: 'mdi:timelapse' - - - name: "Time of Use Time 4" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FD] - icon: 'mdi:timelapse' - - - name: "Time of Use Time 5" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FE] - icon: "mdi:timelapse" - - - name: "Time of Use Time 6" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 9 - registers: [0x00FF] - icon: 'mdi:timelapse' - - - name: "Time of Use Power 1" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0100] - icon: "mdi:lightning-bolt-outline" - - - name: "Time of Use Power 2" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0101] - icon: 'mdi:lightning-bolt-outline' - - - name: "Time of Use Power 3" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0102] - icon: 'mdi:lightning-bolt-outline' - - - name: "Time of Use Power 4" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0103] - icon: 'mdi:lightning-bolt-outline' - - - name: "Time of Use Power 5" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0104] - icon: 'mdi:lightning-bolt-outline' - - - name: "Time of Use Power 6" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0105] - icon: 'mdi:lightning-bolt-outline' - - - name: "Time of Use SOC 1" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x010C] - icon: 'mdi:battery' - - - name: "Time of Use SOC 2" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x010D] - icon: 'mdi:battery' - - - name: "Time of Use SOC 3" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x010E] - icon: 'mdi:battery' - - - name: "Time of Use SOC 4" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x010F] - icon: 'mdi:battery' - - - name: "Time of Use SOC 5" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0110] - icon: 'mdi:battery' - - - name: "Time of Use SOC 6" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - registers: [0x0111] - icon: 'mdi:battery' - - - name: "Time of Use Enable 1" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0112] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of Use Enable 2" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0113] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of Use Enable 3" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0114] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of Use Enable 4" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0115] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of Use Enable 5" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0116] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of Use Enable 6" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x0117] - icon: 'mdi:checkbox-marked-outline' - - - name: "Time of use" - class: "" - state_class: "" - uom: "" - scale: 1 - rule: 1 - mask: 1 - registers: [0x00F8] - icon: 'mdi:checkbox-marked-outline' - isstr: true - lookup: - - key: 0 - value: "Disable" - - key: 1 - value: "Enable" + - group: solar + items: + - name: "PV1 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00BA] + icon: "mdi:solar-power" + + - name: "PV2 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00BB] + icon: "mdi:solar-power" + + - name: "PV1 Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006D] + icon: "mdi:solar-power" + + - name: "PV2 Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x006F] + icon: "mdi:solar-power" + + - name: "PV1 Current" + class: "current" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x006E] + icon: "mdi:solar-power" + + - name: "PV2 Current" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.1 + rule: 1 + registers: [0x0070] + icon: "mdi:solar-power" + + - name: "Daily Production" + class: "energy" + state_class: "measurement" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x006C] + icon: "mdi:solar-power" + + - name: "Total Production" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0060, 0x0061] + icon: "mdi:solar-power" + + - name: "Micro-inverter Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00A6] + icon: "mdi:solar-power" + + - group: Battery + items: + - name: "Total Battery Charge" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0048, 0x0049] + icon: "mdi:battery-plus" + + - name: "Total Battery Discharge" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x004A, 0x004B] + icon: "mdi:battery-minus" + + - name: "Daily Battery Charge" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x0046] + icon: "mdi:battery-plus" + + - name: "Daily Battery Discharge" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x0047] + icon: "mdi:battery-minus" + + - name: "Battery Status" + class: "" + state_class: "measurement" + uom: "" + scale: 1 + rule: 1 + registers: [0x00BD] + isstr: true + lookup: + - key: 0 + value: "Charge" + - key: 1 + value: "Stand-by" + - key: 2 + value: "Discharge" + icon: "mdi:battery" + + - name: "Battery Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00BE] + icon: "mdi:battery" + + - name: "Battery Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.01 + rule: 1 + registers: [0x00B7] + icon: "mdi:battery" + + - name: "Battery SOC" + class: "battery" + state_class: "measurement" + uom: "%" + scale: 1 + rule: 1 + registers: [0x00B8] + icon: "mdi:battery" + + - name: "Battery Current" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.01 + rule: 2 + registers: [0x00BF] + icon: "mdi:battery" + + - name: "Battery Temperature" + class: "temperature" + state_class: "measurement" + uom: "°C" + scale: 0.1 + rule: 1 + offset: 1000 + registers: [0x00B6] + icon: "mdi:battery" + + - group: Grid + items: + - name: "Total Grid Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00A9] + icon: "mdi:transmission-tower" + + - name: "Grid Voltage L1" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0096] + icon: "mdi:transmission-tower" + + - name: "Grid Current L1" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.01 + rule: 1 + registers: [0x00A0] + icon: "mdi:current-ac" + - name: "Grid Voltage L2" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x0097] + icon: "mdi:transmission-tower" + + - name: "Grid Current L2" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.01 + rule: 1 + registers: [0x00A1] + icon: "mdi:current-ac" + + - name: "Internal CT L1 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00A7] + icon: "mdi:transmission-tower" + + - name: "Internal CT L2 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00A8] + icon: "mdi:transmission-tower" + + - name: "External CT L1 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00AA] + icon: "mdi:transmission-tower" + + - name: "External CT L2 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00AB] + icon: "mdi:transmission-tower" + + - name: "Daily Energy Bought" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x004C] + icon: "mdi:transmission-tower-export" + + - name: "Total Energy Bought" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x004E, 0x0050] + icon: "mdi:transmission-tower-export" + + - name: "Daily Energy Sold" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x004D] + icon: "mdi:transmission-tower-import" + + - name: "Total Energy Sold" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0051, 0x0052] + icon: "mdi:transmission-tower-import" + + - name: "Total Grid Production" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 4 + registers: [0x003F, 0x0040] + icon: "mdi:transmission-tower" + + - group: Upload + items: + - name: "Total Load Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00B2] + icon: "mdi:lightning-bolt-outline" + + - name: "Load L1 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00B0] + icon: "mdi:lightning-bolt-outline" + + - name: "Load L2 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00B1] + icon: "mdi:lightning-bolt-outline" + + - name: "Load Voltage" + class: "voltage" + state_class: "measurement" + uom: "V" + scale: 0.1 + rule: 1 + registers: [0x009D] + icon: "mdi:lightning-bolt-outline" + + - name: "Daily Load Consumption" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 1 + registers: [0x0054] + icon: "mdi:lightning-bolt-outline" + + - name: "Total Load Consumption" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + scale: 0.1 + rule: 3 + registers: [0x0055, 0x0056] + icon: "mdi:lightning-bolt-outline" + + - name: "SmartLoad Enable Status" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x00C3] + isstr: true + lookup: + - key: 0 + value: "OFF" + - key: 1 + value: "ON" + icon: "mdi:lightning-bolt-outline" + + - group: Inverter + items: + - name: "Running Status" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x003B] + isstr: true + lookup: + - key: 0 + value: "Stand-by" + - key: 1 + value: "Self-checking" + - key: 2 + value: "Normal" + - key: 3 + value: "FAULT" + icon: "mdi:home-lightning-bolt" + + - name: "Total Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00AF] + icon: "mdi:home-lightning-bolt" + + - name: "Grid Frequency" + class: "frequency" + state_class: "measurement" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x004F] + icon: "mdi:sine-wave" + + - name: "Current L1" + class: "current" + state_class: "measurement" + uom: "A" + scale: 0.01 + rule: 2 + registers: [0x00A4] + icon: "mdi:home-lightning-bolt" + + - name: "Current L2" + class: "current" + uom: "A" + scale: 0.01 + rule: 2 + registers: [0x00A5] + icon: "mdi:home-lightning-bolt" + + - name: "Inverter L1 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00AD] + icon: "mdi:home-lightning-bolt" + + - name: "Inverter L2 Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 2 + registers: [0x00AE] + icon: "mdi:home-lightning-bolt" + + - name: "Load Frequency" + class: "" + state_class: "measurement" + uom: "Hz" + scale: 0.01 + rule: 1 + registers: [0x00C0] + icon: "mdi:sine-wave" + + - name: "DC Temperature" + class: "temperature" + state_class: "measurement" + uom: "°C" + scale: 0.1 + rule: 2 + offset: 1000 + registers: [0x005A] + icon: "mdi:thermometer" + + - name: "AC Temperature" + class: "temperature" + state_class: "measurement" + uom: "°C" + scale: 0.1 + rule: 2 + offset: 1000 + registers: [0x005B] + icon: "mdi:thermometer" + + - name: "Inverter ID" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 5 + registers: [0x0003, 0x0004, 0x0005, 0x0006, 0x0007] + isstr: true + + - name: "Communication Board Version No." + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x000E] + isstr: true + + - name: "Control Board Version No." + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x000D] + isstr: true + + - name: "Grid-connected Status" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x00C2] + isstr: true + lookup: + - key: 0 + value: "Off-Grid" + - key: 1 + value: "On-Grid" + + - name: "Gen-connected Status" + class: "" + uom: "" + state_class: "" + scale: 1 + rule: 1 + registers: [0x00A6] + isstr: true + lookup: + - key: 0 + value: "none" + - key: 1 + value: "On" + + - name: "Gen Power" + class: "power" + state_class: "measurement" + uom: "W" + scale: 1 + rule: 1 + registers: [0x00A6] + + - name: "Work Mode" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 3 + registers: [0x00F4, 0x00F7] + isstr: true + lookup: + - key: 0 + value: "Selling First" + - key: 1 + value: "Zero-Export to Load&Solar Sell" + - key: 2 + value: "Zero-Export to Home&Solar Sell" + - key: 3 + value: "Zero-Export to Load" + - key: 4 + value: "Zero-Export to Home" + icon: "mdi:home-lightning-bolt" + + - group: Alert + items: + - name: "Alert" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 6 + registers: [0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A] + + - group: Time of Use + items: + - name: "Time of use Time 1" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FA] + icon: "mdi:timelapse" + + - name: "Time of use Time 2" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FB] + icon: "mdi:timelapse" + + - name: "Time of Use Time 3" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FC] + icon: "mdi:timelapse" + + - name: "Time of Use Time 4" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FD] + icon: "mdi:timelapse" + + - name: "Time of Use Time 5" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FE] + icon: "mdi:timelapse" + + - name: "Time of Use Time 6" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 9 + registers: [0x00FF] + icon: "mdi:timelapse" + + - name: "Time of Use Power 1" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0100] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use Power 2" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0101] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use Power 3" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0102] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use Power 4" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0103] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use Power 5" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0104] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use Power 6" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0105] + icon: "mdi:lightning-bolt-outline" + + - name: "Time of Use SOC 1" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x010C] + icon: "mdi:battery" + + - name: "Time of Use SOC 2" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x010D] + icon: "mdi:battery" + + - name: "Time of Use SOC 3" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x010E] + icon: "mdi:battery" + + - name: "Time of Use SOC 4" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x010F] + icon: "mdi:battery" + + - name: "Time of Use SOC 5" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0110] + icon: "mdi:battery" + + - name: "Time of Use SOC 6" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0111] + icon: "mdi:battery" + + - name: "Time of Use Enable 1" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0112] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of Use Enable 2" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0113] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of Use Enable 3" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0114] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of Use Enable 4" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0115] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of Use Enable 5" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0116] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of Use Enable 6" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x0117] + icon: "mdi:checkbox-marked-outline" + + - name: "Time of use" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + mask: 1 + registers: [0x00F8] + icon: "mdi:checkbox-marked-outline" + isstr: true + lookup: + - key: 0 + value: "Disable" + - key: 1 + value: "Enable" diff --git a/custom_components/solarman/inverter_definitions/deye_sg01hp3.yaml b/custom_components/solarman/inverter_definitions/deye_sg01hp3.yaml index 7776913..1ec94cf 100644 --- a/custom_components/solarman/inverter_definitions/deye_sg01hp3.yaml +++ b/custom_components/solarman/inverter_definitions/deye_sg01hp3.yaml @@ -2,6 +2,10 @@ # SUN-5/6/8/10/12/15/20/25K-SG01HP3-EU-AM2 | 5-25kW | Three Phase | 2 MPPT | Hybrid Inverter | HV Battery Supported # +default: + update_interval: 60 + digits: 6 + parameters: - group: Parameters items: @@ -644,19 +648,49 @@ parameters: class: "energy" state_class: "total_increasing" uom: "kWh" - scale: 0.1 - rule: 0 - params: ["Today Energy Export", "Today Production", "Today Battery Discharge", "Today Energy Import", "Today Load Consumption", "Today Battery Charge"] - formula: "max(round({0} + {1} + {2} - {3} - {4} - {5}, 1), 0)" + rule: 1 + digits: 1 + registers: [0x0208, 0x0211, 0x0203, 0x0209, 0x020E, 0x0202] + sensors: + - scale: 0.1 + registers: [0x0208] + - scale: 0.1 + registers: [0x0211] + - scale: 0.1 + registers: [0x0203] + - subtract: + scale: 0.1 + registers: [0x0209] + - subtract: + scale: 0.1 + registers: [0x020E] + - subtract: + scale: 0.1 + registers: [0x0202] - name: "Total Losses" class: "energy" state_class: "total_increasing" uom: "kWh" - scale: 0.1 - rule: 0 - params: ["Total Energy Export", "Total Production", "Total Battery Discharge", "Total Energy Import", "Total Load Consumption", "Total Battery Charge"] - formula: "max(round({0} + {1} + {2} - {3} - {4} - {5}, 1), 0)" + rule: 3 + digits: 1 + registers: [0x020A, 0x020B, 0x0216, 0x0217, 0x0206, 0x0207, 0x020C, 0x020D, 0x020F, 0x0210, 0x0204, 0x0205] + sensors: + - scale: 0.1 + registers: [0x020A, 0x020B] + - scale: 0.1 + registers: [0x0216, 0x0217] + - scale: 0.1 + registers: [0x0206, 0x0207] + - subtract: + scale: 0.1 + registers: [0x020C, 0x020D] + - subtract: + scale: 0.1 + registers: [0x020F, 0x0210] + - subtract: + scale: 0.1 + registers: [0x0204, 0x0205] - name: "Today Production" class: "energy" @@ -756,11 +790,26 @@ parameters: state_class: "" uom: "" scale: 1 - rule: 0 - params: ["Device Relay Status enum"] - formula: "'On-grid' if {0} & 4 == 4 else 'Off-grid'" + rule: 1 + registers: [0x0228] + icon: "mdi:information" isstr: true - icon: "mdi:transmission-tower-off" + lookup_default: "X-grid" + lookup: + - key: 0x0001 + value: "Off-grid" + - key: 0x0004 + value: "On-grid" + - key: 0x0005 + value: "On-grid" + - key: 0x0008 + value: "Off-grid" + - key: 0x0009 + value: "Off-grid" + - key: 0x000C + value: "On-grid" + - key: 0x000D + value: "On-grid" # Device - Alarm message (word 1 & 2) [0, 65535] - name: "Device Alert" @@ -1244,10 +1293,20 @@ parameters: class: "power" state_class: "measurement" uom: "W" - scale: 1 - rule: 0 - params: ["Battery Power", "PV1 Power", "PV2 Power", "Inverter Power"] - formula: "max({0} + {1} + {2} - {3}, 0)" + rule: 1 + digits: 0 + registers: [0x024E, 0x02A0, 0x02A1, 0x027C, 0x02B6] + sensors: + - signed: + scale: 10 + registers: [0x024E] + - scale: 10 + registers: [0x02A0] + - scale: 10 + registers: [0x02A1] + - subtract: + signed: + registers: [0x027C, 0x02B6] # Inverter - Frequency - name: "Inverter Frequency" @@ -1380,10 +1439,14 @@ parameters: class: "power" state_class: "measurement" uom: "W" - scale: 1 - rule: 0 - params: ["PV1 Power", "PV2 Power"] - formula: "{0} + {1}" + rule: 1 + digits: 0 + registers: [0x02A0, 0x02A1] + sensors: + - scale: 10 + registers: [0x02A0] + - scale: 10 + registers: [0x02A1] icon: "mdi:solar-power-variant" # Photovoltaic - The power of Input 1 (L:1W, H:10W) diff --git a/custom_components/solarman/inverter_definitions/deye_sg04lp3.yaml b/custom_components/solarman/inverter_definitions/deye_sg04lp3.yaml index dee5c11..f27ada6 100644 --- a/custom_components/solarman/inverter_definitions/deye_sg04lp3.yaml +++ b/custom_components/solarman/inverter_definitions/deye_sg04lp3.yaml @@ -8,6 +8,10 @@ # The 500-2000 register address is a readable register type 0x03 function code. # +default: + update_interval: 60 + digits: 6 + parameters: - group: Parameters items: @@ -625,6 +629,68 @@ parameters: rule: 3 registers: [0x020F, 0x0210] + - name: "Today Losses" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + rule: 1 + digits: 1 + registers: [0x0208, 0x0211, 0x0203, 0x0209, 0x020E, 0x0202] + sensors: + - scale: 0.1 + registers: [0x0208] + - scale: 0.1 + registers: [0x0211] + - scale: 0.1 + registers: [0x0203] + - subtract: + scale: 0.1 + registers: [0x0209] + - subtract: + scale: 0.1 + registers: [0x020E] + - subtract: + scale: 0.1 + registers: [0x0202] + + - name: "Total Losses" + class: "energy" + state_class: "total_increasing" + uom: "kWh" + rule: 3 + digits: 1 + registers: + [ + 0x020A, + 0x020B, + 0x0216, + 0x0217, + 0x0206, + 0x0207, + 0x020C, + 0x020D, + 0x020F, + 0x0210, + 0x0204, + 0x0205, + ] + sensors: + - scale: 0.1 + registers: [0x020A, 0x020B] + - scale: 0.1 + registers: [0x0216, 0x0217] + - scale: 0.1 + registers: [0x0206, 0x0207] + - subtract: + scale: 0.1 + registers: [0x020C, 0x020D] + - subtract: + scale: 0.1 + registers: [0x020F, 0x0210] + - subtract: + scale: 0.1 + registers: [0x0204, 0x0205] + - name: "Today Production" class: "energy" state_class: "total_increasing" @@ -717,6 +783,33 @@ parameters: - key: 0x000D value: "Inv-Grid-Gen" + # Device - AC Grid state + - name: "Device State" + class: "" + state_class: "" + uom: "" + scale: 1 + rule: 1 + registers: [0x0228] + icon: "mdi:information" + isstr: true + lookup_default: "X-grid" + lookup: + - key: 0x0001 + value: "Off-grid" + - key: 0x0004 + value: "On-grid" + - key: 0x0005 + value: "On-grid" + - key: 0x0008 + value: "Off-grid" + - key: 0x0009 + value: "Off-grid" + - key: 0x000C + value: "On-grid" + - key: 0x000D + value: "On-grid" + # Device - Alarm message (word 1 & 2) [0, 65535] - name: "Device Alert" update_interval: 30 @@ -1167,6 +1260,23 @@ parameters: rule: 4 registers: [0x027C, 0x02B6] + # Inverter - Includes consumption of the device itself aswell power losses of AC/DC conversions + - name: "Power losses" + class: "power" + state_class: "measurement" + uom: "W" + rule: 1 + digits: 0 + registers: [0x024E, 0x02A0, 0x02A1, 0x027C, 0x02B6] + sensors: + - signed: + registers: [0x024E] + - registers: [0x02A0] + - registers: [0x02A1] + - subtract: + signed: + registers: [0x027C, 0x02B6] + # Inverter output - Total active power is S16bit (low 16 bits) + S16bit (high 16 bits) - name: "Inverter Frequency" realtime: @@ -1292,6 +1402,20 @@ parameters: - group: Photovoltaic items: + # Photovoltaic - The combined power of Input 1 & 2 + - name: "PV Power" + realtime: + class: "power" + state_class: "measurement" + uom: "W" + rule: 1 + digits: 0 + registers: [0x02A0, 0x02A1] + sensors: + - registers: [0x02A0] + - registers: [0x02A1] + icon: "mdi:solar-power-variant" + # Photovoltaic - The power of Input 1 (L:1W, H:10W) - name: "PV1 Power" realtime: diff --git a/custom_components/solarman/inverter_definitions/deye_sun-12k-sg04lp3.yaml b/custom_components/solarman/inverter_definitions/deye_sun-12k-sg04lp3.yaml index 736f1db..78012bb 100644 --- a/custom_components/solarman/inverter_definitions/deye_sun-12k-sg04lp3.yaml +++ b/custom_components/solarman/inverter_definitions/deye_sun-12k-sg04lp3.yaml @@ -10,6 +10,10 @@ # The 500-2000 register address is a readable register type 0x03 function code. # +default: + update_interval: 60 + digits: 6 + parameters: - group: Parameters items: @@ -582,7 +586,12 @@ parameters: uom: "" scale: 1 rule: 0 - params: ["Today Battery Charge", "Battery Capacity", "Battery Nominal Voltage"] + params: + [ + "Today Battery Charge", + "Battery Capacity", + "Battery Nominal Voltage", + ] formula: "round({0} / ({1} * {2} / 1000), 2)" icon: "mdi:battery-sync" @@ -592,7 +601,12 @@ parameters: uom: "" scale: 1 rule: 0 - params: ["Total Battery Charge", "Battery Capacity", "Battery Nominal Voltage"] + params: + [ + "Total Battery Charge", + "Battery Capacity", + "Battery Nominal Voltage", + ] formula: "round({0} / ({1} * {2} / 1000), 2)" icon: "mdi:battery-sync" @@ -652,19 +666,63 @@ parameters: class: "energy" state_class: "total_increasing" uom: "kWh" - scale: 0.1 - rule: 0 - params: ["Today Energy Bought", "Today Production", "Today Battery Discharge", "Today Energy Sold", "Today Load Consumption", "Today Battery Charge"] - formula: "max(round({0} + {1} + {2} - {3} - {4} - {5}, 1), 0)" + rule: 1 + digits: 1 + registers: [0x0208, 0x0211, 0x0203, 0x0209, 0x020E, 0x0202] + sensors: + - scale: 0.1 + registers: [0x0208] + - scale: 0.1 + registers: [0x0211] + - scale: 0.1 + registers: [0x0203] + - subtract: + scale: 0.1 + registers: [0x0209] + - subtract: + scale: 0.1 + registers: [0x020E] + - subtract: + scale: 0.1 + registers: [0x0202] - name: "Total Losses" class: "energy" state_class: "total_increasing" uom: "kWh" - scale: 0.1 - rule: 0 - params: ["Total Energy Bought", "Total Production", "Total Battery Discharge", "Total Energy Sold", "Total Load Consumption", "Total Battery Charge"] - formula: "max(round({0} + {1} + {2} - {3} - {4} - {5}, 1), 0)" + rule: 3 + digits: 1 + registers: + [ + 0x020A, + 0x020B, + 0x0216, + 0x0217, + 0x0206, + 0x0207, + 0x020C, + 0x020D, + 0x020F, + 0x0210, + 0x0204, + 0x0205, + ] + sensors: + - scale: 0.1 + registers: [0x020A, 0x020B] + - scale: 0.1 + registers: [0x0216, 0x0217] + - scale: 0.1 + registers: [0x0206, 0x0207] + - subtract: + scale: 0.1 + registers: [0x020C, 0x020D] + - subtract: + scale: 0.1 + registers: [0x020F, 0x0210] + - subtract: + scale: 0.1 + registers: [0x0204, 0x0205] - name: "Today Production" class: "energy" @@ -764,11 +822,26 @@ parameters: state_class: "" uom: "" scale: 1 - rule: 0 - params: ["Device Relay Status enum"] - formula: "'On-grid' if {0} & 4 == 4 else 'Off-grid'" + rule: 1 + registers: [0x0228] + icon: "mdi:information" isstr: true - icon: "mdi:transmission-tower-off" + lookup_default: "X-grid" + lookup: + - key: 0x0001 + value: "Off-grid" + - key: 0x0004 + value: "On-grid" + - key: 0x0005 + value: "On-grid" + - key: 0x0008 + value: "Off-grid" + - key: 0x0009 + value: "Off-grid" + - key: 0x000C + value: "On-grid" + - key: 0x000D + value: "On-grid" # Device - Alarm message (word 1 & 2) [0, 65535] - name: "Device Alert" @@ -882,7 +955,13 @@ parameters: uom: "%" scale: 1 rule: 0 - params: ["Total Battery Charge", "Battery Capacity", "Battery Nominal Voltage", "Battery Life Cycle Rating"] + params: + [ + "Total Battery Charge", + "Battery Capacity", + "Battery Nominal Voltage", + "Battery Life Cycle Rating", + ] formula: "round(100 - {0} / ({1} * {2} / 1000) / ({3} * 0.05), 2)" icon: "mdi:battery-heart" @@ -1146,10 +1225,7 @@ parameters: rule: 4 registers: [0x0271, 0x02B2] icon: "mdi:transmission-tower" - attributes: - [ - "Zero Export", - ] + attributes: ["Zero Export"] - group: Inverter items: @@ -1252,10 +1328,17 @@ parameters: class: "power" state_class: "measurement" uom: "W" - scale: 1 - rule: 0 - params: ["Battery Power", "PV1 Power", "PV2 Power", "Inverter Power"] - formula: "max({0} + {1} + {2} - {3}, 0)" + rule: 1 + digits: 0 + registers: [0x024E, 0x02A0, 0x02A1, 0x027C, 0x02B6] + sensors: + - signed: + registers: [0x024E] + - registers: [0x02A0] + - registers: [0x02A1] + - subtract: + signed: + registers: [0x027C, 0x02B6] # Inverter - Frequency - name: "Inverter Frequency" @@ -1388,10 +1471,12 @@ parameters: class: "power" state_class: "measurement" uom: "W" - scale: 1 - rule: 0 - params: ["PV1 Power", "PV2 Power"] - formula: "{0} + {1}" + rule: 1 + digits: 0 + registers: [0x02A0, 0x02A1] + sensors: + - registers: [0x02A0] + - registers: [0x02A1] icon: "mdi:solar-power-variant" # Photovoltaic - The power of Input 1 (L:1W, H:10W) diff --git a/custom_components/solarman/inverter_definitions/solarman_dtsd422-d3.yaml b/custom_components/solarman/inverter_definitions/solarman_dtsd422-d3.yaml new file mode 100644 index 0000000..67851dd --- /dev/null +++ b/custom_components/solarman/inverter_definitions/solarman_dtsd422-d3.yaml @@ -0,0 +1,456 @@ +# +# Solarman Smart Meter DTSD422-D3 +# + +parameters: + - group: Voltage + items: + - name: CT1 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x0001] + + - name: CT2 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x0002] + + - name: CT3 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x0003] + + - name: CT4 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x1001] + + - name: CT5 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x1002] + + - name: CT6 Voltage + realtime: + class: voltage + uom: V + scale: 0.1 + rule: 1 + registers: [0x1003] + + - group: Current + items: + - name: CT1 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x0008, 0x0007] + + - name: CT2 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x000A, 0x0009] + + - name: CT3 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x000C, 0x000B] + + - name: CT4 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x1008, 0x1007] + + - name: CT5 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x100A, 0x1009] + + - name: CT6 Current + realtime: + class: current + state_class: measurement + uom: A + scale: 0.001 + rule: 4 + magnitude: True + registers: [0x100C, 0x100B] + + - group: Active Power + items: + - name: CT1-3 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x000E, 0x000D] + + - name: CT1 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x0010, 0x000F] + + - name: CT2 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x0012, 0x0011] + + - name: CT3 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x0014, 0x0013] + + - name: CT4-6 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x100E, 0x100D] + + - name: CT4 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x1010, 0x100F] + + - name: CT5 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x1012, 0x1011] + + - name: CT6 Active Power + realtime: + class: power + state_class: measurement + uom: W + scale: 1 + rule: 4 + magnitude: True + registers: [0x1014, 0x1013] + + - group: Reactive Power + items: + - name: CT1-3 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x0016, 0x0015] + + - name: CT1 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x0018, 0x0017] + + - name: CT2 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x001A, 0x0019] + + - name: CT3 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x001C, 0x001B] + + - name: CT4-6 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x1016, 0x1015] + + - name: CT4 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x1018, 0x1017] + + - name: CT5 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x101A, 0x1019] + + - name: CT6 Reactive Power + realtime: + class: reactive_power + state_class: measurement + uom: var + scale: 1 + rule: 4 + magnitude: True + registers: [0x101C, 0x101B] + + - group: Apparent Power + items: + - name: CT1-3 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x001E, 0x001D] + + - name: CT1 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x0020, 0x001F] + + - name: CT2 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x0022, 0x0021] + + - name: CT3 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x0024, 0x0023] + + - name: CT4-6 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x101E, 0x101D] + + - name: CT4 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x1020, 0x101F] + + - name: CT5 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x1022, 0x1021] + + - name: CT6 Apparent Power + realtime: + class: apparent_power + state_class: measurement + uom: VA + scale: 1 + rule: 4 + magnitude: True + registers: [0x1024, 0x1023] + + - group: Power Factor + items: + - name: CT1-3 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x0025] + + - name: CT1 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x0026] + + - name: CT2 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x0027] + + - name: CT3 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x0028] + + - name: CT4-6 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x1025] + + - name: CT4 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x1026] + + - name: CT5 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x1027] + + - name: CT6 Power Factor + class: power_factor + state_class: measurement + uom: "" + scale: 0.001 + rule: 2 + magnitude: True + registers: [0x1028] + + - group: Frequency + items: + - name: CT1-3 Frequency + realtime: + class: frequency + state_class: measurement + uom: Hz + scale: 0.01 + rule: 1 + registers: [0x0029] + + - name: CT4-6 Frequency + realtime: + class: frequency + state_class: measurement + uom: Hz + scale: 0.01 + rule: 1 + registers: [0x1029] diff --git a/custom_components/solarman/parser.py b/custom_components/solarman/parser.py index 3f08e7d..8921358 100644 --- a/custom_components/solarman/parser.py +++ b/custom_components/solarman/parser.py @@ -1,21 +1,28 @@ from __future__ import annotations -import yaml import struct import logging -from itertools import groupby -from operator import itemgetter - -from .const import COORDINATOR_QUERY_INTERVAL_DEFAULT, FLOAT_ROUND_TO +from .const import * from .common import * _LOGGER = logging.getLogger(__name__) class ParameterParser: def __init__(self, parameter_definition): - self._lookups = yaml.safe_load(parameter_definition) - self.result = {} + self._lookups = parameter_definition + self._update_interval = TIMINGS_QUERY_INTERVAL_DEFAULT + self._digits = DIGITS_DEFAULT + self._result = {} + + if "default" in parameter_definition: + default = parameter_definition["default"] + if "update_interval" in default: + self._update_interval = default["update_interval"] + if "digits" in default: + self._digits = default["digits"] + + _LOGGER.debug(f"Default update_interval: {self._update_interval}, digits: {self._digits}") def lookup(self): return self._lookups["parameters"] @@ -33,7 +40,7 @@ def is_requestable(self, parameters): return self.is_valid(parameters) and self.is_enabled(parameters) and parameters["rule"] > 0 def is_scheduled(self, parameters, runtime): - return "realtime" in parameters or (runtime % (parameters["update_interval"] if "update_interval" in parameters else COORDINATOR_QUERY_INTERVAL_DEFAULT) == 0) + return "realtime" in parameters or (runtime % (parameters["update_interval"] if "update_interval" in parameters else self._update_interval) == 0) def get_sensors(self): result = [{"name": "Connection Status", "artificial": ""}] @@ -41,6 +48,7 @@ def get_sensors(self): for j in i["items"]: if self.is_sensor(j): result.append(j) + return result def get_requests(self, runtime = 0): @@ -52,7 +60,8 @@ def get_requests(self, runtime = 0): for i in self.lookup(): for j in i["items"]: if self.is_requestable(j) and self.is_scheduled(j, runtime): - self.result[j["name"]] = "" + self._result[j["name"]] = {} + self._result[j["name"]]["state"] = "" for r in j["registers"]: registers.append(r) @@ -67,10 +76,21 @@ def parse(self, rawData, start, length): for j in i["items"]: if self.is_valid(j) and self.is_enabled(j): self.try_parse(rawData, j, start, length) + return + def set_state(self, key, value): + self._result[key] = {} + self._result[key]["state"] = value + + def set_state_number(self, key, value, digits): + if isinstance(value, int) or (isinstance(value, float) and value.is_integer()): + self.set_state(key, int(value)) + else: + self.set_state(key, round(value, digits)) + def get_result(self): - return self.result + return self._result def in_range(self, value, definition): if "range" in definition: @@ -78,25 +98,27 @@ def in_range(self, value, definition): if "min" in range and "max" in range: if value < range["min"] or value > range["max"]: return False + return True def lookup_value(self, value, definition): for o in definition["lookup"]: if (o["key"] == value): return o["value"] + return value if not "lookup_default" in definition else f"{definition["lookup_default"]} [{value}]" - def do_validate(self, title, value, rule): + def do_validate(self, key, value, rule): if "min" in rule: if rule["min"] > value: if "invalidate_all" in rule: - raise ValueError(f"Invalidate complete dataset ({title} ~ {value})") + raise ValueError(f"Invalidate complete dataset ({key} ~ {value})") return False if "max" in rule: if rule["max"] < value: if "invalidate_all" in rule: - raise ValueError(f"Invalidate complete dataset ({title} ~ {value})") + raise ValueError(f"Invalidate complete dataset ({key} ~ {value})") return False return True @@ -131,19 +153,19 @@ def try_parse_field(self, rawData, definition, start, length): case 9: self.try_parse_time(rawData, definition, start, length) case 10: - self.try_parse_raw(rawData,definition, start, length) + self.try_parse_raw(rawData, definition, start, length) + return - def try_parse_unsigned(self, rawData, definition, start, length): - title = definition["name"] - scale = definition["scale"] - value = 0 + def _read_registers(self, rawData, definition, start, length): + scale = definition["scale"] if "scale" in definition else 1 found = True + value = 0 shift = 0 for r in definition["registers"]: index = r - start - if (index >= 0) and (index < length): + if index >= 0 and index < length: value += (rawData[index] & 0xFFFF) << shift shift += 16 else: @@ -151,39 +173,29 @@ def try_parse_unsigned(self, rawData, definition, start, length): if found: if not self.in_range(value, definition): - return + return None if "mask" in definition: - mask = definition["mask"] - value &= mask + value &= definition["mask"] - if "lookup" in definition: - self.result[title] = self.lookup_value(value, definition) - self.result[title + " enum"] = int(value) - else: + if "lookup" not in definition: if "offset" in definition: value = value - definition["offset"] - value = value * scale - if "validation" in definition: - if not self.do_validate(title, value, definition["validation"]): - return - - self.num_to_result(title, value) - return + return value if found else None - def try_parse_signed(self, rawData, definition, start, length): - title = definition["name"] - scale = definition["scale"] - value = 0 + def _read_registers_signed(self, rawData, definition, start, length): + magnitude = definition["magnitude"] if "magnitude" in definition else False + scale = definition["scale"] if "scale" in definition else 1 found = True - shift = 0 maxint = 0 + value = 0 + shift = 0 for r in definition["registers"]: index = r - start - if (index >= 0) and (index < length): + if index >= 0 and index < length: maxint <<= 16 maxint |= 0xFFFF value += (rawData[index] & 0xFFFF) << shift @@ -193,27 +205,67 @@ def try_parse_signed(self, rawData, definition, start, length): if found: if not self.in_range(value, definition): - return + return None if "offset" in definition: value = value - definition["offset"] - if value > maxint / 2: - value = (value - maxint) * scale + if value > (maxint >> 1): + value = (value - maxint) if not magnitude else -(value & (maxint >> 1)) + + value = value * scale + + return value if found else None + + def try_parse_unsigned(self, rawData, definition, start, length): + key = definition["name"] + value = 0 + found = True + + if "sensors" in definition: + for s in definition["sensors"]: + if (n := (self._read_registers(rawData, s, start, length) if not "signed" in s else self._read_registers_signed(rawData, s, start, length))) is not None: + if not "subtract" in s: + value += n + else: + value -= n + else: + found = False + else: + value = self._read_registers(rawData, definition, start, length) + found = value is not None + + if found: + if "lookup" in definition: + self.set_state(key, self.lookup_value(value, definition)) + self._result[key]["value"] = int(value) else: - value = value * scale + if "validation" in definition: + if not self.do_validate(key, value, definition["validation"]): + return + + self.set_state_number(key, value, definition["digits"] if "digits" in definition else self._digits) + + return + + def try_parse_signed(self, rawData, definition, start, length): + key = definition["name"] + value = self._read_registers_signed(rawData, definition, start, length) + if value is not None: if "validation" in definition: - if not self.do_validate(title, value, definition["validation"]): + if not self.do_validate(key, value, definition["validation"]): return - self.num_to_result(title, value) + self.set_state_number(key, value, definition["digits"] if "digits" in definition else self._digits) + return def try_parse_ascii(self, rawData, definition, start, length): - title = definition["name"] + key = definition["name"] found = True value = "" + for r in definition["registers"]: index = r - start if (index >= 0) and (index < length): @@ -223,13 +275,15 @@ def try_parse_ascii(self, rawData, definition, start, length): found = False if found: - self.result[title] = value + self.set_state(key, value) + return def try_parse_bits(self, rawData, definition, start, length): - title = definition["name"] + key = definition["name"] found = True value = [] + for r in definition["registers"]: index = r - start if (index >= 0) and (index < length): @@ -239,13 +293,15 @@ def try_parse_bits(self, rawData, definition, start, length): found = False if found: - self.result[title] = value + self.set_state(key, value) + return def try_parse_version(self, rawData, definition, start, length): - title = definition["name"] + key = definition["name"] found = True value = "" + for r in definition["registers"]: index = r - start if (index >= 0) and (index < length): @@ -255,14 +311,17 @@ def try_parse_version(self, rawData, definition, start, length): found = False if found: - self.result[title] = value + self.set_state(key, value) + return def try_parse_datetime(self, rawData, definition, start, length): - title = definition["name"] + key = definition["name"] found = True value = "" + print("start: ", start) + for i,r in enumerate(definition["registers"]): index = r - start print ("index: ",index) @@ -280,13 +339,15 @@ def try_parse_datetime(self, rawData, definition, start, length): found = False if found: - self.result[title] = value + self.set_state(key, value) + return def try_parse_time(self, rawData, definition, start, length): - title = definition["name"] + key = definition["name"] found = True value = "" + for r in definition["registers"]: index = r - start if (index >= 0) and (index < length): @@ -296,14 +357,16 @@ def try_parse_time(self, rawData, definition, start, length): found = False if found: - self.result[title] = value + self.set_state(key, value) + return def try_parse_raw(self, rawData, definition, start, length): - title = definition['name'] + key = definition["name"] found = True value = [] - for r in definition['registers']: + + for r in definition["registers"]: index = r - start if (index >= 0) and (index < length): temp = rawData[index] @@ -312,18 +375,6 @@ def try_parse_raw(self, rawData, definition, start, length): found = False if found: - self.result[title] = value - return + self.set_state(key, value) - def num_to_result(self, title, value): - if self.is_integer_num(value): - self.result[title] = int(value) - else: - self.result[title] = round(value, FLOAT_ROUND_TO) - - def is_integer_num(self, n): - if isinstance(n, int): - return True - if isinstance(n, float): - return n.is_integer() - return False + return \ No newline at end of file diff --git a/custom_components/solarman/sensor.py b/custom_components/solarman/sensor.py index 5c72134..a2fbf80 100644 --- a/custom_components/solarman/sensor.py +++ b/custom_components/solarman/sensor.py @@ -1,39 +1,35 @@ from __future__ import annotations import re -import string import logging import asyncio -import aiofiles import voluptuous as vol from homeassistant.core import HomeAssistant, callback -from homeassistant.const import CONF_NAME, EntityCategory from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, EntityCategory +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo, format_mac from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo, format_mac from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import * from .common import * +from .api import Inverter from .discovery import InverterDiscovery from .coordinator import InverterCoordinator -from .api import Inverter from .services import * _LOGGER = logging.getLogger(__name__) -_FMT = string.Formatter() - -def _create_sensor(coordinator, inverter_name, inverter, sensor, config, disable_templating): +def _create_sensor(coordinator, sensor, battery_life_cycle_rating): try: if "artificial" in sensor: - entity = SolarmanStatus(coordinator, inverter_name, inverter, sensor["name"]) + entity = SolarmanStatus(coordinator, sensor) elif "isstr" in sensor: - entity = SolarmanSensorText(coordinator, inverter_name, inverter, sensor, config, disable_templating) + entity = SolarmanSensorBase(coordinator, sensor) else: - entity = SolarmanSensor(coordinator, inverter_name, inverter, sensor, config, disable_templating) + entity = SolarmanSensor(coordinator, sensor, battery_life_cycle_rating) entity.update() @@ -45,18 +41,15 @@ def _create_sensor(coordinator, inverter_name, inverter, sensor, config, disable async def async_setup(hass: HomeAssistant, config, async_add_entities: AddEntitiesCallback, id = None): _LOGGER.debug(f"async_setup: {config}") - lookup_path = hass.config.path(LOOKUP_DIRECTORY_PATH) - inverter_name = config.get(CONF_NAME) inverter_discovery = config.get(CONF_INVERTER_DISCOVERY) inverter_host = config.get(CONF_INVERTER_HOST) inverter_serial = config.get(CONF_INVERTER_SERIAL) inverter_port = config.get(CONF_INVERTER_PORT) inverter_mb_slave_id = config.get(CONF_INVERTER_MB_SLAVE_ID) + lookup_path = hass.config.path(LOOKUP_DIRECTORY_PATH) lookup_file = config.get(CONF_LOOKUP_FILE) - battery_nominal_voltage = config.get(CONF_BATTERY_NOMINAL_VOLTAGE) battery_life_cycle_rating = config.get(CONF_BATTERY_LIFE_CYCLE_RATING) - disable_templating = config.get(CONF_DISABLE_TEMPLATING) inverter_discovery = InverterDiscovery(hass, inverter_host) @@ -73,25 +66,15 @@ async def async_setup(hass: HomeAssistant, config, async_add_entities: AddEntiti if not inverter_mb_slave_id: inverter_mb_slave_id = DEFAULT_INVERTER_MB_SLAVE_ID - if not battery_nominal_voltage: - battery_nominal_voltage = DEFAULT_BATTERY_NOMINAL_VOLTAGE - - if not battery_life_cycle_rating: - battery_life_cycle_rating = DEFAULT_BATTERY_LIFE_CYCLE_RATING - - if not disable_templating: - disable_templating = DEFAULT_DISABLE_TEMPLATING - if inverter_host is None: raise vol.Invalid("configuration parameter [inverter_host] does not have a value") if inverter_serial is None: raise vol.Invalid("configuration parameter [inverter_serial] does not have a value") - inverter = Inverter(inverter_host, inverter_mac, inverter_serial, inverter_port, inverter_mb_slave_id, lookup_path, lookup_file) + inverter = Inverter(inverter_host, inverter_serial, inverter_port, inverter_mb_slave_id, inverter_name, inverter_mac, lookup_path, lookup_file) sensors = await inverter.get_sensors() - coordinator = InverterCoordinator(hass, inverter) - hass.data.setdefault(DOMAIN, {})[id] = coordinator + coordinator = InverterCoordinator(hass, inverter) # Fetch initial data so we have data when entities subscribe. # @@ -102,59 +85,60 @@ async def async_setup(hass: HomeAssistant, config, async_add_entities: AddEntiti # coordinator.async_refresh() instead. # _LOGGER.debug(f"async_setup: coordinator.async_config_entry_first_refresh") + await coordinator.async_config_entry_first_refresh() + # Add entities. + # _LOGGER.debug(f"async_setup: async_add_entities") - async_add_entities(_create_sensor(coordinator, inverter_name, inverter, sensor, { "Battery Nominal Voltage": battery_nominal_voltage, "Battery Life Cycle Rating": battery_life_cycle_rating }, disable_templating) for sensor in sensors) + async_add_entities(_create_sensor(coordinator, sensor, battery_life_cycle_rating) for sensor in sensors) + + # Register the services with home assistant. + # _LOGGER.debug(f"async_setup: register_services") - register_services(hass, inverter) + + hass.data.setdefault(DOMAIN, {})[id] = coordinator -# Set-up from configuration.yaml -#async def async_setup_platform(hass: HomeAssistant, config, async_add_entities: AddEntitiesCallback, discovery_info = None): -# _LOGGER.debug(f"async_setup_platform: {config}") -# await async_setup(hass, config, async_add_entities) + register_services(hass, inverter) -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> bool: - _LOGGER.debug(f"async_setup_entry: {entry.options}") - await async_setup(hass, entry.options, async_add_entities, entry.entry_id) +async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry, async_add_entities: AddEntitiesCallback) -> bool: + _LOGGER.debug(f"async_setup_entry: {config.options}") + await async_setup(hass, config.options, async_add_entities, config.entry_id) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - _LOGGER.debug(f"async_unload_entry: remove_services {entry.options}") +async def async_unload_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: + _LOGGER.debug(f"async_unload_entry: {config.options}") remove_services(hass) return True -class InverterCoordinatorEntity(CoordinatorEntity[InverterCoordinator]): - def __init__(self, coordinator: InverterCoordinator, id: str = None, device_name: str = None, device_lookup_file: str = None, manufacturer: str = None): +class SolarmanCoordinatorEntity(CoordinatorEntity[InverterCoordinator]): + def __init__(self, coordinator: InverterCoordinator, name: str = None): super().__init__(coordinator) - self.id = coordinator.inverter.serial - self.device_name = device_name - self.model = device_lookup_file.replace(".yaml", "") - + self.model = self.coordinator.inverter.lookup_file.replace(".yaml", "") + if '_' in self.model: dev_man = self.model.split('_') - self.model = dev_man[1].upper() self.manufacturer = dev_man[0].capitalize() + self.model = dev_man[1].upper() self._attr_device_info = { "connections": {(CONNECTION_NETWORK_MAC, format_mac(self.coordinator.inverter.mac))} - } if self.coordinator.inverter.mac else {} - - self._attr_device_info = self._attr_device_info | { - "identifiers": {(DOMAIN, self.id)}, - "name": self.device_name, + } if self.coordinator.inverter.mac else {} | { + "identifiers": {(DOMAIN, self.coordinator.inverter.serial)}, + "name": self.coordinator.inverter.name, + "manufacturer": self.manufacturer, "model": self.model, - "manufacturer": self.manufacturer + "serial_number": self.coordinator.inverter.serial } - self._attr_extra_state_attributes = { "id": self.id, "integration": DOMAIN } + #self._attr_extra_state_attributes = { "id": self.coordinator.inverter.serial, "integration": DOMAIN } + self._attr_extra_state_attributes = {} -class SolarmanStatus(InverterCoordinatorEntity): - def __init__(self, coordinator, inverter_name, inverter, field_name): - super().__init__(coordinator, inverter.serial, inverter_name, inverter.lookup_file) - self._inverter_name = inverter_name - self._field_name = field_name +class SolarmanStatus(SolarmanCoordinatorEntity): + def __init__(self, coordinator, sensor): + super().__init__(coordinator) + self.sensor_name = sensor["name"] # Return the category of the sensor. self._attr_entity_category = (EntityCategory.DIAGNOSTIC) @@ -163,10 +147,10 @@ def __init__(self, coordinator, inverter_name, inverter, field_name): self._attr_icon = "mdi:information" # Return the name of the sensor. - self._attr_name = "{} {}".format(self._inverter_name, self._field_name) + self._attr_name = "{} {}".format(self.coordinator.inverter.name, self.sensor_name) # Return a unique_id based on the serial number - self._attr_unique_id = "{}_{}_{}".format(self._inverter_name, self.coordinator.inverter.serial, self._field_name) + self._attr_unique_id = "{}_{}_{}".format(self.coordinator.inverter.name, self.coordinator.inverter.serial, self.sensor_name) @property def available(self) -> bool: @@ -185,16 +169,13 @@ def update(self): self._attr_state = self.coordinator.inverter.get_connection_status() self._attr_extra_state_attributes["updated"] = self.coordinator.inverter.status_lastUpdate -class SolarmanSensorText(SolarmanStatus): - def __init__(self, coordinator, inverter_name, inverter, sensor, config, disable_templating): - SolarmanStatus.__init__(self, coordinator, inverter_name, inverter, sensor["name"]) +class SolarmanSensorBase(SolarmanStatus): + def __init__(self, coordinator, sensor): + SolarmanStatus.__init__(self, coordinator, sensor) self._attr_entity_registry_enabled_default = not "disabled" in sensor - if "display_precision" in sensor: - self._attr_suggested_display_precision = sensor["display_precision"] - - if "display_precision" in sensor: - self._suggested_display_precision = sensor["display_precision"] + if "suggested_display_precision" in sensor: + self._attr_suggested_display_precision = sensor["suggested_display_precision"] if "state_class" in sensor and sensor["state_class"]: self._attr_extra_state_attributes = { "state_class": sensor["state_class"] } @@ -205,63 +186,34 @@ def __init__(self, coordinator, inverter_name, inverter, sensor, config, disable self.attributes = sensor["attributes"] if "attributes" in sensor else None - self.config = config - - self.is_ok = True - - self.params = sensor["params"] if "params" in sensor else None - - self.formula = sensor["formula"] if "formula" in sensor else None - - if not disable_templating: - if self.params and self.formula: - self.formula_pcount = len([p for p in _FMT.parse(self.formula) if p[2] is not None]) - if self.formula_pcount != len(self.params): - self.is_ok = False - _LOGGER.error(f"{self._field_name} template is not valid.") - elif "formula" in sensor: - self.is_ok = False - - def _lookup_param(self, p): - if p in self.coordinator.data: - return self.coordinator.data[p] - - if p in self.config: - return self.config[p] - - return 0 - def update(self): - if not self.is_ok: - return - c = len(self.coordinator.data) - if c > 1 or (c == 1 and self._field_name in self.coordinator.data): - if self._field_name in self.coordinator.data: - self._attr_state = self.coordinator.data[self._field_name] + if c > 1 or (c == 1 and self.sensor_name in self.coordinator.data): + if self.sensor_name in self.coordinator.data: + self._attr_state = self.coordinator.data[self.sensor_name]["state"] + if "value" in self.coordinator.data[self.sensor_name]: + self._attr_extra_state_attributes["value"] = self.coordinator.data[self.sensor_name]["value"] if self.attributes: for attr in self.attributes: if attr in self.coordinator.data: - attr_name = attr.replace(f"{self._field_name} ", "") - self._attr_extra_state_attributes[attr_name] = self.coordinator.data[attr] - if self._field_name + " enum" in self.coordinator.data: - self._attr_extra_state_attributes["Value"] = self.coordinator.data[self._field_name + " enum"] - elif self.formula: - if set(self.params) <= self.coordinator.data.keys() | self.config: - params = [self._lookup_param(p) for p in self.params] - if self.formula_pcount == len(params): - formula = self.formula.format(*params) - self._attr_state = eval(formula) - -class SolarmanSensor(SolarmanSensorText): - def __init__(self, coordinator, inverter_name, inverter, sensor, config, disable_templating): - SolarmanSensorText.__init__(self, coordinator, inverter_name, inverter, sensor, config, disable_templating) - - if device_class := sensor["class"]: + attr_name = attr.replace(f"{self.sensor_name} ", "") + self._attr_extra_state_attributes[attr_name] = self.coordinator.data[attr]["state"] + +class SolarmanSensor(SolarmanSensorBase): + def __init__(self, coordinator, sensor, battery_life_cycle_rating): + SolarmanSensorBase.__init__(self, coordinator, sensor) + + if "class" in sensor and (device_class := sensor["class"]): + self._attr_device_class = device_class + + if "device_class" in sensor and (device_class := sensor["device_class"]): self._attr_device_class = device_class - if unit_of_measurement := sensor["uom"]: + if "uom" in sensor and (unit_of_measurement := sensor["uom"]): + self._attr_unit_of_measurement = unit_of_measurement + + if "unit_of_measurement" in sensor and (unit_of_measurement := sensor["unit_of_measurement"]): self._attr_unit_of_measurement = unit_of_measurement - if sensor["name"] == "Battery": - self._attr_extra_state_attributes = self._attr_extra_state_attributes | { "Nominal Voltage": config["Battery Nominal Voltage"], "Life Cycle Rating": config["Battery Life Cycle Rating"] } \ No newline at end of file + if "name" in sensor and sensor["name"] == "Battery": + self._attr_extra_state_attributes = self._attr_extra_state_attributes | { "Life Cycle Rating": battery_life_cycle_rating } \ No newline at end of file diff --git a/custom_components/solarman/services.py b/custom_components/solarman/services.py index 0484c55..87938d3 100644 --- a/custom_components/solarman/services.py +++ b/custom_components/solarman/services.py @@ -1,18 +1,14 @@ -from __future__ import annotations - -import voluptuous as vol - from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv - +import voluptuous as vol from .const import * from .api import Inverter -SERVICE_WRITE_REGISTER = "write_holding_register" -SERVICE_WRITE_MULTIPLE_REGISTERS = "write_multiple_holding_registers" -PARAM_REGISTER = "register" -PARAM_VALUE = "value" -PARAM_VALUES = "values" +SERVICE_WRITE_REGISTER = 'write_holding_register' +SERVICE_WRITE_MULTIPLE_REGISTERS = 'write_multiple_holding_registers' +PARAM_REGISTER = 'register' +PARAM_VALUE = 'value' +PARAM_VALUES = 'values' # Register the services one can invoke on the inverter. # Apart from this, it also need to be defined in the file