More Location integration for #487

For the Weather Skill:
* When talking about the current city, the city name is generally not spoken (more natural)
* A "pretty" name of just the city is used instead of the complete name
* Works around the recurring issue with OWM where they report bad min/max temps (same as the current temp)
* Changed "Location is not valid" to "I don't know that location" (people don't say "not valid")

For the Time Skill:
* The timezone is extracted from the device location setting
* Time responses are more varied and shorter

This change adds MycroftSkill.location_pretty and MycroftSkill.location_timezone properties.
pull/497/head
penrods 2017-02-03 02:08:43 -08:00 committed by Arron Atchison
parent c3070beb7b
commit acfdff416b
8 changed files with 92 additions and 30 deletions

View File

@ -178,8 +178,27 @@ class MycroftSkill(object):
@property
def location(self):
""" Get the JSON data struction holding location information. """
# TODO: Allow Enclosure to override this for devices that
# contain a GPS.
return self.config_core.get('location')
@property
def location_pretty(self):
""" Get a more 'human' version of the location as a string. """
loc = self.location
if type(loc) is dict and loc["city"]:
return loc["city"]["name"]
return None
@property
def location_timezone(self):
""" Get the timezone code, such as 'America/Los_Angeles' """
loc = self.location
if type(loc) is dict and loc["timezone"]:
return loc["timezone"]["code"]
return None
@property
def lang(self):
return self.config_core.get('lang')

View File

@ -50,22 +50,29 @@ class TimeSkill(MycroftSkill):
def get_timezone(self, locale):
try:
# This handles common city names, like "Dallas" or "Paris"
return timezone(self.astral[locale].timezone)
except:
return None
try:
# This handles codes like "America/Los_Angeles"
return timezone(locale)
except:
return None
# This method only handles localtime, for other timezones the task falls
# to Wolfram.
def handle_intent(self, message):
location = message.data.get("Location")
now = datetime.datetime.now(timezone('UTC'))
tz = tzlocal.get_localzone()
location = message.data.get("Location") # optional parameter
nowUTC = datetime.datetime.now(timezone('UTC'))
tz = self.get_timezone(self.location_timezone)
if location:
tz = self.get_timezone(location)
if not tz:
self.speak_dialog("time.tz.not.found", {"location": location})
return
time = now.astimezone(tz).strftime(self.format)
if not tz:
self.speak_dialog("time.tz.not.found", {"location": location})
return
# Convert UTC to appropriate timezone and format
time = nowUTC.astimezone(tz).strftime(self.format)
self.speak_dialog("time.current", {"time": time})
def stop(self):

View File

@ -1 +1,5 @@
It is currently {{time}}
{{time}}
It is {{time}}
{{time}}
It's {{time}}
Currently {{time}}

View File

@ -129,15 +129,31 @@ class WeatherSkill(MycroftSkill):
def handle_current_intent(self, message):
try:
location = self.get_location(message)
location, pretty_location = self.get_location(message)
weather = self.owm.weather_at_place(location).get_weather()
data = self.__build_data_condition(location, weather)
data = self.__build_data_condition(pretty_location, weather)
# BUG: OWM is commonly reporting incorrect high/low data in the
# "current" request. So grab that from the forecast API call.
weather_forecast = self.owm.three_hours_forecast(
location).get_forecast().get_weathers()[0]
data_forecast = self.__build_data_condition(pretty_location,
weather_forecast)
data["temp_min"] = data_forecast["temp_min"]
data["temp_max"] = data_forecast["temp_max"]
weather_code = str(weather.get_weather_icon_name())
img_code = self.CODES[weather_code]
temp = data['temp_current']
self.enclosure.deactivate_mouth_events()
self.enclosure.weather_display(img_code, temp)
self.speak_dialog('current.weather', data)
dialog_name = "current"
if pretty_location == self.location_pretty:
dialog_name += ".local"
self.speak_dialog(dialog_name+".weather", data)
time.sleep(5)
self.enclosure.activate_mouth_events()
except HTTPError as e:
@ -147,16 +163,19 @@ class WeatherSkill(MycroftSkill):
def handle_next_hour_intent(self, message):
try:
location = self.get_location(message)
location, pretty_location = self.get_location(message)
weather = self.owm.three_hours_forecast(
location).get_forecast().get_weathers()[0]
data = self.__build_data_condition(location, weather)
data = self.__build_data_condition(pretty_location, weather)
weather_code = str(weather.get_weather_icon_name())
img_code = self.CODES[weather_code]
temp = data['temp_current']
self.enclosure.deactivate_mouth_events()
self.enclosure.weather_display(img_code, temp)
self.speak_dialog('hour.weather', data)
if pretty_location == self.location_pretty:
self.speak_dialog('hour.weather', data)
else:
self.speak_dialog('hour.weather', data)
time.sleep(5)
self.enclosure.activate_mouth_events()
except HTTPError as e:
@ -166,17 +185,20 @@ class WeatherSkill(MycroftSkill):
def handle_next_day_intent(self, message):
try:
location = self.get_location(message)
location, pretty_location = self.get_location(message)
weather = self.owm.daily_forecast(
location).get_forecast().get_weathers()[1]
data = self.__build_data_condition(
location, weather, 'day', 'min', 'max')
pretty_location, weather, 'day', 'min', 'max')
weather_code = str(weather.get_weather_icon_name())
img_code = self.CODES[weather_code]
temp = data['temp_current']
self.enclosure.deactivate_mouth_events()
self.enclosure.weather_display(img_code, temp)
self.speak_dialog('tomorrow.weather', data)
if pretty_location == self.location_pretty:
self.speak_dialog('tomorrow.local.weather', data)
else:
self.speak_dialog('tomorrow.weather', data)
time.sleep(5)
self.enclosure.activate_mouth_events()
except HTTPError as e:
@ -186,25 +208,28 @@ class WeatherSkill(MycroftSkill):
def get_location(self, message):
try:
location = message.data.get("Location", self.location)
location = message.data.get("Location", None)
if location:
return location, location
location = self.location
if type(location) is dict:
city = location["city"]
state = city["state"]
return city["name"] + ", " + state["name"] + ", " + \
state["country"]["name"]
else:
return location
state["country"]["name"], self.location_pretty
return None
except:
self.speak_dialog("location.not.found")
raise ValueError("Location not found")
def __build_data_condition(
self, location, weather, temp='temp', temp_min='temp_min',
self, location_pretty, weather, temp='temp', temp_min='temp_min',
temp_max='temp_max'):
if type(location) is dict:
location = location["city"]["name"]
data = {
'location': location,
'location': location_pretty,
'scale': self.temperature,
'condition': weather.get_detailed_status(),
'temp_current': self.__get_temperature(weather, temp),

View File

@ -0,0 +1,3 @@
It's currently {{condition}} and {{temp_current}} degrees {{scale}}. Today's forecast is for a high of {{temp_max}} and a low of {{temp_min}}.
Right now, it's {{condition}} and {{temp_current}} degrees, for a high of {{temp_max}} and a low of {{temp_min}}.
With a high of {{temp_max}} and a low of {{temp_min}}, {{location}} has {{condition}} and is currently {{temp_current}} degrees.

View File

@ -0,0 +1,2 @@
In the next few hours, it will be {{condition}}, with a high of {{temp_max}} and a low of {{temp_min}}
The will be a high of {{temp_max}} and a low of {{temp_min}}, with {{condition}} conditions in the following hours

View File

@ -1,3 +1,3 @@
I could not find a valid location
Location is not valid
I could not find a that location
I don't know that location
Location not found

View File

@ -0,0 +1,2 @@
Tomorrow it will be {{condition}}, with a high of {{temp_max}} and a low of {{temp_min}}
Tomorrow there will have a high of {{temp_max}} and a low of {{temp_min}}, with {{condition}} conditions