Skip to content

Commit

Permalink
v0.1.0
Browse files Browse the repository at this point in the history
Language Validation, Device Geolocation, Bugfixes, Documentation
  • Loading branch information
NeonDaniel authored Oct 26, 2022
2 parents fa08c88 + 1af41fa commit 011a630
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 99 deletions.
6 changes: 2 additions & 4 deletions .github/workflows/skill_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ jobs:
run: |
sudo apt install -y gcc libfann-dev swig libssl-dev portaudio19-dev git libpulse-dev
pip install --upgrade pip
pip install pytest mock git+https://github.com/NeonGeckoCom/NeonCore#egg=neon_core
pip install -r requirements.txt
pip install pytest mock git+https://github.com/NeonGeckoCom/NeonCore#egg=neon_core .
- name: Test Skill
run: |
pytest test/test_skill.py
Expand All @@ -58,8 +57,7 @@ jobs:
sudo apt update
sudo apt install -y gcc libfann-dev swig libssl-dev portaudio19-dev git libpulse-dev
pip install --upgrade pip
pip install ovos-core[skills] pytest mock
pip install -r requirements.txt
pip install ovos-core[skills] pytest mock .
- name: Test Skill
run: |
pytest test/test_skill.py
2 changes: 1 addition & 1 deletion .github/workflows/update_skill_json.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
sudo apt update
sudo apt install -y gcc git libpulse-dev
pip install --upgrade pip
pip install neon-utils\~=0.17 ovos-skills-manager
pip install neon-utils~=1.0,\>=1.1.1 ovos-skills-manager
- name: Get Updated skill.json
run: |
python action/skill/scripts/update_skill_json.py
Expand Down
66 changes: 44 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,50 @@ Neon can help you control your user preference settings via this skill.

## Examples

You can use this skill in multiple ways:
* change my units to (metric/imperial)
* change my time format to (half/military) time
* (enable/disable) hesitation
* (enable/disable) (audio recording/transcriptions)
* talk to me (faster/slower/normally)
* change my location to...
* change my timezone to...
* change my dialog mode to (limited/random)
* tell me my name
* tell me my (first/middle/last/preferred/user) name
* tell me my email address
* where am i
* my birthday is...
* my email address is...
* my name is...
* change my (first/middle/last/preferred) name to
* tell me my language settings
* i will speak to you in...
* speak to me in...
* change my (primary/secondary) language to...
* no secondary language
* Change my units to metric.
* Use imperial units.
* Change my time format to military time.
* Change my time format to twelve hour time.

* Enable hesitation.
* Disable hesitation.
* Use limited responses.
* Use standard responses.

* Enable audio recordings.
* Disable audio recordings.
* Permit transcriptions.
* Disable transcriptions.

* Talk to me faster.
* Talk to me slower.
* Talk to me normally.

* Change my location to Seattle.
* Change my timezone to London.

* Tell me my name.
* Tell me my first name.
* Tell me my last name.
* Tell me my username.

* Tell me my email address.

* Where am I?

* My birthday is...
* My email address is...
* My name is...

* Change my first name to Daniel.
* Change my preferred name to Dan.

* Tell me my language settings.
* I will speak to you in Spanish.
* Speak to me in French.
* Change my primary language to German.
* Change my secondary language to Ukrainian.
* No secondary language.


## Contact Support
Expand Down
130 changes: 114 additions & 16 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@
from neon_utils.skills.neon_skill import NeonSkill
from neon_utils.logger import LOG
from neon_utils.user_utils import get_user_prefs
from neon_utils.language_utils import get_supported_languages
from lingua_franca.parse import extract_langcode, get_full_lang_code
from lingua_franca.format import pronounce_lang
from lingua_franca.internal import UnsupportedLanguageError
from ovos_utils.file_utils import read_vocab_file

from ovos_utils.network_utils import is_connected
from mycroft.skills.core import intent_handler, intent_file_handler
from mycroft.util.parse import extract_datetime

Expand All @@ -52,6 +53,72 @@ class UserSettingsSkill(NeonSkill):

def __init__(self):
super(UserSettingsSkill, self).__init__(name="UserSettingsSkill")
self._languages = None

def initialize(self):
if self.settings.get('use_geolocation'):
# TODO: Better check here
if is_connected():
LOG.debug('Internet connected, updating location')
self._request_location_update()
else:
self.add_event('ovos.wifi.setup.completed',
self._request_location_update, once=True)

def _request_location_update(self, _=None):
LOG.info(f'Requesting Geolocation update')
self.add_event('ovos.ipgeo.update.response',
self._handle_location_ipgeo_update, once=True)
self.bus.emit(Message('ovos.ipgeo.update', {'overwrite': True}))

def _handle_location_ipgeo_update(self, message):
updated_location = message.data.get('location')
if not updated_location:
LOG.warning(f"No geolocation config")
return
from neon_utils.user_utils import apply_local_user_profile_updates
from neon_utils.configuration_utils import get_neon_user_config
new_loc = {
'lat': str(updated_location['coordinate']['latitude']),
'lon': str(updated_location['coordinate']['longitude']),
'city': updated_location['city']['name'],
'state': updated_location['city']['state']['name'],
'country': updated_location['city']['state']['country']['name'],
}
name, offset = self._get_timezone_from_location(new_loc)
new_loc['lng'] = new_loc.pop('lon')
new_loc['tz'] = name
new_loc['utc'] = str(round(offset, 1))
apply_local_user_profile_updates({'location': new_loc},
get_neon_user_config())

@property
def stt_languages(self) -> Optional[set]:
self._get_supported_languages()
if not all((self._languages.skills, self._languages.stt)):
LOG.warning("Incomplete language support response. "
"Assuming all languages are supported")
return None
return set((lang for lang in self._languages.stt
if lang in self._languages.skills))

@property
def tts_languages(self):
self._get_supported_languages()
if not all((self._languages.skills, self._languages.tts)):
LOG.warning("Incomplete language support response. "
"Assuming all languages are supported")
return None
return set((lang for lang in self._languages.tts
if lang in self._languages.skills))

def _get_supported_languages(self):
"""
Gather supported languages via the Messagebus API and save the result
"""
if not self._languages:
supported_langs = get_supported_languages()
self._languages = supported_langs

@intent_handler(IntentBuilder("ChangeUnits").require("change")
.require("units").one_of("imperial", "metric").build())
Expand Down Expand Up @@ -318,7 +385,7 @@ def handle_say_my_name(self, message: Message):
profile["user"]["username"]
request = "word_name"

if not name:
if not name or name == profile["user"]["username"] == 'local':
# TODO: Use get_response to ask for the user's name
self.speak_dialog("name_not_known",
{"name_position": self.translate(request)},
Expand Down Expand Up @@ -571,6 +638,13 @@ def handle_set_stt_language(self, message: Message):
return

LOG.info(f"code={code}")
if self.stt_languages and code.split('-')[0] not in self.stt_languages:
LOG.warning(f"{code} not found in: {self.stt_languages}")
self.speak_dialog("language_not_supported",
{"lang": spoken_lang,
"io": self.translate('word_understand')},
private=True)
return
dialog_data = {"io": self.translate("word_stt"),
"lang": spoken_lang}
if code == get_user_prefs(message)["speech"]["stt_language"]:
Expand Down Expand Up @@ -608,6 +682,15 @@ def handle_set_tts_language(self, message: Message):
primary_code, primary_spoken = \
self._get_lang_code_and_name(primary)
LOG.info(f"primary={primary_code}")
if self.tts_languages and \
primary_code.split('-')[0] not in self.tts_languages:
LOG.warning(f"{primary_code} not found in:"
f" {self.tts_languages}")
self.speak_dialog("language_not_supported",
{"lang": primary_spoken,
"io": self.translate('word_speak')},
private=True)
return
gender = self._get_gender(primary) or \
user_settings["speech"]["tts_gender"]
self.update_profile({"speech": {"tts_gender": gender,
Expand All @@ -625,6 +708,15 @@ def handle_set_tts_language(self, message: Message):
secondary_code, secondary_spoken = \
self._get_lang_code_and_name(secondary)
LOG.info(f"secondary={secondary_code}")
if self.tts_languages and \
secondary_code.split('-')[0] not in self.tts_languages:
LOG.warning(f"{secondary_code} not found in:"
f" {self.tts_languages}")
self.speak_dialog("language_not_supported",
{"lang": secondary_spoken,
"io": self.translate('word_speak')},
private=True)
return
gender = self._get_gender(secondary) or \
user_settings["speech"]["secondary_tts_gender"]
self.update_profile(
Expand All @@ -645,6 +737,14 @@ def handle_set_tts_language(self, message: Message):
try:
code, spoken = \
self._get_lang_code_and_name(language)
if self.tts_languages and \
code.split('-')[0] not in self.tts_languages:
LOG.warning(f"{code} not found in: {self.tts_languages}")
self.speak_dialog("language_not_supported",
{"lang": spoken,
"io": self.translate('word_speak')},
private=True)
return
gender = self._get_gender(language) or \
user_settings["speech"]["tts_gender"]
self.update_profile({"speech": {"tts_gender": gender,
Expand Down Expand Up @@ -755,26 +855,24 @@ def _get_lang_code_and_name(self, request: str) -> (str, str):
:returns: lang code and pronounceable language name if found, else None
"""
load_language(self.lang)
short_code = extract_langcode(request)[0]
code = get_full_lang_code(short_code)
if code.split('-')[0] != short_code:
LOG.warning(f"Got {code} from {short_code}. No valid code")
code = None
# TODO: https://github.com/OpenVoiceOS/ovos-lingua-franca/issues/24
# Patching known languages, drop this when #24 resolved
request_overrides = {
"australian": "en-au",
"british": "en-uk",
"mexican": "es-mx",
"ukrainian": "uk-ua",
"japanese": "ja-jp"
}

code = None
# Manually specified languages take priority
request_overrides = self.translate_namedvalues("languages.value")
for lang, c in request_overrides.items():
if lang in request.lower().split():
code = c
break
if not code:
# Ask LF to determine the code
short_code = extract_langcode(request)[0]
code = get_full_lang_code(short_code)
if code.split('-')[0] != short_code:
LOG.warning(f"Got {code} from {short_code}. No valid code")
code = None

if not code:
# Request is not a language, raise an exception
raise UnsupportedLanguageError(f"No language found in {request}")
spoken_lang = pronounce_lang(code)
return code, spoken_lang
Expand Down
1 change: 1 addition & 0 deletions locale/en-us/dialog/language_not_supported.dialog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sorry, I don't {{io}} {{lang}}.
1 change: 1 addition & 0 deletions locale/en-us/dialog/word_speak.dialog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
speak
1 change: 1 addition & 0 deletions locale/en-us/dialog/word_understand.dialog
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
understand
20 changes: 20 additions & 0 deletions locale/en-us/languages.value
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
australian,en-au
british,en-uk
mexican,es-mx
ukrainian,uk-ua
japanese,ja-jp
finnish,fi-fi
bulgarian,bg-bg
romanian,ro-ro
latvian,lv-lv
gaelic,ga-ie
irish,ga-ie
greek,el-gr
estonian,et-ee
arabic,ar-sa
chinese,zh-zh
hindi,hi-in
indonesian,id-id
korean,ko-kr
farsi,fa-ir
slovak,sk-sk
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
pytz>=2021.3
neon-utils>=0.17,<2.0.0,!=1.0.0
neon-utils~=1.1
32 changes: 5 additions & 27 deletions settingsmeta.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
skillMetadata:
sections:
- name: Clap Settings
- name: Location Updates
fields:
- name: default_claps
type: dict
label: Default Claps
value: { 1: '', 2: stop, 3: toggle belkin wemo outlet, 4: cycle desk lamp }
- name: audio_claps
type: dict
label: Audio Claps
value: { 1: '', 2: pause, 3: stop, 4: skip }
- name: home_claps
type: dict
label: Home Control Claps
value: { 1: '', 2: dim desk lamp temperature to 0, 3: toggle belkin wemo outlet, 4: cycle desk lamp }
- name: Blink Settings
fields:
- name: default_blinks
type: dict
label: Default Blinks
value: { 1: '', 2: toggle desk lamp, 3: unmute microphone, 4: turn off office lamp, 5: dim office lamp to 50 }
- name: audio_blinks
type: dict
label: Audio Blinks
value: { 1: '', 2: pause, 3: stop, 4: skip }
- name: home_blinks
type: dict
label: Home Control Blinks
value: { 1: '', 2: dim desk lamp temperature to 0, 3: toggle belkin wemo outlet, 4: cycle}
- name: use_geolocation
type: bool
label: Automatically Set Location by IP Address
value: false
Loading

0 comments on commit 011a630

Please sign in to comment.