1 """Support for the SpaceAPI."""
3 from contextlib
import suppress
5 import voluptuous
as vol
14 ATTR_UNIT_OF_MEASUREMENT,
29 ATTR_ADDRESS =
"address"
30 ATTR_SPACEFED =
"spacefed"
32 ATTR_STREAM =
"stream"
35 ATTR_PROJECTS =
"projects"
36 ATTR_RADIO_SHOW =
"radio_show"
40 ATTR_CLOSED =
"closed"
41 ATTR_CONTACT =
"contact"
42 ATTR_ISSUE_REPORT_CHANNELS =
"issue_report_channels"
43 ATTR_LASTCHANGE =
"lastchange"
46 ATTR_SENSORS =
"sensors"
51 ATTR_SENSOR_LOCATION =
"location"
53 CONF_CONTACT =
"contact"
54 CONF_HUMIDITY =
"humidity"
55 CONF_ICON_CLOSED =
"icon_closed"
56 CONF_ICON_OPEN =
"icon_open"
59 CONF_ISSUE_REPORT_CHANNELS =
"issue_report_channels"
60 CONF_SPACEFED =
"spacefed"
61 CONF_SPACENET =
"spacenet"
62 CONF_SPACESAML =
"spacesaml"
63 CONF_SPACEPHONE =
"spacephone"
65 CONF_STREAM =
"stream"
68 CONF_USTREAM =
"ustream"
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"
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"
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"
99 CONF_JABBER =
"jabber"
100 CONF_ISSUE_MAIL =
"issue_mail"
102 CONF_TEMPERATURE =
"temperature"
104 DATA_SPACEAPI =
"data_spaceapi"
107 ISSUE_REPORT_CHANNELS = [CONF_EMAIL, CONF_ISSUE_MAIL, CONF_ML, CONF_TWITTER]
109 SENSOR_TYPES = [CONF_HUMIDITY, CONF_TEMPERATURE]
110 SPACEAPI_VERSION =
"0.13"
112 URL_API_SPACEAPI =
"/api/spaceapi"
114 LOCATION_SCHEMA = vol.Schema({vol.Optional(CONF_ADDRESS): cv.string})
116 SPACEFED_SCHEMA = vol.Schema(
118 vol.Optional(CONF_SPACENET): cv.boolean,
119 vol.Optional(CONF_SPACESAML): cv.boolean,
120 vol.Optional(CONF_SPACEPHONE): cv.boolean,
124 STREAM_SCHEMA = vol.Schema(
126 vol.Optional(CONF_M4): cv.url,
127 vol.Optional(CONF_MJPEG): cv.url,
128 vol.Optional(CONF_USTREAM): cv.url,
132 FEED_SCHEMA = vol.Schema(
133 {vol.Optional(CONF_FEED_TYPE): cv.string, vol.Required(CONF_FEED_URL): cv.url}
136 FEEDS_SCHEMA = vol.Schema(
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,
145 CACHE_SCHEMA = vol.Schema(
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)"
153 RADIO_SHOW_SCHEMA = vol.Schema(
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,
163 KEYMASTER_SCHEMA = vol.Schema(
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,
173 CONTACT_SCHEMA = vol.Schema(
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)
193 STATE_SCHEMA = vol.Schema(
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,
202 SENSOR_SCHEMA = vol.Schema(
203 {vol.In(SENSOR_TYPES): [cv.entity_id], cv.string: [cv.entity_id]}
206 CONFIG_SCHEMA = vol.Schema(
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)]
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)
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]
234 extra=vol.ALLOW_EXTRA,
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)
247 """View to provide details according to the SpaceAPI."""
249 url = URL_API_SPACEAPI
250 name =
"api:spaceapi"
254 """Get data from a sensor."""
255 if not (sensor_state := hass.states.get(sensor)):
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]
261 sensor_data[ATTR_LOCATION] = spaceapi[CONF_SPACE]
263 if ATTR_UNIT_OF_MEASUREMENT
in sensor_state.attributes:
264 sensor_data[ATTR_UNIT] = sensor_state.attributes[ATTR_UNIT_OF_MEASUREMENT]
269 """Get SpaceAPI data."""
270 hass = request.app[KEY_HASS]
271 spaceapi =
dict(hass.data[DATA_SPACEAPI])
272 is_sensors = spaceapi.get(
"sensors")
274 location = {ATTR_LAT: hass.config.latitude, ATTR_LON: hass.config.longitude}
277 location[ATTR_ADDRESS] = spaceapi[ATTR_LOCATION][CONF_ADDRESS]
283 state_entity = spaceapi[
"state"][ATTR_ENTITY_ID]
285 if (space_state := hass.states.get(state_entity))
is not None:
287 ATTR_OPEN: space_state.state !=
"off",
288 ATTR_LASTCHANGE: dt_util.as_timestamp(space_state.last_updated),
291 state = {ATTR_OPEN:
"null", ATTR_LASTCHANGE: 0}
293 with suppress(KeyError):
295 ATTR_OPEN: spaceapi[
"state"][CONF_ICON_OPEN],
296 ATTR_CLOSED: spaceapi[
"state"][CONF_ICON_CLOSED],
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],
307 ATTR_URL: spaceapi[CONF_URL],
310 with suppress(KeyError):
311 data[ATTR_CAM] = spaceapi[CONF_CAM]
313 with suppress(KeyError):
314 data[ATTR_SPACEFED] = spaceapi[CONF_SPACEFED]
316 with suppress(KeyError):
317 data[ATTR_STREAM] = spaceapi[CONF_STREAM]
319 with suppress(KeyError):
320 data[ATTR_FEEDS] = spaceapi[CONF_FEEDS]
322 with suppress(KeyError):
323 data[ATTR_CACHE] = spaceapi[CONF_CACHE]
325 with suppress(KeyError):
326 data[ATTR_PROJECTS] = spaceapi[CONF_PROJECTS]
328 with suppress(KeyError):
329 data[ATTR_RADIO_SHOW] = spaceapi[CONF_RADIO_SHOW]
331 if is_sensors
is not None:
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
340 return self.json(data)
def get_sensor_data(hass, spaceapi, sensor)
bool setup(HomeAssistant hass, ConfigType config)