Fix spaceapi (#26453)

* fixed latitude/longitude keys to be conform with spaceapi specification

* version is now a string as required by the spaceapi specification

* add spacefed

* fixed lat/lon in spaceapi tests

* extended tests

* add feeds

* extended tests

* add cache

* add more tests

* add projects

* more tests

* add radio_show

* more tests

* add additional contact attributes

* corrected valid issue_repoer_channel options

* validate min length of contact/keymasters

* fixed location as address is not required by spec

* Update homeassistant/components/spaceapi/__init__.py

Co-Authored-By: Fabian Affolter <mail@fabian-affolter.ch>

* Update homeassistant/components/spaceapi/__init__.py

Co-Authored-By: Fabian Affolter <mail@fabian-affolter.ch>

* fixed issue with name change for longitude/latitude
This commit is contained in:
bouni 2019-09-22 01:22:33 +02:00 committed by Fabian Affolter
parent 88dcecab39
commit dc52b858a4
2 changed files with 229 additions and 16 deletions

View file

@ -7,9 +7,7 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_ICON, ATTR_ICON,
ATTR_LATITUDE,
ATTR_LOCATION, ATTR_LOCATION,
ATTR_LONGITUDE,
ATTR_STATE, ATTR_STATE,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
CONF_ADDRESS, CONF_ADDRESS,
@ -26,6 +24,15 @@ import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = "address" ATTR_ADDRESS = "address"
ATTR_SPACEFED = "spacefed"
ATTR_CAM = "cam"
ATTR_STREAM = "stream"
ATTR_FEEDS = "feeds"
ATTR_CACHE = "cache"
ATTR_PROJECTS = "projects"
ATTR_RADIO_SHOW = "radio_show"
ATTR_LAT = "lat"
ATTR_LON = "lon"
ATTR_API = "api" ATTR_API = "api"
ATTR_CLOSE = "close" ATTR_CLOSE = "close"
ATTR_CONTACT = "contact" ATTR_CONTACT = "contact"
@ -49,32 +56,135 @@ CONF_ICONS = "icons"
CONF_IRC = "irc" CONF_IRC = "irc"
CONF_ISSUE_REPORT_CHANNELS = "issue_report_channels" CONF_ISSUE_REPORT_CHANNELS = "issue_report_channels"
CONF_LOCATION = "location" CONF_LOCATION = "location"
CONF_SPACEFED = "spacefed"
CONF_SPACENET = "spacenet"
CONF_SPACESAML = "spacesaml"
CONF_SPACEPHONE = "spacephone"
CONF_CAM = "cam"
CONF_STREAM = "stream"
CONF_M4 = "m4"
CONF_MJPEG = "mjpeg"
CONF_USTREAM = "ustream"
CONF_FEEDS = "feeds"
CONF_FEED_BLOG = "blog"
CONF_FEED_WIKI = "wiki"
CONF_FEED_CALENDAR = "calendar"
CONF_FEED_FLICKER = "flicker"
CONF_FEED_TYPE = "type"
CONF_FEED_URL = "url"
CONF_CACHE = "cache"
CONF_CACHE_SCHEDULE = "schedule"
CONF_PROJECTS = "projects"
CONF_RADIO_SHOW = "radio_show"
CONF_RADIO_SHOW_NAME = "name"
CONF_RADIO_SHOW_URL = "url"
CONF_RADIO_SHOW_TYPE = "type"
CONF_RADIO_SHOW_START = "start"
CONF_RADIO_SHOW_END = "end"
CONF_LOGO = "logo" CONF_LOGO = "logo"
CONF_MAILING_LIST = "mailing_list"
CONF_PHONE = "phone" CONF_PHONE = "phone"
CONF_SIP = "sip"
CONF_KEYMASTERS = "keymasters"
CONF_KEYMASTER_NAME = "name"
CONF_KEYMASTER_IRC_NICK = "irc_nick"
CONF_KEYMASTER_PHONE = "phone"
CONF_KEYMASTER_EMAIL = "email"
CONF_KEYMASTER_TWITTER = "twitter"
CONF_TWITTER = "twitter"
CONF_FACEBOOK = "facebook"
CONF_IDENTICA = "identica"
CONF_FOURSQUARE = "foursquare"
CONF_ML = "ml"
CONF_JABBER = "jabber"
CONF_ISSUE_MAIL = "issue_mail"
CONF_SPACE = "space" CONF_SPACE = "space"
CONF_TEMPERATURE = "temperature" CONF_TEMPERATURE = "temperature"
CONF_TWITTER = "twitter"
DATA_SPACEAPI = "data_spaceapi" DATA_SPACEAPI = "data_spaceapi"
DOMAIN = "spaceapi" DOMAIN = "spaceapi"
ISSUE_REPORT_CHANNELS = [CONF_EMAIL, CONF_IRC, CONF_MAILING_LIST, CONF_TWITTER] ISSUE_REPORT_CHANNELS = [CONF_EMAIL, CONF_ISSUE_MAIL, CONF_ML, CONF_TWITTER]
SENSOR_TYPES = [CONF_HUMIDITY, CONF_TEMPERATURE] SENSOR_TYPES = [CONF_HUMIDITY, CONF_TEMPERATURE]
SPACEAPI_VERSION = 0.13 SPACEAPI_VERSION = "0.13"
URL_API_SPACEAPI = "/api/spaceapi" URL_API_SPACEAPI = "/api/spaceapi"
LOCATION_SCHEMA = vol.Schema({vol.Optional(CONF_ADDRESS): cv.string}, required=True) LOCATION_SCHEMA = vol.Schema({vol.Optional(CONF_ADDRESS): cv.string})
SPACEFED_SCHEMA = vol.Schema(
{
vol.Optional(CONF_SPACENET): cv.boolean,
vol.Optional(CONF_SPACESAML): cv.boolean,
vol.Optional(CONF_SPACEPHONE): cv.boolean,
}
)
STREAM_SCHEMA = vol.Schema(
{
vol.Optional(CONF_M4): cv.url,
vol.Optional(CONF_MJPEG): cv.url,
vol.Optional(CONF_USTREAM): cv.url,
}
)
FEED_SCHEMA = vol.Schema(
{vol.Optional(CONF_FEED_TYPE): cv.string, vol.Required(CONF_FEED_URL): cv.url}
)
FEEDS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_FEED_BLOG): FEED_SCHEMA,
vol.Optional(CONF_FEED_WIKI): FEED_SCHEMA,
vol.Optional(CONF_FEED_CALENDAR): FEED_SCHEMA,
vol.Optional(CONF_FEED_FLICKER): FEED_SCHEMA,
}
)
CACHE_SCHEMA = vol.Schema(
{
vol.Required(CONF_CACHE_SCHEDULE): cv.matches_regex(
r"(m.02|m.05|m.10|m.15|m.30|h.01|h.02|h.04|h.08|h.12|d.01)"
)
}
)
RADIO_SHOW_SCHEMA = vol.Schema(
{
vol.Required(CONF_RADIO_SHOW_NAME): cv.string,
vol.Required(CONF_RADIO_SHOW_URL): cv.url,
vol.Required(CONF_RADIO_SHOW_TYPE): cv.matches_regex(r"(mp3|ogg)"),
vol.Required(CONF_RADIO_SHOW_START): cv.string,
vol.Required(CONF_RADIO_SHOW_END): cv.string,
}
)
KEYMASTER_SCHEMA = vol.Schema(
{
vol.Optional(CONF_KEYMASTER_NAME): cv.string,
vol.Optional(CONF_KEYMASTER_IRC_NICK): cv.string,
vol.Optional(CONF_KEYMASTER_PHONE): cv.string,
vol.Optional(CONF_KEYMASTER_EMAIL): cv.string,
vol.Optional(CONF_KEYMASTER_TWITTER): cv.string,
}
)
CONTACT_SCHEMA = vol.Schema( CONTACT_SCHEMA = vol.Schema(
{ {
vol.Optional(CONF_EMAIL): cv.string, vol.Optional(CONF_EMAIL): cv.string,
vol.Optional(CONF_IRC): cv.string, vol.Optional(CONF_IRC): cv.string,
vol.Optional(CONF_MAILING_LIST): cv.string, vol.Optional(CONF_ML): cv.string,
vol.Optional(CONF_PHONE): cv.string, vol.Optional(CONF_PHONE): cv.string,
vol.Optional(CONF_TWITTER): cv.string, vol.Optional(CONF_TWITTER): cv.string,
vol.Optional(CONF_SIP): cv.string,
vol.Optional(CONF_FACEBOOK): cv.string,
vol.Optional(CONF_IDENTICA): cv.string,
vol.Optional(CONF_FOURSQUARE): cv.string,
vol.Optional(CONF_JABBER): cv.string,
vol.Optional(CONF_ISSUE_MAIL): cv.string,
vol.Optional(CONF_KEYMASTERS): vol.All(
cv.ensure_list, [KEYMASTER_SCHEMA], vol.Length(min=1)
),
}, },
required=False, required=False,
) )
@ -100,12 +210,23 @@ CONFIG_SCHEMA = vol.Schema(
vol.Required(CONF_ISSUE_REPORT_CHANNELS): vol.All( vol.Required(CONF_ISSUE_REPORT_CHANNELS): vol.All(
cv.ensure_list, [vol.In(ISSUE_REPORT_CHANNELS)] cv.ensure_list, [vol.In(ISSUE_REPORT_CHANNELS)]
), ),
vol.Required(CONF_LOCATION): LOCATION_SCHEMA, vol.Optional(CONF_LOCATION): LOCATION_SCHEMA,
vol.Required(CONF_LOGO): cv.url, vol.Required(CONF_LOGO): cv.url,
vol.Required(CONF_SPACE): cv.string, vol.Required(CONF_SPACE): cv.string,
vol.Required(CONF_STATE): STATE_SCHEMA, vol.Required(CONF_STATE): STATE_SCHEMA,
vol.Required(CONF_URL): cv.string, vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_SENSORS): SENSOR_SCHEMA, vol.Optional(CONF_SENSORS): SENSOR_SCHEMA,
vol.Optional(CONF_SPACEFED): SPACEFED_SCHEMA,
vol.Optional(CONF_CAM): vol.All(
cv.ensure_list, [cv.url], vol.Length(min=1)
),
vol.Optional(CONF_STREAM): STREAM_SCHEMA,
vol.Optional(CONF_FEEDS): FEEDS_SCHEMA,
vol.Optional(CONF_CACHE): CACHE_SCHEMA,
vol.Optional(CONF_PROJECTS): vol.All(cv.ensure_list, [cv.url]),
vol.Optional(CONF_RADIO_SHOW): vol.All(
cv.ensure_list, [RADIO_SHOW_SCHEMA]
),
} }
) )
}, },
@ -150,11 +271,14 @@ class APISpaceApiView(HomeAssistantView):
spaceapi = dict(hass.data[DATA_SPACEAPI]) spaceapi = dict(hass.data[DATA_SPACEAPI])
is_sensors = spaceapi.get("sensors") is_sensors = spaceapi.get("sensors")
location = { location = {ATTR_LAT: hass.config.latitude, ATTR_LON: hass.config.longitude}
ATTR_ADDRESS: spaceapi[ATTR_LOCATION][CONF_ADDRESS],
ATTR_LATITUDE: hass.config.latitude, try:
ATTR_LONGITUDE: hass.config.longitude, location[ATTR_ADDRESS] = spaceapi[ATTR_LOCATION][CONF_ADDRESS]
} except KeyError:
pass
except TypeError:
pass
state_entity = spaceapi["state"][ATTR_ENTITY_ID] state_entity = spaceapi["state"][ATTR_ENTITY_ID]
space_state = hass.states.get(state_entity) space_state = hass.states.get(state_entity)
@ -186,6 +310,41 @@ class APISpaceApiView(HomeAssistantView):
ATTR_URL: spaceapi[CONF_URL], ATTR_URL: spaceapi[CONF_URL],
} }
try:
data[ATTR_CAM] = spaceapi[CONF_CAM]
except KeyError:
pass
try:
data[ATTR_SPACEFED] = spaceapi[CONF_SPACEFED]
except KeyError:
pass
try:
data[ATTR_STREAM] = spaceapi[CONF_STREAM]
except KeyError:
pass
try:
data[ATTR_FEEDS] = spaceapi[CONF_FEEDS]
except KeyError:
pass
try:
data[ATTR_CACHE] = spaceapi[CONF_CACHE]
except KeyError:
pass
try:
data[ATTR_PROJECTS] = spaceapi[CONF_PROJECTS]
except KeyError:
pass
try:
data[ATTR_RADIO_SHOW] = spaceapi[CONF_RADIO_SHOW]
except KeyError:
pass
if is_sensors is not None: if is_sensors is not None:
sensors = {} sensors = {}
for sensor_type in is_sensors: for sensor_type in is_sensors:

View file

@ -25,6 +25,34 @@ CONFIG = {
"temperature": ["test.temp1", "test.temp2"], "temperature": ["test.temp1", "test.temp2"],
"humidity": ["test.hum1"], "humidity": ["test.hum1"],
}, },
"spacefed": {"spacenet": True, "spacesaml": False, "spacephone": True},
"cam": ["https://home-assistant.io/cam1", "https://home-assistant.io/cam2"],
"stream": {
"m4": "https://home-assistant.io/m4",
"mjpeg": "https://home-assistant.io/mjpeg",
"ustream": "https://home-assistant.io/ustream",
},
"feeds": {
"blog": {"url": "https://home-assistant.io/blog"},
"wiki": {"type": "mediawiki", "url": "https://home-assistant.io/wiki"},
"calendar": {"type": "ical", "url": "https://home-assistant.io/calendar"},
"flicker": {"url": "https://www.flickr.com/photos/home-assistant"},
},
"cache": {"schedule": "m.02"},
"projects": [
"https://home-assistant.io/projects/1",
"https://home-assistant.io/projects/2",
"https://home-assistant.io/projects/3",
],
"radio_show": [
{
"name": "Radioshow",
"url": "https://home-assistant.io/radio",
"type": "ogg",
"start": "2019-09-02T10:00Z",
"end": "2019-09-02T12:00Z",
}
],
} }
} }
@ -61,11 +89,37 @@ async def test_spaceapi_get(hass, mock_client):
assert data["space"] == "Home" assert data["space"] == "Home"
assert data["contact"]["email"] == "hello@home-assistant.io" assert data["contact"]["email"] == "hello@home-assistant.io"
assert data["location"]["address"] == "In your Home" assert data["location"]["address"] == "In your Home"
assert data["location"]["latitude"] == 32.87336 assert data["location"]["lat"] == 32.87336
assert data["location"]["longitude"] == -117.22743 assert data["location"]["lon"] == -117.22743
assert data["state"]["open"] == "null" assert data["state"]["open"] == "null"
assert data["state"]["icon"]["open"] == "https://home-assistant.io/open.png" assert data["state"]["icon"]["open"] == "https://home-assistant.io/open.png"
assert data["state"]["icon"]["close"] == "https://home-assistant.io/close.png" assert data["state"]["icon"]["close"] == "https://home-assistant.io/close.png"
assert data["spacefed"]["spacenet"] == bool(1)
assert data["spacefed"]["spacesaml"] == bool(0)
assert data["spacefed"]["spacephone"] == bool(1)
assert data["cam"][0] == "https://home-assistant.io/cam1"
assert data["cam"][1] == "https://home-assistant.io/cam2"
assert data["stream"]["m4"] == "https://home-assistant.io/m4"
assert data["stream"]["mjpeg"] == "https://home-assistant.io/mjpeg"
assert data["stream"]["ustream"] == "https://home-assistant.io/ustream"
assert data["feeds"]["blog"]["url"] == "https://home-assistant.io/blog"
assert data["feeds"]["wiki"]["type"] == "mediawiki"
assert data["feeds"]["wiki"]["url"] == "https://home-assistant.io/wiki"
assert data["feeds"]["calendar"]["type"] == "ical"
assert data["feeds"]["calendar"]["url"] == "https://home-assistant.io/calendar"
assert (
data["feeds"]["flicker"]["url"]
== "https://www.flickr.com/photos/home-assistant"
)
assert data["cache"]["schedule"] == "m.02"
assert data["projects"][0] == "https://home-assistant.io/projects/1"
assert data["projects"][1] == "https://home-assistant.io/projects/2"
assert data["projects"][2] == "https://home-assistant.io/projects/3"
assert data["radio_show"][0]["name"] == "Radioshow"
assert data["radio_show"][0]["url"] == "https://home-assistant.io/radio"
assert data["radio_show"][0]["type"] == "ogg"
assert data["radio_show"][0]["start"] == "2019-09-02T10:00Z"
assert data["radio_show"][0]["end"] == "2019-09-02T12:00Z"
async def test_spaceapi_state_get(hass, mock_client): async def test_spaceapi_state_get(hass, mock_client):