text: import pytest
  import numpy as np
  from datetime import datetime, timedelta
  from dateutil import tz
  from freezegun import freeze_time
  from helheimr_heating.sensing import *
  from helheimr_heating.utils import time_utils
  from attributedict.collections import AttributeDict
  
  def test_temp_state():
      assert TemperatureState.from_dict(None) is None
      assert TemperatureState.from_dict([]) is None
      assert TemperatureState.from_dict({'name': 'foo'}) is None
      d = {
          'name': 'test-name',
          'alias': 'Test Alias',
          'abbreviation': 'tst',
          'temperature': 19.5,
          'humidity': 70,
          'air_pressure': 980,
          'battery_level': None,
          'is_reachable': True
      }
      assert TemperatureState.from_dict(d) is not None
  
  
  def test_log():
      # We freeze the datetime to 1999 so we don't interfere with
      # currently logged (real) sensor readings
      initial_datetime = datetime(year=1999, month=7, day=23,
                                  hour=10, minute=4, second=3).astimezone(tz.tzlocal())
      test_end_time = initial_datetime + timedelta(days=7)
      last_test_insert_datetime = None
      with freeze_time(initial_datetime) as frozen_datetime:
          def _check_log_num(expected, dt_from, dt_to):
              rsp = temperature_log.query(dt_from, dt_to)
              assert rsp.success
              assert len(rsp.log_entries) == expected
          # Clear old test entries
          assert temperature_log.clear_old_entries(time_utils.dt_as_local(test_end_time)).success
          _check_log_num(0, None, time_utils.dt_now_local())
          
          # Try invalid inserts
          assert not temperature_log.save_log(None).success
          _check_log_num(0, None, time_utils.dt_now_local())
          assert not temperature_log.save_log([]).success
          _check_log_num(0, None, time_utils.dt_now_local())
          assert not temperature_log.save_log({'bar': None}).success
          _check_log_num(0, None, time_utils.dt_now_local())
          assert not temperature_log.save_log({'foo':'bar'}).success
          _check_log_num(0, None, time_utils.dt_now_local())
  
          # Create dummy temperature readings
          state1 = TemperatureState('sensor1', 'Sensor 1', 'S1', 23.5, 60, 970, 100, True)
          state2 = TemperatureState('sensor2', 'Sensor 2', 'S2', 10, 90, 1230, 99, True)
          # Invalid insert
          assert not temperature_log.save_log({'sensor1': state1, 'sensor2': state2, 'sensor3': None}).success
          _check_log_num(0, None, time_utils.dt_now_local())
  
          # Helper to verify that query() returned the correct sensor readings
          def _check_sensors(log_entry, *args):
              assert 'timestamp' in log_entry
              assert len(log_entry) == len(args) + 1  # sensor readings + timestamp
              for state in args:
                  assert state.name in log_entry
                  assert state == rsp.log_entries[-1][state.name]
          
          # Now insert valid readings:
          assert temperature_log.save_log({'sensor1': state1, 'sensor2': state2}).success  # Sensor log should accept both dict and lists of sensor states
          expected_num = 1
          _check_log_num(expected_num, None, time_utils.dt_now_local())
          rsp = temperature_log.query(None, time_utils.dt_now_local())
          _check_sensors(rsp.log_entries[-1], state1, state2)
  
          for temp1 in [24.1, 25.4, 17.5, 32.9]:
              frozen_datetime.tick(delta=timedelta(minutes=15))
              state1.temperature = temp1
              assert temperature_log.save_log([state1, state2]).success
              last_test_insert_datetime = time_utils.dt_now_local()
              expected_num += 1
              _check_log_num(expected_num, None, time_utils.dt_now_local())
          
          # Insert additional states (and simulate sensor1 crash):
          ## Missing state1
          frozen_datetime.tick(delta=timedelta(minutes=15))
          state2.temperature = 9.8
          assert temperature_log.save_log([state2]).success
          last_test_insert_datetime = time_utils.dt_now_local()
          expected_num += 1
          _check_log_num(expected_num, None, time_utils.dt_now_local())
          rsp = temperature_log.query(None, time_utils.dt_now_local())
          _check_sensors(rsp.log_entries[-1], state2)
          ## Missing state2
          frozen_datetime.tick(delta=timedelta(minutes=15))
          state1.temperature = 8.1
          assert temperature_log.save_log([state1]).success
          last_test_insert_datetime = time_utils.dt_now_local()
          expected_num += 1
          _check_log_num(expected_num, None, time_utils.dt_now_local())
          rsp = temperature_log.query(None, time_utils.dt_now_local())
          _check_sensors(rsp.log_entries[-1], state1)
  
      # Check if other SELECT WHERE clauses are working:
      # Note that we cannot set a fixed expected value because there may be real
      # sensor readings in the same table (having a separate dev database for
      # testing would be overkill for now)
      rsp = temperature_log.query(None, None)
      assert rsp.success
      num_all = len(rsp.log_entries)
      # There should be no entries before the initial test date, thus the
      # following query should also return all entries
      _check_log_num(num_all, initial_datetime, None)
      # The next query should return exactly 1 entry less
      rsp = temperature_log.query(initial_datetime + timedelta(seconds=1), None)
      assert rsp.success
      assert len(rsp.log_entries) + 1 == num_all
      # Check selecting a single entry:
      _check_log_num(1, initial_datetime, initial_datetime + timedelta(seconds=1))
      # Invalid datetime provided:
      assert not temperature_log.clear_old_entries(None).success
  
      # Check that clear() (which uses clear_old_logs()) works properly.
      # We adjust the datetimes s.t. the last inserted test log will remain.
      before_last_test_insert = last_test_insert_datetime - timedelta(seconds=1)
      with freeze_time(before_last_test_insert) as frozen_datetime:
          # Clear should use the system's current datetime. Thus, with the frozen
          # datetime the table should appear to be empty:
          temperature_log.clear()
          _check_log_num(0, None, time_utils.dt_now_local())
      # But there should actually be 1 test entry left:
      _check_log_num(1, None, last_test_insert_datetime)
      # Now we can delete that too
      temperature_log.clear_old_entries(test_end_time)
      _check_log_num(0, None, test_end_time)
  
      # Test inserting a dictionary instead of a TemperatureState object
      with freeze_time(initial_datetime) as frozen_datetime:
          d = {
              'name': 'test-name',
              'alias': 'Test Alias',
              'abbreviation': 'tst',
              'temperature': 19.5,
              'humidity': 70,
              'air_pressure': 980,
              'battery_level': None,
              'is_reachable': True
          }
          assert temperature_log.save_log([d]).success
          _check_log_num(1, None, test_end_time)
  
          frozen_datetime.tick(delta=timedelta(minutes=15))
          d['temperature'] = 15
          assert temperature_log.save_log({'some key': d}).success
          _check_log_num(2, None, test_end_time)
          temperature_log.clear_old_entries(test_end_time)
          _check_log_num(0, None, test_end_time)
  
  
  def test_raspbee_lookups():
      assert sensor_controller.alias(None) is None
      assert sensor_controller.alias('fUbAr') == 'fUbAr'
      assert sensor_controller.alias('TWohnzimmer') == 'Wohnzimmer'
      assert sensor_controller.alias('TBad') == 'Bad'
  
      assert sensor_controller.abbreviation(None) is None
      assert sensor_controller.abbreviation('fUbAr') == 'fUbAr'
      assert sensor_controller.abbreviation('TWohnzimmer') == 'Wohn'
      assert sensor_controller.abbreviation('TSchlafzimmer') == 'SZ'
  
  
  def test_thermometer_query():
      # The sensor controller should act like a forward for the raspbee wrapper
      # when querying the temperature sensors. Thus, this test is (almost) the
      # same as 'test_raspbee_query()'
      rsp = sensor_controller.query_thermometer_states()
      assert rsp.success
      assert len(rsp.temperature_states) > 0
      as_dict = sensor_controller.thermometer_states_dict
      assert as_dict is not None
      assert len(as_dict) == len(rsp.temperature_states)
  
      for k, s in rsp.temperature_states.items():
          assert isinstance(s, AttributeDict)
          assert k == s.name
          assert k in as_dict
          assert s.temperature is not None
          assert s.temperature > 15 and s.temperature < 40
          assert s.humidity is not None
          assert s.humidity > 20 and s.humidity < 95
          assert s.air_pressure is not None
          assert s.air_pressure > 750 and s.air_pressure < 1070
          assert s.is_reachable
      
      rsp = sensor_controller.query_reference_temperature()
      assert rsp.success
      assert isinstance(rsp.temperature, float)
  
      rsp = sensor_controller.query_zigbee_status()
      assert rsp.success
      assert rsp.zigbee_state.api_version is not None
      assert rsp.zigbee_state.sw_version is not None
      assert rsp.zigbee_state.channel > 0
  
  
  def test_plotter():
      from helheimr_heating.sensing import plotter
      dt_from, dt_to, heating_series, sensor_series = plotter.get_demo_series()
      img = plotter._plot_temperature_series(sensor_series, heating_series, dt_from, dt_to,
                                             width_px=1024, height_px=768,
                                             xkcdify=True, font_size=14,
                                             language=None)
      assert isinstance(img, np.ndarray)
      assert img.shape[0] == 768
      assert img.shape[1] == 1024