{
"extension": ".py",
"source": "from decimal import Decimal\n\nimport datetime\nimport unittest\n\nimport pytz\n\nfrom dsmr_parser import telegram_specifications\nfrom dsmr_parser.exceptions import InvalidChecksumError, ParseError\nfrom dsmr_parser.objects import CosemObject, MBusObject\nfrom dsmr_parser.parsers import TelegramParser\nfrom test.example_telegrams import TELEGRAM_V5\n\n\nclass TelegramParserV5Test(unittest.TestCase):\n \"\"\" Test parsing of a DSMR v5.x telegram. \"\"\"\n\n def test_parse(self):\n parser = TelegramParser(telegram_specifications.V5)\n try:\n telegram = parser.parse(TELEGRAM_V5, throw_ex=True)\n except Exception as ex:\n assert False, f\"parse trigged an exception {ex}\"\n print('test: ', type(telegram.P1_MESSAGE_HEADER), telegram.P1_MESSAGE_HEADER.__dict__)\n # P1_MESSAGE_HEADER (1-3:0.2.8)\n assert isinstance(telegram.P1_MESSAGE_HEADER, CosemObject)\n assert telegram.P1_MESSAGE_HEADER.unit is None\n assert isinstance(telegram.P1_MESSAGE_HEADER.value, str)\n assert telegram.P1_MESSAGE_HEADER.value == '50'\n\n # P1_MESSAGE_TIMESTAMP (0-0:1.0.0)\n assert isinstance(telegram.P1_MESSAGE_TIMESTAMP, CosemObject)\n assert telegram.P1_MESSAGE_TIMESTAMP.unit is None\n assert isinstance(telegram.P1_MESSAGE_TIMESTAMP.value, datetime.datetime)\n assert telegram.P1_MESSAGE_TIMESTAMP.value == \\\n datetime.datetime(2017, 1, 2, 18, 20, 2, tzinfo=pytz.UTC)\n\n # ELECTRICITY_USED_TARIFF_1 (1-0:1.8.1)\n assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1, CosemObject)\n assert telegram.ELECTRICITY_USED_TARIFF_1.unit == 'kWh'\n assert isinstance(telegram.ELECTRICITY_USED_TARIFF_1.value, Decimal)\n assert telegram.ELECTRICITY_USED_TARIFF_1.value == Decimal('4.426')\n\n # ELECTRICITY_USED_TARIFF_2 (1-0:1.8.2)\n assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2, CosemObject)\n assert telegram.ELECTRICITY_USED_TARIFF_2.unit == 'kWh'\n assert isinstance(telegram.ELECTRICITY_USED_TARIFF_2.value, Decimal)\n assert telegram.ELECTRICITY_USED_TARIFF_2.value == Decimal('2.399')\n\n # ELECTRICITY_DELIVERED_TARIFF_1 (1-0:2.8.1)\n assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1, CosemObject)\n assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.unit == 'kWh'\n assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_1.value, Decimal)\n assert telegram.ELECTRICITY_DELIVERED_TARIFF_1.value == Decimal('2.444')\n\n # ELECTRICITY_DELIVERED_TARIFF_2 (1-0:2.8.2)\n assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2, CosemObject)\n assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.unit == 'kWh'\n assert isinstance(telegram.ELECTRICITY_DELIVERED_TARIFF_2.value, Decimal)\n assert telegram.ELECTRICITY_DELIVERED_TARIFF_2.value == Decimal('0')\n\n # ELECTRICITY_ACTIVE_TARIFF (0-0:96.14.0)\n assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF, CosemObject)\n assert telegram.ELECTRICITY_ACTIVE_TARIFF.unit is None\n assert isinstance(telegram.ELECTRICITY_ACTIVE_TARIFF.value, str)\n assert telegram.ELECTRICITY_ACTIVE_TARIFF.value == '0002'\n\n # EQUIPMENT_IDENTIFIER (0-0:96.1.1)\n assert isinstance(telegram.EQUIPMENT_IDENTIFIER, CosemObject)\n assert telegram.EQUIPMENT_IDENTIFIER.unit is None\n assert isinstance(telegram.EQUIPMENT_IDENTIFIER.value, str)\n assert telegram.EQUIPMENT_IDENTIFIER.value == '4B384547303034303436333935353037'\n\n # CURRENT_ELECTRICITY_USAGE (1-0:1.7.0)\n assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE, CosemObject)\n assert telegram.CURRENT_ELECTRICITY_USAGE.unit == 'kW'\n assert isinstance(telegram.CURRENT_ELECTRICITY_USAGE.value, Decimal)\n assert telegram.CURRENT_ELECTRICITY_USAGE.value == Decimal('0.244')\n\n # CURRENT_ELECTRICITY_DELIVERY (1-0:2.7.0)\n assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY, CosemObject)\n assert telegram.CURRENT_ELECTRICITY_DELIVERY.unit == 'kW'\n assert isinstance(telegram.CURRENT_ELECTRICITY_DELIVERY.value, Decimal)\n assert telegram.CURRENT_ELECTRICITY_DELIVERY.value == Decimal('0')\n\n # LONG_POWER_FAILURE_COUNT (96.7.9)\n assert isinstance(telegram.LONG_POWER_FAILURE_COUNT, CosemObject)\n assert telegram.LONG_POWER_FAILURE_COUNT.unit is None\n assert isinstance(telegram.LONG_POWER_FAILURE_COUNT.value, int)\n assert telegram.LONG_POWER_FAILURE_COUNT.value == 0\n\n # SHORT_POWER_FAILURE_COUNT (1-0:96.7.21)\n assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT, CosemObject)\n assert telegram.SHORT_POWER_FAILURE_COUNT.unit is None\n assert isinstance(telegram.SHORT_POWER_FAILURE_COUNT.value, int)\n assert telegram.SHORT_POWER_FAILURE_COUNT.value == 13\n\n # VOLTAGE_SAG_L1_COUNT (1-0:32.32.0)\n assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT, CosemObject)\n assert telegram.VOLTAGE_SAG_L1_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SAG_L1_COUNT.value, int)\n assert telegram.VOLTAGE_SAG_L1_COUNT.value == 0\n\n # VOLTAGE_SAG_L2_COUNT (1-0:52.32.0)\n assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT, CosemObject)\n assert telegram.VOLTAGE_SAG_L2_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SAG_L2_COUNT.value, int)\n assert telegram.VOLTAGE_SAG_L2_COUNT.value == 0\n\n # VOLTAGE_SAG_L3_COUNT (1-0:72.32.0)\n assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT, CosemObject)\n assert telegram.VOLTAGE_SAG_L3_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SAG_L3_COUNT.value, int)\n assert telegram.VOLTAGE_SAG_L3_COUNT.value == 0\n\n # VOLTAGE_SWELL_L1_COUNT (1-0:32.36.0)\n assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT, CosemObject)\n assert telegram.VOLTAGE_SWELL_L1_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SWELL_L1_COUNT.value, int)\n assert telegram.VOLTAGE_SWELL_L1_COUNT.value == 0\n\n # VOLTAGE_SWELL_L2_COUNT (1-0:52.36.0)\n assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT, CosemObject)\n assert telegram.VOLTAGE_SWELL_L2_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SWELL_L2_COUNT.value, int)\n assert telegram.VOLTAGE_SWELL_L2_COUNT.value == 0\n\n # VOLTAGE_SWELL_L3_COUNT (1-0:72.36.0)\n assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT, CosemObject)\n assert telegram.VOLTAGE_SWELL_L3_COUNT.unit is None\n assert isinstance(telegram.VOLTAGE_SWELL_L3_COUNT.value, int)\n assert telegram.VOLTAGE_SWELL_L3_COUNT.value == 0\n\n # INSTANTANEOUS_VOLTAGE_L1 (1-0:32.7.0)\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1, CosemObject)\n assert telegram.INSTANTANEOUS_VOLTAGE_L1.unit == 'V'\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L1.value, Decimal)\n assert telegram.INSTANTANEOUS_VOLTAGE_L1.value == Decimal('230.0')\n\n # INSTANTANEOUS_VOLTAGE_L2 (1-0:52.7.0)\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2, CosemObject)\n assert telegram.INSTANTANEOUS_VOLTAGE_L2.unit == 'V'\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L2.value, Decimal)\n assert telegram.INSTANTANEOUS_VOLTAGE_L2.value == Decimal('230.0')\n\n # INSTANTANEOUS_VOLTAGE_L3 (1-0:72.7.0)\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3, CosemObject)\n assert telegram.INSTANTANEOUS_VOLTAGE_L3.unit == 'V'\n assert isinstance(telegram.INSTANTANEOUS_VOLTAGE_L3.value, Decimal)\n assert telegram.INSTANTANEOUS_VOLTAGE_L3.value == Decimal('229.0')\n\n # INSTANTANEOUS_CURRENT_L1 (1-0:31.7.0)\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1, CosemObject)\n assert telegram.INSTANTANEOUS_CURRENT_L1.unit == 'A'\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L1.value, Decimal)\n assert telegram.INSTANTANEOUS_CURRENT_L1.value == Decimal('0.48')\n\n # INSTANTANEOUS_CURRENT_L2 (1-0:51.7.0)\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2, CosemObject)\n assert telegram.INSTANTANEOUS_CURRENT_L2.unit == 'A'\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L2.value, Decimal)\n assert telegram.INSTANTANEOUS_CURRENT_L2.value == Decimal('0.44')\n\n # INSTANTANEOUS_CURRENT_L3 (1-0:71.7.0)\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3, CosemObject)\n assert telegram.INSTANTANEOUS_CURRENT_L3.unit == 'A'\n assert isinstance(telegram.INSTANTANEOUS_CURRENT_L3.value, Decimal)\n assert telegram.INSTANTANEOUS_CURRENT_L3.value == Decimal('0.86')\n\n # TEXT_MESSAGE (0-0:96.13.0)\n assert isinstance(telegram.TEXT_MESSAGE, CosemObject)\n assert telegram.TEXT_MESSAGE.unit is None\n assert telegram.TEXT_MESSAGE.value is None\n\n # INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE (1-0:21.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE.value == Decimal('0.070')\n\n # INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE (1-0:41.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE.value == Decimal('0.032')\n\n # INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE (1-0:61.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE.value == Decimal('0.142')\n\n # INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE (1-0:22.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE.value == Decimal('0')\n\n # INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE (1-0:42.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE.value == Decimal('0')\n\n # INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE (1-0:62.7.0)\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE, CosemObject)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.unit == 'kW'\n assert isinstance(telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.value, Decimal)\n assert telegram.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE.value == Decimal('0')\n\n # There's only one Mbus device (gas meter) in this case. Alternatively\n # use get_mbus_device_by_channel\n gas_meter_devices = telegram.MBUS_DEVICES\n gas_meter_device = gas_meter_devices[0]\n\n # MBUS_DEVICE_TYPE (0-1:96.1.0)\n assert isinstance(gas_meter_device.MBUS_DEVICE_TYPE, CosemObject)\n assert gas_meter_device.MBUS_DEVICE_TYPE.unit is None\n assert isinstance(gas_meter_device.MBUS_DEVICE_TYPE.value, int)\n assert gas_meter_device.MBUS_DEVICE_TYPE.value == 3\n\n # MBUS_EQUIPMENT_IDENTIFIER (0-1:96.1.0)\n assert isinstance(gas_meter_device.MBUS_EQUIPMENT_IDENTIFIER, CosemObject)\n assert gas_meter_device.MBUS_EQUIPMENT_IDENTIFIER.unit is None\n assert isinstance(gas_meter_device.MBUS_EQUIPMENT_IDENTIFIER.value, str)\n assert gas_meter_device.MBUS_EQUIPMENT_IDENTIFIER.value == '3232323241424344313233343536373839'\n\n # MBUS_METER_READING (0-1:24.2.1)\n assert isinstance(gas_meter_device.MBUS_METER_READING, MBusObject)\n assert gas_meter_device.MBUS_METER_READING.unit == 'm3'\n assert isinstance(telegram.MBUS_METER_READING.value, Decimal)\n assert gas_meter_device.MBUS_METER_READING.value == Decimal('0.107')\n\n def test_checksum_valid(self):\n # No exception is raised.\n TelegramParser.validate_checksum(TELEGRAM_V5)\n\n def test_checksum_invalid(self):\n # Remove the electricty used data value. This causes the checksum to\n # not match anymore.\n corrupted_telegram = TELEGRAM_V5.replace(\n '1-0:1.8.1(000004.426*kWh)\\r\\n',\n ''\n )\n\n with self.assertRaises(InvalidChecksumError):\n TelegramParser.validate_checksum(corrupted_telegram)\n\n def test_checksum_missing(self):\n # Remove the checksum value causing a ParseError.\n corrupted_telegram = TELEGRAM_V5.replace('!6EEE\\r\\n', '')\n with self.assertRaises(ParseError):\n TelegramParser.validate_checksum(corrupted_telegram)\n\n def test_gas_timestamp_invalid(self):\n # Issue 120\n # Sometimes a MBUS device (For ex a Gas Meter) returns an invalid timestamp\n # Instead of failing, we should just ignore the timestamp\n invalid_date_telegram = TELEGRAM_V5.replace(\n '0-1:24.2.1(170102161005W)(00000.107*m3)\\r\\n',\n '0-1:24.2.1(632525252525S)(00000.000)\\r\\n'\n )\n invalid_date_telegram = invalid_date_telegram.replace('!6EEE\\r\\n', '!90C2\\r\\n')\n parser = TelegramParser(telegram_specifications.V5)\n telegram = parser.parse(invalid_date_telegram)\n\n # MBUS DEVICE 1\n mbus1 = telegram.get_mbus_device_by_channel(1)\n\n # MBUS_METER_READING (0-1:24.2.1)\n assert isinstance(mbus1.MBUS_METER_READING, MBusObject)\n assert mbus1.MBUS_METER_READING.unit is None\n assert isinstance(mbus1.MBUS_METER_READING.value, Decimal)\n assert mbus1.MBUS_METER_READING.value == Decimal('0.000')\n",
"review": "**7**\n\n### Bugs\n- No direct identification of issues, but exceptions could be handled much better with more detailed information.\n- The gas meter reading asserts for unit contradiction, once as 'm3' and another time as `None`.\n \n### Optimizations\n- Consider using `self.assert` over Python's `assert` statements for better unittest integration.\n- The test could be refactored to be more DRY (Don't Repeat Yourself) by creating helper functions for shared assertion patterns.\n- Error messages in exceptions could be made more informative and specific to enhance debugging.\n- Improve exception messages to clearly communicate which part of the telegram caused the error.\n\n### Good points\n- Comprehensive range of tests covering different data points ensures thorough verification.\n- Assertions use a good combination of type checks and value comparisons.\n- Proper usage of Python's unittest framework provides a structured testing approach.\n \n### Summary\nThe provided code is robust in terms of testing a broad spectrum of possible DSMR telegram parsing scenarios and covers multiple cases such as valid data, checksum validation, and error handling for corrupted content. However, it lacks clarity in exception messages and can be refactored to reduce repetitive code. Moving from Python's generic `assert` to unittest's `self.assert` can better integrate with test runners and produce more informative output during failures.\n\n### Open source alternatives\n- **DSMR-reader**: An open source application to read and visualize DSMR data.\n- **Home Assistant DSMR integration**: A component for Home Assistant that supports parsing data from a smart meter via DSMR.",
"filename": "test_parse_v5.py",
"path": "test/test_parse_v5.py",
"directory": "test",
"grade": 7,
"size": 14292,
"line_count": 275
}