def test_export_okay(self, send_robust_mock, now_mock, export_data_mock, should_export_mock, requests_post_mock): """ Test export() as designed. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2017, 10, 1, hour=15)) export_data_mock.return_value = { 'x': 'y' } # Unimportant for this test. should_export_mock.return_value = True requests_post_mock.return_value = mock.MagicMock(status_code=200, text='Fake accept') self._apply_fake_settings() self.assertFalse(requests_post_mock.called) self.assertFalse(send_robust_mock.called) dsmr_pvoutput.services.export() self.assertIsNotNone(PVOutputAddStatusSettings.get_solo().next_export) self.assertTrue(requests_post_mock.called) self.assertTrue(send_robust_mock.called) # Check API parameters. api_settings = PVOutputAPISettings.get_solo() requests_post_mock.assert_called_once_with( PVOutputAddStatusSettings.API_URL, headers={ 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data={'x': 'y'}, ) # With processing delay as well. requests_post_mock.reset_mock() send_robust_mock.reset_mock() PVOutputAddStatusSettings.objects.update(processing_delay=5, next_export=None) dsmr_pvoutput.services.export() status_settings = PVOutputAddStatusSettings.get_solo() self.assertIsNotNone(status_settings.next_export) self.assertEqual(status_settings.latest_sync, timezone.now()) self.assertTrue(requests_post_mock.called) self.assertTrue(send_robust_mock.called) api_settings = PVOutputAPISettings.get_solo() requests_post_mock.assert_called_once_with( PVOutputAddStatusSettings.API_URL, headers={ 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data={ 'x': 'y', 'delay': 5, }, )
def _apply_fake_settings(self): PVOutputAPISettings.get_solo().update( auth_token='XXXXX', system_identifier=12345, ) PVOutputAddStatusSettings.get_solo().update(export=True, upload_delay=1)
def test_export_okay(self, send_robust_mock, now_mock, export_data_mock, requests_post_mock): """ Test export() as designed. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2017, 10, 1, hour=15)) export_data_mock.return_value = { 'x': 'y' } # Unimportant for this test. requests_post_mock.return_value = mock.MagicMock(status_code=200, text='Fake accept') self._apply_fake_settings() self.assertFalse(requests_post_mock.called) self.assertFalse(send_robust_mock.called) dsmr_pvoutput.services.run(scheduled_process=self.schedule_process) self.assertTrue(requests_post_mock.called) self.assertTrue(send_robust_mock.called) # Check API parameters. api_settings = PVOutputAPISettings.get_solo() requests_post_mock.assert_called_once_with( PVOutputAddStatusSettings.API_URL, headers={ 'User-Agent': settings.DSMRREADER_USER_AGENT, 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data={'x': 'y'}, timeout=settings.DSMRREADER_CLIENT_TIMEOUT, ) # With processing delay as well. requests_post_mock.reset_mock() send_robust_mock.reset_mock() self.schedule_process.reschedule_asap() PVOutputAddStatusSettings.objects.update(processing_delay=5) dsmr_pvoutput.services.run(scheduled_process=self.schedule_process) self.assertTrue(requests_post_mock.called) self.assertTrue(send_robust_mock.called) api_settings = PVOutputAPISettings.get_solo() requests_post_mock.assert_called_once_with( PVOutputAddStatusSettings.API_URL, headers={ 'User-Agent': settings.DSMRREADER_USER_AGENT, 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data={ 'x': 'y', 'delay': 5, }, timeout=settings.DSMRREADER_CLIENT_TIMEOUT, )
def test_export_not_allowed(self, post_mock): """ Test export() blocking behaviour. """ self._apply_fake_settings() PVOutputAddStatusSettings.get_solo().update(export=False) dsmr_pvoutput.services.run(scheduled_process=self.schedule_process) self.assertFalse(post_mock.called) self._apply_fake_settings() PVOutputAPISettings.get_solo().update(auth_token='') dsmr_pvoutput.services.run(scheduled_process=self.schedule_process) self.assertFalse(post_mock.called) self._apply_fake_settings() PVOutputAPISettings.get_solo().update(system_identifier='') dsmr_pvoutput.services.run(scheduled_process=self.schedule_process) self.assertFalse(post_mock.called)
def export(): """ Exports data to PVOutput, calling Add Status. """ if not should_export(): return api_settings = PVOutputAPISettings.get_solo() status_settings = PVOutputAddStatusSettings.get_solo() # Find the first and last consumption of today, taking any delay into account. local_now = timezone.localtime(timezone.now()) start = local_now.replace(hour=0, minute=0, second=0) # Midnight end = local_now - timezone.timedelta(minutes=status_settings.upload_delay) ecs = ElectricityConsumption.objects.filter(read_at__gte=start, read_at__lte=end) if not ecs.exists(): print(' [!] PVOutput: No data found for {}'.format(local_now)) return schedule_next_export() first = ecs[0] last = ecs.order_by('-read_at')[0] diff = last - first # Custom operator total_consumption = diff['delivered_1'] + diff['delivered_2'] net_power = last.currently_delivered - last.currently_returned # Negative when returning more Watt than requested. consumption_timestamp = timezone.localtime(last.read_at) data = { 'd': consumption_timestamp.date().strftime('%Y%m%d'), 't': consumption_timestamp.time().strftime('%H:%M'), 'v3': int(total_consumption * 1000), # Energy Consumption (Wh) 'v4': int(net_power * 1000), # Power Consumption (W) 'n': 1, # Net Flag, always enabled for smart meters } # Optional, paid PVOutput feature. if status_settings.processing_delay: data.update({'delay': status_settings.processing_delay}) print(' - PVOutput | Uploading data @ {}'.format(data)) pvoutput_upload.send_robust(None, data=data) response = requests.post(PVOutputAddStatusSettings.API_URL, headers={ 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data=data) if response.status_code != 200: print(' [!] PVOutput upload failed (HTTP {}): {}'.format( response.status_code, response.text)) schedule_next_export()
def _apply_fake_settings(self): api_settings = PVOutputAPISettings.get_solo() api_settings.auth_token = 'XXXXX' api_settings.system_identifier = 12345 api_settings.save() status_settings = PVOutputAddStatusSettings.get_solo() status_settings.export = True status_settings.upload_delay = 1 status_settings.save()
def should_export(): """ Checks whether we should export data yet, for Add Status calls. """ api_settings = PVOutputAPISettings.get_solo() status_settings = PVOutputAddStatusSettings.get_solo() # Only when enabled and credentials set. if not status_settings.export or not api_settings.auth_token or not api_settings.system_identifier: return False return dsmr_backend.services.is_timestamp_passed(timestamp=status_settings.next_export)
def _on_pvoutput_setting_update(): api_settings = PVOutputAPISettings.get_solo() add_status_settings = PVOutputAddStatusSettings.get_solo() ScheduledProcess.objects.filter( module=settings.DSMRREADER_MODULE_PVOUTPUT_EXPORT).update( planned=timezone.now(), active=all([ api_settings.auth_token, api_settings.system_identifier, add_status_settings.export ]))
def test_should_export_default(self, now_mock): """ Test should_export() default behaviour. """ now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15)) api_settings = PVOutputAPISettings.get_solo() status_settings = PVOutputAddStatusSettings.get_solo() self.assertIsNone(api_settings.auth_token) self.assertFalse(api_settings.system_identifier) self.assertFalse(status_settings.export) self.assertIsNone(status_settings.next_export) self.assertFalse(dsmr_pvoutput.services.should_export())
def export(): """ Exports data to PVOutput, calling Add Status. """ if not should_export(): return api_settings = PVOutputAPISettings.get_solo() status_settings = PVOutputAddStatusSettings.get_solo() try: data = get_export_data(next_export=status_settings.next_export, upload_delay=status_settings.upload_delay) except LookupError: return if not data: logger.warning(' [!] PVOutput: No data found (yet)') return schedule_next_export() # Optional, paid PVOutput feature. if status_settings.processing_delay: data.update({'delay': status_settings.processing_delay}) logger.debug('PVOutput: Uploading data: %s', data) pvoutput_upload.send_robust(None, data=data) response = requests.post(PVOutputAddStatusSettings.API_URL, headers={ 'User-Agent': settings.DSMRREADER_USER_AGENT, 'X-Pvoutput-Apikey': api_settings.auth_token, 'X-Pvoutput-SystemId': api_settings.system_identifier, }, data=data) if response.status_code != 200: logger.error(' [!] PVOutput upload failed (HTTP %s): %s', response.status_code, response.text) else: status_settings.latest_sync = timezone.now() status_settings.save() schedule_next_export()
def should_export(): """ Checks whether we should export data yet, for Add Status calls. """ api_settings = PVOutputAPISettings.get_solo() status_settings = PVOutputAddStatusSettings.get_solo() # Only when enabled and credentials set. if not status_settings.export or not api_settings.auth_token or not api_settings.system_identifier: return False # Nonsense when having no data. capabilities = dsmr_backend.services.get_capabilities() if not capabilities['electricity_returned']: print( ' - [!] PVOutput | No electricity return recorded by application!') return False return dsmr_backend.services.is_timestamp_passed( timestamp=status_settings.next_export)
def get_context_data(self, **kwargs): context_data = super(Configuration, self).get_context_data(**kwargs) # 20+ queries, we should cache this at some point. context_data.update( dict( api_settings=APISettings.get_solo(), backend_settings=BackendSettings.get_solo(), backup_settings=BackupSettings.get_solo(), consumption_settings=ConsumptionSettings.get_solo(), datalogger_settings=DataloggerSettings.get_solo(), dropbox_settings=DropboxSettings.get_solo(), email_settings=EmailSettings.get_solo(), frontend_settings=FrontendSettings.get_solo(), mindergas_settings=MinderGasSettings.get_solo(), mqtt_broker_settings=MQTTBrokerSettings.get_solo(), mqtt_jsondaytotals_settings=JSONDayTotalsMQTTSettings.get_solo( ), mqtt_splittopicdaytotals_settings= SplitTopicDayTotalsMQTTSettings.get_solo(), mqtt_jsoncurrentperiodtotals_settings= JSONCurrentPeriodTotalsMQTTSettings.get_solo(), mqtt_splittopiccurrentperiodtotals_settings= SplitTopicCurrentPeriodTotalsMQTTSettings.get_solo(), mqtt_jsongasconsumption_settings=JSONGasConsumptionMQTTSettings .get_solo(), mqtt_splittopicgasconsumption_settings= SplitTopicGasConsumptionMQTTSettings.get_solo(), mqtt_splittopicmeterstatistics_settings= SplitTopicMeterStatisticsMQTTSettings.get_solo(), mqtt_jsontelegram_settings=JSONTelegramMQTTSettings.get_solo(), mqtt_rawtelegram_settings=RawTelegramMQTTSettings.get_solo(), mqtt_splittopictelegram_settings=SplitTopicTelegramMQTTSettings .get_solo(), notification_settings=NotificationSetting.get_solo(), pvoutput_api_settings=PVOutputAPISettings.get_solo(), pvoutput_addstatus_settings=PVOutputAddStatusSettings.get_solo( ), retention_settings=RetentionSettings.get_solo(), weather_settings=WeatherSettings.get_solo(), influxdb_settings=InfluxdbIntegrationSettings.get_solo(), )) return context_data