From 423eaf09a028b74e601916bbbfd2f414372af249 Mon Sep 17 00:00:00 2001 From: Christoph Hunziker Date: Fri, 20 Sep 2024 21:14:12 +0200 Subject: [PATCH] Codereview raymar9 --- smartmeter_datacollector/factory.py | 1 - .../smartmeter/siemens_td3511.py | 48 +++++++++---------- tests/test_siemens_td3511.py | 6 +-- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/smartmeter_datacollector/factory.py b/smartmeter_datacollector/factory.py index 3aeaac2..0f58c62 100644 --- a/smartmeter_datacollector/factory.py +++ b/smartmeter_datacollector/factory.py @@ -68,7 +68,6 @@ def build_meters(config: ConfigParser) -> List[Meter]: meters.append(SiemensTD3511( port=meter_config.get('port', "/dev/ttyUSB0"), baudrate=meter_config.getint('baudrate', SiemensTD3511.BAUDRATE), - decryption_key=meter_config.get('key'), use_system_time=meter_config.getboolean('systemtime', False) )) else: diff --git a/smartmeter_datacollector/smartmeter/siemens_td3511.py b/smartmeter_datacollector/smartmeter/siemens_td3511.py index 55a1a05..23b6502 100644 --- a/smartmeter_datacollector/smartmeter/siemens_td3511.py +++ b/smartmeter_datacollector/smartmeter/siemens_td3511.py @@ -24,11 +24,10 @@ class SiemensTD3511(Meter): - BAUDRATE = 300 + BAUDRATE = 19200 def __init__(self, port: str, baudrate: int = BAUDRATE, - decryption_key: Optional[str] = None, use_system_time: bool = False) -> None: super().__init__() serial_config = SerialConfig( @@ -65,14 +64,18 @@ def _data_received(self, received_data: bytes) -> None: class SiemensSerialReader(Reader): - """Serial reader according to IEC62056-21, Mode C""" + """Serial reader for Siemens TD-3511. Communication is based on IEC62056-21, Mode C.""" TERMINATION_FLAG = b'!\r\n' BAUDRATE_INIT = 300 - BAUDRATE_DATA = 19200 + METER_ID_REQ = b'/?!\r\n' + METER_PRG_MODE_REQ = '063036310D0A' + METER_F001_REQ = '015232024630303103160D0A' + METER_F009_REQ = '0152320246303039031E0D0A' def __init__(self, serial_config: SerialConfig, callback: Callable[[bytes], None]) -> None: super().__init__(callback) self._termination = serial_config.termination + self._baudrate = serial_config.baudrate self.timestamp = None try: self._serial = aioserial.AioSerial( @@ -84,8 +87,6 @@ def __init__(self, serial_config: SerialConfig, callback: Callable[[bytes], None ) except aioserial.SerialException as ex: raise ReaderError(ex) from ex - self._serial_settings = self._serial.get_settings() - self.meter_id = None async def start_and_listen(self) -> None: while True: @@ -102,23 +103,20 @@ async def start_and_listen(self) -> None: async def _enter_prg_mode(self): LOGGER.info("Try to set meter into programming mode.") - self._serial_settings['baudrate'] = SiemensSerialReader.BAUDRATE_INIT - self._serial.apply_settings(self._serial_settings) - await asyncio.sleep(5.0) - await self._serial.write_async(b"/?!\r\n") - self.meter_id = await self._serial.readline_async(size=-1) - LOGGER.debug("Meter response to init sequence: %s", self.meter_id.decode()) + self._serial.baudrate = SiemensSerialReader.BAUDRATE_INIT + await self._serial.write_async(SiemensSerialReader.METER_ID_REQ) + meter_id = await self._serial.readline_async(size=-1) + LOGGER.debug("Meter response to init sequence: %s", meter_id.decode()) await asyncio.sleep(0.2) - await self._serial.write_async(bytes.fromhex("063036310D0A")) + await self._serial.write_async(bytes.fromhex(SiemensSerialReader.METER_PRG_MODE_REQ)) await asyncio.sleep(0.2) - self._serial_settings['baudrate'] = SiemensSerialReader.BAUDRATE_DATA - self._serial.apply_settings(self._serial_settings) + self._serial.baudrate = self._baudrate return async def _get_f001_dataset(self): # Read dataset F001 self.timestamp = datetime.now(timezone.utc) - await self._serial.write_async(bytes.fromhex('015232024630303103160D0A')) + await self._serial.write_async(bytes.fromhex(SiemensSerialReader.METER_F001_REQ)) data: bytes = await self._serial.readline_async(size=-1) self._callback(data) while True: @@ -134,7 +132,7 @@ async def _get_f001_dataset(self): async def _get_f009_dataset(self): # Read dataset F009 self.timestamp = datetime.now(timezone.utc) - await self._serial.write_async(bytes.fromhex('0152320246303039031E0D0A')) + await self._serial.write_async(bytes.fromhex(SiemensSerialReader.METER_F009_REQ)) data: bytes = await self._serial.readline_async(size=-1) self._callback(data) while True: @@ -190,8 +188,6 @@ class SiemensParser(): def __init__(self, use_system_time: bool = False) -> None: self._use_system_time = use_system_time - self._meter_time = None - self._meter_date = None self._timestamp = None self._meter_id = None self._buffer = [] @@ -206,6 +202,8 @@ def clear_buffer(self): def parse_data_objects(self, timestamp: datetime): # Extract timestamp and meter id self._timestamp = timestamp + meter_time = None + meter_date = None for data in self._buffer: result = re.search(SiemensParser.REGEX, data) if result is None: @@ -218,15 +216,15 @@ def parse_data_objects(self, timestamp: datetime): # Extract date and time try: if obis == "0.9.1": - self._meter_time = datetime.strptime(value, "%H:%M:%S").time() + meter_time = datetime.strptime(value, "%H:%M:%S").time() if obis == "0.9.2": - self._meter_date = datetime.strptime(value, "%y-%m-%d").date() + meter_date = datetime.strptime(value, "%y-%m-%d").date() except ValueError: - self._meter_time = None - self._meter_date = None + meter_time = None + meter_date = None LOGGER.warning("Invalid timestamp received: %s. Using system time instead.", value) - if self._meter_date is not None and self._meter_time is not None and not self._use_system_time: - self._timestamp = datetime.combine(self._meter_date, self._meter_time).astimezone(timezone.utc) + if meter_date is not None and meter_time is not None and not self._use_system_time: + self._timestamp = datetime.combine(meter_date, meter_time).astimezone(timezone.utc) # Extract register data data_points: List[MeterDataPoint] = [] diff --git a/tests/test_siemens_td3511.py b/tests/test_siemens_td3511.py index f5d3bbc..8100523 100644 --- a/tests/test_siemens_td3511.py +++ b/tests/test_siemens_td3511.py @@ -36,7 +36,7 @@ async def test_siemens_td3511_initialization(mocker: MockerFixture): @pytest.fixture def unencrypted_valid_data_siemens() -> List[bytes]: - data_str: List[str] = [] + data_str: List[bytes] = [] data_str.append(b'0.0.0(110002267)\r\n') data_str.append(b'1.8.0(31550.191*kWh)\r\n') data_str.append(b'1.8.1(12853.433*kWh)\r\n') @@ -99,12 +99,12 @@ def data_received(): assert any(data.type == MeterDataPointTypes.ACTIVE_ENERGY_P.value for data in values) assert any(data.type == MeterDataPointTypes.ACTIVE_ENERGY_N.value for data in values) assert all(data.source == "110002267" for data in values) - assert all(data.timestamp.strftime(r"%m/%d/%y %H:%M:%S") == "03/21/24 20:10:29" for data in values) + assert all(data.timestamp.astimezone().strftime(r"%m/%d/%y %H:%M:%S") == "03/21/24 21:10:29" for data in values) @pytest.fixture def unencrypted_invalid_data_siemens() -> List[bytes]: - data_str: List[str] = [] + data_str: List[bytes] = [] data_str.append(b'0.0.0(110002267)\r\n') data_str.append(b'13.8.0(31550.191*kWh)\r\n') data_str.append(b'13.8.1(12853.433*kWh)\r\n')