Skip to content

Commit

Permalink
Codereview raymar9
Browse files Browse the repository at this point in the history
  • Loading branch information
hunzikch committed Sep 20, 2024
1 parent 7cfa8cf commit 423eaf0
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 29 deletions.
1 change: 0 additions & 1 deletion smartmeter_datacollector/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
48 changes: 23 additions & 25 deletions smartmeter_datacollector/smartmeter/siemens_td3511.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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 = []
Expand All @@ -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:
Expand All @@ -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] = []
Expand Down
6 changes: 3 additions & 3 deletions tests/test_siemens_td3511.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 423eaf0

Please sign in to comment.