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

Speed up discovery by leveraging asyncio even more #115

Closed
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
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"aiohttp>=3.5.4, <4",
"async_timeout>=4.0.2",
"voluptuous>=0.11.5",
"mypy_extensions",
],
setup_requires=[
"setuptools_scm",
Expand Down
94 changes: 66 additions & 28 deletions solax/discovery.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
from typing import Type
import asyncio
from typing import Type, List

from solax.http_client import all_variations
from solax.inverter import Inverter, InverterError
from solax.inverters import X1, X3, X3V34, X1Mini, X3HybridG4, XHybrid, X1Smart, X1MiniV34
from solax.inverters import (
X1,
X3,
X3V34,
X1Mini,
X1MiniV34,
X1Smart,
X3HybridG4,
XHybrid,
)

REGISTRY: list[Type[Inverter]] = [
REGISTRY: List[Type[Inverter]] = [
XHybrid,
X3,
X3V34,
Expand All @@ -24,32 +34,28 @@ class DiscoveryError(Exception):


async def discover(host, port, pwd="") -> Inverter:
failures: list = []
failures: List = []
clients = all_variations(host, port, pwd)
for client_name, client in clients.items():
try:
response = await client.request()
except InverterError as ex:
failures.append(
(
client_name,
ex,
)
)
continue
pending = set()

async def identify_inverter(sleep, client_name, client):
await asyncio.sleep(sleep) # don't spam the inverter
response = await client.request()
for inverter_class in REGISTRY:
await asyncio.sleep(0)
try:
inverter = inverter_class(client)

if inverter.identify(response):
return inverter
else:
failures.append(
(
client_name,
inverter_class.__name__,
"did not identify",
)

failures.append(
(
client_name,
inverter_class.__name__,
"did not identify",
)
)
except InverterError as ex:
failures.append(
(
Expand All @@ -58,10 +64,42 @@ async def discover(host, port, pwd="") -> Inverter:
ex,
)
)
msg = (
"Unable to connect to the inverter at "
f"host={host} port={port}, or your inverter is not supported yet.\n"
"Please see https://github.com/squishykid/solax/wiki/DiscoveryError\n"
f"Failures={str(failures)}"

for sleep, (name, client) in enumerate(clients.items()):
pending.add(
asyncio.create_task(
identify_inverter(sleep, name, client),
name=name,
)
)

while pending:
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)

for task in done:
if task.cancelled():
continue

try:
inverter = await task

for loser in pending:
loser.cancel()

return inverter
except RuntimeError as ex:
failures.append(
(
task.get_name(),
ex,
)
)

raise DiscoveryError(
(
"Unable to connect to the inverter at "
f"host={host} port={port}, or your inverter is not supported yet.\n"
"Please see https://github.com/squishykid/solax/wiki/DiscoveryError\n"
f"Failures={str(failures)}"
)
)
raise DiscoveryError(msg)
12 changes: 7 additions & 5 deletions solax/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@


class InverterRawResponse(TypedDict):
Data: list[float]
Data: List[float]
sn: Optional[str]
SN: Optional[str]
version: Optional[str]
ver: Optional[str]
type: Union[int, str]
Information: Optional[list[Any]]
Information: Optional[List[Any]]


@dataclass
class InverterResponse:
data: dict[str, float]
data: Dict[str, float]
serial_number: str
version: str
type: Union[int, str]
Expand Down Expand Up @@ -102,7 +102,7 @@ async def get_data(self) -> InverterResponse:

def map_response_v2(
self, inverter_response: InverterRawResponse
) -> dict[str, float]:
) -> Dict[str, float]:
data = inverter_response["Data"]
highest_index = max(
(max(v.indexes) for v in self.inverter_definition().mapping.values())
Expand Down Expand Up @@ -154,7 +154,9 @@ def identify(self, response: bytes) -> bool:
actual_type = inverter_response.type
old_type_prefix = identification.old_type_prefix
if old_type_prefix is not None:
return isinstance(actual_type, str) and actual_type.startswith(old_type_prefix)
return isinstance(actual_type, str) and actual_type.startswith(
old_type_prefix
)

# compare type and inverter_type,
# instead of type and type, since type and inverter_type
Expand Down
24 changes: 18 additions & 6 deletions solax/inverters/x1_mini_v34.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,32 @@ def inverter_definition(cls) -> InverterDefinition:
"X1",
InverterIdentification(4),
{
"Network Voltage": InverterDataValue((0,), Measurement(Units.V), (div10,)),
"Output Current": InverterDataValue((1,), Measurement(Units.A), (div10,)),
"Network Voltage": InverterDataValue(
(0,), Measurement(Units.V), (div10,)
),
"Output Current": InverterDataValue(
(1,), Measurement(Units.A), (div10,)
),
"AC Power": InverterDataValue((2,), Measurement(Units.W)),
"PV1 Voltage": InverterDataValue((3,), Measurement(Units.V), (div10,)),
"PV2 Voltage": InverterDataValue((4,), Measurement(Units.V), (div10,)),
"PV1 Current": InverterDataValue((5,), Measurement(Units.A), (div10,)),
"PV2 Current": InverterDataValue((6,), Measurement(Units.A), (div10,)),
"PV1 Power": InverterDataValue((7,), Measurement(Units.W)),
"PV2 Power": InverterDataValue((8,), Measurement(Units.W)),
"Grid Frequency": InverterDataValue((9,), Measurement(Units.HZ), (div100,)),
"Grid Frequency": InverterDataValue(
(9,), Measurement(Units.HZ), (div100,)
),
"Total Energy": InverterDataValue((11,), Total(Units.KWH), (div10,)),
"Today's Energy": InverterDataValue((13,), Measurement(Units.KWH), (div10,)),
"Total Feed-in Energy": InverterDataValue((41,), Total(Units.KWH), (div10,)),
"Total Consumption": InverterDataValue((42,), Total(Units.KWH), (div10,)),
"Today's Energy": InverterDataValue(
(13,), Measurement(Units.KWH), (div10,)
),
"Total Feed-in Energy": InverterDataValue(
(41,), Total(Units.KWH), (div10,)
),
"Total Consumption": InverterDataValue(
(42,), Total(Units.KWH), (div10,)
),
"Power Now": InverterDataValue((43,), Measurement(Units.W), (div10,)),
"Inverter Temperature": InverterDataValue((55,), Measurement(Units.C)),
},
Expand Down
43 changes: 32 additions & 11 deletions solax/inverters/x1_smart.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from solax.inverter import Inverter, InverterDefinition, InverterIdentification, InverterDataValue
from solax.units import Total, Units, Measurement
from solax.inverter import (
Inverter,
InverterDataValue,
InverterDefinition,
InverterIdentification,
)
from solax.units import Measurement, Total, Units
from solax.utils import div10, div100, to_signed


Expand All @@ -13,21 +18,37 @@ class X1Smart(Inverter):
def inverter_definition(cls) -> InverterDefinition:
return InverterDefinition(
"X1",
InverterIdentification(8), {
"Network Voltage": InverterDataValue((0,), Measurement(Units.V), (div10,)),
"Output Current": InverterDataValue((1,), Measurement(Units.A), (div10,)),
InverterIdentification(8),
{
"Network Voltage": InverterDataValue(
(0,), Measurement(Units.V), (div10,)
),
"Output Current": InverterDataValue(
(1,), Measurement(Units.A), (div10,)
),
"AC Power": InverterDataValue((2,), Measurement(Units.W)),
"PV1 Voltage": InverterDataValue((3,), Measurement(Units.V), (div10,)),
"PV2 Voltage": InverterDataValue((4,), Measurement(Units.V), (div10,)),
"PV1 Current": InverterDataValue((5,), Measurement(Units.A), (div10,)),
"PV2 Current": InverterDataValue((6,), Measurement(Units.A), (div10,)),
"PV1 Power": InverterDataValue((7,), Measurement(Units.W)),
"PV2 Power": InverterDataValue((8,), Measurement(Units.W)),
"Grid Frequency": InverterDataValue((9,), Measurement(Units.HZ), (div100,)),
"Grid Frequency": InverterDataValue(
(9,), Measurement(Units.HZ), (div100,)
),
"Total Energy": InverterDataValue((11,), Total(Units.KWH), (div10,)),
"Today's Energy": InverterDataValue((13,), Measurement(Units.KWH), (div10,)),
"Today's Energy": InverterDataValue(
(13,), Measurement(Units.KWH), (div10,)
),
"Inverter Temperature": InverterDataValue((39,), Measurement(Units.C)),
"Exported Power": InverterDataValue((48,), Measurement(Units.W), (to_signed,)),
"Total Feed-in Energy": InverterDataValue((50,), Total(Units.KWH), (div100,)),
"Total Consumption": InverterDataValue((52,), Total(Units.KWH), (div100,)),
})
"Exported Power": InverterDataValue(
(48,), Measurement(Units.W), (to_signed,)
),
"Total Feed-in Energy": InverterDataValue(
(50,), Total(Units.KWH), (div100,)
),
"Total Consumption": InverterDataValue(
(52,), Total(Units.KWH), (div100,)
),
},
)
2 changes: 0 additions & 2 deletions solax/inverters/x3.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@


class X3(Inverter):

def identify(self, response: bytes) -> bool:
a = super().identify(response)
return a


@classmethod
def inverter_definition(cls) -> InverterDefinition:
return InverterDefinition(
Expand Down
1 change: 0 additions & 1 deletion solax/inverters/x_hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class XHybrid(Inverter):

# key: name of sensor
# value.0: index
# value.1: unit (String) or None
Expand Down
32 changes: 16 additions & 16 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,39 @@
import solax.inverters as inverter
from solax.discovery import REGISTRY
from tests.samples.expected_values import (
QVOLTHYBG33P_VALUES,
X1_BOOST_VALUES,
X1_HYBRID_G4_VALUES,
X1_MINI_VALUES,
X1_MINI_VALUES_V34,
X1_SMART_VALUES,
# QVOLTHYBG33P_VALUES,
# X1_BOOST_VALUES,
# X1_HYBRID_G4_VALUES,
# X1_MINI_VALUES,
# X1_MINI_VALUES_V34,
# X1_SMART_VALUES,
X1_VALUES,
X3_HYBRID_G4_VALUES,
X3_HYBRID_VALUES,
X3_VALUES,
X3V34_HYBRID_VALUES,
X3V34_HYBRID_VALUES_EPS_MODE,
X3V34_HYBRID_VALUES_NEGATIVE_POWER,
XHYBRID_VALUES,
# XHYBRID_VALUES,
)
from tests.samples.responses import (
QVOLTHYBG33P_RESPONSE_V34,
X1_BOOST_AIR_MINI_RESPONSE,
X1_BOOST_RESPONSE,
# QVOLTHYBG33P_RESPONSE_V34,
# X1_BOOST_AIR_MINI_RESPONSE,
# X1_BOOST_RESPONSE,
X1_HYBRID_G3_2X_MPPT_RESPONSE,
X1_HYBRID_G3_RESPONSE,
X1_HYBRID_G4_RESPONSE,
X1_MINI_RESPONSE_V34,
X1_SMART_RESPONSE,
# X1_HYBRID_G4_RESPONSE,
# X1_MINI_RESPONSE_V34,
# X1_SMART_RESPONSE,
X3_HYBRID_G3_2X_MPPT_RESPONSE,
X3_HYBRID_G3_2X_MPPT_RESPONSE_V34,
X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE,
X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_NEGATIVE_POWER,
X3_HYBRID_G3_RESPONSE,
X3_HYBRID_G4_RESPONSE,
X3_MIC_RESPONSE,
XHYBRID_DE01_RESPONSE,
XHYBRID_DE02_RESPONSE,
# X3_MIC_RESPONSE,
# XHYBRID_DE01_RESPONSE,
# XHYBRID_DE02_RESPONSE,
)

X_FORWARDED_HEADER = {"X-Forwarded-For": "5.8.8.8"}
Expand Down
Loading