Skip to content

Commit

Permalink
Merge pull request #57 from PatrikTrestik/56-charger-use-mode-state-n…
Browse files Browse the repository at this point in the history
…ot-showing

#56 charger use mode state not showing
  • Loading branch information
PatrikTrestik authored Dec 30, 2024
2 parents ceeea9e + c65ffb2 commit c12aee8
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 80 deletions.
11 changes: 10 additions & 1 deletion custom_components/solax_http/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant

from .const import CONF_SN, DOMAIN
from .const import ATTR_MANUFACTURER, CONF_SN, DOMAIN
from .coordinator import SolaxHttpUpdateCoordinator
from .plugin_factory import PluginFactory

Expand All @@ -32,6 +32,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
_LOGGER.debug("Setup %s.%s", DOMAIN, name)

plugin = await PluginFactory.get_plugin_instance(config[CONF_HOST], config[CONF_SN])
plugin.device_info = {
"identifiers": {(DOMAIN, name, plugin.serialnumber)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
"model": plugin.inverter_model,
"serial_number": plugin.serialnumber,
"hw_version": plugin.hw_version,
"sw_version": plugin.sw_version,
}
coordinator = SolaxHttpUpdateCoordinator(hass, entry, plugin)
await coordinator.async_config_entry_first_refresh()

Expand Down
10 changes: 2 additions & 8 deletions custom_components/solax_http/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import ATTR_MANUFACTURER, DOMAIN, BaseHttpButtonEntityDescription
from .const import DOMAIN, BaseHttpButtonEntityDescription
from .coordinator import SolaxHttpUpdateCoordinator
from .plugin_base import plugin_base

Expand All @@ -18,16 +18,10 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities) -> N
coordinator: SolaxHttpUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
plugin: plugin_base = coordinator.plugin

device_info = {
"identifiers": {(DOMAIN, name)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
}

entities = []
for button_info in plugin.BUTTON_TYPES:
if plugin.matchWithMask(button_info.allowedtypes, button_info.blacklist):
button = SolaXHttpButton(coordinator, name, device_info, button_info)
button = SolaXHttpButton(coordinator, name, plugin.device_info, button_info)

entities.append(button)

Expand Down
4 changes: 2 additions & 2 deletions custom_components/solax_http/entity_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ class SolaXEVChargerHttpSensorEntityDescription(BaseHttpSensorEntityDescription)
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.TOTAL,
entity_registry_enabled_default=True,
allowedtypes=G1,
allowedtypes=G1 | G2,
),
SolaXEVChargerHttpSensorEntityDescription(
name="Charge Frequency",
Expand Down Expand Up @@ -695,7 +695,7 @@ class SolaXEVChargerHttpSensorEntityDescription(BaseHttpSensorEntityDescription)
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
allowedtypes=G1,
allowedtypes=G1 | G2,
),
SolaXEVChargerHttpSensorEntityDescription(
name="Charge Added Total",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/solax_http/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"name": "SolaX HTTP API",
"requirements": [],
"issue_tracker": "https://github.com/PatrikTrestik/homeassistant-solax-http/issues",
"version": "1.3.3"
"version": "1.3.4"
}
8 changes: 1 addition & 7 deletions custom_components/solax_http/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities) -> N
coordinator: SolaxHttpUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
plugin: plugin_base = coordinator.plugin

device_info = {
"identifiers": {(DOMAIN, name)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
}

entities = []
for number_info in plugin.NUMBER_TYPES:
if plugin.matchWithMask(number_info.allowedtypes, number_info.blacklist):
select = SolaXHttpNumber(coordinator, name, device_info, number_info)
select = SolaXHttpNumber(coordinator, name, plugin.device_info, number_info)

entities.append(select)

Expand Down
12 changes: 11 additions & 1 deletion custom_components/solax_http/plugin_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from homeassistant.components.select import SelectEntityDescription
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.components.time import TimeEntityDescription
from homeassistant.helpers.device_registry import DeviceInfo

from .entity_definitions import ALL_POW_GROUP, ALL_VER_GROUP, ALL_X_GROUP

Expand All @@ -16,18 +17,27 @@

@dataclass
class plugin_base:
"""Base plugin class for Solax HTTP integration."""

plugin_name: str
TIME_TYPES: list[TimeEntityDescription]
SENSOR_TYPES: list[SensorEntityDescription]
BUTTON_TYPES: list[ButtonEntityDescription]
NUMBER_TYPES: list[NumberEntityDescription]
SELECT_TYPES: list[SelectEntityDescription]

device_info: DeviceInfo = None
invertertype = None
serialnumber: str = ""
hw_version: str = "Unknown"
sw_version: str = "Unknown"

async def initialize(self) -> None:
pass

@property
def inverter_model(self) -> str:
return "Unknown"

def map_data(self, descr, data) -> any:
return None

Expand Down
12 changes: 9 additions & 3 deletions custom_components/solax_http/plugin_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def _http_post(url, payload, retry=3):
return None

@staticmethod
async def _read_serial_number(host: str, pwd: str):
async def _read_device_info(host: str, pwd: str):
httpData = None
text = await PluginFactory._http_post(
f"http://{host}", f"optType=ReadRealTimeData&pwd={pwd}"
Expand All @@ -69,7 +69,10 @@ async def _read_serial_number(host: str, pwd: str):
httpData = json.loads(text)
except json.decoder.JSONDecodeError:
_LOGGER.error("Failed to decode json: %s", text)
return httpData["Information"][2]
return {
"sn": httpData["Information"][2],
"firmware": httpData["Information"][4],
}

@staticmethod
def _determine_type(sn: str):
Expand Down Expand Up @@ -118,7 +121,8 @@ def _determine_type(sn: str):
@staticmethod
async def get_plugin_instance(host: str, pwd: str):
"""Get an instance of plugin based on serial number/type."""
sn = await PluginFactory._read_serial_number(host, pwd)
info = await PluginFactory._read_device_info(host, pwd)
sn = info["sn"]
if sn is None:
_LOGGER.warning("Attempt to read serialnumber failed")
return None
Expand All @@ -135,6 +139,7 @@ async def get_plugin_instance(host: str, pwd: str):
NUMBER_TYPES=NUMBER_TYPES,
BUTTON_TYPES=BUTTON_TYPES,
SELECT_TYPES=SELECT_TYPES,
sw_version=info["firmware"],
)
if invertertype & V20:
return solax_ev_charger_plugin_g2(
Expand All @@ -146,5 +151,6 @@ async def get_plugin_instance(host: str, pwd: str):
NUMBER_TYPES=NUMBER_TYPES,
BUTTON_TYPES=BUTTON_TYPES,
SELECT_TYPES=SELECT_TYPES,
sw_version=info["firmware"],
)
raise ValueError(f"Unknown inverter type: {sn}")
44 changes: 23 additions & 21 deletions custom_components/solax_http/plugin_solax_ev_charger.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Module provides the solax_ev_charger_plugin class for SolaX EV Charger integration."""

from dataclasses import dataclass
import datetime
import logging

from .entity_definitions import ALL_POW_GROUP, ALL_VER_GROUP, ALL_X_GROUP, S16
from .entity_definitions import POW7, POW11, POW22, S16, X1, X3
from .plugin_base import plugin_base

_LOGGER = logging.getLogger(__name__)
Expand All @@ -17,6 +19,26 @@ class solax_ev_charger_plugin(plugin_base):

serialnumber: str = None
invertertype: int = None
hw_version: str = "G1"

@property
def inverter_model(self) -> str:
"""Return the inverter model based on the inverter type."""
if (self.invertertype & X1) != 0:
phase = "X1"
elif (self.invertertype & X3) != 0:
phase = "X3"
else:
phase = ""
if (self.invertertype & POW7) != 0:
p = "7kW"
elif (self.invertertype & POW11) != 0:
p = "11kW"
elif (self.invertertype & POW22) != 0:
p = "22kW"
else:
p = ""
return f"{phase}-EVC-{p}"

def map_payload(self, address, payload):
"""Map the payload to the corresponding register based on the address."""
Expand Down Expand Up @@ -213,23 +235,3 @@ def map_data(self, descr, data) -> any:
pass # probably a WORDS instance

return return_value

def matchWithMask(self, entitymask, blacklist=None):
if self.invertertype is None or self.invertertype == 0:
return False
# returns true if the entity needs to be created for an inverter
powmatch = ((self.invertertype & entitymask & ALL_POW_GROUP) != 0) or (
entitymask & ALL_POW_GROUP == 0
)
xmatch = ((self.invertertype & entitymask & ALL_X_GROUP) != 0) or (
entitymask & ALL_X_GROUP == 0
)
vermatch = ((self.invertertype & entitymask & ALL_VER_GROUP) != 0) or (
entitymask & ALL_VER_GROUP == 0
)
blacklisted = False
if blacklist:
for start in blacklist:
if self._serialnumber.startswith(start):
blacklisted = True
return (powmatch and xmatch and vermatch) and not blacklisted
51 changes: 36 additions & 15 deletions custom_components/solax_http/plugin_solax_ev_charger_g2.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from ctypes import cast
import datetime
import logging
"""Module provides the solax_ev_charger_plugin_G2 class for SolaX EV Charger integration."""

from dataclasses import dataclass
import logging

from .entity_definitions import POW7, POW11, POW22, S16, X1, X3
from .plugin_base import plugin_base
from .entity_definitions import ALL_POW_GROUP, ALL_X_GROUP, ALL_VER_GROUP, S16


_LOGGER = logging.getLogger(__name__)

Expand All @@ -19,12 +18,32 @@ class solax_ev_charger_plugin_g2(plugin_base):

serialnumber: str = None
invertertype: int = None
hw_version: str = "G2"

@property
def inverter_model(self) -> str:
"""Return the inverter model based on the inverter type."""
if (self.invertertype & X1) != 0:
phase = "X1"
elif (self.invertertype & X3) != 0:
phase = "X3"
else:
phase = ""
if (self.invertertype & POW7) != 0:
p = "7kW"
elif (self.invertertype & POW11) != 0:
p = "11kW"
elif (self.invertertype & POW22) != 0:
p = "22kW"
else:
p = ""
return f"{phase}-HAC-{p}"

def map_payload(self, address, payload):
"""Map the payload to the corresponding register based on the address."""

match address:
case 0x60D: #Mode
case 0x60D: # Mode
return [{"reg": 52, "val": f"{payload}"}]
# case 0x60C:
# return [{"reg": 1, "val": f"{payload}"}]
Expand Down Expand Up @@ -72,6 +91,8 @@ def map_payload(self, address, payload):
return None

def map_data(self, descr, data) -> any:
"""Map data from the given dictionary based on the descriptor's register value."""

Set = data["Set"]
Data = data["Data"]
Info = data["Info"]
Expand All @@ -94,8 +115,8 @@ def map_data(self, descr, data) -> any:
# return_value = Info.get(2)
# case 0x60C: #Grid Data Source
# return_value = Set.get(0)
case 0x60D: #Mode
return_value = Set.get(2)
case 0x60D: # Mode
return_value = Set.get(1)
# case 0x60E: #Eco Gear
# return_value = Set.get(2)
# case 0x60F: #Green Gear
Expand Down Expand Up @@ -163,8 +184,8 @@ def map_data(self, descr, data) -> any:
return_value = Data.get(34)
case 0xE:
return_value = Data.get(35)
# case 0xF: #E Actual Charge
# return_value = Data.get(12)
case 0xF: # E Actual Charge
return_value = Data.get(13)
case 0x10:
datH = Data.get(16)
datL = Data.get(15)
Expand Down Expand Up @@ -192,11 +213,11 @@ def map_data(self, descr, data) -> any:
# ver = str(Set.get(19))
# if ver is not None:
# return_value = f"{ver[0]}.{ver[1:]}"
# case 0x2B: #Charge time
# datH = Data.get(81)
# datL = Data.get(80)
# if datH is not None and datL is not None:
# return_value = datH * 65536 + datL + 1
case 0x2B: # Charge time
datH = Data.get(50)
datL = Data.get(49)
if datH is not None and datL is not None:
return_value = datH * 65536 + datL + 1
case _:
return_value = None

Expand Down
8 changes: 1 addition & 7 deletions custom_components/solax_http/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities) -> N
coordinator: SolaxHttpUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
plugin: plugin_base = coordinator.plugin

device_info = {
"identifiers": {(DOMAIN, name)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
}

entities = []
for select_info in plugin.SELECT_TYPES:
if plugin.matchWithMask(select_info.allowedtypes, select_info.blacklist):
select = SolaXHttpSelect(coordinator, name, device_info, select_info)
select = SolaXHttpSelect(coordinator, name, plugin.device_info, select_info)

entities.append(select)

Expand Down
8 changes: 1 addition & 7 deletions custom_components/solax_http/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities):
coordinator: SolaxHttpUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
plugin: plugin_base = coordinator.plugin

device_info = {
"identifiers": {(DOMAIN, name)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
}

entities = []

for sensor_description in plugin.SENSOR_TYPES:
Expand All @@ -37,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities):
sensor = SolaXHttpSensor(
coordinator,
name,
device_info,
plugin.device_info,
newdescr,
)
entities.append(sensor)
Expand Down
8 changes: 1 addition & 7 deletions custom_components/solax_http/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities) -> N
coordinator: SolaxHttpUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
plugin: plugin_base = coordinator.plugin

device_info = {
"identifiers": {(DOMAIN, name)},
"name": name,
"manufacturer": ATTR_MANUFACTURER,
}

entities = []
for time_info in plugin.TIME_TYPES:
if plugin.matchWithMask(time_info.allowedtypes, time_info.blacklist):
time = SolaXHttpTime(coordinator, name, device_info, time_info)
time = SolaXHttpTime(coordinator, name, plugin.device_info, time_info)

entities.append(time)

Expand Down
Binary file modified doc/Mapping.xlsx
Binary file not shown.

0 comments on commit c12aee8

Please sign in to comment.