text: """Support for Buienradar.nl weather service."""
  import logging
  
  from buienradar.constants import (
      ATTRIBUTION,
      CONDCODE,
      CONDITION,
      DETAILED,
      EXACT,
      EXACTNL,
      FORECAST,
      IMAGE,
      MEASURED,
      PRECIPITATION_FORECAST,
      STATIONNAME,
      TIMEFRAME,
      VISIBILITY,
      WINDGUST,
      WINDSPEED,
  )
  import voluptuous as vol
  
  from homeassistant.components.sensor import PLATFORM_SCHEMA
  from homeassistant.const import (
      ATTR_ATTRIBUTION,
      CONF_LATITUDE,
      CONF_LONGITUDE,
      CONF_MONITORED_CONDITIONS,
      CONF_NAME,
      TEMP_CELSIUS,
  )
  import homeassistant.helpers.config_validation as cv
  from homeassistant.helpers.entity import Entity
  from homeassistant.util import dt as dt_util
  
  from .const import DEFAULT_TIMEFRAME
  from .util import BrData
  
  _LOGGER = logging.getLogger(__name__)
  
  MEASURED_LABEL = "Measured"
  TIMEFRAME_LABEL = "Timeframe"
  SYMBOL = "symbol"
  
  # Schedule next call after (minutes):
  SCHEDULE_OK = 10
  # When an error occurred, new call after (minutes):
  SCHEDULE_NOK = 2
  
  # Supported sensor types:
  # Key: ['label', unit, icon]
  SENSOR_TYPES = {
      "stationname": ["Stationname", None, None],
      # new in json api (>1.0.0):
      "barometerfc": ["Barometer value", None, "mdi:gauge"],
      # new in json api (>1.0.0):
      "barometerfcname": ["Barometer", None, "mdi:gauge"],
      # new in json api (>1.0.0):
      "barometerfcnamenl": ["Barometer", None, "mdi:gauge"],
      "condition": ["Condition", None, None],
      "conditioncode": ["Condition code", None, None],
      "conditiondetailed": ["Detailed condition", None, None],
      "conditionexact": ["Full condition", None, None],
      "symbol": ["Symbol", None, None],
      # new in json api (>1.0.0):
      "feeltemperature": ["Feel temperature", TEMP_CELSIUS, "mdi:thermometer"],
      "humidity": ["Humidity", "%", "mdi:water-percent"],
      "temperature": ["Temperature", TEMP_CELSIUS, "mdi:thermometer"],
      "groundtemperature": ["Ground temperature", TEMP_CELSIUS, "mdi:thermometer"],
      "windspeed": ["Wind speed", "km/h", "mdi:weather-windy"],
      "windforce": ["Wind force", "Bft", "mdi:weather-windy"],
      "winddirection": ["Wind direction", None, "mdi:compass-outline"],
      "windazimuth": ["Wind direction azimuth", "°", "mdi:compass-outline"],
      "pressure": ["Pressure", "hPa", "mdi:gauge"],
      "visibility": ["Visibility", "km", None],
      "windgust": ["Wind gust", "km/h", "mdi:weather-windy"],
      "precipitation": ["Precipitation", "mm/h", "mdi:weather-pouring"],
      "irradiance": ["Irradiance", "W/m2", "mdi:sunglasses"],
      "precipitation_forecast_average": [
          "Precipitation forecast average",
          "mm/h",
          "mdi:weather-pouring",
      ],
      "precipitation_forecast_total": [
          "Precipitation forecast total",
          "mm",
          "mdi:weather-pouring",
      ],
      # new in json api (>1.0.0):
      "rainlast24hour": ["Rain last 24h", "mm", "mdi:weather-pouring"],
      # new in json api (>1.0.0):
      "rainlasthour": ["Rain last hour", "mm", "mdi:weather-pouring"],
      "temperature_1d": ["Temperature 1d", TEMP_CELSIUS, "mdi:thermometer"],
      "temperature_2d": ["Temperature 2d", TEMP_CELSIUS, "mdi:thermometer"],
      "temperature_3d": ["Temperature 3d", TEMP_CELSIUS, "mdi:thermometer"],
      "temperature_4d": ["Temperature 4d", TEMP_CELSIUS, "mdi:thermometer"],
      "temperature_5d": ["Temperature 5d", TEMP_CELSIUS, "mdi:thermometer"],
      "mintemp_1d": ["Minimum temperature 1d", TEMP_CELSIUS, "mdi:thermometer"],
      "mintemp_2d": ["Minimum temperature 2d", TEMP_CELSIUS, "mdi:thermometer"],
      "mintemp_3d": ["Minimum temperature 3d", TEMP_CELSIUS, "mdi:thermometer"],
      "mintemp_4d": ["Minimum temperature 4d", TEMP_CELSIUS, "mdi:thermometer"],
      "mintemp_5d": ["Minimum temperature 5d", TEMP_CELSIUS, "mdi:thermometer"],
      "rain_1d": ["Rain 1d", "mm", "mdi:weather-pouring"],
      "rain_2d": ["Rain 2d", "mm", "mdi:weather-pouring"],
      "rain_3d": ["Rain 3d", "mm", "mdi:weather-pouring"],
      "rain_4d": ["Rain 4d", "mm", "mdi:weather-pouring"],
      "rain_5d": ["Rain 5d", "mm", "mdi:weather-pouring"],
      # new in json api (>1.0.0):
      "minrain_1d": ["Minimum rain 1d", "mm", "mdi:weather-pouring"],
      "minrain_2d": ["Minimum rain 2d", "mm", "mdi:weather-pouring"],
      "minrain_3d": ["Minimum rain 3d", "mm", "mdi:weather-pouring"],
      "minrain_4d": ["Minimum rain 4d", "mm", "mdi:weather-pouring"],
      "minrain_5d": ["Minimum rain 5d", "mm", "mdi:weather-pouring"],
      # new in json api (>1.0.0):
      "maxrain_1d": ["Maximum rain 1d", "mm", "mdi:weather-pouring"],
      "maxrain_2d": ["Maximum rain 2d", "mm", "mdi:weather-pouring"],
      "maxrain_3d": ["Maximum rain 3d", "mm", "mdi:weather-pouring"],
      "maxrain_4d": ["Maximum rain 4d", "mm", "mdi:weather-pouring"],
      "maxrain_5d": ["Maximum rain 5d", "mm", "mdi:weather-pouring"],
      "rainchance_1d": ["Rainchance 1d", "%", "mdi:weather-pouring"],
      "rainchance_2d": ["Rainchance 2d", "%", "mdi:weather-pouring"],
      "rainchance_3d": ["Rainchance 3d", "%", "mdi:weather-pouring"],
      "rainchance_4d": ["Rainchance 4d", "%", "mdi:weather-pouring"],
      "rainchance_5d": ["Rainchance 5d", "%", "mdi:weather-pouring"],
      "sunchance_1d": ["Sunchance 1d", "%", "mdi:weather-partly-cloudy"],
      "sunchance_2d": ["Sunchance 2d", "%", "mdi:weather-partly-cloudy"],
      "sunchance_3d": ["Sunchance 3d", "%", "mdi:weather-partly-cloudy"],
      "sunchance_4d": ["Sunchance 4d", "%", "mdi:weather-partly-cloudy"],
      "sunchance_5d": ["Sunchance 5d", "%", "mdi:weather-partly-cloudy"],
      "windforce_1d": ["Wind force 1d", "Bft", "mdi:weather-windy"],
      "windforce_2d": ["Wind force 2d", "Bft", "mdi:weather-windy"],
      "windforce_3d": ["Wind force 3d", "Bft", "mdi:weather-windy"],
      "windforce_4d": ["Wind force 4d", "Bft", "mdi:weather-windy"],
      "windforce_5d": ["Wind force 5d", "Bft", "mdi:weather-windy"],
      "windspeed_1d": ["Wind speed 1d", "km/h", "mdi:weather-windy"],
      "windspeed_2d": ["Wind speed 2d", "km/h", "mdi:weather-windy"],
      "windspeed_3d": ["Wind speed 3d", "km/h", "mdi:weather-windy"],
      "windspeed_4d": ["Wind speed 4d", "km/h", "mdi:weather-windy"],
      "windspeed_5d": ["Wind speed 5d", "km/h", "mdi:weather-windy"],
      "winddirection_1d": ["Wind direction 1d", None, "mdi:compass-outline"],
      "winddirection_2d": ["Wind direction 2d", None, "mdi:compass-outline"],
      "winddirection_3d": ["Wind direction 3d", None, "mdi:compass-outline"],
      "winddirection_4d": ["Wind direction 4d", None, "mdi:compass-outline"],
      "winddirection_5d": ["Wind direction 5d", None, "mdi:compass-outline"],
      "windazimuth_1d": ["Wind direction azimuth 1d", "°", "mdi:compass-outline"],
      "windazimuth_2d": ["Wind direction azimuth 2d", "°", "mdi:compass-outline"],
      "windazimuth_3d": ["Wind direction azimuth 3d", "°", "mdi:compass-outline"],
      "windazimuth_4d": ["Wind direction azimuth 4d", "°", "mdi:compass-outline"],
      "windazimuth_5d": ["Wind direction azimuth 5d", "°", "mdi:compass-outline"],
      "condition_1d": ["Condition 1d", None, None],
      "condition_2d": ["Condition 2d", None, None],
      "condition_3d": ["Condition 3d", None, None],
      "condition_4d": ["Condition 4d", None, None],
      "condition_5d": ["Condition 5d", None, None],
      "conditioncode_1d": ["Condition code 1d", None, None],
      "conditioncode_2d": ["Condition code 2d", None, None],
      "conditioncode_3d": ["Condition code 3d", None, None],
      "conditioncode_4d": ["Condition code 4d", None, None],
      "conditioncode_5d": ["Condition code 5d", None, None],
      "conditiondetailed_1d": ["Detailed condition 1d", None, None],
      "conditiondetailed_2d": ["Detailed condition 2d", None, None],
      "conditiondetailed_3d": ["Detailed condition 3d", None, None],
      "conditiondetailed_4d": ["Detailed condition 4d", None, None],
      "conditiondetailed_5d": ["Detailed condition 5d", None, None],
      "conditionexact_1d": ["Full condition 1d", None, None],
      "conditionexact_2d": ["Full condition 2d", None, None],
      "conditionexact_3d": ["Full condition 3d", None, None],
      "conditionexact_4d": ["Full condition 4d", None, None],
      "conditionexact_5d": ["Full condition 5d", None, None],
      "symbol_1d": ["Symbol 1d", None, None],
      "symbol_2d": ["Symbol 2d", None, None],
      "symbol_3d": ["Symbol 3d", None, None],
      "symbol_4d": ["Symbol 4d", None, None],
      "symbol_5d": ["Symbol 5d", None, None],
  }
  
  CONF_TIMEFRAME = "timeframe"
  
  PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
      {
          vol.Optional(
              CONF_MONITORED_CONDITIONS, default=["symbol", "temperature"]
          ): vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES.keys())]),
          vol.Inclusive(
              CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together"
          ): cv.latitude,
          vol.Inclusive(
              CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together"
          ): cv.longitude,
          vol.Optional(CONF_TIMEFRAME, default=60): vol.All(
              vol.Coerce(int), vol.Range(min=5, max=120)
          ),
          vol.Optional(CONF_NAME, default="br"): cv.string,
      }
  )
  
  
  async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
      """Create the buienradar sensor."""
  
      latitude = config.get(CONF_LATITUDE, hass.config.latitude)
      longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
      timeframe = config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME)
  
      if None in (latitude, longitude):
          _LOGGER.error("Latitude or longitude not set in HomeAssistant config")
          return False
  
      coordinates = {CONF_LATITUDE: float(latitude), CONF_LONGITUDE: float(longitude)}
  
      _LOGGER.debug(
          "Initializing buienradar sensor coordinate %s, timeframe %s",
          coordinates,
          timeframe,
      )
  
      dev = []
      for sensor_type in config[CONF_MONITORED_CONDITIONS]:
          dev.append(BrSensor(sensor_type, config.get(CONF_NAME), coordinates))
      async_add_entities(dev)
  
      data = BrData(hass, coordinates, timeframe, dev)
      # schedule the first update in 1 minute from now:
      await data.schedule_update(1)
  
  
  class BrSensor(Entity):
      """Representation of an Buienradar sensor."""
  
      def __init__(self, sensor_type, client_name, coordinates):
          """Initialize the sensor."""
  
          self.client_name = client_name
          self._name = SENSOR_TYPES[sensor_type][0]
          self.type = sensor_type
          self._state = None
          self._unit_of_measurement = SENSOR_TYPES[self.type][1]
          self._entity_picture = None
          self._attribution = None
          self._measured = None
          self._stationname = None
          self._unique_id = self.uid(coordinates)
  
          # All continuous sensors should be forced to be updated
          self._force_update = self.type != SYMBOL and not self.type.startswith(CONDITION)
  
          if self.type.startswith(PRECIPITATION_FORECAST):
              self._timeframe = None
  
      def uid(self, coordinates):
          """Generate a unique id using coordinates and sensor type."""
          # The combination of the location, name and sensor type is unique
          return "%2.6f%2.6f%s" % (
              coordinates[CONF_LATITUDE],
              coordinates[CONF_LONGITUDE],
              self.type,
          )
  
      def load_data(self, data):
          """Load the sensor with relevant data."""
          # Find sensor
  
          # Check if we have a new measurement,
          # otherwise we do not have to update the sensor
          if self._measured == data.get(MEASURED):
              return False
  
          self._attribution = data.get(ATTRIBUTION)
          self._stationname = data.get(STATIONNAME)
          self._measured = data.get(MEASURED)
  
          if (
              self.type.endswith("_1d")
              or self.type.endswith("_2d")
              or self.type.endswith("_3d")
              or self.type.endswith("_4d")
              or self.type.endswith("_5d")
          ):
  
              # update forcasting sensors:
              fcday = 0
              if self.type.endswith("_2d"):
                  fcday = 1
              if self.type.endswith("_3d"):
                  fcday = 2
              if self.type.endswith("_4d"):
                  fcday = 3
              if self.type.endswith("_5d"):
                  fcday = 4
  
              # update weather symbol & status text
              if self.type.startswith(SYMBOL) or self.type.startswith(CONDITION):
                  try:
                      condition = data.get(FORECAST)[fcday].get(CONDITION)
                  except IndexError:
                      _LOGGER.warning("No forecast for fcday=%s...", fcday)
                      return False
  
                  if condition:
                      new_state = condition.get(CONDITION, None)
                      if self.type.startswith(SYMBOL):
                          new_state = condition.get(EXACTNL, None)
                      if self.type.startswith("conditioncode"):
                          new_state = condition.get(CONDCODE, None)
                      if self.type.startswith("conditiondetailed"):
                          new_state = condition.get(DETAILED, None)
                      if self.type.startswith("conditionexact"):
                          new_state = condition.get(EXACT, None)
  
                      img = condition.get(IMAGE, None)
  
                      if new_state != self._state or img != self._entity_picture:
                          self._state = new_state
                          self._entity_picture = img
                          return True
                  return False
  
              if self.type.startswith(WINDSPEED):
                  # hass wants windspeeds in km/h not m/s, so convert:
                  try:
                      self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
                      if self._state is not None:
                          self._state = round(self._state * 3.6, 1)
                      return True
                  except IndexError:
                      _LOGGER.warning("No forecast for fcday=%s...", fcday)
                      return False
  
              # update all other sensors
              try:
                  self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
                  return True
              except IndexError:
                  _LOGGER.warning("No forecast for fcday=%s...", fcday)
                  return False
  
          if self.type == SYMBOL or self.type.startswith(CONDITION):
              # update weather symbol & status text
              condition = data.get(CONDITION, None)
              if condition:
                  if self.type == SYMBOL:
                      new_state = condition.get(EXACTNL, None)
                  if self.type == CONDITION:
                      new_state = condition.get(CONDITION, None)
                  if self.type == "conditioncode":
                      new_state = condition.get(CONDCODE, None)
                  if self.type == "conditiondetailed":
                      new_state = condition.get(DETAILED, None)
                  if self.type == "conditionexact":
                      new_state = condition.get(EXACT, None)
  
                  img = condition.get(IMAGE, None)
  
                  if new_state != self._state or img != self._entity_picture:
                      self._state = new_state
                      self._entity_picture = img
                      return True
  
              return False
  
          if self.type.startswith(PRECIPITATION_FORECAST):
              # update nested precipitation forecast sensors
              nested = data.get(PRECIPITATION_FORECAST)
              self._timeframe = nested.get(TIMEFRAME)
              self._state = nested.get(self.type[len(PRECIPITATION_FORECAST) + 1 :])
              return True
  
          if self.type == WINDSPEED or self.type == WINDGUST:
              # hass wants windspeeds in km/h not m/s, so convert:
              self._state = data.get(self.type)
              if self._state is not None:
                  self._state = round(data.get(self.type) * 3.6, 1)
              return True
  
          if self.type == VISIBILITY:
              # hass wants visibility in km (not m), so convert:
              self._state = data.get(self.type)
              if self._state is not None:
                  self._state = round(self._state / 1000, 1)
              return True
  
          # update all other sensors
          self._state = data.get(self.type)
          return True
  
      @property
      def attribution(self):
          """Return the attribution."""
          return self._attribution
  
      @property
      def unique_id(self):
          """Return the unique id."""
          return self._unique_id
  
      @property
      def name(self):
          """Return the name of the sensor."""
          return f"{self.client_name} {self._name}"
  
      @property
      def state(self):
          """Return the state of the device."""
          return self._state
  
      @property
      def should_poll(self):
          """No polling needed."""
          return False
  
      @property
      def entity_picture(self):
          """Weather symbol if type is symbol."""
          return self._entity_picture
  
      @property
      def device_state_attributes(self):
          """Return the state attributes."""
  
          if self.type.startswith(PRECIPITATION_FORECAST):
              result = {ATTR_ATTRIBUTION: self._attribution}
              if self._timeframe is not None:
                  result[TIMEFRAME_LABEL] = "%d min" % (self._timeframe)
  
              return result
  
          result = {
              ATTR_ATTRIBUTION: self._attribution,
              SENSOR_TYPES["stationname"][0]: self._stationname,
          }
          if self._measured is not None:
              # convert datetime (Europe/Amsterdam) into local datetime
              local_dt = dt_util.as_local(self._measured)
              result[MEASURED_LABEL] = local_dt.strftime("%c")
  
          return result
  
      @property
      def unit_of_measurement(self):
          """Return the unit of measurement of this entity, if any."""
          return self._unit_of_measurement
  
      @property
      def icon(self):
          """Return possible sensor specific icon."""
          return SENSOR_TYPES[self.type][2]
  
      @property
      def force_update(self):
          """Return true for continuous sensors, false for discrete sensors."""
          return self._force_update
  