Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for the SpaceAPI."""
2 
3 from contextlib import suppress
4 
5 import voluptuous as vol
6 
7 from homeassistant.components.http import KEY_HASS, HomeAssistantView
8 from homeassistant.const import (
9  ATTR_ENTITY_ID,
10  ATTR_ICON,
11  ATTR_LOCATION,
12  ATTR_NAME,
13  ATTR_STATE,
14  ATTR_UNIT_OF_MEASUREMENT,
15  CONF_ADDRESS,
16  CONF_EMAIL,
17  CONF_ENTITY_ID,
18  CONF_LOCATION,
19  CONF_SENSORS,
20  CONF_STATE,
21  CONF_URL,
22 )
23 import homeassistant.core as ha
24 from homeassistant.core import HomeAssistant
26 from homeassistant.helpers.typing import ConfigType
27 import homeassistant.util.dt as dt_util
28 
29 ATTR_ADDRESS = "address"
30 ATTR_SPACEFED = "spacefed"
31 ATTR_CAM = "cam"
32 ATTR_STREAM = "stream"
33 ATTR_FEEDS = "feeds"
34 ATTR_CACHE = "cache"
35 ATTR_PROJECTS = "projects"
36 ATTR_RADIO_SHOW = "radio_show"
37 ATTR_LAT = "lat"
38 ATTR_LON = "lon"
39 ATTR_API = "api"
40 ATTR_CLOSED = "closed"
41 ATTR_CONTACT = "contact"
42 ATTR_ISSUE_REPORT_CHANNELS = "issue_report_channels"
43 ATTR_LASTCHANGE = "lastchange"
44 ATTR_LOGO = "logo"
45 ATTR_OPEN = "open"
46 ATTR_SENSORS = "sensors"
47 ATTR_SPACE = "space"
48 ATTR_UNIT = "unit"
49 ATTR_URL = "url"
50 ATTR_VALUE = "value"
51 ATTR_SENSOR_LOCATION = "location"
52 
53 CONF_CONTACT = "contact"
54 CONF_HUMIDITY = "humidity"
55 CONF_ICON_CLOSED = "icon_closed"
56 CONF_ICON_OPEN = "icon_open"
57 CONF_ICONS = "icons"
58 CONF_IRC = "irc"
59 CONF_ISSUE_REPORT_CHANNELS = "issue_report_channels"
60 CONF_SPACEFED = "spacefed"
61 CONF_SPACENET = "spacenet"
62 CONF_SPACESAML = "spacesaml"
63 CONF_SPACEPHONE = "spacephone"
64 CONF_CAM = "cam"
65 CONF_STREAM = "stream"
66 CONF_M4 = "m4"
67 CONF_MJPEG = "mjpeg"
68 CONF_USTREAM = "ustream"
69 CONF_FEEDS = "feeds"
70 CONF_FEED_BLOG = "blog"
71 CONF_FEED_WIKI = "wiki"
72 CONF_FEED_CALENDAR = "calendar"
73 CONF_FEED_FLICKER = "flicker"
74 CONF_FEED_TYPE = "type"
75 CONF_FEED_URL = "url"
76 CONF_CACHE = "cache"
77 CONF_CACHE_SCHEDULE = "schedule"
78 CONF_PROJECTS = "projects"
79 CONF_RADIO_SHOW = "radio_show"
80 CONF_RADIO_SHOW_NAME = "name"
81 CONF_RADIO_SHOW_URL = "url"
82 CONF_RADIO_SHOW_TYPE = "type"
83 CONF_RADIO_SHOW_START = "start"
84 CONF_RADIO_SHOW_END = "end"
85 CONF_LOGO = "logo"
86 CONF_PHONE = "phone"
87 CONF_SIP = "sip"
88 CONF_KEYMASTERS = "keymasters"
89 CONF_KEYMASTER_NAME = "name"
90 CONF_KEYMASTER_IRC_NICK = "irc_nick"
91 CONF_KEYMASTER_PHONE = "phone"
92 CONF_KEYMASTER_EMAIL = "email"
93 CONF_KEYMASTER_TWITTER = "twitter"
94 CONF_TWITTER = "twitter"
95 CONF_FACEBOOK = "facebook"
96 CONF_IDENTICA = "identica"
97 CONF_FOURSQUARE = "foursquare"
98 CONF_ML = "ml"
99 CONF_JABBER = "jabber"
100 CONF_ISSUE_MAIL = "issue_mail"
101 CONF_SPACE = "space"
102 CONF_TEMPERATURE = "temperature"
103 
104 DATA_SPACEAPI = "data_spaceapi"
105 DOMAIN = "spaceapi"
106 
107 ISSUE_REPORT_CHANNELS = [CONF_EMAIL, CONF_ISSUE_MAIL, CONF_ML, CONF_TWITTER]
108 
109 SENSOR_TYPES = [CONF_HUMIDITY, CONF_TEMPERATURE]
110 SPACEAPI_VERSION = "0.13"
111 
112 URL_API_SPACEAPI = "/api/spaceapi"
113 
114 LOCATION_SCHEMA = vol.Schema({vol.Optional(CONF_ADDRESS): cv.string})
115 
116 SPACEFED_SCHEMA = vol.Schema(
117  {
118  vol.Optional(CONF_SPACENET): cv.boolean,
119  vol.Optional(CONF_SPACESAML): cv.boolean,
120  vol.Optional(CONF_SPACEPHONE): cv.boolean,
121  }
122 )
123 
124 STREAM_SCHEMA = vol.Schema(
125  {
126  vol.Optional(CONF_M4): cv.url,
127  vol.Optional(CONF_MJPEG): cv.url,
128  vol.Optional(CONF_USTREAM): cv.url,
129  }
130 )
131 
132 FEED_SCHEMA = vol.Schema(
133  {vol.Optional(CONF_FEED_TYPE): cv.string, vol.Required(CONF_FEED_URL): cv.url}
134 )
135 
136 FEEDS_SCHEMA = vol.Schema(
137  {
138  vol.Optional(CONF_FEED_BLOG): FEED_SCHEMA,
139  vol.Optional(CONF_FEED_WIKI): FEED_SCHEMA,
140  vol.Optional(CONF_FEED_CALENDAR): FEED_SCHEMA,
141  vol.Optional(CONF_FEED_FLICKER): FEED_SCHEMA,
142  }
143 )
144 
145 CACHE_SCHEMA = vol.Schema(
146  {
147  vol.Required(CONF_CACHE_SCHEDULE): cv.matches_regex(
148  r"(m.02|m.05|m.10|m.15|m.30|h.01|h.02|h.04|h.08|h.12|d.01)"
149  )
150  }
151 )
152 
153 RADIO_SHOW_SCHEMA = vol.Schema(
154  {
155  vol.Required(CONF_RADIO_SHOW_NAME): cv.string,
156  vol.Required(CONF_RADIO_SHOW_URL): cv.url,
157  vol.Required(CONF_RADIO_SHOW_TYPE): cv.matches_regex(r"(mp3|ogg)"),
158  vol.Required(CONF_RADIO_SHOW_START): cv.string,
159  vol.Required(CONF_RADIO_SHOW_END): cv.string,
160  }
161 )
162 
163 KEYMASTER_SCHEMA = vol.Schema(
164  {
165  vol.Optional(CONF_KEYMASTER_NAME): cv.string,
166  vol.Optional(CONF_KEYMASTER_IRC_NICK): cv.string,
167  vol.Optional(CONF_KEYMASTER_PHONE): cv.string,
168  vol.Optional(CONF_KEYMASTER_EMAIL): cv.string,
169  vol.Optional(CONF_KEYMASTER_TWITTER): cv.string,
170  }
171 )
172 
173 CONTACT_SCHEMA = vol.Schema(
174  {
175  vol.Optional(CONF_EMAIL): cv.string,
176  vol.Optional(CONF_IRC): cv.string,
177  vol.Optional(CONF_ML): cv.string,
178  vol.Optional(CONF_PHONE): cv.string,
179  vol.Optional(CONF_TWITTER): cv.string,
180  vol.Optional(CONF_SIP): cv.string,
181  vol.Optional(CONF_FACEBOOK): cv.string,
182  vol.Optional(CONF_IDENTICA): cv.string,
183  vol.Optional(CONF_FOURSQUARE): cv.string,
184  vol.Optional(CONF_JABBER): cv.string,
185  vol.Optional(CONF_ISSUE_MAIL): cv.string,
186  vol.Optional(CONF_KEYMASTERS): vol.All(
187  cv.ensure_list, [KEYMASTER_SCHEMA], vol.Length(min=1)
188  ),
189  },
190  required=False,
191 )
192 
193 STATE_SCHEMA = vol.Schema(
194  {
195  vol.Required(CONF_ENTITY_ID): cv.entity_id,
196  vol.Inclusive(CONF_ICON_CLOSED, CONF_ICONS): cv.url,
197  vol.Inclusive(CONF_ICON_OPEN, CONF_ICONS): cv.url,
198  },
199  required=False,
200 )
201 
202 SENSOR_SCHEMA = vol.Schema(
203  {vol.In(SENSOR_TYPES): [cv.entity_id], cv.string: [cv.entity_id]}
204 )
205 
206 CONFIG_SCHEMA = vol.Schema(
207  {
208  DOMAIN: vol.Schema(
209  {
210  vol.Required(CONF_CONTACT): CONTACT_SCHEMA,
211  vol.Required(CONF_ISSUE_REPORT_CHANNELS): vol.All(
212  cv.ensure_list, [vol.In(ISSUE_REPORT_CHANNELS)]
213  ),
214  vol.Optional(CONF_LOCATION): LOCATION_SCHEMA,
215  vol.Required(CONF_LOGO): cv.url,
216  vol.Required(CONF_SPACE): cv.string,
217  vol.Required(CONF_STATE): STATE_SCHEMA,
218  vol.Required(CONF_URL): cv.string,
219  vol.Optional(CONF_SENSORS): SENSOR_SCHEMA,
220  vol.Optional(CONF_SPACEFED): SPACEFED_SCHEMA,
221  vol.Optional(CONF_CAM): vol.All(
222  cv.ensure_list, [cv.url], vol.Length(min=1)
223  ),
224  vol.Optional(CONF_STREAM): STREAM_SCHEMA,
225  vol.Optional(CONF_FEEDS): FEEDS_SCHEMA,
226  vol.Optional(CONF_CACHE): CACHE_SCHEMA,
227  vol.Optional(CONF_PROJECTS): vol.All(cv.ensure_list, [cv.url]),
228  vol.Optional(CONF_RADIO_SHOW): vol.All(
229  cv.ensure_list, [RADIO_SHOW_SCHEMA]
230  ),
231  }
232  )
233  },
234  extra=vol.ALLOW_EXTRA,
235 )
236 
237 
238 def setup(hass: HomeAssistant, config: ConfigType) -> bool:
239  """Register the SpaceAPI with the HTTP interface."""
240  hass.data[DATA_SPACEAPI] = config[DOMAIN]
241  hass.http.register_view(APISpaceApiView)
242 
243  return True
244 
245 
246 class APISpaceApiView(HomeAssistantView):
247  """View to provide details according to the SpaceAPI."""
248 
249  url = URL_API_SPACEAPI
250  name = "api:spaceapi"
251 
252  @staticmethod
253  def get_sensor_data(hass, spaceapi, sensor):
254  """Get data from a sensor."""
255  if not (sensor_state := hass.states.get(sensor)):
256  return None
257  sensor_data = {ATTR_NAME: sensor_state.name, ATTR_VALUE: sensor_state.state}
258  if ATTR_SENSOR_LOCATION in sensor_state.attributes:
259  sensor_data[ATTR_LOCATION] = sensor_state.attributes[ATTR_SENSOR_LOCATION]
260  else:
261  sensor_data[ATTR_LOCATION] = spaceapi[CONF_SPACE]
262  # Some sensors don't have a unit of measurement
263  if ATTR_UNIT_OF_MEASUREMENT in sensor_state.attributes:
264  sensor_data[ATTR_UNIT] = sensor_state.attributes[ATTR_UNIT_OF_MEASUREMENT]
265  return sensor_data
266 
267  @ha.callback
268  def get(self, request):
269  """Get SpaceAPI data."""
270  hass = request.app[KEY_HASS]
271  spaceapi = dict(hass.data[DATA_SPACEAPI])
272  is_sensors = spaceapi.get("sensors")
273 
274  location = {ATTR_LAT: hass.config.latitude, ATTR_LON: hass.config.longitude}
275 
276  try:
277  location[ATTR_ADDRESS] = spaceapi[ATTR_LOCATION][CONF_ADDRESS]
278  except KeyError:
279  pass
280  except TypeError:
281  pass
282 
283  state_entity = spaceapi["state"][ATTR_ENTITY_ID]
284 
285  if (space_state := hass.states.get(state_entity)) is not None:
286  state = {
287  ATTR_OPEN: space_state.state != "off",
288  ATTR_LASTCHANGE: dt_util.as_timestamp(space_state.last_updated),
289  }
290  else:
291  state = {ATTR_OPEN: "null", ATTR_LASTCHANGE: 0}
292 
293  with suppress(KeyError):
294  state[ATTR_ICON] = {
295  ATTR_OPEN: spaceapi["state"][CONF_ICON_OPEN],
296  ATTR_CLOSED: spaceapi["state"][CONF_ICON_CLOSED],
297  }
298 
299  data = {
300  ATTR_API: SPACEAPI_VERSION,
301  ATTR_CONTACT: spaceapi[CONF_CONTACT],
302  ATTR_ISSUE_REPORT_CHANNELS: spaceapi[CONF_ISSUE_REPORT_CHANNELS],
303  ATTR_LOCATION: location,
304  ATTR_LOGO: spaceapi[CONF_LOGO],
305  ATTR_SPACE: spaceapi[CONF_SPACE],
306  ATTR_STATE: state,
307  ATTR_URL: spaceapi[CONF_URL],
308  }
309 
310  with suppress(KeyError):
311  data[ATTR_CAM] = spaceapi[CONF_CAM]
312 
313  with suppress(KeyError):
314  data[ATTR_SPACEFED] = spaceapi[CONF_SPACEFED]
315 
316  with suppress(KeyError):
317  data[ATTR_STREAM] = spaceapi[CONF_STREAM]
318 
319  with suppress(KeyError):
320  data[ATTR_FEEDS] = spaceapi[CONF_FEEDS]
321 
322  with suppress(KeyError):
323  data[ATTR_CACHE] = spaceapi[CONF_CACHE]
324 
325  with suppress(KeyError):
326  data[ATTR_PROJECTS] = spaceapi[CONF_PROJECTS]
327 
328  with suppress(KeyError):
329  data[ATTR_RADIO_SHOW] = spaceapi[CONF_RADIO_SHOW]
330 
331  if is_sensors is not None:
332  sensors = {}
333  for sensor_type in is_sensors:
334  sensors[sensor_type] = []
335  for sensor in spaceapi["sensors"][sensor_type]:
336  sensor_data = self.get_sensor_dataget_sensor_data(hass, spaceapi, sensor)
337  sensors[sensor_type].append(sensor_data)
338  data[ATTR_SENSORS] = sensors
339 
340  return self.json(data)
def get_sensor_data(hass, spaceapi, sensor)
Definition: __init__.py:253
bool setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:238