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))
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_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()
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'])
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))
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)))
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)
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 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"))
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': []})
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
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
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))
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()
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
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)
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 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
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 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()
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)
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)
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)
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'])
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
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'))
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 )
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)
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 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())
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 get(self, request): data = {} data['capabilities'] = dsmr_backend.services.get_capabilities() frontend_settings = FrontendSettings.get_solo() form = DashboardGraphForm(request.GET) if not form.is_valid(): return HttpResponseBadRequest(form.errors) # Optimize queries for large datasets by restricting the data to the last week in the first place. base_timestamp = timezone.now() - timezone.timedelta(days=7) electricity = ElectricityConsumption.objects.filter( read_at__gt=base_timestamp).order_by('-read_at') gas = GasConsumption.objects.filter( read_at__gt=base_timestamp).order_by('-read_at') temperature = TemperatureReading.objects.filter( read_at__gt=base_timestamp).order_by('-read_at') # Apply any offset requested by the user. electricity_offset = form.cleaned_data.get('electricity_offset') electricity = electricity[electricity_offset:electricity_offset + frontend_settings.dashboard_graph_width] gas_offset = form.cleaned_data.get('gas_offset') gas = gas[gas_offset:gas_offset + frontend_settings.dashboard_graph_width] temperature = temperature[:frontend_settings.dashboard_graph_width] # Reverse all sets gain. electricity = electricity[::-1] gas = gas[::-1] temperature = temperature[::-1] # By default we only display the time, scrolling should enable a more verbose x-axis. graph_x_format_electricity = 'DSMR_GRAPH_SHORT_TIME_FORMAT' graph_x_format_gas = 'DSMR_GRAPH_SHORT_TIME_FORMAT' if electricity_offset > 0: graph_x_format_electricity = 'DSMR_GRAPH_LONG_TIME_FORMAT' if gas_offset > 0: graph_x_format_gas = 'DSMR_GRAPH_LONG_TIME_FORMAT' data['electricity_x'] = [ formats.date_format(timezone.localtime(x.read_at), graph_x_format_electricity) for x in electricity ] data['electricity_y'] = [ float(x.currently_delivered * 1000) for x in electricity ] data['electricity_returned_y'] = [ float(x.currently_returned * 1000) for x in electricity ] data['gas_x'] = [ formats.date_format(timezone.localtime(x.read_at), graph_x_format_gas) for x in gas ] data['gas_y'] = [float(x.currently_delivered) for x in gas] # Some users have multiple phases installed. if DataloggerSettings.get_solo( ).track_phases and data['capabilities']['multi_phases']: data['phases_l1_y'] = self._parse_phases_data( electricity, 'phase_currently_delivered_l1') data['phases_l2_y'] = self._parse_phases_data( electricity, 'phase_currently_delivered_l2') data['phases_l3_y'] = self._parse_phases_data( electricity, 'phase_currently_delivered_l3') if WeatherSettings.get_solo().track: data['temperature_x'] = [ formats.date_format(timezone.localtime(x.read_at), 'DSMR_GRAPH_SHORT_TIME_FORMAT') for x in temperature ] data['temperature_y'] = [ float(x.degrees_celcius) for x in temperature ] return HttpResponse(json.dumps(data), content_type='application/json')