commit
						ba29e34cf6
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,5 @@ | |||||||
| .idea | .idea | ||||||
| *.pyc | *.pyc | ||||||
|  | .tox | ||||||
|  | .cache | ||||||
|  | *.egg-info | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | language: python | ||||||
|  | python: | ||||||
|  |   - 2.7 | ||||||
|  |   - 3.4 | ||||||
|  |   - 3.5 | ||||||
|  | install: pip install tox-travis | ||||||
|  | script: tox | ||||||
|  | matrix: | ||||||
|  |   allow_failures: | ||||||
|  |     - python: 2.7 | ||||||
							
								
								
									
										32
									
								
								dsmr_parser/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								dsmr_parser/__main__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | import argparse | ||||||
|  | from dsmr_parser.serial import SERIAL_SETTINGS_V2_2, SERIAL_SETTINGS_V4, SerialReader | ||||||
|  | from dsmr_parser import telegram_specifications | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def console(): | ||||||
|  |     """Output DSMR data to console.""" | ||||||
|  | 
 | ||||||
|  |     parser = argparse.ArgumentParser(description=console.__doc__) | ||||||
|  |     parser.add_argument('--device', default='/dev/ttyUSB0', | ||||||
|  |                         help='port to read DSMR data from') | ||||||
|  |     parser.add_argument('--version', default='2.2', choices=['2.2', '4'], | ||||||
|  |                         help='DSMR version (2.2, 4)') | ||||||
|  | 
 | ||||||
|  |     args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  |     settings = { | ||||||
|  |         '2.2': (SERIAL_SETTINGS_V2_2, telegram_specifications.V2_2), | ||||||
|  |         '4': (SERIAL_SETTINGS_V4, telegram_specifications.V4), | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     serial_reader = SerialReader( | ||||||
|  |         device=args.device, | ||||||
|  |         serial_settings=settings[args.version][0], | ||||||
|  |         telegram_specification=settings[args.version][1], | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     for telegram in serial_reader.read(): | ||||||
|  |         for obiref, obj in telegram.items(): | ||||||
|  |             if obj: | ||||||
|  |                 print(obj.value, obj.unit) | ||||||
|  |         print() | ||||||
| @ -27,6 +27,10 @@ INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE = r'1-0:42\.7\.0' | |||||||
| INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'1-0:62\.7\.0' | INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE = r'1-0:62\.7\.0' | ||||||
| EQUIPMENT_IDENTIFIER_GAS = r'0-\d:96\.1\.0' | EQUIPMENT_IDENTIFIER_GAS = r'0-\d:96\.1\.0' | ||||||
| HOURLY_GAS_METER_READING = r'0-1:24\.2\.1' | HOURLY_GAS_METER_READING = r'0-1:24\.2\.1' | ||||||
|  | GAS_METER_READING = r'0-\d:24\.3\.0' | ||||||
|  | ACTUAL_TRESHOLD_ELECTRICITY = r'0-0:17\.0\.0' | ||||||
|  | ACTUAL_SWITCH_POSITION = r'0-0:96\.3\.10' | ||||||
|  | VALVE_POSITION_GAS = r'0-\d:24\.4\.0' | ||||||
| 
 | 
 | ||||||
| ELECTRICITY_USED_TARIFF_ALL = ( | ELECTRICITY_USED_TARIFF_ALL = ( | ||||||
|     ELECTRICITY_USED_TARIFF_1, |     ELECTRICITY_USED_TARIFF_1, | ||||||
|  | |||||||
| @ -19,6 +19,21 @@ class MBusObject(DSMRObject): | |||||||
|         return self.values[1]['unit'] |         return self.values[1]['unit'] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class MBusObjectV2_2(DSMRObject): | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def datetime(self): | ||||||
|  |         return self.values[0]['value'] | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         return self.values[5]['value'] | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def unit(self): | ||||||
|  |         return self.values[4]['value'] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class CosemObject(DSMRObject): | class CosemObject(DSMRObject): | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|  | |||||||
| @ -1,9 +1,9 @@ | |||||||
| import logging | import logging | ||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| from .objects import MBusObject, CosemObject | from .objects import MBusObject, MBusObjectV2_2, CosemObject | ||||||
| from .exceptions import ParseError | from .exceptions import ParseError | ||||||
| 
 | from .obis_references import GAS_METER_READING | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| @ -29,7 +29,7 @@ class TelegramParser(object): | |||||||
|         telegram = {} |         telegram = {} | ||||||
| 
 | 
 | ||||||
|         for line_value in line_values: |         for line_value in line_values: | ||||||
|             obis_reference, dsmr_object = self.parse_line(line_value) |             obis_reference, dsmr_object = self.parse_line(line_value.strip()) | ||||||
| 
 | 
 | ||||||
|             telegram[obis_reference] = dsmr_object |             telegram[obis_reference] = dsmr_object | ||||||
| 
 | 
 | ||||||
| @ -47,6 +47,26 @@ class TelegramParser(object): | |||||||
|         return obis_reference, parser.parse(line_value) |         return obis_reference, parser.parse(line_value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class TelegramParserV2_2(TelegramParser): | ||||||
|  |     def parse(self, line_values): | ||||||
|  |         """Join lines for gas meter.""" | ||||||
|  | 
 | ||||||
|  |         def join_lines(line_values): | ||||||
|  |             join_next = re.compile(GAS_METER_READING) | ||||||
|  | 
 | ||||||
|  |             join = None | ||||||
|  |             for line_value in line_values: | ||||||
|  |                 if join: | ||||||
|  |                     yield join.strip() + line_value | ||||||
|  |                     join = None | ||||||
|  |                 elif join_next.match(line_value): | ||||||
|  |                     join = line_value | ||||||
|  |                 else: | ||||||
|  |                     yield line_value | ||||||
|  | 
 | ||||||
|  |         return super().parse(join_lines(line_values)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class DSMRObjectParser(object): | class DSMRObjectParser(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *value_formats): |     def __init__(self, *value_formats): | ||||||
| @ -85,7 +105,11 @@ class MBusParser(DSMRObjectParser): | |||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def parse(self, line): |     def parse(self, line): | ||||||
|         return MBusObject(self._parse(line)) |         values = self._parse(line) | ||||||
|  |         if len(values) == 2: | ||||||
|  |             return MBusObject(values) | ||||||
|  |         else: | ||||||
|  |             return MBusObjectV2_2(values) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CosemParser(DSMRObjectParser): | class CosemParser(DSMRObjectParser): | ||||||
|  | |||||||
| @ -1,6 +1,16 @@ | |||||||
| import serial | import serial | ||||||
| 
 | 
 | ||||||
| from dsmr_parser.parsers import TelegramParser | from dsmr_parser.parsers import TelegramParser, TelegramParserV2_2 | ||||||
|  | 
 | ||||||
|  | SERIAL_SETTINGS_V2_2 = { | ||||||
|  |     'baudrate': 9600, | ||||||
|  |     'bytesize': serial.SEVENBITS, | ||||||
|  |     'parity': serial.PARITY_NONE, | ||||||
|  |     'stopbits': serial.STOPBITS_ONE, | ||||||
|  |     'xonxoff': 0, | ||||||
|  |     'rtscts': 0, | ||||||
|  |     'timeout': 20 | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| SERIAL_SETTINGS_V4 = { | SERIAL_SETTINGS_V4 = { | ||||||
|     'baudrate': 115200, |     'baudrate': 115200, | ||||||
| @ -26,7 +36,12 @@ class SerialReader(object): | |||||||
|     def __init__(self, device, serial_settings, telegram_specification): |     def __init__(self, device, serial_settings, telegram_specification): | ||||||
|         self.serial_settings = serial_settings |         self.serial_settings = serial_settings | ||||||
|         self.serial_settings['port'] = device |         self.serial_settings['port'] = device | ||||||
|         self.telegram_parser = TelegramParser(telegram_specification) | 
 | ||||||
|  |         if serial_settings is SERIAL_SETTINGS_V2_2: | ||||||
|  |             telegram_parser = TelegramParserV2_2 | ||||||
|  |         else: | ||||||
|  |             telegram_parser = TelegramParser | ||||||
|  |         self.telegram_parser = telegram_parser(telegram_specification) | ||||||
| 
 | 
 | ||||||
|     def read(self): |     def read(self): | ||||||
|         """ |         """ | ||||||
| @ -52,4 +67,3 @@ class SerialReader(object): | |||||||
|                 if is_end_of_telegram(line): |                 if is_end_of_telegram(line): | ||||||
|                     yield self.telegram_parser.parse(telegram) |                     yield self.telegram_parser.parse(telegram) | ||||||
|                     telegram = [] |                     telegram = [] | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
| 
 | 
 | ||||||
| from .obis_references import * | from . import obis_references as obis | ||||||
| from .parsers import CosemParser, ValueParser, MBusParser | from .parsers import CosemParser, ValueParser, MBusParser | ||||||
| from .value_types import timestamp | from .value_types import timestamp | ||||||
| 
 | 
 | ||||||
| @ -13,36 +13,61 @@ This module contains DSMR telegram specifications. Each specifications describes | |||||||
| how the telegram lines are parsed. | how the telegram lines are parsed. | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| V4 = { | V2_2 = { | ||||||
|     P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), |     obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), | ||||||
|     P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), |     obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)), | ||||||
|     ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)), |     obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)), | ||||||
|     ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)), |     obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)), | ||||||
|     ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)), |     obis.ELECTRICITY_DELIVERED_TARIFF_2: CosemParser(ValueParser(Decimal)), | ||||||
|     ELECTRICITY_DELIVERED_TARIFF_2: CosemParser(ValueParser(Decimal)), |     obis.ELECTRICITY_ACTIVE_TARIFF: CosemParser(ValueParser(str)), | ||||||
|     ELECTRICITY_ACTIVE_TARIFF: CosemParser(ValueParser(str)), |     obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), | ||||||
|     EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), |     obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), | ||||||
|     CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), |     obis.ACTUAL_TRESHOLD_ELECTRICITY: CosemParser(ValueParser(Decimal)), | ||||||
|     CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), |     obis.ACTUAL_SWITCH_POSITION: CosemParser(ValueParser(str)), | ||||||
|     LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), |     obis.TEXT_MESSAGE_CODE: CosemParser(ValueParser(int)), | ||||||
|     # POWER_EVENT_FAILURE_LOG: ProfileGenericParser(), TODO |     obis.TEXT_MESSAGE: CosemParser(ValueParser(str)), | ||||||
|     VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), |     obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), | ||||||
|     VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), |     obis.DEVICE_TYPE: CosemParser(ValueParser(str)), | ||||||
|     VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), |     obis.VALVE_POSITION_GAS: CosemParser(ValueParser(str)), | ||||||
|     VOLTAGE_SWELL_L1_COUNT: CosemParser(ValueParser(int)), |     obis.GAS_METER_READING: MBusParser( | ||||||
|     VOLTAGE_SWELL_L2_COUNT: CosemParser(ValueParser(int)), |         ValueParser(timestamp), | ||||||
|     VOLTAGE_SWELL_L3_COUNT: CosemParser(ValueParser(int)), |         ValueParser(int), | ||||||
|     TEXT_MESSAGE_CODE: CosemParser(ValueParser(int)), |         ValueParser(int), | ||||||
|     TEXT_MESSAGE: CosemParser(ValueParser(str)), |         ValueParser(int), | ||||||
|     DEVICE_TYPE: CosemParser(ValueParser(int)), |         ValueParser(str), | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), |         ValueParser(Decimal), | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: CosemParser(ValueParser(Decimal)), |     ), | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: CosemParser(ValueParser(Decimal)), |  | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: CosemParser(ValueParser(Decimal)), |  | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: CosemParser(ValueParser(Decimal)), |  | ||||||
|     INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), |  | ||||||
|     EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), |  | ||||||
|     HOURLY_GAS_METER_READING: MBusParser(ValueParser(timestamp), |  | ||||||
|                                          ValueParser(Decimal)) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | V4 = { | ||||||
|  |     obis.P1_MESSAGE_HEADER: CosemParser(ValueParser(str)), | ||||||
|  |     obis.P1_MESSAGE_TIMESTAMP: CosemParser(ValueParser(timestamp)), | ||||||
|  |     obis.ELECTRICITY_USED_TARIFF_1: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.ELECTRICITY_USED_TARIFF_2: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.ELECTRICITY_DELIVERED_TARIFF_1: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.ELECTRICITY_DELIVERED_TARIFF_2: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.ELECTRICITY_ACTIVE_TARIFF: CosemParser(ValueParser(str)), | ||||||
|  |     obis.EQUIPMENT_IDENTIFIER: CosemParser(ValueParser(str)), | ||||||
|  |     obis.CURRENT_ELECTRICITY_USAGE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.CURRENT_ELECTRICITY_DELIVERY: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.LONG_POWER_FAILURE_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     # POWER_EVENT_FAILURE_LOG: ProfileGenericParser(), TODO | ||||||
|  |     obis.VOLTAGE_SAG_L1_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.VOLTAGE_SAG_L2_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.VOLTAGE_SAG_L3_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.VOLTAGE_SWELL_L1_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.VOLTAGE_SWELL_L2_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.VOLTAGE_SWELL_L3_COUNT: CosemParser(ValueParser(int)), | ||||||
|  |     obis.TEXT_MESSAGE_CODE: CosemParser(ValueParser(int)), | ||||||
|  |     obis.TEXT_MESSAGE: CosemParser(ValueParser(str)), | ||||||
|  |     obis.DEVICE_TYPE: CosemParser(ValueParser(int)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE: CosemParser(ValueParser(Decimal)), | ||||||
|  |     obis.EQUIPMENT_IDENTIFIER_GAS: CosemParser(ValueParser(str)), | ||||||
|  |     obis.HOURLY_GAS_METER_READING: MBusParser(ValueParser(timestamp), | ||||||
|  |                                               ValueParser(Decimal)) | ||||||
|  | } | ||||||
|  | |||||||
| @ -6,7 +6,10 @@ import pytz | |||||||
| def timestamp(value): | def timestamp(value): | ||||||
| 
 | 
 | ||||||
|     naive_datetime = datetime.datetime.strptime(value[:-1], '%y%m%d%H%M%S') |     naive_datetime = datetime.datetime.strptime(value[:-1], '%y%m%d%H%M%S') | ||||||
|     is_dst = value[12] == 'S'  # assume format 160322150000W |     if len(value) == 13: | ||||||
|  |         is_dst = value[12] == 'S'  # assume format 160322150000W | ||||||
|  |     else: | ||||||
|  |         is_dst = False | ||||||
| 
 | 
 | ||||||
|     local_tz = pytz.timezone('Europe/Amsterdam') |     local_tz = pytz.timezone('Europe/Amsterdam') | ||||||
|     localized_datetime = local_tz.localize(naive_datetime, is_dst=is_dst) |     localized_datetime = local_tz.localize(naive_datetime, is_dst=is_dst) | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								setup.py
									
									
									
									
									
								
							| @ -7,7 +7,10 @@ setup( | |||||||
|     version='0.1', |     version='0.1', | ||||||
|     packages=find_packages(), |     packages=find_packages(), | ||||||
|     install_requires=[ |     install_requires=[ | ||||||
|         'pyserial==3.0.1', |         'pyserial>=3,<4', | ||||||
|         'pytz==2016.3' |         'pytz' | ||||||
|     ] |     ], | ||||||
|  |     entry_points={ | ||||||
|  |         'console_scripts': ['dsmr_console=dsmr_parser.__main__:console'] | ||||||
|  |     }, | ||||||
| ) | ) | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								test/test_parse.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								test/test_parse.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | """Test telegram parsing.""" | ||||||
|  | 
 | ||||||
|  | from dsmr_parser.parsers import TelegramParserV2_2 | ||||||
|  | from dsmr_parser import telegram_specifications | ||||||
|  | from dsmr_parser.obis_references import CURRENT_ELECTRICITY_USAGE, GAS_METER_READING | ||||||
|  | 
 | ||||||
|  | TELEGRAM_V2_2 = [ | ||||||
|  |     "/ISk5\2MT382-1004", | ||||||
|  |     "", | ||||||
|  |     "0-0:96.1.1(00000000000000)", | ||||||
|  |     "1-0:1.8.1(00001.001*kWh)", | ||||||
|  |     "1-0:1.8.2(00001.001*kWh)", | ||||||
|  |     "1-0:2.8.1(00001.001*kWh)", | ||||||
|  |     "1-0:2.8.2(00001.001*kWh)", | ||||||
|  |     "0-0:96.14.0(0001)", | ||||||
|  |     "1-0:1.7.0(0001.01*kW)", | ||||||
|  |     "1-0:2.7.0(0000.00*kW)", | ||||||
|  |     "0-0:17.0.0(0999.00*kW)", | ||||||
|  |     "0-0:96.3.10(1)", | ||||||
|  |     "0-0:96.13.1()", | ||||||
|  |     "0-0:96.13.0()", | ||||||
|  |     "0-1:24.1.0(3)", | ||||||
|  |     "0-1:96.1.0(000000000000)", | ||||||
|  |     "0-1:24.3.0(161107190000)(00)(60)(1)(0-1:24.2.1)(m3)", | ||||||
|  |     "(00001.001)", | ||||||
|  |     "0-1:24.4.0(1)", | ||||||
|  |     "!", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_parse_v2_2(): | ||||||
|  |     """Test if telegram parsing results in correct results.""" | ||||||
|  | 
 | ||||||
|  |     parser = TelegramParserV2_2(telegram_specifications.V2_2) | ||||||
|  |     result = parser.parse(TELEGRAM_V2_2) | ||||||
|  | 
 | ||||||
|  |     assert float(result[CURRENT_ELECTRICITY_USAGE].value) == 1.01 | ||||||
|  |     assert result[CURRENT_ELECTRICITY_USAGE].unit == 'kW' | ||||||
|  |     assert float(result[GAS_METER_READING].value) == 1.001 | ||||||
|  |     assert result[GAS_METER_READING].unit == 'm3' | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user