Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport of new inverter_definitions and const.py #454

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,5 @@ dmypy.json
# Pyre type checker
.pyre/

# vscode
# vscode
.vscode/
9 changes: 5 additions & 4 deletions custom_components/solarman/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@

import logging

from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant

from .const import *

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[str] = ["sensor"]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Solarman Collector from a config entry."""
_LOGGER.debug(f'__init__.py:async_setup_entry({entry.as_dict()})')
_LOGGER.debug(f"__init__.py:async_setup_entry({entry.as_dict()})")
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug(f'__init__.py:async_unload_entry({entry.as_dict()})')
_LOGGER.debug(f"__init__.py:async_unload_entry({entry.as_dict()})")
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
Expand All @@ -31,6 +32,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
_LOGGER.debug(f'__init__.py:update_listener({entry.as_dict()})')
_LOGGER.debug(f"__init__.py:update_listener({entry.as_dict()})")
hass.data[DOMAIN][entry.entry_id].config(entry)
entry.title = entry.options[CONF_NAME]
62 changes: 29 additions & 33 deletions custom_components/solarman/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@
from __future__ import annotations

import logging
from socket import gaierror, getaddrinfo, herror, timeout
from typing import Any
from socket import getaddrinfo, herror, gaierror, timeout

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from voluptuous.schema_builder import Schema

from homeassistant import config_entries
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_SCAN_INTERVAL,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from voluptuous.schema_builder import Schema

from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME, CONF_SCAN_INTERVAL)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from .const import *

_LOGGER = logging.getLogger(__name__)


def step_user_data_schema(data: dict[str, Any] = {CONF_NAME: SENSOR_PREFIX, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVEID: DEFAULT_INVERTER_MB_SLAVEID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE}) -> Schema:
_LOGGER.debug(f'config_flow.py:step_user_data_schema: {data}')
def step_user_data_schema(
data: dict[str, Any] = {
CONF_NAME: SENSOR_PREFIX,
CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER,
CONF_INVERTER_MB_SLAVEID: DEFAULT_INVERTER_MB_SLAVEID,
CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE,
}
) -> Schema:
_LOGGER.debug(f"config_flow.py:step_user_data_schema: {data}")
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME, default=data.get(CONF_NAME)): str,
Expand All @@ -32,10 +39,9 @@ def step_user_data_schema(data: dict[str, Any] = {CONF_NAME: SENSOR_PREFIX, CONF
vol.Optional(CONF_INVERTER_MB_SLAVEID, default=data.get(CONF_INVERTER_MB_SLAVEID)): int,
vol.Optional(CONF_LOOKUP_FILE, default=data.get(CONF_LOOKUP_FILE)): vol.In(LOOKUP_FILES),
},
extra=vol.PREVENT_EXTRA
extra=vol.PREVENT_EXTRA,
)
_LOGGER.debug(
f'config_flow.py:step_user_data_schema: STEP_USER_DATA_SCHEMA: {STEP_USER_DATA_SCHEMA}')
_LOGGER.debug(f"config_flow.py:step_user_data_schema: STEP_USER_DATA_SCHEMA: {STEP_USER_DATA_SCHEMA}")
return STEP_USER_DATA_SCHEMA


Expand All @@ -46,12 +52,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""

_LOGGER.debug(f'config_flow.py:validate_input: {data}')
_LOGGER.debug(f"config_flow.py:validate_input: {data}")

try:
getaddrinfo(
data[CONF_INVERTER_HOST], data[CONF_INVERTER_PORT], family=0, type=0, proto=0, flags=0
)
getaddrinfo(data[CONF_INVERTER_HOST], data[CONF_INVERTER_PORT], family=0, type=0, proto=0, flags=0)
except herror:
raise InvalidHost
except gaierror:
Expand All @@ -71,18 +75,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@callback
def async_get_options_flow(entry: config_entries.ConfigEntry) -> OptionsFlow:
"""Get the options flow for this handler."""
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_get_options_flow: {entry}')
_LOGGER.debug(f"config_flow.py:ConfigFlow.async_get_options_flow: {entry}")
return OptionsFlow(entry)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: {user_input}')
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult:
_LOGGER.debug(f"config_flow.py:ConfigFlow.async_step_user: {user_input}")
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=step_user_data_schema()
)
return self.async_show_form(step_id="user", data_schema=step_user_data_schema())

errors = {}

Expand All @@ -96,14 +96,12 @@ async def async_step_user(
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: validation passed: {user_input}')
_LOGGER.debug(f"config_flow.py:ConfigFlow.async_step_user: validation passed: {user_input}")
# await self.async_set_unique_id(user_input.device_id) # not sure this is permitted as the user can change the device_id
# self._abort_if_unique_id_configured()
return self.async_create_entry(
title=info["title"], data=user_input, options=user_input
)
return self.async_create_entry(title=info["title"], data=user_input, options=user_input)

_LOGGER.debug(f'config_flow.py:ConfigFlow.async_step_user: validation failed: {user_input}')
_LOGGER.debug(f"config_flow.py:ConfigFlow.async_step_user: validation failed: {user_input}")

return self.async_show_form(
step_id="user",
Expand All @@ -117,14 +115,12 @@ class OptionsFlow(config_entries.OptionsFlow):

def __init__(self, entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
_LOGGER.debug(f'config_flow.py:OptionsFlow.__init__: {entry}')
_LOGGER.debug(f"config_flow.py:OptionsFlow.__init__: {entry}")
self.entry = entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult:
"""Manage the options."""
_LOGGER.debug(f'config_flow.py:OptionsFlow.async_step_init: {user_input}')
_LOGGER.debug(f"config_flow.py:OptionsFlow.async_step_init: {user_input}")
if user_input is None:
return self.async_show_form(
step_id="init",
Expand Down
34 changes: 11 additions & 23 deletions custom_components/solarman/const.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,19 @@
from datetime import timedelta

DOMAIN = 'solarman'
DOMAIN = "solarman"

DEFAULT_PORT_INVERTER = 8899
DEFAULT_INVERTER_MB_SLAVEID = 1
DEFAULT_LOOKUP_FILE = 'deye_hybrid.yaml'
LOOKUP_FILES = [
'deye_4mppt.yaml',
'deye_hybrid.yaml',
'deye_sg04lp3.yaml',
'deye_string.yaml',
'sofar_lsw3.yaml',
'sofar_wifikit.yaml',
'solis_hybrid.yaml',
'solis_1p8k-5g.yaml',
'sofar_g3hyd.yaml',
'sofar_hyd3k-6k-es.yaml',
'zcs_azzurro-ktl-v3.yaml',
'custom_parameters.yaml'
]
DEFAULT_LOOKUP_FILE = "deye_hybrid.yaml"

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
LOOKUP_FILES = os.listdir(os.path.dirname(__file__) + "/inverter_definitions")

CONF_INVERTER_HOST = 'inverter_host'
CONF_INVERTER_PORT = 'inverter_port'
CONF_INVERTER_SERIAL = 'inverter_serial'
CONF_INVERTER_MB_SLAVEID = 'inverter_mb_slaveid'
CONF_LOOKUP_FILE = 'lookup_file'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=15)

SENSOR_PREFIX = 'Solarman'
CONF_INVERTER_HOST = "inverter_host"
CONF_INVERTER_PORT = "inverter_port"
CONF_INVERTER_SERIAL = "inverter_serial"
CONF_INVERTER_MB_SLAVEID = "inverter_mb_slaveid"
CONF_LOOKUP_FILE = "lookup_file"

SENSOR_PREFIX = "Solarman"
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# to use modbus function in Afore BNTxxxKTL inverters, You first need to change protocol from RS485 to MODBUS in inverter menu

requests:
- start: 0x0000
end: 0x001A
mb_functioncode: 0x04

- start: 0x0000
end: 0x000F
mb_functioncode: 0x03

parameters:
- group: solar
items:
- name: "PV1 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [0x0007]
icon: "mdi:solar-power"

- name: "PV2 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [0x0009]
icon: "mdi:solar-power"

- name: "PV1 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 1
registers: [0x0008]
icon: "mdi:solar-power"

- name: "PV2 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 1
registers: [0x000A]
icon: "mdi:solar-power"

- name: "Daily Production Wh"
class: "energy"
state_class: "total"
uom: "Wh"
scale: 1
rule: 1
registers: [0x000F]
icon: "mdi:solar-power"

- name: "Total Production Wh"
class: "energy"
state_class: "total_increasing"
uom: "Wh"
scale: 1
rule: 3
registers: [0x0015, 0x0014]
icon: "mdi:solar-power"

- name: "Today generation time "
class: ""
state_class: "measurement"
uom: "s"
scale: 1
rule: 1
registers: [0x0013]
icon: "mdi:clock-outline"

- group: Output
items:
- name: "Output active power"
class: "power"
state_class: "measurement"
uom: "W"
scale: 1
rule: 1
registers: [0x0011]
icon: "mdi:home-lightning-bolt"

- name: "Grid frequency"
class: "frequency"
state_class: "measurement"
uom: "Hz"
scale: 0.1
rule: 1
registers: [0x000B]
icon: "mdi:home-lightning-bolt"

- name: "L1 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [0x0001]
icon: "mdi:home-lightning-bolt"

- name: "L1 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 1
registers: [0x0004]
icon: "mdi:home-lightning-bolt"

- name: "L2 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [0x0002]
icon: "mdi:home-lightning-bolt"

- name: "L2 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 1
registers: [0x0005]
icon: "mdi:home-lightning-bolt"

- name: "L3 Voltage"
class: "voltage"
state_class: "measurement"
uom: "V"
scale: 0.1
rule: 1
registers: [0x0003]
icon: "mdi:home-lightning-bolt"

- name: "L3 Current"
class: "current"
state_class: "measurement"
uom: "A"
scale: 0.01
rule: 1
registers: [0x0006]
icon: "mdi:home-lightning-bolt"

- group: Inverter
items:
- name: "Inverter module temperature"
class: "temperature"
uom: "°C"
scale: 0.1
rule: 1
registers: [0x000C]
icon: "mdi:thermometer"

- name: "Inverter inner temperature"
class: "temperature"
state_class: "measurement"
uom: "°C"
scale: 0.1
rule: 1

registers: [0x000D]
icon: "mdi:thermometer"
Loading