Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for getting the state of a Thermoworks Smoke Thermometer.
2 
3 Requires Smoke Gateway Wifi with an internet connection.
4 """
5 
6 from __future__ import annotations
7 
8 import logging
9 
10 from requests import RequestException
11 from requests.exceptions import HTTPError
12 from stringcase import camelcase, snakecase
13 import thermoworks_smoke
14 import voluptuous as vol
15 
17  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
18  SensorDeviceClass,
19  SensorEntity,
20 )
21 from homeassistant.const import (
22  ATTR_BATTERY_LEVEL,
23  CONF_EMAIL,
24  CONF_EXCLUDE,
25  CONF_MONITORED_CONDITIONS,
26  CONF_PASSWORD,
27  UnitOfTemperature,
28 )
29 from homeassistant.core import HomeAssistant
31 from homeassistant.helpers.entity_platform import AddEntitiesCallback
32 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
33 
34 _LOGGER = logging.getLogger(__name__)
35 
36 PROBE_1 = "probe1"
37 PROBE_2 = "probe2"
38 PROBE_1_MIN = "probe1_min"
39 PROBE_1_MAX = "probe1_max"
40 PROBE_2_MIN = "probe2_min"
41 PROBE_2_MAX = "probe2_max"
42 BATTERY_LEVEL = "battery"
43 FIRMWARE = "firmware"
44 
45 SERIAL_REGEX = "^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"
46 
47 # map types to labels
48 SENSOR_TYPES = {
49  PROBE_1: "Probe 1",
50  PROBE_2: "Probe 2",
51  PROBE_1_MIN: "Probe 1 Min",
52  PROBE_1_MAX: "Probe 1 Max",
53  PROBE_2_MIN: "Probe 2 Min",
54  PROBE_2_MAX: "Probe 2 Max",
55 }
56 
57 # exclude these keys from thermoworks data
58 EXCLUDE_KEYS = [FIRMWARE]
59 
60 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
61  {
62  vol.Required(CONF_EMAIL): cv.string,
63  vol.Required(CONF_PASSWORD): cv.string,
64  vol.Optional(CONF_MONITORED_CONDITIONS, default=[PROBE_1, PROBE_2]): vol.All(
65  cv.ensure_list, [vol.In(SENSOR_TYPES)]
66  ),
67  vol.Optional(CONF_EXCLUDE, default=[]): vol.All(
68  cv.ensure_list, [cv.matches_regex(SERIAL_REGEX)]
69  ),
70  }
71 )
72 
73 
75  hass: HomeAssistant,
76  config: ConfigType,
77  add_entities: AddEntitiesCallback,
78  discovery_info: DiscoveryInfoType | None = None,
79 ) -> None:
80  """Set up the thermoworks sensor."""
81 
82  email = config[CONF_EMAIL]
83  password = config[CONF_PASSWORD]
84  monitored_variables = config[CONF_MONITORED_CONDITIONS]
85  excluded = config[CONF_EXCLUDE]
86 
87  try:
88  mgr = thermoworks_smoke.initialize_app(email, password, True, excluded)
89  except HTTPError as error:
90  msg = f"{error.strerror}"
91  if "EMAIL_NOT_FOUND" in msg or "INVALID_PASSWORD" in msg:
92  _LOGGER.error("Invalid email and password combination")
93  else:
94  _LOGGER.error(msg)
95  else:
97  (
98  ThermoworksSmokeSensor(variable, serial, mgr)
99  for serial in mgr.serials()
100  for variable in monitored_variables
101  ),
102  True,
103  )
104 
105 
107  """Implementation of a thermoworks smoke sensor."""
108 
109  def __init__(self, sensor_type, serial, mgr):
110  """Initialize the sensor."""
111  self.typetype = sensor_type
112  self.serialserial = serial
113  self.mgrmgr = mgr
114  self._attr_name_attr_name = f"{mgr.name(serial)} {SENSOR_TYPES[sensor_type]}"
115  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT
116  self._attr_unique_id_attr_unique_id = f"{serial}-{sensor_type}"
117  self._attr_device_class_attr_device_class = SensorDeviceClass.TEMPERATURE
118  self.update_unitupdate_unit()
119 
120  def update_unit(self):
121  """Set the units from the data."""
122  if PROBE_2 in self.typetype:
123  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = self.mgrmgr.units(self.serialserial, PROBE_2)
124  else:
125  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = self.mgrmgr.units(self.serialserial, PROBE_1)
126 
127  def update(self) -> None:
128  """Get the monitored data from firebase."""
129 
130  try:
131  values = self.mgrmgr.data(self.serialserial)
132 
133  # set state from data based on type of sensor
134  self._attr_native_value_attr_native_value = values.get(camelcase(self.typetype))
135 
136  # set units
137  self.update_unitupdate_unit()
138 
139  # set basic attributes for all sensors
140  self._attr_extra_state_attributes_attr_extra_state_attributes = {
141  "time": values["time"],
142  "localtime": values["localtime"],
143  }
144 
145  # set extended attributes for main probe sensors
146  if self.typetype in (PROBE_1, PROBE_2):
147  for key, val in values.items():
148  # add all attributes that don't contain any probe name
149  # or contain a matching probe name
150  if (self.typetype == PROBE_1 and key.find(PROBE_2) == -1) or (
151  self.typetype == PROBE_2 and key.find(PROBE_1) == -1
152  ):
153  if key == BATTERY_LEVEL:
154  key = ATTR_BATTERY_LEVEL
155  else:
156  # strip probe label and convert to snake_case
157  key = snakecase(key.replace(self.typetype, ""))
158  # add to attrs
159  if key and key not in EXCLUDE_KEYS:
160  self._attr_extra_state_attributes_attr_extra_state_attributes[key] = val
161  # store actual unit because attributes are not converted
162  self._attr_extra_state_attributes_attr_extra_state_attributes["unit_of_min_max"] = (
163  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement
164  )
165 
166  except (RequestException, ValueError, KeyError):
167  _LOGGER.warning("Could not update status for %s", self.namename)
str|UndefinedType|None name(self)
Definition: entity.py:738
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:79