Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GeoJSON platform #16610

Merged

Conversation

exxamalte
Copy link
Contributor

@exxamalte exxamalte commented Sep 13, 2018

Description:

This is the first platform of the new geo location component. It fetches data from a GeoJSON feed and generates entities for each external event that is in the vicinity of the Home Assistant server. The radius around Home Assistant is configurable, and so is the scan interval.

I tried to keep this as simple as possible. Future enhancements may include the ability to access other properties of the feed or the ability to define additional filters.
More platforms for specific GeoJSON feeds are in the making, and so are similar platforms for GeoRSS feeds.

Related issue (if applicable): evolving #15953

Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#6316

Example entry for configuration.yaml (if applicable):

geo_location:
  - platform: geo_json_events
    url: http://www.rfs.nsw.gov.au/feeds/majorIncidents.json

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • New dependencies have been added to the REQUIREMENTS variable (example).
  • New dependencies are only imported inside functions that use them (example).
  • New or updated dependencies have been added to requirements_all.txt by running script/gen_requirements_all.py.
  • Tests have been added to verify that the new code works.

@exxamalte
Copy link
Contributor Author

@MartinHjelmare: Here comes the first platform for the new geo location component. I decided to go with GeoJSON feeds first before looking at GeoRSS again, because GeoJSON is a bit simpler and does not depend on the feedreader component.

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at the tests.

Please rename device to entity consistently cause that's what is referred to here.

from homeassistant.components.sensor.rest import RestData
from homeassistant.const import CONF_RADIUS, CONF_URL, CONF_SCAN_INTERVAL, \
EVENT_HOMEASSISTANT_START, ATTR_ID, LENGTH_KILOMETERS, LENGTH_METERS
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import this from the geo_location component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS_IN_KM):
vol.Coerce(float),
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already present in the base platform config schema. Define the constant SCAN_INTERVAL to set default scan interval for this platform.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

})


def setup_platform(hass, config, add_entities, disc_info=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename disc_info to discovery_info to keep variable names consistent all over home assistant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


import homeassistant.helpers.config_validation as cv
from homeassistant.components.geo_location import GeoLocationEvent
from homeassistant.components.sensor.rest import RestData
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't depend on another platform. Service I/O code should be hosted in a standalone library published on pypi.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I understand the direction the project is taking here.
However, at this point there would really not be much more than what is currently implemented in the RestData class that would need to be outsourced.
And before I go down that path of an external library, could you please have a look at the GeoJsonDistanceHelper inside the platform. Would you expect that to be outsourced, too, or does this still count as data transformation and can stay inside the platform?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeoJsonDistanceHelper certainly looks general enough to fit in a standalone library. I think that's a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, let me take a look at moving the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the meantime I moved all code that interacts with the external GeoJSON feed as well as the code that calculates distances into a separate library: https://pypi.org/project/geojson-client/

def setup_platform(hass, config, add_entities, disc_info=None):
"""Set up the GeoJSON Events platform."""
url = config.get(CONF_URL)
scan_interval = config.get(CONF_SCAN_INTERVAL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scan_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

# Update existing device's details with event data.
latitude, longitude, _, name, category = \
self._extract_data(feature)
device.distance = entry[ATTR_DISTANCE]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should invert the flow here so that the entity fetches the new state from a data holding instance in async_update. That's our standard. Use schedule_update_ha_state(True) on the entity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. The manager class I implemented is currently fetching the data from the new library and can keep a copy of all entries. Each entity then fetches the original entry on async_update and updates its values.

"""Return the name of the entity."""
return self._name

@name.setter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all the setters and invert the flow as commented above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

return DEFAULT_UNIT_OF_MEASUREMENT

@property
def external_id(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be made a regular instance attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

return self._external_id

@property
def category(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the category altogether for this generic platform since there is no real standard defining categories in GeoJSON feeds.

vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_CATEGORIES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please validate possible categories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't be possible because the external GeoJSON feed can basically make up any category it wants.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. How will the user know what to put here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user would need to look at what categories the provider of the GeoJSON feed has documented, or do a bit of reverse-engineering.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an example of categories from some established feed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, for example this feed http://www.rfs.nsw.gov.au/feeds/majorIncidents.json uses the following categories:

  • Emergency Warning
  • Watch and Act
  • Advice
  • Not Applicable

Documented on the provider's overview page when you scroll down to the table "Alert Level".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed categories from this, and move this into a more specific platform, including validation.

@exxamalte
Copy link
Contributor Author

@MartinHjelmare : Sorry, I just realised from some of your questions that it may make sense to briefly discuss the general direction for new platforms in the geo location component. Could you please have a look at the last comment in here: home-assistant/architecture#42

@MartinHjelmare
Copy link
Member

Those are good questions. I don't have an opinion/answer at the moment.

@exxamalte
Copy link
Contributor Author

@MartinHjelmare : As suggested I moved all code that accesses the external GeoJSON feed into a separate library. I refactored the code in this PR as requested and to integrate with the new library.
As suggested in the last comment in home-assistant/architecture#42, this PR may not go anywhere, but before I go ahead and integrate a first specific GeoJSON feed as a new platform, I was hoping you could take a quick look at this to confirm whether the refactoring go into the right direction? Thanks.

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks ok.

"""No polling needed for GeoJSON location events."""
return False

@asyncio.coroutine
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Python 3.5 async syntax, ie async def.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the GeoJSON Events platform."""
url = config.get(CONF_URL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dict[key] for required config keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

Copy link
Member

@MartinHjelmare MartinHjelmare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@MartinHjelmare
Copy link
Member

Maybe update the PR description about the category filter.

@exxamalte
Copy link
Contributor Author

Updated the PR description. Doco will be added tonight.

@MartinHjelmare MartinHjelmare merged commit 18d37ff into home-assistant:dev Sep 21, 2018
@ghost ghost removed the in progress label Sep 21, 2018
@balloob balloob mentioned this pull request Sep 28, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Feb 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants