def test_meter_statistics_change(self): self.assertEqual(MeterStatistics.objects.count(), 1) self.assertEqual(MeterStatisticsChange.objects.count(), 0) # Fixture update. MeterStatistics.get_solo().update( timestamp=timezone.now(), # Changed (ignored) dsmr_version=50, # Changed (ignored) electricity_tariff=1, # Changed (ignored) power_failure_count=8, # Changed long_power_failure_count=2, voltage_sag_count_l1=2, voltage_sag_count_l2=3, # Changed voltage_sag_count_l3=1, # Changed voltage_swell_count_l1=0, voltage_swell_count_l2=1, # Changed voltage_swell_count_l3=2, # Changed rejected_telegrams=155, # Changed (ignored) latest_telegram='/OTHER-TEST\r\n!1234' # Changed (ignored) ) # Some changes should not be logged and some should. self.assertEqual(MeterStatisticsChange.objects.count(), 5) self.assertTrue(MeterStatisticsChange.objects.filter(field='power_failure_count').exists()) self.assertTrue(MeterStatisticsChange.objects.filter(field='voltage_sag_count_l2').exists()) self.assertTrue(MeterStatisticsChange.objects.filter(field='voltage_sag_count_l3').exists()) self.assertTrue(MeterStatisticsChange.objects.filter(field='power_failure_count').exists()) self.assertTrue(MeterStatisticsChange.objects.filter(field='voltage_swell_count_l3').exists()) self.assertTrue(MeterStatisticsChange.objects.filter( field='power_failure_count', old_value='7', new_value='8', ).exists())
def test_get(self, now_mock): now_mock.return_value = timezone.make_aware( timezone.datetime(2020, 1, 1)) MeterStatistics.get_solo() MeterStatistics.objects.all().update( timestamp=timezone.now(), dsmr_version='50', electricity_tariff=1, power_failure_count=123, long_power_failure_count=456, voltage_sag_count_l1=11, voltage_sag_count_l2=22, voltage_sag_count_l3=33, voltage_swell_count_l1=44, voltage_swell_count_l2=55, voltage_swell_count_l3=66, ) result = self._request('meter-statistics') self.assertEqual(result['id'], 1) self.assertEqual(result['timestamp'], '2020-01-01T00:00:00+01:00') self.assertEqual(result['dsmr_version'], '50') self.assertEqual(result['electricity_tariff'], 1) self.assertEqual(result['latest_telegram'], None) self.assertEqual(result['power_failure_count'], 123) self.assertEqual(result['long_power_failure_count'], 456) self.assertEqual(result['voltage_sag_count_l1'], 11) self.assertEqual(result['voltage_sag_count_l2'], 22) self.assertEqual(result['voltage_sag_count_l3'], 33) self.assertEqual(result['voltage_swell_count_l1'], 44) self.assertEqual(result['voltage_swell_count_l2'], 55) self.assertEqual(result['voltage_swell_count_l3'], 66)
def setUp(self): self.assertEqual(DsmrReading.objects.all().count(), 7) self.assertTrue(DsmrReading.objects.unprocessed().exists()) ConsumptionSettings.get_solo() MeterStatistics.get_solo() MeterStatistics.objects.all().update(dsmr_version='50') self.schedule_process = ScheduledProcess.objects.get(module=settings.DSMRREADER_MODULE_GENERATE_CONSUMPTION) self.schedule_process.update(active=True, planned=timezone.make_aware(timezone.datetime(2000, 1, 1)))
def test_track_meter_statistics(self): telegram = ''.join([ "/XMX5LGBBFFB123456789\r\n", "\r\n", "1-3:0.2.8(40)\r\n", "0-0:1.0.0(151110192959W)\r\n", "0-0:96.1.1(xxxxxxxxxxxxx)\r\n", "1-0:1.8.1(000510.747*kWh)\r\n", "1-0:2.8.1(000000.123*kWh)\r\n", "1-0:1.8.2(000500.013*kWh)\r\n", "1-0:2.8.2(000123.456*kWh)\r\n", "0-0:96.14.0(0001)\r\n", "1-0:1.7.0(00.192*kW)\r\n", "1-0:2.7.0(00.123*kW)\r\n", "0-0:17.0.0(999.9*kW)\r\n", "0-0:96.3.10(1)\r\n", "0-0:96.7.21(00003)\r\n", "0-0:96.7.9(00000)\r\n", "1-0:99.97.0(0)(0-0:96.7.19)\r\n", "1-0:32.32.0(00002)\r\n", "1-0:52.32.0(00002)\r\n", "1-0:72.32.0(00000)\r\n", "1-0:32.36.0(00000)\r\n", "1-0:52.36.0(00000)\r\n", "1-0:72.36.0(00000)\r\n", "0-0:96.13.1()\r\n", "0-0:96.13.0()\r\n", "1-0:31.7.0(000*A)\r\n", "1-0:51.7.0(000*A)\r\n", "1-0:71.7.0(001*A)\r\n", "1-0:21.7.0(00.000*kW)\r\n", "1-0:41.7.0(00.000*kW)\r\n", "1-0:61.7.0(00.192*kW)\r\n", "1-0:22.7.0(00.000*kW)\r\n", "1-0:42.7.0(00.000*kW)\r\n", "1-0:62.7.0(00.000*kW)\r\n", "0-1:24.1.0(003)\r\n", "0-1:96.1.0(xxxxxxxxxxxxx)\r\n", "0-1:24.2.1(151110190000W)(00845.206*m3)\r\n", "0-1:24.4.0(1)\r\n", "!AF0C\n", ]) self.assertIsNone(MeterStatistics.get_solo().electricity_tariff ) # Empty model in DB. dsmr_datalogger.services.telegram_to_reading(data=telegram) # Should be populated now. meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '40') self.assertIsNotNone(meter_statistics.electricity_tariff) self.assertEqual(meter_statistics.electricity_tariff, 1) self.assertEqual(meter_statistics.power_failure_count, 3) self.assertEqual(meter_statistics.voltage_sag_count_l1, 2) self.assertEqual(meter_statistics.voltage_sag_count_l2, 2)
def setUp(self): self.support_gas_readings = True self.assertEqual(DsmrReading.objects.all().count(), 3) MeterStatistics.get_solo() MeterStatistics.objects.all().update(dsmr_version='42') if self.support_gas_readings: self.assertTrue(DsmrReading.objects.unprocessed().exists()) else: self.assertFalse(DsmrReading.objects.unprocessed().exists()) ConsumptionSettings.get_solo()
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2020, 1, 1)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2019, 10, 31, 13, 22, 39, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('5675.956')) self.assertEqual(reading.electricity_returned_1, Decimal('0.002')) self.assertEqual(reading.electricity_delivered_2, Decimal('0')) self.assertEqual(reading.electricity_returned_2, Decimal('0')) self.assertEqual(reading.electricity_currently_delivered, Decimal('3.074')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertIsNone(reading.extra_device_timestamp) self.assertIsNone(reading.extra_device_delivered) self.assertIsNone(reading.phase_voltage_l1) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '42') self.assertIsNone(meter_statistics.electricity_tariff) self.assertIsNone(meter_statistics.power_failure_count) self.assertIsNone(meter_statistics.long_power_failure_count) self.assertIsNone(meter_statistics.voltage_sag_count_l1) self.assertIsNone(meter_statistics.voltage_sag_count_l2) self.assertIsNone(meter_statistics.voltage_sag_count_l3) self.assertIsNone(meter_statistics.voltage_swell_count_l1) self.assertIsNone(meter_statistics.voltage_swell_count_l2) self.assertIsNone(meter_statistics.voltage_swell_count_l3)
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 4, 10, hour=14, minute=30, second=15)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2016, 4, 10, 12, 30, 15, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('1234.784')) self.assertEqual(reading.electricity_returned_1, Decimal('0')) self.assertEqual(reading.electricity_delivered_2, Decimal('4321.725')) self.assertEqual(reading.electricity_returned_2, Decimal('0.002')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.36')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, datetime(2016, 4, 10, 11, 0, 0, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('7890.693')) self.assertIsNone(reading.phase_voltage_l1) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) meter_statistics = MeterStatistics.get_solo() self.assertIsNone(meter_statistics.dsmr_version) self.assertEqual(meter_statistics.electricity_tariff, 1) self.assertEqual(meter_statistics.power_failure_count, None) self.assertEqual(meter_statistics.long_power_failure_count, None) self.assertEqual(meter_statistics.voltage_sag_count_l1, None) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, None) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2019, 11, 8, hour=20)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2019, 11, 8, 13, 55, 19, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('1043.936')) self.assertEqual(reading.electricity_returned_1, Decimal('0')) self.assertEqual(reading.electricity_delivered_2, Decimal('870.706')) self.assertEqual(reading.electricity_returned_2, Decimal('0')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.189')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, datetime(2019, 11, 8, 13, 55, 5, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('241.773')) self.assertEqual(reading.phase_voltage_l1, Decimal('235.4')) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '50') self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, 8) self.assertEqual(meter_statistics.long_power_failure_count, 3) self.assertEqual(meter_statistics.voltage_sag_count_l1, 5) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, 1) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def test_dashboard_xhr_header(self, now_mock): now_mock.return_value = timezone.make_aware( timezone.datetime(2015, 11, 15)) # This makes sure all possible code paths are covered. for current_tariff in (None, 1, 2): if MeterStatistics.objects.exists(): meter_statistics = MeterStatistics.get_solo() meter_statistics.electricity_tariff = current_tariff meter_statistics.save() response = self.client.get( reverse('{}:dashboard-xhr-header'.format(self.namespace))) self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response['Content-Type'], 'application/json') # No response when no data at all. if self.support_data: json_response = json.loads(response.content.decode("utf-8")) self.assertIn('timestamp', json_response) self.assertIn('currently_delivered', json_response) self.assertIn('currently_returned', json_response) # Costs only makes sense when set. if EnergySupplierPrice.objects.exists() and MeterStatistics.objects.exists() \ and current_tariff is not None: self.assertIn('latest_electricity_cost', json_response) self.assertEqual(json_response['latest_electricity_cost'], '0.23' if current_tariff == 1 else '0.46')
def test_reading_values(self): """ Test whether dsmr_datalogger reads the correct values. """ DataloggerSettings.get_solo() DataloggerSettings.objects.all().update(track_phases=True) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2016, 3, 3, 15, 43, 47, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('1073.079')) self.assertEqual(reading.electricity_returned_1, Decimal('0')) self.assertEqual(reading.electricity_delivered_2, Decimal('1263.199')) self.assertEqual(reading.electricity_returned_2, Decimal('0')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.143')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertIsNone(reading.extra_device_timestamp) self.assertIsNone(reading.extra_device_delivered) self.assertEqual(reading.phase_currently_delivered_l1, Decimal('0.143')) self.assertEqual(reading.phase_currently_delivered_l2, None) self.assertEqual(reading.phase_currently_delivered_l3, None) meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '42') self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, 6) self.assertEqual(meter_statistics.long_power_failure_count, 3) self.assertEqual(meter_statistics.voltage_sag_count_l1, 0) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, 0) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def get_context_data(self, **kwargs): context_data = super(Statistics, self).get_context_data(**kwargs) context_data['capabilities'] = dsmr_backend.services.get_capabilities() context_data['electricity_statistics'] = ElectricityStatistics.get_solo().export() try: latest_reading = DsmrReading.objects.all().order_by('-pk')[0] except IndexError: pass else: context_data['latest_reading'] = latest_reading context_data['delivered_sum'] = latest_reading.electricity_delivered_1 + \ latest_reading.electricity_delivered_2 context_data['returned_sum'] = latest_reading.electricity_returned_1 + \ latest_reading.electricity_returned_2 context_data['datalogger_settings'] = DataloggerSettings.get_solo() context_data['meter_statistics'] = MeterStatistics.get_solo() today = timezone.localtime(timezone.now()).date() try: context_data['energy_prices'] = EnergySupplierPrice.objects.by_date(today) except EnergySupplierPrice.DoesNotExist: context_data['energy_prices'] = [] return context_data
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2019, 8, 21, hour=22)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual( reading.timestamp, datetime(2019, 8, 21, 19, 0, 25, tzinfo=pytz.UTC) ) self.assertEqual(reading.electricity_delivered_1, Decimal('260.129')) self.assertEqual(reading.electricity_returned_1, Decimal('0.010')) self.assertEqual(reading.electricity_delivered_2, Decimal('338.681')) self.assertEqual(reading.electricity_returned_2, Decimal('0.425')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.261')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual( reading.extra_device_timestamp, datetime(2019, 8, 21, 19, 0, 11, tzinfo=pytz.UTC) ) self.assertEqual(reading.extra_device_delivered, Decimal('29.553')) meter_statistics = MeterStatistics.get_solo() self.assertIsNone(meter_statistics.dsmr_version) self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, None) self.assertEqual(meter_statistics.long_power_failure_count, None) self.assertEqual(meter_statistics.voltage_sag_count_l1, None) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, None) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def _dump_application_info(self): pending_migrations = [] for line in self._intercept_command_stdout('showmigrations', no_color=True).split("\n"): if line.startswith(' [ ]'): pending_migrations.append(line) pending_migrations_count = len(pending_migrations) self._print_header('DSMR-reader') self._pretty_print( 'App / Python / Database', 'v{} / v{} / {}'.format(settings.DSMRREADER_VERSION, platform.python_version(), connection.vendor)) self._pretty_print( 'Backend sleep / Datalogger sleep / Retention cleanup', '{}s / {}s / {}h'.format( BackendSettings.get_solo().process_sleep, DataloggerSettings.get_solo().process_sleep, RetentionSettings.get_solo().data_retention_in_hours or '-')) self._pretty_print( 'Telegram latest version read / Parser settings', '"{}" / "{}"'.format(MeterStatistics.get_solo().dsmr_version, DataloggerSettings.get_solo().dsmr_version)) if pending_migrations_count > 0: self._pretty_print('(!) Database migrations pending', '{} (!)'.format(pending_migrations_count))
def publish_split_topic_meter_statistics() -> None: """ Publishes meter statistics to a broker, formatted in a separate topic per field name, if set and enabled. """ split_topic_settings = meter_statistics.SplitTopicMeterStatisticsMQTTSettings.get_solo( ) if not split_topic_settings.enabled: return publish_split_topic_data(mapping_format=split_topic_settings.formatting, data_source=MeterStatistics.get_solo())
def live_electricity_consumption(use_naturaltime=True): """ Returns the current latest/live electricity consumption. """ data = {} try: latest_reading = DsmrReading.objects.all().order_by('-pk')[0] except IndexError: # Don't even bother when no data available. return data latest_timestamp = timezone.localtime(latest_reading.timestamp) # In case the smart meter is running a clock in the future. if latest_timestamp > timezone.now(): latest_timestamp = timezone.now() data['timestamp'] = latest_timestamp data['currently_delivered'] = int( latest_reading.electricity_currently_delivered * 1000) data['currently_returned'] = int( latest_reading.electricity_currently_returned * 1000) if use_naturaltime: data['timestamp'] = str(naturaltime(data['timestamp'])) try: # This WILL fail when we either have no prices at all or conflicting ranges. prices = EnergySupplierPrice.objects.by_date( target_date=timezone.now().date()) except (EnergySupplierPrice.DoesNotExist, EnergySupplierPrice.MultipleObjectsReturned): return data # We need to current tariff to get the right price. tariff = MeterStatistics.get_solo().electricity_tariff cost_per_hour = None tariff_map = { 1: prices.electricity_delivered_1_price, 2: prices.electricity_delivered_2_price, } try: cost_per_hour = latest_reading.electricity_currently_delivered * tariff_map[ tariff] except KeyError: pass else: data['cost_per_hour'] = formats.number_format( round_decimal(cost_per_hour)) return data
def test_reading_values(self): """ Test whether dsmr_datalogger reads the correct values. """ DataloggerSettings.get_solo() self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2015, 7, 1, 17, 29, 59, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('510.747')) self.assertEqual(reading.electricity_returned_1, Decimal('0.123')) self.assertEqual(reading.electricity_delivered_2, Decimal('500.013')) self.assertEqual(reading.electricity_returned_2, Decimal('123.456')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.192')) self.assertEqual(reading.electricity_currently_returned, Decimal('0.123')) self.assertEqual(reading.extra_device_timestamp, datetime(2015, 7, 1, 17, 0, 0, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('845.206')) self.assertEqual(reading.phase_currently_delivered_l1, Decimal('0.123')) self.assertEqual(reading.phase_currently_delivered_l2, Decimal('0.456')) self.assertEqual(reading.phase_currently_delivered_l3, Decimal('0.789')) self.assertEqual(reading.phase_currently_returned_l1, Decimal('0.111')) self.assertEqual(reading.phase_currently_returned_l2, Decimal('0.555')) self.assertEqual(reading.phase_currently_returned_l3, Decimal('0.999')) self.assertIsNone(reading.phase_voltage_l1) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) self.assertEqual(reading.phase_power_current_l1, 0) self.assertEqual(reading.phase_power_current_l2, 0) self.assertEqual(reading.phase_power_current_l3, 1) # Different data source. meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '40') self.assertEqual(meter_statistics.electricity_tariff, Decimal('1')) self.assertEqual(meter_statistics.power_failure_count, 3) self.assertEqual(meter_statistics.long_power_failure_count, 0) self.assertEqual(meter_statistics.voltage_sag_count_l1, 2) self.assertEqual(meter_statistics.voltage_sag_count_l2, 2) self.assertEqual(meter_statistics.voltage_sag_count_l3, 0) self.assertEqual(meter_statistics.voltage_swell_count_l1, 0) self.assertEqual(meter_statistics.voltage_swell_count_l2, 0) self.assertEqual(meter_statistics.voltage_swell_count_l3, 0)
def test_reading_values(self): DataloggerSettings.get_solo() self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2010, 12, 9, 10, 30, 20, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('123456.789')) self.assertEqual(reading.electricity_returned_1, Decimal('123456.789')) self.assertEqual(reading.electricity_delivered_2, Decimal('123456.789')) self.assertEqual(reading.electricity_returned_2, Decimal('123456.789')) self.assertEqual(reading.electricity_currently_delivered, Decimal('1.193')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, datetime(2010, 12, 9, 10, 25, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('12785.123')) self.assertEqual(reading.phase_currently_delivered_l1, Decimal('1.111')) self.assertEqual(reading.phase_currently_delivered_l2, Decimal('2.222')) self.assertEqual(reading.phase_currently_delivered_l3, Decimal('3.333')) self.assertEqual(reading.phase_currently_returned_l1, Decimal('4.444')) self.assertEqual(reading.phase_currently_returned_l2, Decimal('5.555')) self.assertEqual(reading.phase_currently_returned_l3, Decimal('6.666')) self.assertEqual(reading.phase_voltage_l1, Decimal('220.1')) self.assertEqual(reading.phase_voltage_l2, Decimal('220.2')) self.assertEqual(reading.phase_voltage_l3, Decimal('220.3')) self.assertEqual(reading.phase_power_current_l1, 1) self.assertEqual(reading.phase_power_current_l2, 2) self.assertEqual(reading.phase_power_current_l3, 3) meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '50') self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, 4) self.assertEqual(meter_statistics.long_power_failure_count, 2) self.assertEqual(meter_statistics.voltage_sag_count_l1, 2) self.assertEqual(meter_statistics.voltage_sag_count_l2, 1) self.assertEqual(meter_statistics.voltage_sag_count_l3, 0) self.assertEqual(meter_statistics.voltage_swell_count_l1, 0) self.assertEqual(meter_statistics.voltage_swell_count_l2, 3) self.assertEqual(meter_statistics.voltage_swell_count_l3, 0)
def test_reading_values(self): """ Test whether dsmr_datalogger reads the correct values. """ DataloggerSettings.get_solo() self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2016, 3, 17, 21, 10, 58, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('1255.252')) self.assertEqual(reading.electricity_returned_1, Decimal('0')) self.assertEqual(reading.electricity_delivered_2, Decimal('1284.838')) self.assertEqual(reading.electricity_returned_2, Decimal('0')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.187')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, datetime(2016, 3, 17, 21, 0, 0, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('1438.997')) self.assertEqual(reading.phase_currently_delivered_l1, Decimal('0.187')) self.assertIsNone(reading.phase_currently_delivered_l2) self.assertIsNone(reading.phase_currently_delivered_l3) self.assertEqual(reading.phase_currently_returned_l1, Decimal('0.999')) self.assertIsNone(reading.phase_currently_returned_l2) self.assertIsNone(reading.phase_currently_returned_l3) self.assertIsNone(reading.phase_voltage_l1) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) self.assertEqual(reading.phase_power_current_l1, 1) self.assertEqual(reading.phase_power_current_l2, None) self.assertEqual(reading.phase_power_current_l3, None) # Different data source. meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '42') self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, 8) self.assertEqual(meter_statistics.long_power_failure_count, 0) self.assertEqual(meter_statistics.voltage_sag_count_l1, 0) self.assertIsNone(meter_statistics.voltage_sag_count_l2) self.assertIsNone(meter_statistics.voltage_sag_count_l3) self.assertEqual(meter_statistics.voltage_swell_count_l1, 0) self.assertIsNone(meter_statistics.voltage_swell_count_l2) self.assertIsNone(meter_statistics.voltage_swell_count_l3)
def get(self, request): data = {} try: latest_reading = DsmrReading.objects.all().order_by('-pk')[0] except IndexError: # Don't even bother when no data available. return HttpResponse(json.dumps(data), content_type='application/json') latest_timestamp = latest_reading.timestamp # In case the smart meter is running a clock in the future. if latest_timestamp > timezone.now(): latest_timestamp = timezone.now() data['timestamp'] = naturaltime(latest_timestamp) data['currently_delivered'] = int(latest_reading.electricity_currently_delivered * 1000) data['currently_returned'] = int(latest_reading.electricity_currently_returned * 1000) try: # This WILL fail when we either have no prices at all or conflicting ranges. prices = EnergySupplierPrice.objects.by_date(target_date=timezone.now().date()) except (EnergySupplierPrice.DoesNotExist, EnergySupplierPrice.MultipleObjectsReturned): return HttpResponse(json.dumps(data), content_type='application/json') # We need to current tariff to get the right price. tariff = MeterStatistics.get_solo().electricity_tariff currently_delivered = latest_reading.electricity_currently_delivered cost_per_hour = None tariff_map = { 1: prices.electricity_delivered_1_price, 2: prices.electricity_delivered_2_price, } try: cost_per_hour = currently_delivered * tariff_map[tariff] except KeyError: pass else: data['latest_electricity_cost'] = formats.number_format( dsmr_consumption.services.round_decimal(cost_per_hour) ) return HttpResponse(json.dumps(data), content_type='application/json')
def get_context_data(self, **kwargs): context_data = super(LiveGraphs, self).get_context_data(**kwargs) context_data[ 'capabilities'] = dsmr_backend.services.backend.get_capabilities() context_data['meter_statistics'] = MeterStatistics.get_solo() context_data['datalogger_settings'] = DataloggerSettings.get_solo() context_data['frontend_settings'] = FrontendSettings.get_solo() context_data['notification_count'] = Notification.objects.unread( ).count() context_data['sorted_graphs_json'] = json.dumps( list(SortedGraph.objects.all().values_list('graph_type', flat=True))) today = timezone.localtime(timezone.now()).date() context_data[ 'month_statistics'] = dsmr_stats.services.month_statistics( target_date=today) return context_data
def test_reading_values(self): """ Test whether dsmr_datalogger reads the correct values. """ DataloggerSettings.get_solo() self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2016, 2, 10, 19, 30, 34, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('756.849')) self.assertEqual(reading.electricity_returned_1, Decimal('0')) self.assertEqual(reading.electricity_delivered_2, Decimal('714.405')) self.assertEqual(reading.electricity_returned_2, Decimal('0')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.111')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, datetime(2016, 2, 10, 19, 0, 0, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('1197.484')) self.assertEqual(reading.phase_currently_delivered_l1, Decimal('0.123')) self.assertEqual(reading.phase_currently_delivered_l2, Decimal('0.456')) self.assertEqual(reading.phase_currently_delivered_l3, Decimal('0.789')) self.assertEqual(reading.phase_currently_returned_l1, Decimal('0.222')) self.assertEqual(reading.phase_currently_returned_l2, Decimal('0.444')) self.assertEqual(reading.phase_currently_returned_l3, Decimal('0.666')) self.assertIsNone(reading.phase_voltage_l1) self.assertIsNone(reading.phase_voltage_l2) self.assertIsNone(reading.phase_voltage_l3) # Different data source. meter_statistics = MeterStatistics.get_solo() self.assertEqual(meter_statistics.dsmr_version, '42') self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, 3) self.assertEqual(meter_statistics.long_power_failure_count, 0) self.assertEqual(meter_statistics.voltage_sag_count_l1, 2) self.assertEqual(meter_statistics.voltage_sag_count_l2, 2) self.assertEqual(meter_statistics.voltage_sag_count_l3, 0) self.assertEqual(meter_statistics.voltage_swell_count_l1, 0) self.assertEqual(meter_statistics.voltage_swell_count_l2, 0) self.assertEqual(meter_statistics.voltage_swell_count_l3, 0)
def _compact_gas(dsmr_reading: DsmrReading, gas_grouping_type: int) -> None: """ Compacts any DSMR readings to gas consumption records, optionally grouped. Only when there is support for gas. There is quite some distinction between DSMR v4 and v5. DSMR v4 will update only once per hour and backtracks the time by reporting it over the previous hour. DSMR v5 will just allow small intervals, depending on whether the readings are grouped per minute or not. """ if not dsmr_reading.extra_device_timestamp or not dsmr_reading.extra_device_delivered: # Some households aren't connected to a gas meter at all. return gas_read_at = dsmr_reading.extra_device_timestamp dsmr_version = MeterStatistics.get_solo().dsmr_version # User requests grouping? Truncate any precision, making the gas reading's timestamp collide with the previous one, # until at least an hour passed by. if gas_grouping_type == ConsumptionSettings.GAS_GROUPING_BY_HOUR: gas_read_at = gas_read_at.replace(minute=0, second=0, microsecond=0) # DSMR v4 readings should reflect to the previous hour, to keep it compatible with the existing implementation. if dsmr_version is not None and dsmr_version.startswith('4'): gas_read_at = gas_read_at - timezone.timedelta(hours=1) # We will not override data, just ignore it. Also subject to DSMR v4 and grouped gas readings. if GasConsumption.objects.filter(read_at=gas_read_at).exists(): return # DSMR protocol does not expose current gas rate, so we have to calculate it ourselves. # Relative to the previous gas consumption, if any. try: previous = GasConsumption.objects.filter( # LT filter prevents negative values when importing historic data. read_at__lt=gas_read_at).order_by('-read_at')[0] except IndexError: gas_diff = 0 else: gas_diff = dsmr_reading.extra_device_delivered - previous.delivered GasConsumption.objects.create( read_at=gas_read_at, delivered=dsmr_reading.extra_device_delivered, currently_delivered=gas_diff)
def _compact_gas(dsmr_reading, grouping_type, **kwargs): """ Compacts any DSMR readings to gas consumption records, optionally grouped. Only when there is support for gas. There is quite some distinction between DSMR v4 and v5. DSMR v4 will update only once per hour and backtracks the time by reporting it over the previous hour. DSMR v5 will just allow small intervals, depending on whether the readings are grouped per minute or not. """ if not dsmr_reading.extra_device_timestamp or not dsmr_reading.extra_device_delivered: # Some households aren't connected to a gas meter at all. return read_at = dsmr_reading.extra_device_timestamp dsmr_version = MeterStatistics.get_solo().dsmr_version # User requests grouping? We will truncate the 'seconds' marker, which will only affect DSMR v5 readings. if grouping_type == ConsumptionSettings.COMPACTOR_GROUPING_BY_MINUTE: read_at = read_at.replace(second=0, microsecond=0) # DSMR v4 readings should reflect to the previous hour, to keep it compatible with the existing implementation. if dsmr_version is not None and dsmr_version.startswith('4'): read_at = read_at - timezone.timedelta(hours=1) # We will not override data, just ignore it then. DSMR v4 will hit this a lot, DSMR v5 not. if GasConsumption.objects.filter(read_at=read_at).exists(): return # DSMR does not expose current gas rate, so we have to calculate it ourselves, relative to the previous gas # consumption, if any. try: previous = GasConsumption.objects.filter( read_at__lt= read_at # Prevents negative values when importing historic data. ).order_by('-read_at')[0] except IndexError: gas_diff = 0 else: gas_diff = dsmr_reading.extra_device_delivered - previous.delivered GasConsumption.objects.create( read_at=read_at, delivered=dsmr_reading.extra_device_delivered, currently_delivered=gas_diff)
def get_context_data(self, **kwargs): context_data = super(Statistics, self).get_context_data(**kwargs) context_data['capabilities'] = dsmr_backend.services.get_capabilities() try: latest_reading = DsmrReading.objects.all().order_by('-pk')[0] except IndexError: pass else: context_data['latest_reading'] = latest_reading context_data['delivered_sum'] = latest_reading.electricity_delivered_1 + \ latest_reading.electricity_delivered_2 context_data['returned_sum'] = latest_reading.electricity_returned_1 + \ latest_reading.electricity_returned_2 context_data['datalogger_settings'] = DataloggerSettings.get_solo() context_data['meter_statistics'] = MeterStatistics.get_solo() return context_data
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2020, 3, 5)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2020, 3, 5, 21, 29, 45, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('172.987')) self.assertEqual(reading.electricity_returned_1, Decimal('23.457')) self.assertEqual(reading.electricity_delivered_2, Decimal('160.643')) self.assertEqual(reading.electricity_returned_2, Decimal('4.819')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0.638')) self.assertEqual(reading.electricity_currently_returned, Decimal('0')) self.assertEqual(reading.extra_device_timestamp, None) # Error handled. self.assertEqual(reading.extra_device_delivered, None) # Should be NONE too due to timestamp. self.assertEqual(reading.phase_voltage_l1, Decimal('230.3')) self.assertEqual(reading.phase_voltage_l2, Decimal('230.5')) self.assertEqual(reading.phase_voltage_l3, Decimal('229.3')) self.assertEqual(reading.phase_power_current_l1, 0) self.assertEqual(reading.phase_power_current_l2, 0) self.assertEqual(reading.phase_power_current_l3, 1) meter_statistics = MeterStatistics.get_solo() self.assertIsNone(meter_statistics.dsmr_version) self.assertEqual(meter_statistics.electricity_tariff, 2) self.assertEqual(meter_statistics.power_failure_count, None) self.assertEqual(meter_statistics.long_power_failure_count, None) self.assertEqual(meter_statistics.voltage_sag_count_l1, None) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, None) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def test_reading_values(self, now_mock): """ Test whether dsmr_datalogger reads the correct values. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2021, 1, 1)) self._fake_dsmr_reading() self.assertTrue(DsmrReading.objects.exists()) reading = DsmrReading.objects.get() self.assertEqual(reading.timestamp, datetime(2020, 8, 7, 6, 27, 11, tzinfo=pytz.UTC)) self.assertEqual(reading.electricity_delivered_1, Decimal('1924.771')) self.assertEqual(reading.electricity_returned_1, Decimal('1968.710')) self.assertEqual(reading.electricity_delivered_2, Decimal('2549.919')) self.assertEqual(reading.electricity_returned_2, Decimal('692.984')) self.assertEqual(reading.electricity_currently_delivered, Decimal('0')) self.assertEqual(reading.electricity_currently_returned, Decimal('0.611')) self.assertEqual(reading.extra_device_timestamp, datetime(2020, 8, 7, 6, 25, 2, tzinfo=pytz.UTC)) self.assertEqual(reading.extra_device_delivered, Decimal('1414.287')) self.assertEqual(reading.phase_voltage_l1, Decimal('235.6')) self.assertEqual(reading.phase_voltage_l2, None) self.assertEqual(reading.phase_voltage_l3, None) self.assertEqual(reading.phase_power_current_l1, 2) self.assertEqual(reading.phase_power_current_l2, None) self.assertEqual(reading.phase_power_current_l3, None) meter_statistics = MeterStatistics.get_solo() self.assertIsNone(meter_statistics.dsmr_version) self.assertEqual(meter_statistics.electricity_tariff, 1) self.assertEqual(meter_statistics.power_failure_count, None) self.assertEqual(meter_statistics.long_power_failure_count, None) self.assertEqual(meter_statistics.voltage_sag_count_l1, None) self.assertEqual(meter_statistics.voltage_sag_count_l2, None) self.assertEqual(meter_statistics.voltage_sag_count_l3, None) self.assertEqual(meter_statistics.voltage_swell_count_l1, None) self.assertEqual(meter_statistics.voltage_swell_count_l2, None) self.assertEqual(meter_statistics.voltage_swell_count_l3, None)
def publish_split_topic_meter_statistics(): """ Publishes meter statistics to a broker, formatted in a separate topic per field name, if set and enabled. """ split_topic_settings = meter_statistics.SplitTopicMeterStatisticsMQTTSettings.get_solo( ) if not split_topic_settings.enabled: return # User specified formatting. config_parser = configparser.ConfigParser() config_parser.read_string(split_topic_settings.formatting) topic_mapping = config_parser['mapping'] serialized_reading = json.loads( serializers.serialize('json', [MeterStatistics.get_solo()])) reading_fields = dict(serialized_reading[0]['fields'].items()) reading_fields['id'] = serialized_reading[0]['pk'] # Copy all fields described in the mapping. for k, v in reading_fields.items(): if k not in topic_mapping: continue queue.Message.objects.create(topic=topic_mapping[k], payload=v)
def publish_split_topic_meter_statistics(): """ Publishes meter statistics to a broker, formatted in a separate topic per field name, if set and enabled. """ split_topic_settings = meter_statistics.SplitTopicMeterStatisticsMQTTSettings.get_solo( ) if not split_topic_settings.enabled: return # User specified formatting. config_parser = configparser.ConfigParser() config_parser.read_string(split_topic_settings.formatting) topic_mapping = config_parser['mapping'] mqtt_messages = [] serialized_reading = json.loads( serializers.serialize('json', [MeterStatistics.get_solo()])) reading_fields = dict(serialized_reading[0]['fields'].items()) reading_fields['id'] = serialized_reading[0]['pk'] # Copy all fields described in the mapping. for k, v in reading_fields.items(): if k not in topic_mapping: continue mqtt_messages.append({ 'topic': topic_mapping[k], 'payload': v, }) broker_kwargs = get_broker_configuration() try: publish.multiple(msgs=mqtt_messages, **broker_kwargs) except ValueError as error: logger.error( 'MQTT publish_split_topic_meter_statistics() | {}'.format(error))
def test_patch(self, now_mock): now_mock.return_value = timezone.make_aware( timezone.datetime(2020, 1, 1)) instance = MeterStatistics.get_solo() EXPECTED = dict( dsmr_version=None, electricity_tariff=None, power_failure_count=None, long_power_failure_count=None, voltage_sag_count_l1=None, voltage_sag_count_l2=None, voltage_sag_count_l3=None, voltage_swell_count_l1=None, voltage_swell_count_l2=None, voltage_swell_count_l3=None, ) # Check default (empty) state in database. [ self.assertEqual(getattr(instance, k), expected_value, k) for k, expected_value in EXPECTED.items() ] # Partial update. self._request('meter-statistics', expected_code=200, method='patch', data=dict( timestamp='2020-01-15T12:34:56+01:00', dsmr_version='50', electricity_tariff=1, power_failure_count=123, long_power_failure_count=456, )) EXPECTED = dict( dsmr_version='50', electricity_tariff=1, power_failure_count=123, long_power_failure_count=456, voltage_sag_count_l1=None, voltage_sag_count_l2=None, voltage_sag_count_l3=None, voltage_swell_count_l1=None, voltage_swell_count_l2=None, voltage_swell_count_l3=None, ) # Check partial updated state in database. instance.refresh_from_db() [ self.assertEqual(getattr(instance, k), expected_value, k) for k, expected_value in EXPECTED.items() ] self.assertEqual(str(instance.timestamp), '2020-01-15 11:34:56+00:00') # Shifted to UTC self._request('meter-statistics', expected_code=200, method='patch', data=dict( dsmr_version='42', electricity_tariff=2, power_failure_count=77777, long_power_failure_count=8888, voltage_sag_count_l1=11, voltage_sag_count_l2=22, voltage_sag_count_l3=33, voltage_swell_count_l1=44, voltage_swell_count_l2=55, voltage_swell_count_l3=66, )) EXPECTED = dict( dsmr_version='42', electricity_tariff=2, power_failure_count=77777, long_power_failure_count=8888, voltage_sag_count_l1=11, voltage_sag_count_l2=22, voltage_sag_count_l3=33, voltage_swell_count_l1=44, voltage_swell_count_l2=55, voltage_swell_count_l3=66, ) # Check final updated state in database. instance.refresh_from_db() [ self.assertEqual(getattr(instance, k), expected_value, k) for k, expected_value in EXPECTED.items() ]
def setUp(self): self.assertEqual(DsmrReading.objects.all().count(), 6) self.assertTrue(DsmrReading.objects.unprocessed().exists()) ConsumptionSettings.get_solo() MeterStatistics.get_solo() MeterStatistics.objects.all().update(dsmr_version='50')