add abstract inverter interface class

This commit is contained in:
Stefan Allius
2024-10-04 01:35:44 +02:00
parent 84034127e3
commit cd2f41a713
6 changed files with 212 additions and 19 deletions

View File

@@ -1,15 +1,12 @@
import asyncio
import weakref
import logging
import json
if __name__ == "app.src.inverter":
from app.src.iter_registry import IterRegistry
from app.src.config import Config
from app.src.mqtt import Mqtt
from app.src.infos import Infos
else: # pragma: no cover
from iter_registry import IterRegistry
from config import Config
from mqtt import Mqtt
from infos import Infos
@@ -17,7 +14,7 @@ else: # pragma: no cover
logger_mqtt = logging.getLogger('mqtt')
class Inverter(metaclass=IterRegistry):
class Inverter():
'''class Inverter is a baseclass
The class has some class method for managing common resources like a
@@ -40,8 +37,6 @@ class Inverter(metaclass=IterRegistry):
async_create_remote(): Establish a client connection to the TSUN cloud
async_publ_mqtt(): Publish data to MQTT broker
'''
_registry = []
@classmethod
def class_init(cls) -> None:
logging.debug('Inverter.class_init')
@@ -109,6 +104,3 @@ class Inverter(metaclass=IterRegistry):
logging.info('Close MQTT Task')
loop.run_until_complete(cls.mqtt.close())
cls.mqtt = None
def __init__(self):
self._registry.append(weakref.ref(self))

View File

@@ -1,3 +1,5 @@
from abc import abstractmethod
import weakref
import asyncio
import logging
import traceback
@@ -6,6 +8,7 @@ from aiomqtt import MqttCodeError
from asyncio import StreamReader, StreamWriter
if __name__ == "app.src.inverter_base":
from app.src.iter_registry import AbstractIterMeta
from app.src.inverter import Inverter
from app.src.async_stream import StreamPtr
from app.src.async_stream import AsyncStreamClient
@@ -13,6 +16,7 @@ if __name__ == "app.src.inverter_base":
from app.src.config import Config
from app.src.infos import Infos
else: # pragma: no cover
from iter_registry import AbstractIterMeta
from inverter import Inverter
from async_stream import StreamPtr
from async_stream import AsyncStreamClient
@@ -23,11 +27,43 @@ else: # pragma: no cover
logger_mqtt = logging.getLogger('mqtt')
class InverterBase(Inverter):
class InverterIfc(metaclass=AbstractIterMeta):
@abstractmethod
def __init__(self, reader: StreamReader, writer: StreamWriter,
config_id: str, prot_class,
client_mode: bool):
pass # pragma: no cover
@abstractmethod
def __enter__(self):
pass # pragma: no cover
@abstractmethod
def __exit__(self, exc_type, exc, tb):
pass # pragma: no cover
@abstractmethod
def healthy(self) -> bool:
pass # pragma: no cover
@abstractmethod
async def disc(self, shutdown_started=False) -> None:
pass # pragma: no cover
@abstractmethod
async def async_create_remote(self) -> None:
pass # pragma: no cover
class InverterBase(InverterIfc, Inverter):
_registry = []
def __init__(self, reader: StreamReader, writer: StreamWriter,
config_id: str, prot_class,
client_mode: bool = False):
super().__init__()
Inverter.__init__(self)
self._registry.append(weakref.ref(self))
self.addr = writer.get_extra_info('peername')
self.config_id = config_id
self.prot_class = prot_class

View File

@@ -1,3 +1,4 @@
from abc import ABCMeta
class IterRegistry(type):
@@ -6,3 +7,12 @@ class IterRegistry(type):
obj = ref()
if obj is not None:
yield obj
class AbstractIterMeta(ABCMeta):
def __iter__(cls):
for ref in cls._registry:
obj = ref()
print(f'obj: {obj}')
if obj is not None:
yield obj

View File

@@ -0,0 +1,153 @@
# test_with_pytest.py
import pytest
import asyncio
import sys,gc
from mock import patch
from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.gen3.talent import Talent
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.protocol_ifc import ProtocolIfcImpl
from app.src.async_stream import AsyncStream, AsyncIfcImpl
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
pytest_plugins = ('pytest_asyncio',)
# initialize the proxy statistics
Infos.static_init()
@pytest.fixture
def config_conn():
Config.act_config = {
'mqtt':{
'host': test_hostname,
'port': test_port,
'user': '',
'passwd': ''
},
'ha':{
'auto_conf_prefix': 'homeassistant',
'discovery_prefix': 'homeassistant',
'entity_prefix': 'tsun',
'proxy_node_id': 'test_1',
'proxy_unique_id': ''
},
'tsun':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234}, 'inverters':{'allow_all':True}
}
@pytest.fixture(scope="module", autouse=True)
def module_init():
Singleton._instances.clear()
yield
class FakeReader():
def __init__(self):
self.on_recv = asyncio.Event()
async def read(self, max_len: int):
await self.on_recv.wait()
return b''
def feed_eof(self):
return
class FakeWriter():
def write(self, buf: bytes):
return
def get_extra_info(self, sel: str):
if sel == 'peername':
return 'remote.intern'
elif sel == 'sockname':
return 'sock:1234'
assert False
def is_closing(self):
return False
def close(self):
return
async def wait_closed(self):
return
class TestType(Enum):
RD_TEST_0_BYTES = 1
RD_TEST_TIMEOUT = 2
RD_TEST_EXCEPT = 3
test = TestType.RD_TEST_0_BYTES
@pytest.fixture
def patch_open_connection():
async def new_conn(conn):
await asyncio.sleep(0)
return FakeReader(), FakeWriter()
def new_open(host: str, port: int):
global test
if test == TestType.RD_TEST_TIMEOUT:
raise ConnectionRefusedError
elif test == TestType.RD_TEST_EXCEPT:
raise ValueError("Value cannot be negative") # Compliant
return new_conn(None)
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@pytest.fixture
def patch_healthy():
with patch.object(AsyncStream, 'healthy') as conn:
yield conn
def test_protocol_iter():
ProtocolIfcImpl._registry.clear()
cnt = 0
ifc = AsyncIfcImpl()
prot = ProtocolIfcImpl(('test.intern', 123), ifc, True)
for p in ProtocolIfcImpl:
assert p == prot
cnt += 1
del p
del prot
assert cnt == 1
for p in ProtocolIfcImpl:
assert False
def test_inverter_iter():
InverterBase._registry.clear()
cnt = 0
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
for inv in InverterBase:
assert inv == inverter
cnt += 1
del inv
del inverter
assert cnt == 1
for inv in InverterBase:
assert False
def test_method_calls(patch_healthy):
spy = patch_healthy
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
# inverter.healthy()
for inv in InverterBase:
inv.healthy()
del inv
spy.assert_called_once()
del inverter
cnt = 0
for inv in InverterBase:
cnt += 1
assert cnt == 0

View File

@@ -8,6 +8,7 @@ from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.inverter import Inverter
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3.inverter_g3 import InverterG3
from app.src.async_stream import AsyncStream
@@ -103,18 +104,18 @@ def test_method_calls(patch_healthy):
spy = patch_healthy
reader = FakeReader()
writer = FakeWriter()
Inverter._registry.clear()
InverterBase._registry.clear()
with InverterG3(reader, writer) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
for inv in Inverter:
for inv in InverterBase:
inv.healthy()
del inv
spy.assert_called_once()
del inverter
cnt = 0
for inv in Inverter:
for inv in InverterBase:
cnt += 1
assert cnt == 0
@@ -131,7 +132,7 @@ async def test_remote_conn(config_conn, patch_open_connection):
del inverter
cnt = 0
for inv in Inverter:
for inv in InverterBase:
print(f'Inverter refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@@ -157,8 +158,8 @@ async def test_remote_except(config_conn, patch_open_connection):
del inverter
cnt = 0
for inv in Inverter:
print(f'Inverter refs:{gc.get_referrers(inv)}')
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0

View File

@@ -7,6 +7,7 @@ from enum import Enum
from app.src.infos import Infos
from app.src.config import Config
from app.src.inverter import Inverter
from app.src.inverter_base import InverterBase
from app.src.singleton import Singleton
from app.src.gen3plus.inverter_g3p import InverterG3P
@@ -96,7 +97,7 @@ def patch_open_connection():
def test_method_calls():
reader = FakeReader()
writer = FakeWriter()
Inverter._registry.clear()
InverterBase._registry.clear()
with InverterG3P(reader, writer, client_mode=False) as inverter:
assert inverter.local.stream