-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:
map
for entities and multiple map services
Why: * openstreetmap isn't always the best option. * there are more than just configuration with location info. This change addreses the need by: * introduce a `--service` option to let you choose which map service to use. currently: openstreetmap, google and bing implemented * add optional entity parameter to lookup and it has latitiude and longitude in its state it will be mapped. * bing has extra feature of allowing showing a name/label, here `friendly_name` is used otherwise `entity_id` Example usage: `hass-cli map person.max` `hass-cli --service bing device_tracker.maxs_phone` Future idea: allow to map multiple points, can be done as directions in google but Bing has better support.
- Loading branch information
1 parent
498d8ea
commit 542c0db
Showing
2 changed files
with
106 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,68 @@ | ||
"""Map plugin for Home Assistant CLI (hass-cli).""" | ||
import sys | ||
import webbrowser | ||
|
||
import click | ||
import homeassistant_cli.autocompletion as autocompletion | ||
from homeassistant_cli.cli import pass_context | ||
from homeassistant_cli.config import Configuration | ||
import homeassistant_cli.remote as api | ||
|
||
OSM_URL = 'https://www.openstreetmap.org' | ||
ZOOM = 17 | ||
OSM_URL = "https://www.openstreetmap.org/" | ||
GOOGLE_URL = "https://www.google.com/maps/search/" | ||
BING_URL = "https://www.bing.com/maps" | ||
SERVICE = { | ||
'openstreetmap': OSM_URL + '?mlat={0}&mlon={1}#map=17/{0}/{1}', | ||
'google': GOOGLE_URL + '?api=1&query={0},{1}', | ||
'bing': BING_URL + '?v=2&cp={0}~{1}&lvl=17&sp=point.{0}_{1}_{2}', | ||
} | ||
|
||
|
||
@click.command('map') | ||
@click.argument( # type: ignore | ||
'entity', required=False, autocompletion=autocompletion.entities | ||
) | ||
@click.option( | ||
'--service', default='openstreetmap', type=click.Choice(SERVICE.keys()) | ||
) | ||
@pass_context | ||
def cli(ctx: Configuration) -> None: | ||
"""Print the current location on a map.""" | ||
response = api.get_config(ctx) | ||
def cli(ctx: Configuration, service: str, entity: str) -> None: | ||
"""Show the location of the config or an entity on a map.""" | ||
latitude = None | ||
longitude = None | ||
|
||
if response: | ||
url = '{0}/?mlat={2}&mlon={3}#map={1}/{2}/{3}'.format( | ||
OSM_URL, ZOOM, response.get('latitude'), response.get('longitude') | ||
) | ||
webbrowser.open_new_tab(url) | ||
if entity: | ||
thing = entity | ||
data = api.get_state(ctx, entity) | ||
if data: | ||
attr = data.get('attributes', {}) | ||
latitude = attr.get('latitude') | ||
longitude = attr.get('longitude') | ||
thing = attr.get('friendly_name', entity) | ||
else: | ||
thing = "configuration" | ||
response = api.get_config(ctx) | ||
if response: | ||
latitude = response.get('latitude') | ||
longitude = response.get('longitude') | ||
thing = response.get('location_name', thing) | ||
|
||
if latitude and longitude: | ||
urlpattern = SERVICE.get(service) | ||
import urllib.parse | ||
|
||
if urlpattern: | ||
url = urlpattern.format( | ||
latitude, longitude, urllib.parse.quote_plus(thing) | ||
) | ||
ctx.echo( | ||
"{} location is at {}, {}".format(thing, latitude, longitude) | ||
) | ||
webbrowser.open_new_tab(url) | ||
else: | ||
ctx.echo( | ||
"Could not find url pattern for service {}".format(service) | ||
) | ||
else: | ||
ctx.echo("No exact location info found in {}".format(thing)) | ||
sys.exit(2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""Tests file for hass-cli map.""" | ||
from typing import no_type_check | ||
from unittest.mock import patch | ||
|
||
from click.testing import CliRunner | ||
import homeassistant_cli.cli as cli | ||
import pytest | ||
import requests_mock | ||
|
||
|
||
@no_type_check | ||
@pytest.mark.parametrize( | ||
"service,url", | ||
[ | ||
("openstreetmap", "https://www.openstreetmap.org"), | ||
("bing", "https://www.bing.com"), | ||
("google", "https://www.google.com"), | ||
], | ||
) | ||
def test_map_services(service, url, default_entities) -> None: | ||
"""Test map feature.""" | ||
entity_id = 'zone.school' | ||
school = next( | ||
(x for x in default_entities if x['entity_id'] == entity_id), "ERROR!" | ||
) | ||
|
||
print(school) | ||
with requests_mock.Mocker() as mock, patch( | ||
'webbrowser.open_new_tab' | ||
) as mocked_browser: | ||
mock.get( | ||
"http://localhost:8123/api/states/{}".format(entity_id), | ||
json=school, | ||
status_code=200, | ||
) | ||
|
||
runner = CliRunner() | ||
result = runner.invoke( | ||
cli.cli, | ||
["--output=json", "map", "--service", service, "zone.school"], | ||
catch_exceptions=False, | ||
) | ||
assert result.exit_code == 0 | ||
|
||
callurl = mocked_browser.call_args[0][0] | ||
|
||
assert callurl.startswith(url) | ||
assert str(school.get('attributes').get('latitude')) in callurl | ||
assert ( | ||
str(school.get('attributes').get('longitude')) in callurl | ||
) # typing: ignore |