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)
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('.')
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()
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 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))
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])
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()
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
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
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])
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)
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
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))
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)
def initialize(self): self.sleep_time = BackendSettings.get_solo().process_sleep