def test_backend_restart_required(self, *mocks):
        """ Test restart flag check. """
        BackendSettings.get_solo().update(restart_required=True)
        self.assertTrue(BackendSettings.get_solo().restart_required)

        # When not implemented correctly this should loop forever, without run_once=True, timing out the tests...
        self._intercept_command_stdout('dsmr_backend')

        self.assertFalse(BackendSettings.get_solo().restart_required)
Esempio n. 2
0
    def response_change(self, request, obj):
        if 'send_test_email' not in request.POST.keys():
            return super(EmailSettingsAdmin,
                         self).response_change(request, obj)

        email_settings = EmailSettings.get_solo()

        with translation.override(
                language=BackendSettings.get_solo().language):
            subject = _('DSMR-reader email test')
            body = _('Test for your email settings.')

        try:
            dsmr_backend.services.email.send(email_settings.email_to,
                                             subject=subject,
                                             body=body)
        except Exception as error:
            self.message_user(request,
                              _('Failed to send test email: {}'.format(error)),
                              level=logging.ERROR)
            return HttpResponseRedirect('.')

        self.message_user(
            request,
            _('Email sent succesfully, please check your email inbox (and spam folder).'
              ))
        return HttpResponseRedirect('.')
Esempio n. 3
0
def notify():
    """ Sends notifications about daily energy usage """
    if not notify_pre_check():
        return

    # Just post the latest reading of the day before.
    today = timezone.localtime(timezone.now())
    midnight = timezone.make_aware(
        timezone.datetime(
            year=today.year,
            month=today.month,
            day=today.day,
            hour=0,
        ))
    target_day = midnight - timezone.timedelta(hours=12)

    try:
        day_statistics = DayStatistics.objects.get(day=target_day)
    except DayStatistics.DoesNotExist:
        return  # Try again in a next run

    # For backend logging in Supervisor.
    logger.debug(
        'Notification:  Creating new notification containing daily usage.')

    with translation.override(language=BackendSettings.get_solo().language):
        message = create_consumption_message(day_statistics)
        send_notification(message, str(_('Daily usage notification')))

    set_next_notification()
Esempio n. 4
0
    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))
Esempio n. 5
0
def run(scheduled_process):
    """ Creates a new statistics backup and sends it per email. """
    email_backup_settings = EmailBackupSettings.get_solo()

    if not email_backup_settings.interval:
        logger.debug(
            ' - Email backup interval not set, skipping backup for a day')
        return scheduled_process.delay(timezone.timedelta(days=1))

    temp_dir = tempfile.TemporaryDirectory()
    backup_file = dsmr_backup.services.backup.create_partial(
        folder=temp_dir.name, models_to_backup=(DayStatistics, HourStatistics))

    with translation.override(language=BackendSettings.get_solo().language):
        subject = _('DSMR-reader day/hour statistics backup')
        body = _(
            'This is an automated email, containing a backup of the day and hour statistics in the attachment.'
        )

    email_settings = EmailSettings.get_solo()
    dsmr_backend.services.email.send(to=email_settings.email_to,
                                     subject=subject,
                                     body=body,
                                     attachment=backup_file)

    scheduled_process.delay(
        timezone.timedelta(days=email_backup_settings.interval))
Esempio n. 6
0
    def initialize(self):
        self.sleep_time = BackendSettings.get_solo().process_sleep

        self.persistent_clients = dsmr_backend.services.persistent_clients.initialize(
        )
        logger.debug('Persistent clients initialized: %s',
                     [x.__class__ for x in self.persistent_clients])
Esempio n. 7
0
    def _check_restart_required(self):
        if not BackendSettings.get_solo().restart_required:
            return

        BackendSettings.objects.update(restart_required=False)
        logger.warning(
            'Detected backend restart required, stopping process...')
        raise StopInfiniteRun()
Esempio n. 8
0
def get_capabilities() -> CapabilityReport:  # noqa: C901
    """
    Returns the capabilities of the data tracked, such as whether the meter supports gas readings or
    if there have been any readings regarding electricity being returned.
    """
    # Caching time should be limited, but enough to make it matter, as this call is used A LOT.
    capability_report = cache.get(settings.DSMRREADER_CAPABILITIES_CACHE)

    if capability_report is not None:
        return capability_report

    # Override capabilities when requested.
    backend_settings = BackendSettings.get_solo()

    capability_report = CapabilityReport()

    # We rely on consumption because source readings might be deleted after a while.
    if ElectricityConsumption.objects.exists():
        capability_report.add(Capability.ELECTRICITY)

    # We can not rely on meter positions, as the manufacturer sometimes initializes meters
    # with testing data. So we just have to wait for the first power returned.
    if not backend_settings.disable_electricity_returned_capability and \
            ElectricityConsumption.objects.filter(currently_returned__gt=0).exists():
        capability_report.add(Capability.ELECTRICITY_RETURNED)

    if ElectricityConsumption.objects.filter(
            Q(phase_currently_delivered_l2__isnull=False, )
            | Q(phase_currently_delivered_l3__isnull=False, )
            | Q(phase_voltage_l2__isnull=False, )
            | Q(phase_voltage_l3__isnull=False, )).exists():
        capability_report.add(Capability.MULTI_PHASES)

    if ElectricityConsumption.objects.filter(
            phase_voltage_l1__isnull=False).exists():
        capability_report.add(Capability.VOLTAGE)

    if ElectricityConsumption.objects.filter(
            phase_power_current_l1__isnull=False).exists():
        capability_report.add(Capability.POWER_CURRENT)

    if not backend_settings.disable_gas_capability and GasConsumption.objects.exists(
    ):
        capability_report.add(Capability.GAS)

    if WeatherSettings.get_solo().track and TemperatureReading.objects.exists(
    ):
        capability_report.add(Capability.WEATHER)

    if EnergySupplierPrice.objects.exists():
        capability_report.add(Capability.COSTS)

    if len(capability_report) > 0:
        capability_report.add(Capability.ANY)

    cache.set(settings.DSMRREADER_CAPABILITIES_CACHE, capability_report)

    return capability_report
Esempio n. 9
0
def get_capabilities(capability=None):
    """
    Returns the capabilities of the data tracked, such as whether the meter supports gas readings or
    if there have been any readings regarding electricity being returned.

    Optionally returns a single capability when requested.
    """
    # Caching time should be limited, but enough to make it matter, as this call is used A LOT.
    capabilities = cache.get(settings.DSMRREADER_CAPABILITIES_CACHE)

    if capabilities is None:
        capabilities = {
            # We rely on consumption because source readings might be deleted after a while.
            'electricity': ElectricityConsumption.objects.exists(),
            'electricity_returned': ElectricityConsumption.objects.filter(
                # We can not rely on meter positions, as the manufacturer sometimes initializes meters
                # with testing data. So we just have to wait for the first power returned.
                currently_returned__gt=0
            ).exists(),
            'multi_phases': ElectricityConsumption.objects.filter(
                Q(
                    phase_currently_delivered_l2__isnull=False,
                ) | Q(
                    phase_currently_delivered_l3__isnull=False,
                ) | Q(
                    phase_voltage_l2__isnull=False,
                ) | Q(
                    phase_voltage_l3__isnull=False,
                )
            ).exists(),
            'voltage': ElectricityConsumption.objects.filter(
                phase_voltage_l1__isnull=False,
            ).exists(),
            'power_current': ElectricityConsumption.objects.filter(
                phase_power_current_l1__isnull=False,
            ).exists(),
            'gas': GasConsumption.objects.exists(),
            'weather': WeatherSettings.get_solo().track and TemperatureReading.objects.exists()
        }

        # Override capabilities when requested.
        backend_settings = BackendSettings.get_solo()

        if backend_settings.disable_gas_capability:
            capabilities['gas'] = False

        if backend_settings.disable_electricity_returned_capability:
            capabilities['electricity_returned'] = False

        capabilities['any'] = any(capabilities.values())
        cache.set(settings.DSMRREADER_CAPABILITIES_CACHE, capabilities)

    # Single selection.
    if capability is not None:
        return capabilities[capability]

    return capabilities
Esempio n. 10
0
    def test_disabled_capabilities(self):
        """ Whether disable capabilities affects the outcome. """
        BackendSettings.get_solo()
        ElectricityConsumption.objects.create(
            read_at=timezone.now(),
            delivered_1=1,
            returned_1=2,
            delivered_2=3,
            returned_2=4,
            currently_delivered=5,
            currently_returned=6,
        )
        GasConsumption.objects.create(
            read_at=timezone.now(),
            delivered=1,
            currently_delivered=1,
        )

        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertTrue(
            dsmr_backend.services.backend.get_capability(Capability.GAS))
        self.assertTrue(capabilities[Capability.GAS])
        self.assertTrue(
            dsmr_backend.services.backend.get_capability(
                Capability.ELECTRICITY_RETURNED))
        self.assertTrue(capabilities[Capability.ELECTRICITY_RETURNED])

        # Disable gas.
        BackendSettings.objects.all().update(disable_gas_capability=True)

        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(Capability.GAS))
        self.assertFalse(capabilities[Capability.GAS])

        # Disable return.
        BackendSettings.objects.all().update(
            disable_electricity_returned_capability=True)

        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(
                Capability.ELECTRICITY_RETURNED))
        self.assertFalse(capabilities[Capability.ELECTRICITY_RETURNED])
Esempio n. 11
0
    def handle(self, **options):
        with translation.override(
                language=BackendSettings.get_solo().language):
            try:
                day_statistics = DayStatistics.objects.all().order_by(
                    '-day')[0]
            except IndexError:
                message = 'Test. 1. 2. 3.'
            else:
                message = dsmr_notification.services.create_consumption_message(
                    day_statistics)

            dsmr_notification.services.send_notification(
                title='Test message from DSMR-reader', message=message)
Esempio n. 12
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. 13
0
def check_status():
    """ Checks the status of the application. """
    status_settings = StatusNotificationSetting.get_solo()
    notification_settings = NotificationSetting.get_solo()

    if notification_settings.notification_service is None or \
            not dsmr_backend.services.backend.is_timestamp_passed(timestamp=status_settings.next_check):
        return

    if not DsmrReading.objects.exists():
        return StatusNotificationSetting.objects.update(
            next_check=timezone.now() + timezone.timedelta(minutes=5))

    # Check for recent data.
    has_recent_reading = DsmrReading.objects.filter(
        timestamp__gt=timezone.now() -
        timezone.timedelta(minutes=settings.
                           DSMRREADER_STATUS_READING_OFFSET_MINUTES)).exists()

    if has_recent_reading:
        return StatusNotificationSetting.objects.update(
            next_check=timezone.now() + timezone.timedelta(minutes=5))

    # Alert!
    logger.debug(
        'Notification: Sending notification about datalogger lagging behind...'
    )

    with translation.override(language=BackendSettings.get_solo().language):
        send_notification(
            str(
                _('It has been over {} hour(s) since the last reading received. Please check your datalogger.'
                  .format(settings.
                          DSMRREADER_STATUS_NOTIFICATION_COOLDOWN_HOURS))),
            str(_('Datalogger check')))

    StatusNotificationSetting.objects.update(
        next_check=timezone.now() + timezone.timedelta(
            hours=settings.DSMRREADER_STATUS_NOTIFICATION_COOLDOWN_HOURS))
Esempio n. 14
0
    def test_backend_restart_required_signal(self):
        self.assertFalse(BackendSettings.get_solo().restart_required)

        backend_restart_required.send_robust(None)

        self.assertTrue(BackendSettings.get_solo().restart_required)
Esempio n. 15
0
 def initialize(self):
     self.sleep_time = BackendSettings.get_solo().process_sleep