Esempio n. 1
0
 def _apply_fake_settings(self):
     PVOutputAPISettings.get_solo().update(
         auth_token='XXXXX',
         system_identifier=12345,
     )
     PVOutputAddStatusSettings.get_solo().update(export=True,
                                                 upload_delay=1)
Esempio n. 2
0
    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,
            },
        )
Esempio n. 3
0
    def test_get_next_export(self, now_mock):
        PVOutputAddStatusSettings.get_solo()

        PVOutputAddStatusSettings.objects.update(
            upload_interval=PVOutputAddStatusSettings.INTERVAL_5_MINUTES)
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2018, 2, 1, hour=12, minute=13, second=15))
        result = dsmr_pvoutput.services.get_next_export()

        self.assertEqual(result.hour, 12)
        self.assertEqual(result.minute, 15)
        self.assertEqual(result.second, 0)

        PVOutputAddStatusSettings.objects.update(
            upload_interval=PVOutputAddStatusSettings.INTERVAL_15_MINUTES)
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2018, 2, 1, hour=12, minute=25, second=50))
        result = dsmr_pvoutput.services.get_next_export()
        self.assertEqual(result.hour, 12)
        self.assertEqual(result.minute, 30)
        self.assertEqual(result.second, 0)

        # Pass hour mark.
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2018, 2, 1, hour=12, minute=59, second=30))
        result = dsmr_pvoutput.services.get_next_export()
        self.assertEqual(result.hour, 13)
        self.assertEqual(result.minute, 0)
        self.assertEqual(result.second, 0)
Esempio n. 4
0
def status_info():
    """ Returns the status info of the application. """
    capabilities = get_capabilities()
    status = {
        'capabilities': capabilities,
        'electricity': get_electricity_status(capabilities),
        'gas': get_gas_status(capabilities),
        'readings': get_reading_status(),
        'statistics': get_statistics_status(),
        'tools': {
            'backup': {
                'enabled': False,
                'latest_backup': None,
            },
            'dropbox': {
                'enabled': False,
                'latest_sync': None,
            },
            'pvoutput': {
                'enabled': False,
                'latest_sync': None,
            },
            'mindergas': {
                'enabled': False,
                'latest_sync': None,
            },
            'mqtt': get_mqtt_status(),
        }
    }

    # (External) tools below.
    backup_settings = BackupSettings.get_solo()

    if backup_settings.daily_backup:
        status['tools']['backup']['enabled'] = True
        status['tools']['backup'][
            'latest_backup'] = backup_settings.latest_backup

    dropbox_settings = DropboxSettings.get_solo()

    if dropbox_settings.access_token:
        status['tools']['dropbox']['enabled'] = True
        status['tools']['dropbox'][
            'latest_sync'] = dropbox_settings.latest_sync

    pvoutput_settings = PVOutputAddStatusSettings.get_solo()

    if pvoutput_settings.export:
        status['tools']['pvoutput']['enabled'] = True
        status['tools']['pvoutput'][
            'latest_sync'] = pvoutput_settings.latest_sync

    mindergas_settings = MinderGasSettings.get_solo()

    if mindergas_settings.export:
        status['tools']['mindergas']['enabled'] = True
        status['tools']['mindergas'][
            'latest_sync'] = mindergas_settings.latest_sync

    return status
Esempio n. 5
0
    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)
Esempio n. 6
0
def schedule_next_export():
    """ Schedules the next export, according to user preference. """
    next_export = get_next_export()
    logger.debug(' - PVOutput | Delaying the next export until: %s', timezone.localtime(next_export))

    status_settings = PVOutputAddStatusSettings.get_solo()
    status_settings.next_export = next_export
    status_settings.save()
Esempio n. 7
0
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()
Esempio n. 8
0
def schedule_next_export():
    """ Schedules the next export, according to user preference. """
    next_export = get_next_export()
    print(
        ' - PVOutput | Delaying the next export until: {}'.format(next_export))

    status_settings = PVOutputAddStatusSettings.get_solo()
    status_settings.next_export = next_export
    status_settings.save()
Esempio n. 9
0
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)
Esempio n. 10
0
    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()
Esempio n. 11
0
def get_next_export():
    """ Rounds the timestamp to the nearest upload interval, preventing the uploads to shift forward. """
    status_settings = PVOutputAddStatusSettings.get_solo()

    next_export = timezone.now() + timezone.timedelta(minutes=status_settings.upload_interval)

    # Make sure it shifts back to the closest interval point possible.
    minute_marker = next_export.minute
    minute_marker = minute_marker - (minute_marker % status_settings.upload_interval)

    return next_export.replace(minute=minute_marker, second=0, microsecond=0)
Esempio n. 12
0
def schedule_next_export():
    """ Schedules the next export, according to user preference. """
    status_settings = PVOutputAddStatusSettings.get_solo()

    next_export = timezone.now() + timezone.timedelta(
        minutes=status_settings.upload_interval)
    print(
        ' - PVOutput | Delaying the next export until: {}'.format(next_export))

    status_settings.next_export = next_export
    status_settings.save()
Esempio n. 13
0
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
            ]))
Esempio n. 14
0
    def test_export_not_allowed(self, now_mock, should_export_mock):
        """ Test export() blocking behaviour. """
        now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15))
        should_export_mock.return_value = False
        self._apply_fake_settings()

        # Nothing should happen.
        dsmr_pvoutput.services.export()

        self.assertTrue(should_export_mock.called)

        self.assertIsNone(PVOutputAddStatusSettings.get_solo().next_export)
Esempio n. 15
0
    def test_export_fail(self, now_mock, should_export_mock, requests_post_mock):
        """ Test export() failing by denied API call. """
        now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 10, 1, hour=15))
        should_export_mock.return_value = True
        self._apply_fake_settings()

        requests_post_mock.return_value = mock.MagicMock(status_code=400, text='Error message')
        dsmr_pvoutput.services.export()
        settings = PVOutputAddStatusSettings.get_solo()

        self.assertEqual(settings.next_export, timezone.now() + timezone.timedelta(minutes=5))
        self.assertTrue(requests_post_mock.called)
Esempio n. 16
0
    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())
Esempio n. 17
0
def check_pvoutput_sync(**kwargs):
    from dsmr_pvoutput.models.settings import PVOutputAddStatusSettings
    pvoutput_settings = PVOutputAddStatusSettings.get_solo()

    if not pvoutput_settings.export:
        return

    offset = timezone.now() - timezone.timedelta(
        minutes=settings.
        DSMRREADER_STATUS_ALLOWED_SCHEDULED_PROCESS_LAGG_IN_MINUTES)

    if pvoutput_settings.next_export > offset:
        return

    return MonitoringStatusIssue(
        __name__, _('Waiting for the next PVOutput export to be executed'),
        pvoutput_settings.next_export)
Esempio n. 18
0
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()
Esempio n. 19
0
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)
Esempio n. 20
0
 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
Esempio n. 21
0
 def test_check_pvoutput_sync_disabled(self):
     PVOutputAddStatusSettings.get_solo().update(
         export=False,
         next_export=timezone.now() - timezone.timedelta(minutes=1))
     self.assertIsNone(check_pvoutput_sync())
Esempio n. 22
0
 def setUp(self):
     PVOutputAddStatusSettings.get_solo().update(export=True,
                                                 next_export=timezone.now())