Beispiel #1
0
    def test_weather_parsing_error(self, now_mock, urlopen_mock):
        """ Tests whether temperature readings are skipped when tracking is disabled. """
        now_mock.return_value = timezone.make_aware(timezone.datetime(2017, 1, 1))
        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.next_sync = timezone.now() - timezone.timedelta(minutes=15)
        weather_settings.save()
        weather_settings.refresh_from_db()

        self.assertGreater(timezone.now(), weather_settings.next_sync)

        # Fake URL opener and its http response object used for reading data.
        http_response_mock = mock.MagicMock()

        test_data_file = os.path.join(
            settings.BASE_DIR, '..', 'dsmr_weather', 'tests', 'data', 'invalid-api.buienradar.nl.xml'
        )

        with open(test_data_file, 'r') as data:
            # Http response is in bytes, so make sure to equalize it.
            http_response_mock.read.return_value = bytes(data.read(), encoding='utf-8')

        urlopen_mock.return_value = http_response_mock

        self.assertFalse(TemperatureReading.objects.all().exists())
        dsmr_weather.services.read_weather()
        self.assertFalse(TemperatureReading.objects.all().exists())

        # Make sure that the next_sync is pushed forward as well.
        weather_settings = WeatherSettings.get_solo()
        self.assertEqual(weather_settings.next_sync, timezone.now() + timezone.timedelta(minutes=5))
    def test_next_sync_setting_retroactive(self):
        """ Test whether the migration can also handle existing data. """
        now = timezone.now().replace(microsecond=0)

        TemperatureReading.objects.create(
            read_at=now + timezone.timedelta(hours=1),
            degrees_celcius=20,
        )
        TemperatureReading.objects.create(
            read_at=now,
            degrees_celcius=20,
        )

        self.assertIsNone(WeatherSettings.get_solo().next_sync)

        # Now we fake applying the migration (again for this test).
        MigrationRecorder.Migration.objects.filter(
            app='dsmr_weather',
            name='0004_next_sync_setting_retroactive').delete()
        MigrationExecutor(connection=connection).migrate([
            (self.app, '0004_next_sync_setting_retroactive')
        ])

        # When having existing data, next_sync should be based on latest reading.
        self.assertEqual(WeatherSettings.get_solo().next_sync,
                         now + timezone.timedelta(hours=2))
Beispiel #3
0
class TestSettings(TestCase):
    """ Tests for settings defaults. """
    def setUp(self):
        self.instance = WeatherSettings().get_solo()

    def test_admin(self):
        """ Model should be registered in Django Admin. """
        self.assertTrue(site.is_registered(WeatherSettings))

    def test_to_string(self):
        self.assertNotEqual(str(self.instance), '{} object'.format(self.instance.__class__.__name__))

    def test_track(self):
        self.assertFalse(self.instance.track)

    def test_buienradar_station(self):
        self.assertEqual(self.instance.buienradar_station, 6260)

    def test_handle_settings_update_hook(self):
        sp = ScheduledProcess.objects.get(module=settings.DSMRREADER_MODULE_WEATHER_UPDATE)
        self.assertFalse(sp.active)

        self.instance.track = True
        self.instance.save()

        sp.refresh_from_db()
        self.assertTrue(sp.active)
Beispiel #4
0
    def test_weather_capabilities(self):
        """ Capability check for gas consumption. """
        weather_settings = WeatherSettings.get_solo()
        self.assertFalse(weather_settings.track)
        self.assertFalse(TemperatureReading.objects.exists())

        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertFalse(capabilities['weather'])
        self.assertFalse(capabilities['any'])

        # Should not have any affect.
        weather_settings.track = True
        weather_settings.save(update_fields=['track'])
        self.assertTrue(WeatherSettings.get_solo().track)

        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertFalse(capabilities['weather'])

        TemperatureReading.objects.create(read_at=timezone.now(),
                                          degrees_celcius=0.0)
        capabilities = dsmr_backend.services.backend.get_capabilities()
        self.assertTrue(
            dsmr_backend.services.backend.get_capabilities('weather'))
        self.assertTrue(capabilities['weather'])
        self.assertTrue(capabilities['any'])
Beispiel #5
0
 def setUp(self):
     WeatherSettings.get_solo()
     self.schedule_process = ScheduledProcess.objects.get(
         module=settings.DSMRREADER_MODULE_WEATHER_UPDATE)
     self.schedule_process.update(active=True,
                                  planned=timezone.make_aware(
                                      timezone.datetime(2017, 1, 1)))
Beispiel #6
0
    def test_weather_parsing(self, urlopen_mock):
        """ Tests whether temperature readings are skipped when tracking is disabled. """
        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.next_sync = timezone.now() - timezone.timedelta(minutes=15)
        weather_settings.save()
        weather_settings.refresh_from_db()

        self.assertGreater(timezone.now(), weather_settings.next_sync)

        # Fake URL opener and its http response object used for reading data.
        http_response_mock = mock.MagicMock()

        test_data_file = os.path.join(
            settings.BASE_DIR, '..', 'dsmr_weather', 'tests', 'data', 'api.buienradar.nl.xml'
        )

        with open(test_data_file, 'r') as data:
            # Http response is in bytes, so make sure to equalize it.
            http_response_mock.read.return_value = bytes(data.read(), encoding='utf-8')

        urlopen_mock.return_value = http_response_mock

        self.assertFalse(TemperatureReading.objects.all().exists())
        dsmr_weather.services.read_weather()
        self.assertTrue(TemperatureReading.objects.all().exists())

        # Test data snapshot read 4.8 degrees @ De Bilt.
        reading = TemperatureReading.objects.get()
        self.assertEqual(weather_settings.buienradar_station, 6260)
        self.assertEqual(reading.degrees_celcius, Decimal('4.8'))

        # Make sure that the next_sync is pushed forward as well.
        weather_settings = WeatherSettings.get_solo()
Beispiel #7
0
    def test_next_sync_setting_retroactive(self):
        """ Test whether the migration can also handle existing data. """
        if connection.vendor == 'sqlite':  # pragma: no cover
            return self.skipTest(reason='SQLite cannot be used while foreign key constraint checks are enabled')

        now = timezone.now().replace(microsecond=0)

        TemperatureReading.objects.create(
            read_at=now + timezone.timedelta(hours=1),
            degrees_celcius=20,
        )
        TemperatureReading.objects.create(
            read_at=now,
            degrees_celcius=20,
        )

        self.assertIsNone(WeatherSettings.get_solo().next_sync)

        # Now we fake applying the migration (again for this test).
        MigrationRecorder.Migration.objects.filter(
            app='dsmr_weather', name='0004_next_sync_setting_retroactive'
        ).delete()
        MigrationExecutor(connection=connection).migrate([(self.app, '0004_next_sync_setting_retroactive')])

        # When having existing data, next_sync should be based on latest reading.
        self.assertEqual(WeatherSettings.get_solo().next_sync, now + timezone.timedelta(hours=2))
Beispiel #8
0
def get_temperature_from_api():
    # For backend logging in Supervisor.
    logger.debug('Buienradar: Reading temperature: %s',
                 settings.DSMRREADER_BUIENRADAR_API_URL)

    try:
        response = requests.get(settings.DSMRREADER_BUIENRADAR_API_URL)
    except Exception as error:
        logger.exception(error)
        raise AssertionError(
            _('Failed to connect to or read from Buienradar API'))

    if response.status_code != 200:
        logger.error('Buienradar: Failed reading temperature: HTTP %s',
                     response.status_code)
        raise AssertionError(_('Unexpected status code received'))

    # Find our selected station.
    station_id = WeatherSettings.get_solo().buienradar_station
    station_data = [
        x for x in response.json()['actual']['stationmeasurements']
        if x['stationid'] == station_id
    ]

    if not station_data:
        raise AssertionError(_('Selected station info not found'))

    temperature = station_data[0]['groundtemperature']
    logger.debug('Buienradar: Read temperature: %s', temperature)

    hour_mark = timezone.now().replace(minute=0, second=0, microsecond=0)
    return TemperatureReading.objects.create(
        read_at=hour_mark, degrees_celcius=Decimal(temperature))
def read_weather():
    """ Reads the current weather state, if enabled, and stores it. """
    # Only when explicitly enabled in settings.
    weather_settings = WeatherSettings.get_solo()

    if not weather_settings.track:
        return

    # Fetch XML from API.
    request = urllib.request.urlopen(BUIENRADAR_API_URL)
    response_bytes = request.read()
    request.close()
    response_string = response_bytes.decode("utf8")

    # Use simplified xPath engine to extract current temperature.
    root = ET.fromstring(response_string)
    xpath = BUIENRADAR_XPATH.format(
        weather_station_id=weather_settings.buienradar_station
    )
    temperature_element = root.find(xpath)
    temperature = temperature_element.text

    # Gas readings trigger these readings, so the 'read at' timestamp should be somewhat in sync.
    # Therefor we align temperature readings with them, having them grouped by hour that is..
    read_at = timezone.now().replace(minute=0, second=0, microsecond=0)

    TemperatureReading.objects.create(read_at=read_at, degrees_celcius=temperature)
Beispiel #10
0
    def test_empty_capabilities(self):
        """ Capability check for empty database. """
        capabilities = dsmr_backend.services.backend.get_capabilities()

        self.assertFalse(WeatherSettings.get_solo().track)

        self.assertFalse(capabilities[Capability.ELECTRICITY])
        self.assertFalse(capabilities[Capability.ELECTRICITY_RETURNED])
        self.assertFalse(capabilities[Capability.GAS])
        self.assertFalse(capabilities[Capability.MULTI_PHASES])
        self.assertFalse(capabilities[Capability.WEATHER])
        self.assertFalse(capabilities[Capability.POWER_CURRENT])
        self.assertFalse(capabilities[Capability.COSTS])
        self.assertFalse(capabilities[Capability.ANY])

        self.assertFalse(
            dsmr_backend.services.backend.get_capability(
                Capability.ELECTRICITY))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(
                Capability.ELECTRICITY_RETURNED))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(Capability.GAS))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(
                Capability.MULTI_PHASES))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(Capability.WEATHER))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(
                Capability.POWER_CURRENT))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(Capability.COSTS))
        self.assertFalse(
            dsmr_backend.services.backend.get_capability(Capability.ANY))
    def test_weather_parsing(self, urlopen_mock):
        """ Tests whether temperature readings are skipped when tracking is disabled. """
        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        # Fake URL opener and its http response object used for reading data.
        http_response_mock = mock.MagicMock()

        test_data_file = os.path.join(settings.BASE_DIR, "..", "dsmr_weather", "tests", "data", "api.buienradar.nl.xml")

        with open(test_data_file, "r") as data:
            # Http response is in bytes, so make sure to equalize it.
            http_response_mock.read.return_value = bytes(data.read(), encoding="utf-8")

        urlopen_mock.return_value = http_response_mock

        self.assertFalse(TemperatureReading.objects.all().exists())
        dsmr_weather.services.read_weather()
        self.assertTrue(TemperatureReading.objects.all().exists())

        # Test data snapshot read 4.8 degrees @ De Bilt.
        reading = TemperatureReading.objects.get()
        self.assertEqual(weather_settings.buienradar_station, 6260)
        self.assertEqual(reading.degrees_celcius, Decimal("4.8"))
Beispiel #12
0
    def test_dashboard_xhr_temperature(self, now_mock):
        now_mock.return_value = timezone.make_aware(timezone.datetime(2018, 7, 1))

        if self.support_data:
            weather_settings = WeatherSettings.get_solo()
            weather_settings.track = True
            weather_settings.save()

            TemperatureReading.objects.create(
                read_at=timezone.now(),
                degrees_celcius=20,
            )

            TemperatureReading.objects.create(
                read_at=timezone.now() - timezone.timedelta(hours=1),
                degrees_celcius=30,
            )

        response = self.client.get(
            reverse('{}:dashboard-xhr-temperature'.format(self.namespace))
        )
        json_content = json.loads(response.content.decode("utf8"))

        if self.support_data:
            self.assertEqual(json_content, {'degrees_celcius': [30.0, 20.0], 'read_at': ['Sat 23:00', 'Sun 0:00']})
        else:
            self.assertEqual(json_content, {'read_at': [], 'degrees_celcius': []})
Beispiel #13
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.
    """
    capabilities = {
        # We rely on consumption because DSMR readings might be flushed in the future.
        '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(
            phase_currently_delivered_l2__isnull=False,
            phase_currently_delivered_l3__isnull=False
        ).exists(),
        'gas': GasConsumption.objects.exists(),
        'weather': WeatherSettings.get_solo().track and TemperatureReading.objects.exists()
    }
    capabilities['any'] = any(capabilities.values())

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

    return capabilities
Beispiel #14
0
    def test_okay(self, now_mock, requests_mock):
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2017, 1, 1))

        response_mock = mock.MagicMock()
        response_mock.json.return_value = {
            'actual': {
                'stationmeasurements': [{
                    'stationid':
                    WeatherSettings.get_solo().buienradar_station,
                    'groundtemperature':
                    123.4,
                }]
            }
        }
        type(response_mock).status_code = mock.PropertyMock(return_value=200)
        requests_mock.return_value = response_mock

        self.assertFalse(TemperatureReading.objects.exists())
        dsmr_weather.services.run(self.schedule_process)

        self.assertTrue(TemperatureReading.objects.exists())
        self.assertEqual(TemperatureReading.objects.get().degrees_celcius,
                         Decimal('123.4'))

        self.schedule_process.refresh_from_db()
        self.assertEqual(self.schedule_process.planned,
                         timezone.now() + timezone.timedelta(hours=1))
Beispiel #15
0
    def get_context_data(self, **kwargs):
        frontend_settings = FrontendSettings.get_solo()
        weather_settings = WeatherSettings.get_solo()
        context_data = super(Dashboard, self).get_context_data(**kwargs)
        context_data['capabilities'] = dsmr_backend.services.get_capabilities()
        context_data['datalogger_settings'] = DataloggerSettings.get_solo()
        context_data['frontend_settings'] = frontend_settings
        context_data['track_temperature'] = weather_settings.track
        context_data['notifications'] = Notification.objects.unread()

        try:
            latest_electricity = ElectricityConsumption.objects.all().order_by(
                '-read_at')[0]
        except IndexError:
            # Don't even bother when no data available.
            return context_data

        context_data[
            'consumption'] = dsmr_consumption.services.day_consumption(
                day=timezone.localtime(latest_electricity.read_at).date())
        today = timezone.localtime(timezone.now()).date()
        context_data[
            'month_statistics'] = dsmr_stats.services.month_statistics(
                target_date=today)
        return context_data
Beispiel #16
0
    def test_next_sync(self, urlopen_mock):
        """ Test next_sync setting. """
        # We just want to see whether it's called.
        urlopen_mock.side_effect = AssertionError('MOCK')

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        self.assertTrue(weather_settings.track)
        self.assertIsNone(weather_settings.next_sync)
        self.assertFalse(urlopen_mock.called)

        with self.assertRaises(AssertionError):
            dsmr_weather.services.read_weather()

        # The default next_sync setting should allow initial sync.
        self.assertTrue(urlopen_mock.called)

        # Now disallow.
        weather_settings.next_sync = timezone.now() + timezone.timedelta(minutes=15)
        weather_settings.save()

        urlopen_mock.reset_mock()
        self.assertFalse(urlopen_mock.called)

        dsmr_weather.services.read_weather()

        # Should be skipped now.
        self.assertFalse(urlopen_mock.called)
Beispiel #17
0
def read_weather():
    """ Reads the current weather state, if enabled, and stores it. """
    # Only when explicitly enabled in settings.
    if not should_sync():
        return

    # For backend logging in Supervisor.
    print(' - Performing temperature reading at Buienradar.')

    weather_settings = WeatherSettings.get_solo()

    # Fetch XML from API.
    request = urllib.request.urlopen(BUIENRADAR_API_URL)
    response_bytes = request.read()
    request.close()
    response_string = response_bytes.decode("utf8")

    # Use simplified xPath engine to extract current temperature.
    root = ET.fromstring(response_string)
    xpath = BUIENRADAR_XPATH.format(
        weather_station_id=weather_settings.buienradar_station
    )
    temperature_element = root.find(xpath)
    temperature = temperature_element.text

    # Gas readings trigger these readings, so the 'read at' timestamp should be somewhat in sync.
    # Therefor we align temperature readings with them, having them grouped by hour that is..
    read_at = timezone.now().replace(minute=0, second=0, microsecond=0)
    TemperatureReading.objects.create(read_at=read_at, degrees_celcius=temperature)

    # Push next sync back for an hour.
    weather_settings.next_sync = read_at + timezone.timedelta(hours=1)
    weather_settings.save()
Beispiel #18
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.
    """
    capabilities = {
        # We rely on consumption because DSMR readings might be flushed in the future.
        '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(),
        'gas': GasConsumption.objects.exists(),
        'weather': WeatherSettings.get_solo().track and TemperatureReading.objects.exists()
    }
    capabilities['any'] = any(capabilities.values())

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

    return capabilities
Beispiel #19
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
Beispiel #20
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
Beispiel #21
0
def should_sync():
    """ Checks whether we should sync yet. """
    weather_settings = WeatherSettings.get_solo()

    if not weather_settings.track:
        return False

    if weather_settings.next_sync is not None and timezone.now() < weather_settings.next_sync:
        return False

    return True
 def get_context_data(self, **kwargs):
     context_data = super(Configuration, self).get_context_data(**kwargs)
     context_data['api_settings'] = APISettings.get_solo()
     context_data['consumption_settings'] = ConsumptionSettings.get_solo()
     context_data['datalogger_settings'] = DataloggerSettings.get_solo()
     context_data['frontend_settings'] = FrontendSettings.get_solo()
     context_data['weather_settings'] = WeatherSettings.get_solo()
     context_data['backup_settings'] = BackupSettings.get_solo()
     context_data['dropbox_settings'] = DropboxSettings.get_solo()
     context_data['mindergas_settings'] = MinderGasSettings.get_solo()
     return context_data
Beispiel #23
0
    def test_live_graphs(self, now_mock):
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2015, 11, 15))

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        response = self.client.get(
            reverse('{}:live-graphs'.format(self.namespace)))
        self.assertEqual(response.status_code, 200, response.content)
        self.assertIn('frontend_settings', response.context)
Beispiel #24
0
def read_weather():
    """ Reads the current weather state, if enabled, and stores it. """
    # Only when explicitly enabled in settings.
    if not should_sync():
        return

    # For backend logging in Supervisor.
    logger.debug(' - Performing temperature reading at Buienradar.')

    weather_settings = WeatherSettings.get_solo()

    try:
        # Fetch XML from API.
        request = urllib.request.urlopen(BUIENRADAR_API_URL)
    except Exception as e:
        logger.error(' [!] Failed reading temperature: %s', e)
        dsmr_frontend.services.display_dashboard_message(
            message=_('Failed to read Buienradar API'))

        # Try again in 5 minutes.
        weather_settings.next_sync = timezone.now() + timezone.timedelta(
            minutes=5)
        weather_settings.save()
        return

    response_bytes = request.read()
    request.close()
    response_string = response_bytes.decode("utf8")

    # Use simplified xPath engine to extract current temperature.
    root = ET.fromstring(response_string)
    xpath = BUIENRADAR_XPATH.format(
        weather_station_id=weather_settings.buienradar_station)
    temperature_element = root.find(xpath)
    temperature = temperature_element.text
    logger.debug(' - Read temperature: %s', temperature)

    # Gas readings trigger these readings, so the 'read at' timestamp should be somewhat in sync.
    # Therefor we align temperature readings with them, having them grouped by hour that is..
    read_at = timezone.now().replace(minute=0, second=0, microsecond=0)

    try:
        TemperatureReading.objects.create(read_at=read_at,
                                          degrees_celcius=Decimal(temperature))
    except Exception:
        # Try again in 5 minutes.
        weather_settings.next_sync = timezone.now() + timezone.timedelta(
            minutes=5)
    else:
        # Push next sync back for an hour.
        weather_settings.next_sync = read_at + timezone.timedelta(hours=1)

    weather_settings.save()
Beispiel #25
0
    def test_dashboard(self, now_mock):
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2015, 11, 15))

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        response = self.client.get(
            reverse('{}:dashboard'.format(self.namespace)))
        self.assertEqual(response.status_code, 200)
        self.assertIn('track_temperature', response.context)
Beispiel #26
0
    def test_next_sync(self, now_mock, urlopen_mock):
        """ Test next_sync setting. """
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2017, 1, 1))

        # We just want to see whether it's called.
        urlopen_mock.side_effect = AssertionError('MOCK')

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        self.assertTrue(weather_settings.track)
        self.assertIsNone(weather_settings.next_sync)
        self.assertFalse(urlopen_mock.called)
        self.assertFalse(TemperatureReading.objects.all().exists())

        # Any errors fetching the data should result in a retry later.
        dsmr_weather.services.read_weather()

        weather_settings = WeatherSettings.get_solo()
        self.assertFalse(TemperatureReading.objects.all().exists())
        self.assertEqual(weather_settings.next_sync,
                         timezone.now() + timezone.timedelta(minutes=5))

        # The default next_sync setting should allow initial sync.
        self.assertTrue(urlopen_mock.called)

        # Now disallow.
        weather_settings.next_sync = timezone.now() + timezone.timedelta(
            minutes=15)
        weather_settings.save()

        urlopen_mock.reset_mock()
        self.assertFalse(urlopen_mock.called)

        dsmr_weather.services.read_weather()

        # Should be skipped now.
        self.assertFalse(urlopen_mock.called)
Beispiel #27
0
    def get_context_data(self, **kwargs):
        context_data = super(Dashboard, self).get_context_data(**kwargs)
        context_data['capabilities'] = dsmr_backend.services.get_capabilities()
        context_data['datalogger_settings'] = DataloggerSettings.get_solo()
        context_data['frontend_settings'] = FrontendSettings.get_solo()
        context_data['track_temperature'] = WeatherSettings.get_solo().track
        context_data['notifications'] = Notification.objects.unread()

        today = timezone.localtime(timezone.now()).date()
        context_data[
            'month_statistics'] = dsmr_stats.services.month_statistics(
                target_date=today)
        return context_data
    def test_weather_capabilities(self):
        """ Capability check for gas consumption. """
        weather_settings = WeatherSettings.get_solo()
        self.assertFalse(weather_settings.track)
        self.assertFalse(TemperatureReading.objects.exists())

        capabilities = dsmr_backend.services.get_capabilities()
        self.assertFalse(capabilities['weather'])
        self.assertFalse(capabilities['any'])

        # Should not have any affect.
        weather_settings.track = True
        weather_settings.save(update_fields=['track'])
        self.assertTrue(WeatherSettings.get_solo().track)

        capabilities = dsmr_backend.services.get_capabilities()
        self.assertFalse(capabilities['weather'])

        TemperatureReading.objects.create(read_at=timezone.now(), degrees_celcius=0.0)
        capabilities = dsmr_backend.services.get_capabilities()
        self.assertTrue(dsmr_backend.services.get_capabilities('weather'))
        self.assertTrue(capabilities['weather'])
        self.assertTrue(capabilities['any'])
Beispiel #29
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.
    """
    capabilities = cache.get(
        'capabilities'
    )  # Caching time should be very low, but enough to make it matter.

    if capabilities is None:
        capabilities = {
            # We rely on consumption because DSMR readings might be flushed in the future.
            '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(
                phase_currently_delivered_l2__isnull=False,
                phase_currently_delivered_l3__isnull=False).exists(),
            'voltage':
            ElectricityConsumption.objects.filter(
                phase_voltage_l1__isnull=False, ).exists(),
            'gas':
            GasConsumption.objects.exists(),
            'weather':
            WeatherSettings.get_solo().track
            and TemperatureReading.objects.exists()
        }

        # Override capabilities when requested.
        if settings.DSMRREADER_DISABLED_CAPABILITIES:
            for k in capabilities.keys():
                if k in settings.DSMRREADER_DISABLED_CAPABILITIES:
                    capabilities[k] = False

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

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

    return capabilities
Beispiel #30
0
    def test_dashboard_xhr_graphs(self, now_mock):
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2015, 11, 15))

        if self.support_data:
            weather_settings = WeatherSettings.get_solo()
            weather_settings.track = True
            weather_settings.save()

            # Test with phases feature switched on as well.
            DataloggerSettings.get_solo()
            DataloggerSettings.objects.update(track_phases=True)

        # Send seperate offset as well.
        response = self.client.get(reverse('{}:dashboard-xhr-graphs'.format(
            self.namespace)),
                                   data={
                                       'electricity_offset': 24,
                                       'gas_offset': 10
                                   })
        self.assertEqual(response.status_code, 200, response.content)

        # Send invalid offset.
        response = self.client.get(reverse('{}:dashboard-xhr-graphs'.format(
            self.namespace)),
                                   data={
                                       'electricity_offset': 'abc',
                                       'gas_offset': 'xzy'
                                   })
        self.assertEqual(response.status_code, 400)

        response = self.client.get(
            reverse('{}:dashboard-xhr-graphs'.format(self.namespace)))
        self.assertEqual(response.status_code, 200, response.content)
        json_content = json.loads(response.content.decode("utf8"))

        if self.support_data:
            self.assertGreater(len(json_content['electricity_x']), 0)
            self.assertGreater(len(json_content['electricity_y']), 0)

            self.assertIn('phases_l1_y', json_content)
            self.assertIn('phases_l2_y', json_content)
            self.assertIn('phases_l3_y', json_content)

        if self.support_gas:
            self.assertGreater(len(json_content['gas_x']), 0)
            self.assertGreater(len(json_content['gas_y']), 0)
    def test_empty_capabilities(self):
        """ Capability check for empty database. """
        capabilities = dsmr_backend.services.get_capabilities()

        self.assertFalse(WeatherSettings.get_solo().track)

        self.assertFalse(capabilities['electricity'])
        self.assertFalse(capabilities['electricity_returned'])
        self.assertFalse(capabilities['gas'])
        self.assertFalse(capabilities['weather'])
        self.assertFalse(capabilities['any'])

        self.assertFalse(dsmr_backend.services.get_capabilities('electricity'))
        self.assertFalse(dsmr_backend.services.get_capabilities('electricity_returned'))
        self.assertFalse(dsmr_backend.services.get_capabilities('gas'))
        self.assertFalse(dsmr_backend.services.get_capabilities('weather'))
        self.assertFalse(dsmr_backend.services.get_capabilities('any'))
Beispiel #32
0
    def test_dashboard(self, now_mock):
        now_mock.return_value = timezone.make_aware(timezone.datetime(2015, 11, 15))

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        response = self.client.get(
            reverse('{}:dashboard'.format(self.namespace))
        )
        self.assertEqual(response.status_code, 200, response.content)
        self.assertIn('frontend_settings', response.context)
        self.assertIn('notification_count', response.context)
        self.assertEqual(
            response.context['frontend_settings'].dashboard_graph_width,
            FrontendSettings.get_solo().dashboard_graph_width
        )
Beispiel #33
0
    def test_empty_capabilities(self):
        """ Capability check for empty database. """
        capabilities = dsmr_backend.services.backend.get_capabilities()

        self.assertFalse(WeatherSettings.get_solo().track)

        self.assertFalse(capabilities['electricity'])
        self.assertFalse(capabilities['electricity_returned'])
        self.assertFalse(capabilities['gas'])
        self.assertFalse(capabilities['multi_phases'])
        self.assertFalse(capabilities['weather'])
        self.assertFalse(capabilities['any'])

        self.assertFalse(dsmr_backend.services.backend.get_capabilities('electricity'))
        self.assertFalse(dsmr_backend.services.backend.get_capabilities('electricity_returned'))
        self.assertFalse(dsmr_backend.services.backend.get_capabilities('gas'))
        self.assertFalse(dsmr_backend.services.backend.get_capabilities('multi_phases'))
        self.assertFalse(dsmr_backend.services.backend.get_capabilities('weather'))
        self.assertFalse(dsmr_backend.services.backend.get_capabilities('any'))
    def test_dashboard(self, now_mock):
        now_mock.return_value = timezone.make_aware(
            timezone.datetime(2015, 11, 15))

        weather_settings = WeatherSettings.get_solo()
        weather_settings.track = True
        weather_settings.save()

        response = self.client.get(
            reverse('{}:dashboard'.format(self.namespace)))
        self.assertEqual(response.status_code, 200, response.content)
        self.assertIn('capabilities', response.context)
        self.assertIn('today_date_format', response.context)
        self.assertIn('month_date_format', response.context)
        self.assertIn('year_date_format', response.context)
        self.assertIn('datalogger_settings', response.context)
        self.assertIn('frontend_settings', response.context)
        self.assertIn('notification_count', response.context)
        self.assertIn('period_totals', response.context)
Beispiel #35
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
    def get_context_data(self, **kwargs):
        frontend_settings = FrontendSettings.get_solo()
        context_data = super(Dashboard, self).get_context_data(**kwargs)
        context_data['capabilities'] = dsmr_backend.services.get_capabilities()
        context_data['frontend_settings'] = frontend_settings
        context_data['notifications'] = Notification.objects.unread()

        electricity = ElectricityConsumption.objects.all().order_by('read_at')
        gas = GasConsumption.objects.all().order_by('read_at')
        temperature = TemperatureReading.objects.all().order_by('read_at')

        # User might want to sort things backwards.
        if frontend_settings.reverse_dashboard_graphs:
            electricity = electricity.reverse()[:self.electricity_max]
            gas = gas.reverse()[:self.gas_max]
            temperature = temperature.reverse()[:self.temperature_max]
        else:
            # We can't slice using negative offsets, so we should fetch a (quick) count first)
            electricity_offset = max(0, electricity.count() - self.electricity_max)
            gas_offset = max(0, gas.count() - self.gas_max)
            temperature_offset = max(0, temperature.count() - self.temperature_max)

            electricity = electricity[electricity_offset:]
            gas = gas[gas_offset:]
            temperature = temperature[temperature_offset:]

        context_data['electricity_x'] = json.dumps(
            [formats.date_format(
                timezone.localtime(x.read_at), 'DSMR_GRAPH_SHORT_TIME_FORMAT'
            ) for x in electricity]
        )
        context_data['electricity_y'] = json.dumps(
            [float(x.currently_delivered * 1000) for x in electricity]
        )
        context_data['electricity_returned_y'] = json.dumps(
            [float(x.currently_returned * 1000) for x in electricity]
        )

        context_data['gas_x'] = json.dumps(
            [formats.date_format(
                timezone.localtime(x.read_at), 'DSMR_GRAPH_SHORT_TIME_FORMAT'
            ) for x in gas]
        )
        context_data['gas_y'] = json.dumps(
            [float(x.currently_delivered) for x in gas]
        )

        context_data['track_temperature'] = WeatherSettings.get_solo().track
        context_data['temperature_count'] = temperature.count()

        if context_data['track_temperature']:
            context_data['temperature_x'] = json.dumps(
                [formats.date_format(
                    timezone.localtime(x.read_at), 'DSMR_GRAPH_SHORT_TIME_FORMAT'
                ) for x in temperature]
            )
            context_data['temperature_y'] = json.dumps(
                [float(x.degrees_celcius) for x in temperature]
            )

            try:
                latest_temperature = TemperatureReading.objects.all().order_by('-read_at')[0]
            except IndexError:
                pass
            else:
                context_data['latest_temperature_read'] = latest_temperature.read_at
                context_data['latest_temperature'] = latest_temperature.degrees_celcius

        try:
            latest_electricity = ElectricityConsumption.objects.all().order_by('-read_at')[0]
        except IndexError:
            # Don't even bother when no data available.
            return context_data

        context_data['latest_electricity_read'] = latest_electricity.read_at
        context_data['latest_electricity'] = int(
            latest_electricity.currently_delivered * 1000
        )
        context_data['latest_electricity_returned'] = int(
            latest_electricity.currently_returned * 1000
        )

        try:
            latest_gas = GasConsumption.objects.all().order_by('-read_at')[0]
        except IndexError:
            pass
        else:
            context_data['latest_gas_read'] = latest_gas.read_at
            context_data['latest_gas'] = latest_gas.currently_delivered

        context_data['consumption'] = dsmr_consumption.services.day_consumption(
            day=timezone.localtime(latest_electricity.read_at).date()
        )
        today = timezone.localtime(timezone.now()).date()
        context_data['month_statistics'] = dsmr_stats.services.month_statistics(target_date=today)
        return context_data
 def test_weather_tracking(self):
     """ Tests whether temperature readings are skipped when tracking is disabled. """
     self.assertFalse(WeatherSettings.get_solo().track)
     self.assertFalse(TemperatureReading.objects.all().exists())
     dsmr_weather.services.read_weather()
     self.assertFalse(TemperatureReading.objects.all().exists())
Beispiel #38
0
 def test_weather_tracking(self):
     """ Tests whether temperature readings are skipped when tracking is disabled. """
     self.assertFalse(WeatherSettings.get_solo().track)
     self.assertFalse(TemperatureReading.objects.all().exists())
     dsmr_weather.services.read_weather()
     self.assertFalse(TemperatureReading.objects.all().exists())