Skip to content

Commit

Permalink
Add potential meter values to DT inverter
Browse files Browse the repository at this point in the history
  • Loading branch information
mletenay committed Jun 16, 2024
1 parent e1f7869 commit a4b8c7f
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 21 deletions.
29 changes: 25 additions & 4 deletions goodwe/dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
from typing import Tuple

from .exceptions import InverterError, RequestRejectedException
from .exceptions import InverterError, RequestFailedException, RequestRejectedException
from .inverter import Inverter
from .inverter import OperationMode
from .inverter import SensorKind as Kind
Expand Down Expand Up @@ -113,6 +113,12 @@ class DT(Inverter):
# 30172 reserved
)

# Inverter's meter data
# Modbus registers from offset 0x75f4 (30196)
__all_sensors_meter: Tuple[Sensor, ...] = (
PowerS("active_power", 30196, "Active Power", Kind.GRID),
)

# Modbus registers of inverter settings, offsets are modbus register addresses
__all_settings: Tuple[Sensor, ...] = (
Timestamp("time", 40313, "Inverter time"),
Expand All @@ -139,9 +145,12 @@ class DT(Inverter):
def __init__(self, host: str, port: int, comm_addr: int = 0, timeout: int = 1, retries: int = 3):
super().__init__(host, port, comm_addr if comm_addr else 0x7f, timeout, retries)
self._READ_DEVICE_VERSION_INFO: ProtocolCommand = self._read_command(0x7531, 0x0028)
self._READ_DEVICE_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
self._READ_RUNNING_DATA: ProtocolCommand = self._read_command(0x7594, 0x0049)
self._READ_METER_DATA: ProtocolCommand = self._read_command(0x75f4, 0x01)
self._sensors = self.__all_sensors
self._sensors_meter = self.__all_sensors_meter
self._settings: dict[str, Sensor] = {s.id_: s for s in self.__all_settings}
self._has_meter: bool = True

@staticmethod
def _single_phase_only(s: Sensor) -> bool:
Expand Down Expand Up @@ -185,8 +194,17 @@ async def read_device_info(self):
pass

async def read_runtime_data(self) -> Dict[str, Any]:
response = await self._read_from_socket(self._READ_DEVICE_RUNNING_DATA)
response = await self._read_from_socket(self._READ_RUNNING_DATA)
data = self._map_response(response, self._sensors)

if self._has_meter:
try:
response = await self._read_from_socket(self._READ_METER_DATA)
data.update(self._map_response(response, self._sensors_meter))
except (RequestRejectedException, RequestFailedException):
logger.info("Meter values not supported, disabling further attempts.")
self._has_meter = False

return data

async def read_setting(self, setting_id: str) -> Any:
Expand Down Expand Up @@ -266,7 +284,10 @@ async def set_ongrid_battery_dod(self, dod: int) -> None:
raise InverterError("Operation not supported, inverter has no batteries.")

def sensors(self) -> Tuple[Sensor, ...]:
return self._sensors
result = self._sensors
if self._has_meter:
result = result + self._sensors_meter
return result

def settings(self) -> Tuple[Sensor, ...]:
return tuple(self._settings.values())
47 changes: 30 additions & 17 deletions tests/test_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from unittest import TestCase

from goodwe.dt import DT
from goodwe.exceptions import RequestFailedException
from goodwe.exceptions import RequestFailedException, RequestRejectedException
from goodwe.modbus import ILLEGAL_DATA_ADDRESS
from goodwe.protocol import ProtocolCommand, ProtocolResponse


Expand All @@ -13,7 +14,7 @@ class DtMock(TestCase, DT):
def __init__(self, methodName='runTest', port=8899):
TestCase.__init__(self, methodName)
DT.__init__(self, "localhost", port)
self.sensor_map = {s.id_: s.unit for s in self.sensors()}
self.sensor_map = {s.id_: s for s in self.sensors()}
self._mock_responses = {}

def mock_response(self, command: ProtocolCommand, filename: str):
Expand All @@ -24,6 +25,10 @@ async def _read_from_socket(self, command: ProtocolCommand) -> ProtocolResponse:
root_dir = os.path.dirname(os.path.abspath(__file__))
filename = self._mock_responses.get(command)
if filename is not None:
if ILLEGAL_DATA_ADDRESS == filename:
raise RequestRejectedException(ILLEGAL_DATA_ADDRESS)
if 'NO RESPONSE' == filename:
raise RequestFailedException()
with open(root_dir + '/sample/dt/' + filename, 'r') as f:
response = bytes.fromhex(f.read())
if not command.validator(response):
Expand All @@ -33,10 +38,11 @@ async def _read_from_socket(self, command: ProtocolCommand) -> ProtocolResponse:
self.request = command.request
return ProtocolResponse(bytes.fromhex("aa557f00010203040506070809"), command)

def assertSensor(self, sensor, expected_value, expected_unit, data):
self.assertEqual(expected_value, data.get(sensor))
self.assertEqual(expected_unit, self.sensor_map.get(sensor))
self.sensor_map.pop(sensor)
def assertSensor(self, sensor_name, expected_value, expected_unit, data):
self.assertEqual(expected_value, data.get(sensor_name))
sensor = self.sensor_map.get(sensor_name);
self.assertEqual(expected_unit, sensor.unit)
self.sensor_map.pop(sensor_name)

@classmethod
def setUpClass(cls):
Expand All @@ -47,23 +53,23 @@ class GW6000_DT_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW6000-DT_running_data.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW6000-DT_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW6000_DT_runtime_data(self):
self.loop.run_until_complete(self.read_device_info())
data = self.loop.run_until_complete(self.read_runtime_data())
self.assertEqual(42, len(data))

self.sensor_map = {s.id_: s for s in self.sensors()}

self.assertSensor('timestamp', datetime.strptime('2021-08-31 12:03:02', '%Y-%m-%d %H:%M:%S'), '', data)
self.assertSensor('vpv1', 320.8, 'V', data)
self.assertSensor('ipv1', 3.1, 'A', data)
self.assertSensor('ppv1', 994, 'W', data)
self.assertSensor('vpv2', 324.1, 'V', data)
self.assertSensor('ipv2', 3.2, 'A', data)
self.assertSensor('ppv2', 1037, 'W', data)
self.assertSensor('vpv3', None, 'V', data)
self.assertSensor('ipv3', None, 'A', data)
self.assertSensor('ppv3', None, 'W', data)
self.assertSensor('ppv', 2031, 'W', data)
self.assertSensor('vline1', 0, 'V', data)
self.assertSensor('vline2', 0, 'V', data)
Expand Down Expand Up @@ -122,8 +128,9 @@ class GW8K_DT_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW8K-DT_running_data.hex')
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW8K-DT_device_info.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW8K-DT_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW8K_DT_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -199,8 +206,9 @@ class GW5000D_NS_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW5000D-NS_running_data.hex')
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'Mock_device_info.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW5000D-NS_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW5000D_NS_runtime_data(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -255,8 +263,9 @@ class GW5000_MS_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW5000-MS_running_data.hex')
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW5000-MS_device_info.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW5000-MS_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW6000_MS_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -316,7 +325,8 @@ class GW10K_MS_30_Test(DtMock):
def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW10K-MS-30_device_info.hex')
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW10K-MS-30_running_data.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW10K-MS-30_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW10K_MS_30_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -375,7 +385,8 @@ class GW10K_MS_TCP_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName, 502)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW10K-MS-30_tcp_running_data.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW10K-MS-30_tcp_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW10K_MS_TCP_runtime_data(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -430,8 +441,9 @@ class GW20KAU_DT_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW20KAU-DT_running_data.hex')
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW20KAU-DT_device_info.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW20KAU-DT_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW20KAU_DT_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down Expand Up @@ -497,8 +509,9 @@ class GW17K_DT_Test(DtMock):

def __init__(self, methodName='runTest'):
DtMock.__init__(self, methodName)
self.mock_response(self._READ_DEVICE_RUNNING_DATA, 'GW17K-DT_running_data.hex')
self.mock_response(self._READ_DEVICE_VERSION_INFO, 'GW17K-DT_device_info.hex')
self.mock_response(self._READ_RUNNING_DATA, 'GW17K-DT_running_data.hex')
self.mock_response(self._READ_METER_DATA, ILLEGAL_DATA_ADDRESS)

def test_GW20KAU_DT_device_info(self):
self.loop.run_until_complete(self.read_device_info())
Expand Down

0 comments on commit a4b8c7f

Please sign in to comment.