Merge tag 'release/v0.8.9'
commit
8bc55ac7f8
|
@ -2,7 +2,6 @@
|
|||
*.pyc
|
||||
*.swp
|
||||
*~
|
||||
msm
|
||||
mimic
|
||||
pocketsphinx-python
|
||||
*.egg-info/
|
||||
|
|
|
@ -7,7 +7,7 @@ The following guidelines for contribution should be followed if you want to subm
|
|||
## How to prepare
|
||||
|
||||
* You need a [GitHub account](https://github.com/signup/free)
|
||||
* Submit an [issue ticket](https://github.com/MycroftAI/mycroft/issues) for your issue if there is no one yet.
|
||||
* Submit an [issue ticket](https://github.com/MycroftAI/mycroft/issues) for your issue if there is not one yet.
|
||||
* Describe the issue and include steps to reproduce if it's a bug.
|
||||
* Ensure to mention the earliest version that you know is affected.
|
||||
* If you are able and want to fix this, fork the repository on GitHub
|
||||
|
@ -17,29 +17,29 @@ The following guidelines for contribution should be followed if you want to subm
|
|||
|
||||
1. [Fork the Project](https://help.github.com/articles/fork-a-repo/)
|
||||
2. [Create a new Issue](https://help.github.com/articles/creating-an-issue/)
|
||||
3. Create a **feature** or **bugfix** branch based on **master** with your issue identifier. For example, if your issue identifier is: **issues-123** then you will create either: **feature/issues-123** or **bugfix/issues-123**. Use **feature** prefix for issues related to new functionalities or enhancements and **bugfix** in case of bugs found on the **master** branch
|
||||
4. Make sure you stick to the coding style and OO patterns that is used already.
|
||||
5. Make use of the `.editorconfig`-file if provided with the repository.
|
||||
6. Make commits of logical units and describe them properly. Use your issue identifier at the very begin of each commit. For instance:
|
||||
3. Create a **feature** or **bugfix** branch based on **dev** with your issue identifier. For example, if your issue identifier is: **issue-123** then you will create either: **feature/issue-123** or **bugfix/issue-123**. Use **feature** prefix for issues related to new functionalities or enhancements and **bugfix** in case of bugs found on the **dev** branch
|
||||
4. Make sure you stick to the coding style and OO patterns that are used already.
|
||||
5. Document code using [Google-style docstrings](http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). Our automated documentation tools expect that format. All functions and class methods that are expected to be called externally should include a docstring. (And those that aren't [should be prefixed with a single underscore](https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references)).
|
||||
6. Make commits in logical units and describe them properly. Use your issue identifier at the very begin of each commit. For instance:
|
||||
`git commit -m "Issues-123 - Fixing 'A' sound on Spelling Skill"`
|
||||
7. Before committing, format your code following the PEP8 rules and organize your imports removing unused libs. To check whether you are following these rules, install pep8 and run `pep8 mycroft test` while in the `mycroft-core` folder. This will check for formatting issues in the `mycroft` and `test` folders.
|
||||
8. Once you have committed everything and are done with your branch, you have to rebase your code with master. Do the following steps:
|
||||
8. Once you have committed everything and are done with your branch, you have to rebase your code with **dev**. Do the following steps:
|
||||
1. Make sure you do not have any changes left on your branch
|
||||
2. Checkout on master branch and make sure it is up-to-date
|
||||
3. Checkout your branch and rebase it with master
|
||||
2. Checkout on dev branch and make sure it is up-to-date
|
||||
3. Checkout your branch and rebase it with dev
|
||||
4. Resolve any conflicts you have
|
||||
5. You will have to force your push since the historical base has changed
|
||||
6. Suggested steps are:
|
||||
```
|
||||
git checkout master
|
||||
git checkout dev
|
||||
git fetch
|
||||
git reset --hard origin/master
|
||||
git reset --hard origin/dev
|
||||
git checkout <your_branch_name>
|
||||
git rebase master
|
||||
git rebase dev
|
||||
git push -f
|
||||
```
|
||||
9. If possible, create unit tests for your changes
|
||||
* [Unit Tests for most contributions](https://github.com/MycroftAI/mycroft-core/tree/master/test)
|
||||
* [Unit Tests for most contributions](https://github.com/MycroftAI/mycroft-core/tree/dev/test)
|
||||
* [Intent Tests for new skills](https://docs.mycroft.ai/development/creating-a-skill#testing-your-skill)
|
||||
* We utilize TRAVIS-CI, which will test each pull request. To test locally you can run: `./start.sh unittest`
|
||||
10. Once everything is OK, you can finally [create a Pull Request (PR) on Github](https://help.github.com/articles/using-pull-requests/) in order to be reviewed and merged.
|
||||
|
|
|
@ -63,9 +63,6 @@ echo Building with $CORES cores.
|
|||
cd ${TOP}
|
||||
${TOP}/scripts/install-mimic.sh
|
||||
|
||||
cd ${TOP}
|
||||
${TOP}/scripts/install-msm.sh
|
||||
|
||||
# install pygtk for desktop_launcher skill
|
||||
${TOP}/scripts/install-pygtk.sh
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# @author Augusto Monteiro
|
||||
#
|
||||
# This script assists in the installation and management of
|
||||
# skills loaded from Github.
|
||||
|
||||
mycroft_skill_folder="/opt/mycroft/skills"
|
||||
mycroft_virtualenv=~/.virtualenvs/mycroft/bin/activate
|
||||
|
||||
echo "####### Mycroft Skill Manager #######"
|
||||
|
||||
function help() {
|
||||
echo "msm: Mycroft Skill Manager"
|
||||
echo -e " Copyright (c) 2017 Mycroft AI, Inc. All rights reserved.\n"
|
||||
echo "usage: msm install <repository> or <name>"
|
||||
echo " Installs the given Skill into the /opt/mycroft/skills directory"
|
||||
echo " where <repository> is the address of the skill in Github."
|
||||
echo -e "example: msm install https://github.com/ethanaward/demo_skill.git\n"
|
||||
}
|
||||
|
||||
function install() {
|
||||
cd $mycroft_skill_folder
|
||||
if [ -z "$2" ]; then
|
||||
echo "You must pass the git url or skill name"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$2" == "git@"* || "$2" == "https://"* || "$2" == "http://"* ]]; then
|
||||
repo=$2
|
||||
else
|
||||
skill_list="`curl -s "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules"`"
|
||||
skills=`echo "$skill_list" | grep -n 'submodule' | sed 's/[[:space:]]//g' | sed 's/\[submodule"//g' | sed 's/"\]//g'`
|
||||
exact_match=`echo "$skills" | grep -i ".*:$2$"`
|
||||
skill=`echo "$skills" | grep -i ".*:.*$2.*"`
|
||||
if [ ! -z $exact_match ]; then
|
||||
skill=$exact_match
|
||||
fi
|
||||
git_line=`echo "$skill" | sed 's/\:.*//'`
|
||||
|
||||
if [[ $skill == *$'\n'* ]]; then
|
||||
echo -e "Your search has multiple choices\n\n$skill" | sed 's/.*://g'
|
||||
exit 2
|
||||
else
|
||||
if [ -z $git_line ]; then
|
||||
echo "Skill not found"
|
||||
exit 3
|
||||
fi
|
||||
repo_line=$(($git_line + 2))
|
||||
repo=`echo "$skill_list" | sed -n $repo_line'{p;q;}' | sed 's/[[:space:]]//g' | sed 's/url=//g'`
|
||||
fi
|
||||
fi
|
||||
git_name=`echo "$repo" | sed 's/.*\///'`
|
||||
name=`echo "$git_name" | sed 's/.git//'`
|
||||
echo "Cloning repository"
|
||||
git clone $repo >> /dev/null
|
||||
cd $name
|
||||
if [ -f "requirements.txt" ]; then
|
||||
echo "Installing libraries requirements"
|
||||
pip install -r requirements.txt
|
||||
fi
|
||||
echo "Skill installed!"
|
||||
}
|
||||
|
||||
function update() {
|
||||
cd $mycroft_skill_folder
|
||||
for d in *; do
|
||||
if git -C "$d" rev-parse --git-dir > /dev/null 2>&1; then
|
||||
cd $d
|
||||
git fetch
|
||||
git reset --hard origin/master
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function install_defaults() {
|
||||
skills=( "alarm" "audio-record" "configuration" "date-time" "desktop-launcher" "ip" "joke" "hello-world" "media" "npr-news" "naptime" "pairing" "personal" "reminder" "installer" "speak" "spelling" "stop" "stock" "volume" "weather" "wiki" "wolfram-alpha" )
|
||||
for i in "${skills[@]}"
|
||||
do
|
||||
if [ ! -d "$mycroft_skill_folder/skill-$i" ]; then
|
||||
install "" "https://github.com/MycroftAI/skill-$i.git"
|
||||
fi
|
||||
done
|
||||
update
|
||||
echo "Installed!"
|
||||
}
|
||||
|
||||
function list() {
|
||||
curl -s "https://raw.githubusercontent.com/MycroftAI/mycroft-skills/master/.gitmodules" | grep 'submodule "' | sed 's/\[submodule "//g'| sed 's/"\]//g'
|
||||
}
|
||||
|
||||
if [ "$1" = "install" ]; then
|
||||
install $*
|
||||
elif [ "$1" = "list" ]; then
|
||||
echo -e "Searching...\n"
|
||||
list
|
||||
elif [ "$1" = "update" ]; then
|
||||
update
|
||||
elif [ "$1" = "default" ]; then
|
||||
install_defaults
|
||||
else
|
||||
help
|
||||
fi
|
15
mycroft.sh
15
mycroft.sh
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
./scripts/prepare-msm.sh
|
||||
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
@ -49,13 +51,18 @@ function start-mycroft {
|
|||
echo "Mycroft $1$2 started"
|
||||
}
|
||||
|
||||
function debug-start-mycroft {
|
||||
screen -c $SCRIPTS/mycroft-$1.screen $DIR/start.sh $1 $2
|
||||
function start-mycroft-nolog {
|
||||
screen -mdS mycroft-$1$2 $DIR/start.sh $1 $2 $3
|
||||
sleep 1
|
||||
verify-start mycroft-$1$2
|
||||
echo "Mycroft $1$2 started"
|
||||
}
|
||||
|
||||
function debug-start-mycroft {
|
||||
$DIR/start.sh $1 $2
|
||||
echo "Mycroft $1$2 started"
|
||||
}
|
||||
|
||||
function stop-mycroft {
|
||||
if screen -list | grep -q "$1";
|
||||
then
|
||||
|
@ -96,7 +103,7 @@ then
|
|||
start-mycroft service
|
||||
start-mycroft skills
|
||||
start-mycroft voice
|
||||
start-mycroft cli --quiet
|
||||
start-mycroft-nolog cli --quiet --simple
|
||||
exit 0
|
||||
elif [[ "$1" == "start" && "$2" == "-v" ]]
|
||||
then
|
||||
|
@ -108,7 +115,7 @@ elif [[ "$1" == "start" && "$2" == "-c" ]]
|
|||
then
|
||||
start-mycroft service
|
||||
start-mycroft skills
|
||||
start-mycroft cli
|
||||
start-mycroft-nolog cli --simple
|
||||
exit 0
|
||||
elif [[ "$1" == "start" && "$2" == "-d" ]]
|
||||
then
|
||||
|
|
|
@ -27,8 +27,9 @@ from mycroft.identity import IdentityManager
|
|||
from mycroft.messagebus.client.ws import WebsocketClient
|
||||
from mycroft.messagebus.message import Message
|
||||
from mycroft.tts import TTSFactory
|
||||
from mycroft.util import kill, play_wav, resolve_resource_file, create_signal
|
||||
from mycroft.util import kill, create_signal
|
||||
from mycroft.util.log import getLogger
|
||||
from mycroft.lock import Lock as PIDLock # Create/Support PID locking file
|
||||
|
||||
logger = getLogger("SpeechClient")
|
||||
ws = None
|
||||
|
@ -41,15 +42,6 @@ config = ConfigurationManager.get()
|
|||
|
||||
def handle_record_begin():
|
||||
logger.info("Begin Recording...")
|
||||
|
||||
# If enabled, play a wave file with a short sound to audibly
|
||||
# indicate recording has begun.
|
||||
if config.get('confirm_listening'):
|
||||
file = resolve_resource_file(
|
||||
config.get('sounds').get('start_listening'))
|
||||
if file:
|
||||
play_wav(file)
|
||||
|
||||
ws.emit(Message('recognizer_loop:record_begin'))
|
||||
|
||||
|
||||
|
@ -103,7 +95,10 @@ def handle_speak(event):
|
|||
chunks = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s',
|
||||
utterance)
|
||||
for chunk in chunks:
|
||||
mute_and_speak(chunk)
|
||||
try:
|
||||
mute_and_speak(chunk)
|
||||
except:
|
||||
logger.error('Error in mute_and_speak', exc_info=True)
|
||||
else:
|
||||
mute_and_speak(utterance)
|
||||
|
||||
|
@ -140,6 +135,7 @@ def connect():
|
|||
def main():
|
||||
global ws
|
||||
global loop
|
||||
lock = PIDLock("voice")
|
||||
ws = WebsocketClient()
|
||||
tts.init(ws)
|
||||
ConfigurationManager.init(ws)
|
||||
|
|
|
@ -30,10 +30,16 @@ from speech_recognition import (
|
|||
)
|
||||
|
||||
from mycroft.configuration import ConfigurationManager
|
||||
from mycroft.util import check_for_signal, get_ipc_directory
|
||||
from mycroft.util import (
|
||||
check_for_signal,
|
||||
get_ipc_directory,
|
||||
resolve_resource_file,
|
||||
play_wav
|
||||
)
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
listener_config = ConfigurationManager.get().get('listener')
|
||||
config = ConfigurationManager.get()
|
||||
listener_config = config.get('listener')
|
||||
logger = getLogger(__name__)
|
||||
__author__ = 'seanfitz'
|
||||
|
||||
|
@ -130,9 +136,13 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
|
||||
# The minimum seconds of noise before a
|
||||
# phrase can be considered complete
|
||||
MIN_LOUD_SEC_PER_PHRASE = 0.1
|
||||
MIN_LOUD_SEC_PER_PHRASE = 0.5
|
||||
|
||||
# The maximum length a phrase can be recorded,
|
||||
# The minimum seconds of silence required at the end
|
||||
# before a phrase will be considered complete
|
||||
MIN_SILENCE_AT_END = 0.25
|
||||
|
||||
# The maximum seconds a phrase can be recorded,
|
||||
# provided there is noise the entire time
|
||||
RECORDING_TIMEOUT = 10.0
|
||||
|
||||
|
@ -163,22 +173,30 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
hyp = self.wake_word_recognizer.transcribe(frame_data)
|
||||
return self.wake_word_recognizer.found_wake_word(hyp)
|
||||
|
||||
def record_phrase(self, source, sec_per_buffer):
|
||||
"""
|
||||
This attempts to record an entire spoken phrase. Essentially,
|
||||
this waits for a period of silence and then returns the audio
|
||||
def _record_phrase(self, source, sec_per_buffer):
|
||||
"""Record an entire spoken phrase.
|
||||
|
||||
:rtype: bytearray
|
||||
:param source: AudioSource
|
||||
:param sec_per_buffer: Based on source.SAMPLE_RATE
|
||||
:return: bytearray representing the frame_data of the recorded phrase
|
||||
Essentially, this code waits for a period of silence and then returns
|
||||
the audio. If silence isn't detected, it will terminate and return
|
||||
a buffer of RECORDING_TIMEOUT duration.
|
||||
|
||||
Args:
|
||||
source (AudioSource): Source producing the audio chunks
|
||||
sec_per_buffer (float): Fractional number of seconds in each chunk
|
||||
|
||||
Returns:
|
||||
bytearray: complete audio buffer recorded, including any
|
||||
silence at the end of the user's utterance
|
||||
"""
|
||||
|
||||
num_loud_chunks = 0
|
||||
noise = 0
|
||||
|
||||
max_noise = 25
|
||||
min_noise = 0
|
||||
|
||||
silence_duration = 0
|
||||
|
||||
def increase_noise(level):
|
||||
if level < max_noise:
|
||||
return level + 200 * sec_per_buffer
|
||||
|
@ -217,7 +235,7 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
num_loud_chunks += 1
|
||||
else:
|
||||
noise = decrease_noise(noise)
|
||||
self.adjust_threshold(energy, sec_per_buffer)
|
||||
self._adjust_threshold(energy, sec_per_buffer)
|
||||
|
||||
if num_chunks % 10 == 0:
|
||||
with open(self.mic_level_file, 'w') as f:
|
||||
|
@ -226,7 +244,14 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
f.close()
|
||||
|
||||
was_loud_enough = num_loud_chunks > min_loud_chunks
|
||||
|
||||
quiet_enough = noise <= min_noise
|
||||
if quiet_enough:
|
||||
silence_duration += sec_per_buffer
|
||||
if silence_duration < self.MIN_SILENCE_AT_END:
|
||||
quiet_enough = False # gotta be silent for min of 1/4 sec
|
||||
else:
|
||||
silence_duration = 0
|
||||
recorded_too_much_silence = num_chunks > max_chunks_of_silence
|
||||
if quiet_enough and (was_loud_enough or recorded_too_much_silence):
|
||||
phrase_complete = True
|
||||
|
@ -239,7 +264,13 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
def sec_to_bytes(sec, source):
|
||||
return sec * source.SAMPLE_RATE * source.SAMPLE_WIDTH
|
||||
|
||||
def wait_until_wake_word(self, source, sec_per_buffer):
|
||||
def _wait_until_wake_word(self, source, sec_per_buffer):
|
||||
"""Listen continuously on source until a wake word is spoken
|
||||
|
||||
Args:
|
||||
source (AudioSource): Source producing the audio chunks
|
||||
sec_per_buffer (float): Fractional number of seconds in each chunk
|
||||
"""
|
||||
num_silent_bytes = int(self.SILENCE_SEC * source.SAMPLE_RATE *
|
||||
source.SAMPLE_WIDTH)
|
||||
|
||||
|
@ -255,7 +286,17 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
max_size = self.sec_to_bytes(self.SAVED_WW_SEC, source)
|
||||
|
||||
said_wake_word = False
|
||||
|
||||
# Rolling buffer to track the audio energy (loudness) heard on
|
||||
# the source recently. An average audio energy is maintained
|
||||
# based on these levels.
|
||||
energies = []
|
||||
idx_energy = 0
|
||||
avg_energy = 0.0
|
||||
energy_avg_samples = int(5 / sec_per_buffer) # avg over last 5 secs
|
||||
|
||||
counter = 0
|
||||
|
||||
while not said_wake_word:
|
||||
if check_for_signal('buttonPress'):
|
||||
said_wake_word = True
|
||||
|
@ -265,16 +306,33 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
|
||||
energy = self.calc_energy(chunk, source.SAMPLE_WIDTH)
|
||||
if energy < self.energy_threshold * self.multiplier:
|
||||
self.adjust_threshold(energy, sec_per_buffer)
|
||||
self._adjust_threshold(energy, sec_per_buffer)
|
||||
|
||||
if counter > 2:
|
||||
if len(energies) < energy_avg_samples:
|
||||
# build the average
|
||||
energies.append(energy)
|
||||
avg_energy += float(energy)/energy_avg_samples
|
||||
else:
|
||||
# maintain the running average and rolling buffer
|
||||
avg_energy -= float(energies[idx_energy])/energy_avg_samples
|
||||
avg_energy += float(energy)/energy_avg_samples
|
||||
energies[idx_energy] = energy
|
||||
idx_energy = (idx_energy+1) % energy_avg_samples
|
||||
|
||||
# maintain the threshold using average
|
||||
if energy < avg_energy*1.5:
|
||||
if energy > self.energy_threshold:
|
||||
# bump the threshold to just above this value
|
||||
self.energy_threshold = energy*1.2
|
||||
|
||||
# Periodically output energy level stats. This can be used to
|
||||
# visualize the microphone input, e.g. a needle on a meter.
|
||||
if counter % 3:
|
||||
with open(self.mic_level_file, 'w') as f:
|
||||
f.write("Energy: cur=" + str(energy) + " thresh=" +
|
||||
str(self.energy_threshold))
|
||||
f.close()
|
||||
counter = 0
|
||||
else:
|
||||
counter += 1
|
||||
counter += 1
|
||||
|
||||
# At first, the buffer is empty and must fill up. After that
|
||||
# just drop the first chunk bytes to keep it the same size.
|
||||
|
@ -290,7 +348,7 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
said_wake_word = self.wake_word_in_audio(byte_data + silence)
|
||||
|
||||
@staticmethod
|
||||
def create_audio_data(raw_data, source):
|
||||
def _create_audio_data(raw_data, source):
|
||||
"""
|
||||
Constructs an AudioData instance with the same parameters
|
||||
as the source and the specified frame_data
|
||||
|
@ -298,31 +356,54 @@ class ResponsiveRecognizer(speech_recognition.Recognizer):
|
|||
return AudioData(raw_data, source.SAMPLE_RATE, source.SAMPLE_WIDTH)
|
||||
|
||||
def listen(self, source, emitter):
|
||||
"""
|
||||
Listens for audio that Mycroft should respond to
|
||||
"""Listens for chunks of audio that Mycroft should perform STT on.
|
||||
|
||||
:param source: an ``AudioSource`` instance for reading from
|
||||
:param emitter: a pyee EventEmitter for sending when the wakeword
|
||||
has been found
|
||||
This will listen continuously for a wake-up-word, then return the
|
||||
audio chunk containing the spoken phrase that comes immediately
|
||||
afterwards.
|
||||
|
||||
Args:
|
||||
source (AudioSource): Source producing the audio chunks
|
||||
emitter (EventEmitter): Emitter for notifications of when recording
|
||||
begins and ends.
|
||||
|
||||
Returns:
|
||||
AudioData: audio with the user's utterance, minus the wake-up-word
|
||||
"""
|
||||
assert isinstance(source, AudioSource), "Source must be an AudioSource"
|
||||
|
||||
# bytes_per_sec = source.SAMPLE_RATE * source.SAMPLE_WIDTH
|
||||
sec_per_buffer = float(source.CHUNK) / source.SAMPLE_RATE
|
||||
|
||||
# Every time a new 'listen()' request begins, reset the threshold
|
||||
# used for silence detection. This is as good of a reset point as
|
||||
# any, as we expect the user and Mycroft to not be talking.
|
||||
# NOTE: adjust_for_ambient_noise() doc claims it will stop early if
|
||||
# speech is detected, but there is no code to actually do that.
|
||||
self.adjust_for_ambient_noise(source, 1.0)
|
||||
|
||||
logger.debug("Waiting for wake word...")
|
||||
self.wait_until_wake_word(source, sec_per_buffer)
|
||||
self._wait_until_wake_word(source, sec_per_buffer)
|
||||
|
||||
logger.debug("Recording...")
|
||||
emitter.emit("recognizer_loop:record_begin")
|
||||
frame_data = self.record_phrase(source, sec_per_buffer)
|
||||
audio_data = self.create_audio_data(frame_data, source)
|
||||
|
||||
# If enabled, play a wave file with a short sound to audibly
|
||||
# indicate recording has begun.
|
||||
if config.get('confirm_listening'):
|
||||
file = resolve_resource_file(
|
||||
config.get('sounds').get('start_listening'))
|
||||
if file:
|
||||
play_wav(file)
|
||||
|
||||
frame_data = self._record_phrase(source, sec_per_buffer)
|
||||
audio_data = self._create_audio_data(frame_data, source)
|
||||
emitter.emit("recognizer_loop:record_end")
|
||||
logger.debug("Thinking...")
|
||||
|
||||
return audio_data
|
||||
|
||||
def adjust_threshold(self, energy, seconds_per_buffer):
|
||||
def _adjust_threshold(self, energy, seconds_per_buffer):
|
||||
if self.dynamic_energy_threshold and energy > 0:
|
||||
# account for different chunk sizes and rates
|
||||
damping = (
|
||||
|
|
|
@ -32,6 +32,8 @@ import time # nopep8
|
|||
import subprocess # nopep8
|
||||
import curses # nopep8
|
||||
import curses.ascii # nopep8
|
||||
import textwrap # nopep8
|
||||
import json # nopep8
|
||||
from threading import Thread, Lock # nopep8
|
||||
from mycroft.messagebus.client.ws import WebsocketClient # nopep8
|
||||
from mycroft.messagebus.message import Message # nopep8
|
||||
|
@ -46,7 +48,7 @@ mutex = Lock()
|
|||
logger = getLogger("CLIClient")
|
||||
|
||||
utterances = []
|
||||
chat = []
|
||||
chat = [] # chat history, oldest at the lowest index
|
||||
line = "What time is it"
|
||||
bSimple = '--simple' in sys.argv
|
||||
bQuiet = '--quiet' in sys.argv
|
||||
|
@ -56,16 +58,19 @@ log_line_lr_scroll = 0 # amount to scroll left/right for long lines
|
|||
longest_visible_line = 0 # for HOME key
|
||||
|
||||
mergedLog = []
|
||||
log_filters = ["enclosure.mouth.viseme"]
|
||||
filteredLog = []
|
||||
default_log_filters = ["enclosure.mouth.viseme"]
|
||||
log_filters = list(default_log_filters)
|
||||
log_files = []
|
||||
|
||||
# Values used to display the audio meter
|
||||
show_meter = True
|
||||
meter_peak = 20
|
||||
meter_cur = 8
|
||||
meter_thresh = 5
|
||||
meter_cur = -1
|
||||
meter_thresh = -1
|
||||
|
||||
screen_mode = 0 # 0 = main, 1 = help, others in future?
|
||||
last_redraw = 0 # time when last full-redraw happened
|
||||
|
||||
##############################################################################
|
||||
# Helper functions
|
||||
|
@ -81,6 +86,30 @@ def stripNonAscii(text):
|
|||
return ''.join([i if ord(i) < 128 else ' ' for i in text])
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Settings
|
||||
|
||||
config_file = os.path.join(os.path.expanduser("~"), ".mycroft_cli.conf")
|
||||
|
||||
|
||||
def load_settings():
|
||||
global log_filters
|
||||
|
||||
try:
|
||||
with open(config_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
if "filters" in config:
|
||||
log_filters = config["filters"]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def save_settings():
|
||||
config["filters"] = log_filters
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config, f)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Log file monitoring
|
||||
|
||||
|
@ -94,8 +123,6 @@ class LogMonitorThread(Thread):
|
|||
log_files.append(filename)
|
||||
|
||||
def run(self):
|
||||
global mergedLog
|
||||
|
||||
while True:
|
||||
try:
|
||||
st_results = os.stat(self.filename)
|
||||
|
@ -111,6 +138,8 @@ class LogMonitorThread(Thread):
|
|||
def read_file_from(self, bytefrom):
|
||||
global meter_cur
|
||||
global meter_thresh
|
||||
global filteredLog
|
||||
global mergedLog
|
||||
|
||||
with open(self.filename, 'rb') as fh:
|
||||
fh.seek(bytefrom)
|
||||
|
@ -126,10 +155,13 @@ class LogMonitorThread(Thread):
|
|||
ignore = True
|
||||
break
|
||||
|
||||
if not ignore:
|
||||
if ignore:
|
||||
mergedLog.append(self.logid+line.strip())
|
||||
else:
|
||||
if bSimple:
|
||||
print line.strip()
|
||||
else:
|
||||
filteredLog.append(self.logid+line.strip())
|
||||
mergedLog.append(self.logid+line.strip())
|
||||
|
||||
|
||||
|
@ -184,6 +216,35 @@ def start_mic_monitor(filename):
|
|||
thread.start()
|
||||
|
||||
|
||||
def add_log_message(message):
|
||||
""" Show a message for the user (mixed in the logs) """
|
||||
global filteredLog
|
||||
global mergedLog
|
||||
|
||||
message = "@"+message # the first byte is a code
|
||||
filteredLog.append(message)
|
||||
mergedLog.append(message)
|
||||
scr.erase()
|
||||
scr.refresh()
|
||||
|
||||
|
||||
def rebuild_filtered_log():
|
||||
global filteredLog
|
||||
global mergedLog
|
||||
|
||||
filteredLog = []
|
||||
for line in mergedLog:
|
||||
# Apply filters
|
||||
ignore = False
|
||||
for filtered_text in log_filters:
|
||||
if filtered_text in line:
|
||||
ignore = True
|
||||
break
|
||||
|
||||
if not ignore:
|
||||
filteredLog.append(line)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Capturing output from Mycroft
|
||||
|
||||
|
@ -229,6 +290,7 @@ def init_screen():
|
|||
global CLR_LOG1
|
||||
global CLR_LOG2
|
||||
global CLR_LOG_DEBUG
|
||||
global CLR_LOG_CMDMESSAGE
|
||||
global CLR_METER_CUR
|
||||
global CLR_METER
|
||||
|
||||
|
@ -244,13 +306,14 @@ def init_screen():
|
|||
# 3 = dk green 7 = dk cyan
|
||||
# 4 = dk yellow 8 = lt gray
|
||||
CLR_HEADING = curses.color_pair(1)
|
||||
CLR_CHAT_RESP = curses.color_pair(7)
|
||||
CLR_CHAT_QUERY = curses.color_pair(1)
|
||||
CLR_CHAT_RESP = curses.color_pair(4)
|
||||
CLR_CHAT_QUERY = curses.color_pair(7)
|
||||
CLR_CMDLINE = curses.color_pair(7)
|
||||
CLR_INPUT = curses.color_pair(1)
|
||||
CLR_INPUT = curses.color_pair(7)
|
||||
CLR_LOG1 = curses.color_pair(3)
|
||||
CLR_LOG2 = curses.color_pair(6)
|
||||
CLR_LOG_DEBUG = curses.color_pair(4)
|
||||
CLR_LOG_CMDMESSAGE = curses.color_pair(2)
|
||||
CLR_METER_CUR = curses.color_pair(2)
|
||||
CLR_METER = curses.color_pair(4)
|
||||
|
||||
|
@ -261,15 +324,15 @@ def page_log(page_up):
|
|||
log_line_offset += 10
|
||||
else:
|
||||
log_line_offset -= 10
|
||||
if log_line_offset > len(mergedLog):
|
||||
log_line_offset = len(mergedLog)-10
|
||||
if log_line_offset > len(filteredLog):
|
||||
log_line_offset = len(filteredLog)-10
|
||||
if log_line_offset < 0:
|
||||
log_line_offset = 0
|
||||
draw_screen()
|
||||
|
||||
|
||||
def draw_meter():
|
||||
if not show_meter:
|
||||
if not show_meter or meter_cur == -1:
|
||||
return
|
||||
|
||||
# The meter will look something like this:
|
||||
|
@ -280,7 +343,8 @@ def draw_meter():
|
|||
# *
|
||||
# *
|
||||
# *
|
||||
# Cur Threshold
|
||||
# Where the left side is the current level and the right side is
|
||||
# the threshold level for 'silence'.
|
||||
global scr
|
||||
global meter_peak
|
||||
|
||||
|
@ -295,36 +359,46 @@ def draw_meter():
|
|||
h_thresh = clamp(int((float(meter_thresh) / scale) * height), 0, height-1)
|
||||
clr = curses.color_pair(4) # dark yellow
|
||||
|
||||
str_level = "{0:3} ".format(int(meter_cur)) # e.g. ' 4'
|
||||
str_thresh = "{0:4.2f}".format(meter_thresh) # e.g. '3.24'
|
||||
meter_width = len(str_level) + len(str_thresh) + 4
|
||||
for i in range(0, height):
|
||||
meter = ""
|
||||
if i == h_cur:
|
||||
meter = "{0:3} ".format(int(meter_cur)) # e.g. ' 4'
|
||||
# current energy level
|
||||
meter = str_level
|
||||
else:
|
||||
meter = " "
|
||||
meter = " " * len(str_level)
|
||||
|
||||
if i == h_thresh:
|
||||
meter += "---"
|
||||
else:
|
||||
meter += " "
|
||||
|
||||
if i == h_thresh:
|
||||
meter += "{0:4.2f}".format(meter_thresh) # e.g. '3.24'
|
||||
# add threshold indicator
|
||||
meter += "--- "
|
||||
else:
|
||||
meter += " "
|
||||
|
||||
scr.addstr(curses.LINES-1-i, curses.COLS-12, meter, clr)
|
||||
if i == h_thresh:
|
||||
# 'silence' threshold energy level
|
||||
meter += str_thresh
|
||||
|
||||
# draw the line
|
||||
meter += " " * (meter_width - len(meter))
|
||||
scr.addstr(curses.LINES-1-i, curses.COLS-len(meter)-1, meter, clr)
|
||||
|
||||
# draw an asterisk if the audio energy is at this level
|
||||
if i <= h_cur:
|
||||
if meter_cur > meter_thresh:
|
||||
clr_bar = curses.color_pair(3) # dark green for loud
|
||||
else:
|
||||
clr_bar = curses.color_pair(5) # dark blue for 'silent'
|
||||
scr.addstr(curses.LINES-1-i, curses.COLS-7, "*", clr_bar)
|
||||
scr.addstr(curses.LINES-1-i, curses.COLS-len(str_thresh)-4, "*",
|
||||
clr_bar)
|
||||
|
||||
|
||||
def draw_screen():
|
||||
global scr
|
||||
global log_line_offset
|
||||
global longest_visible_line
|
||||
global last_redraw
|
||||
|
||||
if not scr:
|
||||
return
|
||||
|
@ -332,10 +406,14 @@ def draw_screen():
|
|||
if not screen_mode == 0:
|
||||
return
|
||||
|
||||
scr.erase()
|
||||
if time.time() - last_redraw > 5: # every 5 seconds
|
||||
scr.clear()
|
||||
last_redraw = time.time()
|
||||
else:
|
||||
scr.erase()
|
||||
|
||||
# Display log output at the top
|
||||
cLogs = len(mergedLog)
|
||||
cLogs = len(filteredLog)
|
||||
cLogLinesToShow = curses.LINES-13
|
||||
start = clamp(cLogs - cLogLinesToShow, 0, cLogs - 1) - log_line_offset
|
||||
end = cLogs - log_line_offset
|
||||
|
@ -354,7 +432,7 @@ def draw_screen():
|
|||
y = 2
|
||||
len_line = 0
|
||||
for i in range(start, end):
|
||||
log = mergedLog[i]
|
||||
log = filteredLog[i]
|
||||
logid = log[0]
|
||||
if len(log) > 25 and log[5] == '-' and log[8] == '-':
|
||||
log = log[27:] # skip logid & date/time at the front of log line
|
||||
|
@ -368,6 +446,8 @@ def draw_screen():
|
|||
else:
|
||||
if logid == "1":
|
||||
clr = CLR_LOG1
|
||||
elif logid == "@":
|
||||
clr = CLR_LOG_CMDMESSAGE
|
||||
else:
|
||||
clr = CLR_LOG2
|
||||
|
||||
|
@ -390,35 +470,53 @@ def draw_screen():
|
|||
y += 1
|
||||
|
||||
# Log legend in the lower-right
|
||||
scr.addstr(curses.LINES-10, curses.COLS/2 + 2, "Log Output Legend",
|
||||
scr.addstr(curses.LINES-10, curses.COLS/2 + 2,
|
||||
make_titlebar("Log Output Legend", curses.COLS/2 - 2),
|
||||
CLR_HEADING)
|
||||
scr.addstr(curses.LINES-9, curses.COLS/2 + 2, "=" * (curses.COLS/2 - 4),
|
||||
CLR_HEADING)
|
||||
scr.addstr(curses.LINES-8, curses.COLS/2 + 2,
|
||||
scr.addstr(curses.LINES-9, curses.COLS/2 + 2,
|
||||
"DEBUG output",
|
||||
CLR_LOG_DEBUG)
|
||||
scr.addstr(curses.LINES-7, curses.COLS/2 + 2,
|
||||
scr.addstr(curses.LINES-8, curses.COLS/2 + 2,
|
||||
os.path.basename(log_files[0])+", other",
|
||||
CLR_LOG1)
|
||||
if len(log_files) > 1:
|
||||
scr.addstr(curses.LINES-6, curses.COLS/2 + 2,
|
||||
scr.addstr(curses.LINES-7, curses.COLS/2 + 2,
|
||||
os.path.basename(log_files[1]), CLR_LOG2)
|
||||
|
||||
# History log in the middle
|
||||
scr.addstr(curses.LINES-10, 0, "History", CLR_HEADING)
|
||||
scr.addstr(curses.LINES-9, 0, "=" * (curses.COLS/2), CLR_HEADING)
|
||||
chat_width = curses.COLS/2 - 2
|
||||
chat_height = 7
|
||||
chat_out = []
|
||||
scr.addstr(curses.LINES-10, 0, make_titlebar("History", chat_width),
|
||||
CLR_HEADING)
|
||||
|
||||
cChat = len(chat)
|
||||
if cChat:
|
||||
y = curses.LINES-8
|
||||
for i in range(cChat-clamp(cChat, 1, 5), cChat):
|
||||
chat_line = chat[i]
|
||||
if chat_line.startswith(">> "):
|
||||
clr = CLR_CHAT_RESP
|
||||
else:
|
||||
clr = CLR_CHAT_QUERY
|
||||
scr.addstr(y, 0, stripNonAscii(chat_line), clr)
|
||||
y += 1
|
||||
# Build a nicely wrapped version of the chat log
|
||||
idx_chat = len(chat)-1
|
||||
while len(chat_out) < chat_height and idx_chat >= 0:
|
||||
if chat[idx_chat][0] == '>':
|
||||
wrapper = textwrap.TextWrapper(initial_indent="",
|
||||
subsequent_indent=" ",
|
||||
width=chat_width)
|
||||
else:
|
||||
wrapper = textwrap.TextWrapper(width=chat_width)
|
||||
|
||||
chatlines = wrapper.wrap(chat[idx_chat])
|
||||
for txt in reversed(chatlines):
|
||||
if len(chat_out) >= chat_height:
|
||||
break
|
||||
chat_out.insert(0, txt)
|
||||
|
||||
idx_chat -= 1
|
||||
|
||||
# Output the chat
|
||||
y = curses.LINES-9
|
||||
for txt in chat_out:
|
||||
if txt.startswith(">> ") or txt.startswith(" "):
|
||||
clr = CLR_CHAT_RESP
|
||||
else:
|
||||
clr = CLR_CHAT_QUERY
|
||||
scr.addstr(y, 1, stripNonAscii(txt), clr)
|
||||
y += 1
|
||||
|
||||
# Command line at the bottom
|
||||
l = line
|
||||
|
@ -429,15 +527,20 @@ def draw_screen():
|
|||
l = line[1:]
|
||||
else:
|
||||
scr.addstr(curses.LINES-2, 0,
|
||||
"Input (':' for command mode, Ctrl+C to quit):",
|
||||
CLR_CMDLINE)
|
||||
scr.addstr(curses.LINES-1, 0, ">", CLR_CMDLINE)
|
||||
make_titlebar("Input (':' for command, Ctrl+C to quit)",
|
||||
curses.COLS-1),
|
||||
CLR_HEADING)
|
||||
scr.addstr(curses.LINES-1, 0, ">", CLR_HEADING)
|
||||
|
||||
draw_meter()
|
||||
scr.addstr(curses.LINES-1, 2, l, CLR_INPUT)
|
||||
scr.refresh()
|
||||
|
||||
|
||||
def make_titlebar(title, bar_length):
|
||||
return title + " " + ("=" * (bar_length - 1 - len(title)))
|
||||
|
||||
|
||||
def show_help():
|
||||
global scr
|
||||
global screen_mode
|
||||
|
@ -461,9 +564,12 @@ def show_help():
|
|||
CLR_CMDLINE)
|
||||
scr.addstr(11, 0, "=" * (curses.COLS-1),
|
||||
CLR_CMDLINE)
|
||||
scr.addstr(12, 0, ":help this screen")
|
||||
scr.addstr(13, 0, ":quit or :exit exit the program")
|
||||
scr.addstr(14, 0, ":meter [show|hide] display of microphone level")
|
||||
scr.addstr(12, 0, ":help this screen")
|
||||
scr.addstr(13, 0, ":quit or :exit exit the program")
|
||||
scr.addstr(14, 0, ":meter (show|hide) display of microphone level")
|
||||
scr.addstr(15, 0, ":filter [remove] 'str' adds or removes a log filter")
|
||||
scr.addstr(16, 0, ":filter (clear|reset) reset filters")
|
||||
scr.addstr(17, 0, ":filter (show|list) display current filters")
|
||||
|
||||
scr.addstr(curses.LINES-1, 0, center(23) + "Press any key to return",
|
||||
CLR_HEADING)
|
||||
|
@ -486,6 +592,8 @@ def center(str_len):
|
|||
|
||||
def handle_cmd(cmd):
|
||||
global show_meter
|
||||
global log_filters
|
||||
global mergedLog
|
||||
|
||||
if "show" in cmd and "log" in cmd:
|
||||
pass
|
||||
|
@ -494,10 +602,38 @@ def handle_cmd(cmd):
|
|||
elif "exit" in cmd or "quit" in cmd:
|
||||
return 1
|
||||
elif "meter" in cmd:
|
||||
# microphone level meter
|
||||
if "hide" in cmd or "off" in cmd:
|
||||
show_meter = False
|
||||
elif "show" in cmd or "on" in cmd:
|
||||
show_meter = True
|
||||
elif "filter" in cmd:
|
||||
if "show" in cmd or "list" in cmd:
|
||||
# display active filters
|
||||
add_log_message("Filters: "+str(log_filters))
|
||||
return
|
||||
|
||||
if "reset" in cmd or "clear" in cmd:
|
||||
log_filters = list(default_log_filters)
|
||||
else:
|
||||
# extract last word(s)
|
||||
cmd = cmd.strip()
|
||||
last_char = cmd[-1]
|
||||
if last_char == '"' or last_char == "'":
|
||||
parts = cmd.split(last_char)
|
||||
word = parts[-2]
|
||||
else:
|
||||
parts = cmd.split(" ")
|
||||
word = parts[-1]
|
||||
|
||||
if "remove" in cmd and word in log_filters:
|
||||
log_filters.remove(word)
|
||||
else:
|
||||
log_filters.append(word)
|
||||
|
||||
rebuild_filtered_log()
|
||||
add_log_message("Filters: "+str(log_filters))
|
||||
|
||||
# TODO: More commands
|
||||
# elif "find" in cmd:
|
||||
# ... search logs for given string
|
||||
|
@ -645,7 +781,6 @@ def simple_cli():
|
|||
event_thread.start()
|
||||
try:
|
||||
while True:
|
||||
# TODO: Change this mechanism
|
||||
# Sleep for a while so all the output that results
|
||||
# from the previous command finishes before we print.
|
||||
time.sleep(1.5)
|
||||
|
@ -683,5 +818,7 @@ if __name__ == "__main__":
|
|||
sys.stderr = sys.__stderr__
|
||||
simple_cli()
|
||||
else:
|
||||
load_settings()
|
||||
curses.wrapper(main)
|
||||
curses.endwin()
|
||||
save_settings()
|
||||
|
|
|
@ -118,10 +118,23 @@ input {
|
|||
|
||||
.panel .body li .connect-item input {
|
||||
padding: 5px;
|
||||
height: 26px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
width: calc(100% - 160px);
|
||||
width: calc(100% - 145px);
|
||||
font-size: 16px;
|
||||
padding-right: 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
background: url("../img/eye.png") no-repeat center;
|
||||
border: none;
|
||||
background-size: 30px;
|
||||
outline: none;
|
||||
margin-left: -42px;
|
||||
}
|
||||
|
||||
.panel .body li .error-item span {
|
||||
|
@ -242,19 +255,21 @@ span.connected {
|
|||
}
|
||||
|
||||
#centered {
|
||||
margin-right:auto;
|
||||
margin-left:auto;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#footer {
|
||||
position:fixed;
|
||||
bottom:10px;
|
||||
width:100%;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#cancelBtn {
|
||||
background: white;
|
||||
z-index: 999;
|
||||
color: #ff6666;
|
||||
padding: 7px; /* 10px of .button -3px to account for the thick border */
|
||||
padding: 7px; /* 10px of .button -3px to account for the thick border */
|
||||
border: 3px solid #ff6666;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -143,7 +143,13 @@ var WifiSetup = {
|
|||
connect.passwordInput = document.createElement("input");
|
||||
label.textContent = "Password: ";
|
||||
connect.passwordInput.type = "password";
|
||||
connect.passwordToggle = document.createElement("button");
|
||||
connect.passwordToggle.className = "password-toggle";
|
||||
connect.passwordToggle.addEventListener("click", function () {
|
||||
connect.passwordInput.type = connect.passwordInput.type == "text" ? "password" : "text";
|
||||
});
|
||||
connect.appendChild(connect.passwordInput);
|
||||
connect.appendChild(connect.passwordToggle);
|
||||
} else {
|
||||
label.className = "public";
|
||||
label.textContent = this.selectedNetword.ssid;
|
||||
|
|
|
@ -14,14 +14,15 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import inflection
|
||||
import re
|
||||
from genericpath import exists, isfile
|
||||
from os.path import join, dirname, expanduser
|
||||
|
||||
import inflection
|
||||
|
||||
from mycroft.util.log import getLogger
|
||||
from mycroft.util.json_helper import load_commented_json
|
||||
|
||||
__author__ = 'seanfitz, jdorleans'
|
||||
|
||||
|
@ -30,11 +31,33 @@ LOG = getLogger(__name__)
|
|||
DEFAULT_CONFIG = join(dirname(__file__), 'mycroft.conf')
|
||||
SYSTEM_CONFIG = '/etc/mycroft/mycroft.conf'
|
||||
USER_CONFIG = join(expanduser('~'), '.mycroft/mycroft.conf')
|
||||
REMOTE_CONFIG = "mycroft.ai"
|
||||
|
||||
load_order = [DEFAULT_CONFIG, REMOTE_CONFIG, SYSTEM_CONFIG, USER_CONFIG]
|
||||
|
||||
|
||||
class ConfigurationLoader(object):
|
||||
"""
|
||||
A utility for loading Mycroft configuration files.
|
||||
|
||||
Mycroft configuration comes from four potential locations:
|
||||
* Defaults found in 'mycroft.conf' in the code
|
||||
* Remote settings (coming from home.mycroft.ai)
|
||||
* System settings (typically found at /etc/mycroft/mycroft.conf
|
||||
* User settings (typically found at /home/<user>/.mycroft/mycroft.conf
|
||||
These get loaded in that order on top of each other. So a value specified
|
||||
in the Default would be overridden by a value with the same name found
|
||||
in the Remote. And a value in the Remote would be overridden by a value
|
||||
set in the User settings. Not all values exist at all levels.
|
||||
|
||||
See comments in the 'mycroft.conf' for more information about specific
|
||||
settings and where they reside.
|
||||
|
||||
Note:
|
||||
Values are overridden by name. This includes all data under that name,
|
||||
so you if a value contains a complex structure, you cannot specify
|
||||
only a single component of that structure -- you have to override the
|
||||
entire structure.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
|
@ -78,9 +101,8 @@ class ConfigurationLoader(object):
|
|||
def __load(config, location):
|
||||
if exists(location) and isfile(location):
|
||||
try:
|
||||
with open(location) as f:
|
||||
config.update(json.load(f))
|
||||
LOG.debug("Configuration '%s' loaded" % location)
|
||||
config.update(load_commented_json(location))
|
||||
LOG.debug("Configuration '%s' loaded" % location)
|
||||
except Exception, e:
|
||||
LOG.error("Error loading configuration '%s'" % location)
|
||||
LOG.error(repr(e))
|
||||
|
@ -126,6 +148,8 @@ class RemoteConfiguration(object):
|
|||
def __load(config, setting):
|
||||
for k, v in setting.iteritems():
|
||||
if k not in RemoteConfiguration.IGNORED_SETTINGS:
|
||||
# Translate the CamelCase values stored remotely into the
|
||||
# Python-style names used within mycroft-core.
|
||||
key = inflection.underscore(re.sub(r"Setting(s)?", "", k))
|
||||
if isinstance(v, dict):
|
||||
config[key] = config.get(key, {})
|
||||
|
@ -147,19 +171,39 @@ class RemoteConfiguration(object):
|
|||
|
||||
class ConfigurationManager(object):
|
||||
"""
|
||||
Static management utility for calling up cached configuration.
|
||||
Static management utility for accessing the cached configuration.
|
||||
This configuration is periodically updated from the remote server
|
||||
to keep in sync.
|
||||
"""
|
||||
|
||||
__config = None
|
||||
__listener = None
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
The cached configuration.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary representing the Mycroft configuration
|
||||
"""
|
||||
return ConfigurationManager.get()
|
||||
|
||||
@staticmethod
|
||||
def init(ws):
|
||||
ConfigurationManager.__listener = ConfigurationListener(ws)
|
||||
# Start listening for configuration update events on the messagebus
|
||||
ConfigurationManager.__listener = _ConfigurationListener(ws)
|
||||
|
||||
@staticmethod
|
||||
def load_defaults():
|
||||
ConfigurationManager.__config = ConfigurationLoader.load()
|
||||
return RemoteConfiguration.load(ConfigurationManager.__config)
|
||||
for location in load_order:
|
||||
LOG.info("Loading configuration: " + location)
|
||||
if location == REMOTE_CONFIG:
|
||||
RemoteConfiguration.load(ConfigurationManager.__config)
|
||||
else:
|
||||
ConfigurationManager.__config = ConfigurationLoader.load(
|
||||
ConfigurationManager.__config, [location])
|
||||
return ConfigurationManager.__config
|
||||
|
||||
@staticmethod
|
||||
def load_local(locations=None, keep_user_config=True):
|
||||
|
@ -177,7 +221,8 @@ class ConfigurationManager(object):
|
|||
"""
|
||||
Get cached configuration.
|
||||
|
||||
:return: A dictionary representing Mycroft configuration.
|
||||
Returns:
|
||||
dict: A dictionary representing the Mycroft configuration
|
||||
"""
|
||||
if not ConfigurationManager.__config:
|
||||
ConfigurationManager.load_defaults()
|
||||
|
@ -205,14 +250,21 @@ class ConfigurationManager(object):
|
|||
"""
|
||||
ConfigurationManager.update(config)
|
||||
location = SYSTEM_CONFIG if is_system else USER_CONFIG
|
||||
with open(location, 'rw') as f:
|
||||
config = json.load(f).update(config)
|
||||
loc_config = load_commented_json(location)
|
||||
with open(location, 'w') as f:
|
||||
config = loc_config.update(config)
|
||||
json.dump(config, f)
|
||||
|
||||
|
||||
class ConfigurationListener(object):
|
||||
class _ConfigurationListener(object):
|
||||
""" Utility to synchronize remote configuration changes locally
|
||||
|
||||
This listens to the messagebus for 'configuration.updated', and
|
||||
refreshes the cached configuration when this is encountered.
|
||||
"""
|
||||
|
||||
def __init__(self, ws):
|
||||
super(ConfigurationListener, self).__init__()
|
||||
super(_ConfigurationListener, self).__init__()
|
||||
ws.on("configuration.updated", self.updated)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -1,15 +1,56 @@
|
|||
{
|
||||
// Definition and documentation of all variables used by mycroft-core.
|
||||
//
|
||||
// Settings seen here are considered DEFAULT. Settings can also be
|
||||
// overridden at the REMOTE level (set by the user via
|
||||
// https://home.mycroft.ai), at the SYSTEM level (typically in the file
|
||||
// '/etc/mycroft/mycroft.conf'), or at the USER level (typically in the
|
||||
// file '~/.mycroft/mycroft.conf').
|
||||
//
|
||||
// The Override: comment indicates at what level (if any) this is
|
||||
// overridden by the system to a value besides the default shown here.
|
||||
|
||||
// Language used for speech-to-text and text-to-speech.
|
||||
// Code is a BCP-47 identifier (https://tools.ietf.org/html/bcp47), lowercased
|
||||
// TODO: save unmodified, lowercase upon demand
|
||||
// Override: none
|
||||
"lang": "en-us",
|
||||
|
||||
// Measurement units, either 'metric' or 'english'
|
||||
// Override: REMOTE
|
||||
"system_unit": "metric",
|
||||
|
||||
// Time format, either 'half' (e.g. "11:37 pm") or 'full' (e.g. "23:37")
|
||||
// Override: REMOTE
|
||||
"time_format": "half",
|
||||
|
||||
// Date format, either 'MDY' (e.g. "11-29-1978") or 'DMY' (e.g. "29-11-1978")
|
||||
// Override: REMOTE
|
||||
"date_format": "MDY",
|
||||
|
||||
// Play a beep when system begins to listen?
|
||||
// Override: none
|
||||
"confirm_listening": true,
|
||||
|
||||
// File locations of sounds to play for system events
|
||||
// Override: none
|
||||
"sounds": {
|
||||
"start_listening": "snd/start_listening.wav",
|
||||
"end_listening": "snd/end_listening.wav"
|
||||
},
|
||||
|
||||
// Mechanism used to play WAV audio files
|
||||
// Override: SYSTEM
|
||||
"play_wav_cmdline": "aplay %1",
|
||||
|
||||
// Mechanism used to play MP3 audio files
|
||||
// Override: SYSTEM
|
||||
"play_mp3_cmdline": "mpg123 %1",
|
||||
|
||||
// Location where the system resides
|
||||
// NOTE: Although this is set here, an Enclosure can override the value.
|
||||
// For example a mycroft-core running in a car could use the GPS.
|
||||
// Override: REMOTE
|
||||
"location": {
|
||||
"city": {
|
||||
"code": "Lawrence",
|
||||
|
@ -34,22 +75,36 @@
|
|||
"offset": -21600000
|
||||
}
|
||||
},
|
||||
|
||||
// General skill values
|
||||
// Override: none
|
||||
"skills": {
|
||||
// Directory to look for user skills
|
||||
"directory": "~/.mycroft/skills",
|
||||
// TODO: Old unused kludge, remove from code
|
||||
"stop_threshold": 2.0
|
||||
},
|
||||
|
||||
// Address of the REMOTE server
|
||||
// Override: none
|
||||
"server": {
|
||||
"url": "https://api.mycroft.ai",
|
||||
"version": "v1",
|
||||
"update": true,
|
||||
"metrics": false
|
||||
},
|
||||
|
||||
// The mycroft-core messagebus' websocket
|
||||
// Override: none
|
||||
"websocket": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 8181,
|
||||
"route": "/core",
|
||||
"ssl": false
|
||||
},
|
||||
|
||||
// Settings used by the wake-up-word listener
|
||||
// Override: REMOTE
|
||||
"listener": {
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
|
@ -59,20 +114,52 @@
|
|||
"multiplier": 1.0,
|
||||
"energy_ratio": 1.5
|
||||
},
|
||||
|
||||
// Mark 1 enclosure settings
|
||||
// Override: SYSTEM (e.g. Picroft)
|
||||
"enclosure": {
|
||||
// Platform name (e.g. 'Picroft', 'Mark_1'
|
||||
// Override: SYSTEM (set by specific enclosures)
|
||||
# "platform": "picroft",
|
||||
|
||||
// COMM params to the Arduino/faceplate
|
||||
"port": "/dev/ttyAMA0",
|
||||
"rate": 9600,
|
||||
"timeout": 5.0,
|
||||
|
||||
// ??
|
||||
"update": true,
|
||||
|
||||
// Run a self test at bootup?
|
||||
"test": false
|
||||
},
|
||||
|
||||
// Level of logs to store, one of "CRITICAL", "ERROR", "WARNGIN", "INFO", "DEBUG"
|
||||
// Override: none
|
||||
"log_level": "DEBUG",
|
||||
|
||||
// Messagebus types that will NOT be output to logs
|
||||
// Override: none
|
||||
"ignore_logs": ["enclosure.mouth.viseme"],
|
||||
|
||||
// Settings related to remote sessions
|
||||
// Overrride: none
|
||||
"session": {
|
||||
// Time To Live, in seconds
|
||||
"ttl": 180
|
||||
},
|
||||
|
||||
// Speech to Text parameters
|
||||
// Override: REMOTE
|
||||
"stt": {
|
||||
// Engine. Options: "mycroft", "google", "wit", "ibm"
|
||||
"module": "mycroft"
|
||||
},
|
||||
|
||||
// Text to Speech parameters
|
||||
// Override: REMOTE
|
||||
"tts": {
|
||||
// Engine. Options: "mimic", "google", "marytts", "fatts", "espeak", "spdsay"
|
||||
"module": "mimic",
|
||||
"mimic": {
|
||||
"voice": "ap"
|
||||
|
@ -82,6 +169,12 @@
|
|||
"voice": "m1"
|
||||
}
|
||||
},
|
||||
|
||||
// =================================================================
|
||||
// All of the follow are specific to particular skills and will soon
|
||||
// be removed from this file.
|
||||
// =================================================================
|
||||
|
||||
"wifi": {
|
||||
"setup": false
|
||||
},
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
|
||||
# Time-stamp: <2017-04-06 15:55:05 dmendyke> -*- mode: python; -*-
|
||||
|
||||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# Required Modules
|
||||
from signal import getsignal, signal, SIGKILL, SIGINT, SIGTERM # signals
|
||||
import os # Operating System functions
|
||||
|
||||
|
||||
#
|
||||
# Written by Daniel Mendyke [dmendyke@jaguarlandrover.com]
|
||||
__author__ = 'dmendyke'
|
||||
|
||||
|
||||
#
|
||||
# Wrapper around chain of handler functions for a specific system level signal.
|
||||
# Often used to trap Ctrl-C for specific application purposes.
|
||||
class Signal(object): # python 3+ class Signal
|
||||
|
||||
'''
|
||||
Capture and replace a signal handler with a user supplied function.
|
||||
The user supplied function is always called first then the previous
|
||||
handler, if it exists, will be called. It is possible to chain several
|
||||
signal handlers together by creating multiply instances of objects of
|
||||
this class, providing a different user functions for each instance. All
|
||||
provided user functions will be called in LIFO order.
|
||||
'''
|
||||
|
||||
#
|
||||
# Constructor
|
||||
# Get the previous handler function then set the passed function
|
||||
# as the new handler function for this signal
|
||||
|
||||
def __init__(self, sig_value, func):
|
||||
'''
|
||||
Create an instance of the signal handler class.
|
||||
|
||||
sig_value: The ID value of the signal to be captured.
|
||||
func: User supplied function that will act as the new signal handler.
|
||||
'''
|
||||
super(Signal, self).__init__() # python 3+ 'super().__init__()
|
||||
self.__sig_value = sig_value
|
||||
self.__user_func = func # store user passed function
|
||||
self.__previous_func = getsignal(sig_value) # get current handler
|
||||
signal(sig_value, self)
|
||||
|
||||
#
|
||||
# Called to handle the passed signal
|
||||
def __call__(self, signame, sf):
|
||||
'''
|
||||
Allows the instance of this class to be called as a function.
|
||||
When called it runs the user supplied signal handler than
|
||||
checks to see if there is a previously defined handler. If
|
||||
there is a previously defined handler call it.
|
||||
'''
|
||||
self.__user_func() # call user function
|
||||
try:
|
||||
if self.__previous_func:
|
||||
self.__previous_func(signame, sf)
|
||||
except KeyboardInterrupt as kbi:
|
||||
pass
|
||||
|
||||
#
|
||||
# reset the signal handler
|
||||
def __del__(self):
|
||||
'''
|
||||
Class destructor. Called during garbage collection.
|
||||
Resets the signal handler to the previous function.
|
||||
'''
|
||||
signal(self.__sig_value, self.__previous_func)
|
||||
|
||||
# End class Signal
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#
|
||||
# Create, delete and manipulate a PID file for this service
|
||||
# ------------------------------------------------------------------------------
|
||||
class Lock(object): # python 3+ 'class Lock'
|
||||
|
||||
'''
|
||||
Create and maintains the PID lock file for this application process.
|
||||
The PID lock file is located in /tmp/mycroft/*.pid. If another process
|
||||
of the same type is started, this class will 'attempt' to stop the
|
||||
previously running process and then change the process ID in the lock file.
|
||||
'''
|
||||
|
||||
#
|
||||
# Class constants
|
||||
DIRECTORY = '/tmp/mycroft'
|
||||
FILE = '/{}.pid'
|
||||
|
||||
#
|
||||
# Constructor
|
||||
def __init__(self, service):
|
||||
'''
|
||||
Builds the instance of this object. Holds the lock until the
|
||||
object is garbage collected.
|
||||
|
||||
service: Text string. The name of the service application
|
||||
to be locked (ie: skills, voice)
|
||||
'''
|
||||
super(Lock, self).__init__() # python 3+ 'super().__init__()'
|
||||
self.__pid = os.getpid() # PID of this application
|
||||
self.path = Lock.DIRECTORY + Lock.FILE.format(service)
|
||||
self.set_handlers() # set signal handlers
|
||||
self.create()
|
||||
|
||||
#
|
||||
# Reset the signal handlers to the 'delete' function
|
||||
def set_handlers(self):
|
||||
'''
|
||||
Trap both SIGINT and SIGTERM to gracefully clean up PID files
|
||||
'''
|
||||
self.__handlers = {SIGINT: Signal(SIGINT, self.delete)}
|
||||
self.__handlers = {SIGTERM: Signal(SIGTERM, self.delete)}
|
||||
|
||||
#
|
||||
# Check to see if the PID already exists
|
||||
# If it does exits perform several things:
|
||||
# Stop the current process
|
||||
# Delete the exiting file
|
||||
def exists(self):
|
||||
'''
|
||||
Check to see if the PID lock file currently exists. If it does
|
||||
than send a SIGTERM signal to the process defined by the value
|
||||
in the lock file. Catch the keyboard interrupt exception to
|
||||
prevent propagation if stopped by use of Ctrl-C.
|
||||
'''
|
||||
if not os.path.isfile(self.path):
|
||||
return
|
||||
with open(self.path, 'r') as L:
|
||||
try:
|
||||
os.kill(int(L.read()), SIGKILL)
|
||||
except Exception as E:
|
||||
pass
|
||||
|
||||
#
|
||||
# Create a lock file for this server process
|
||||
def touch(self):
|
||||
'''
|
||||
If needed, create the '/tmp/mycroft' directory than open the
|
||||
lock file for writting and store the current process ID (PID)
|
||||
as text.
|
||||
'''
|
||||
if not os.path.exists(Lock.DIRECTORY):
|
||||
os.makedirs(Lock.DIRECTORY)
|
||||
with open(self.path, 'w') as L:
|
||||
L.write('{}'.format(self.__pid))
|
||||
|
||||
#
|
||||
# Create the PID file
|
||||
def create(self):
|
||||
'''
|
||||
Checks to see if a lock file for this service already exists,
|
||||
if so have it killed. In either case write the process ID of
|
||||
the current service process to to the existing or newly created
|
||||
lock file in /tmp/mycroft/
|
||||
'''
|
||||
self.exists() # check for current running process
|
||||
self.touch()
|
||||
|
||||
#
|
||||
# Delete the PID file - but only if it has not been overwritten
|
||||
# by a duplicate service application
|
||||
def delete(self, *args):
|
||||
'''
|
||||
If the PID lock file contains the PID of this process delete it.
|
||||
|
||||
*args: Ignored. Required as this fuction is called as a signel
|
||||
handler.
|
||||
'''
|
||||
with open(self.path, 'r') as L:
|
||||
if self.__pid == L.read():
|
||||
os.unlink(self.path)
|
||||
|
||||
# End class Lock
|
|
@ -21,6 +21,8 @@ import tornado.web as web
|
|||
from mycroft.configuration import ConfigurationManager
|
||||
from mycroft.messagebus.service.ws import WebsocketEventHandler
|
||||
from mycroft.util import validate_param
|
||||
from mycroft.lock import Lock # creates/supports PID locking file
|
||||
|
||||
|
||||
__author__ = 'seanfitz', 'jdorleans'
|
||||
|
||||
|
@ -31,6 +33,7 @@ settings = {
|
|||
|
||||
def main():
|
||||
import tornado.options
|
||||
lock = Lock("service")
|
||||
tornado.options.parse_command_line()
|
||||
config = ConfigurationManager.get().get("websocket")
|
||||
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import time
|
||||
from alsaaudio import Mixer
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import yaml
|
||||
from adapt.intent import IntentBuilder
|
||||
from os.path import dirname, join
|
||||
|
||||
from mycroft.skills.scheduled_skills import ScheduledCRUDSkill
|
||||
from mycroft.util import play_mp3
|
||||
|
||||
__author__ = 'jdorleans'
|
||||
|
||||
|
||||
# TODO - Localization
|
||||
class AlarmSkill(ScheduledCRUDSkill):
|
||||
def __init__(self):
|
||||
super(AlarmSkill, self).__init__("AlarmSkill", None)
|
||||
self.alarm_on = False
|
||||
self.max_delay = self.config.get('max_delay')
|
||||
self.repeat_time = self.config.get('repeat_time')
|
||||
self.extended_delay = self.config.get('extended_delay')
|
||||
self.file_path = join(dirname(__file__), self.config.get('filename'))
|
||||
|
||||
def initialize(self):
|
||||
super(AlarmSkill, self).initialize()
|
||||
intent = IntentBuilder(
|
||||
'AlarmSkillStopIntent').require('AlarmSkillStopVerb') \
|
||||
.require('AlarmSkillKeyword').build()
|
||||
self.register_intent(intent, self.__handle_stop)
|
||||
|
||||
def load_data(self):
|
||||
try:
|
||||
with self.file_system.open(self.PENDING_TASK, 'r') as f:
|
||||
self.data = yaml.safe_load(f)
|
||||
assert self.data
|
||||
except:
|
||||
self.data = {}
|
||||
|
||||
def load_repeat_data(self):
|
||||
try:
|
||||
with self.file_system.open(self.REPEAT_TASK, 'r') as f:
|
||||
self.repeat_data = yaml.safe_load(f)
|
||||
assert self.repeat_data
|
||||
except:
|
||||
self.repeat_data = {}
|
||||
|
||||
def __handle_stop(self, message):
|
||||
if self.alarm_on:
|
||||
self.speak_dialog('alarm.off')
|
||||
self.alarm_on = False
|
||||
|
||||
def notify(self, timestamp):
|
||||
with self.LOCK:
|
||||
if self.data.__contains__(timestamp):
|
||||
volume = None
|
||||
self.alarm_on = True
|
||||
delay = self.__calculate_delay(self.max_delay)
|
||||
|
||||
while self.alarm_on and datetime.now() < delay:
|
||||
play_mp3(self.file_path).communicate()
|
||||
self.speak_dialog('alarm.stop')
|
||||
time.sleep(self.repeat_time + 2)
|
||||
if not volume and datetime.now() >= delay:
|
||||
mixer = Mixer()
|
||||
volume = mixer.getvolume()[0]
|
||||
mixer.setvolume(100)
|
||||
delay = self.__calculate_delay(self.extended_delay)
|
||||
if volume:
|
||||
Mixer().setvolume(volume)
|
||||
self.remove(timestamp)
|
||||
self.alarm_on = False
|
||||
self.save()
|
||||
|
||||
@staticmethod
|
||||
def __calculate_delay(seconds):
|
||||
return datetime.now() + timedelta(seconds=seconds)
|
||||
|
||||
def save(self):
|
||||
with self.file_system.open(self.PENDING_TASK, 'w') as f:
|
||||
yaml.safe_dump(self.data, f)
|
||||
with self.file_system.open(self.REPEAT_TASK, 'w') as f:
|
||||
yaml.safe_dump(self.repeat_data, f)
|
||||
if not self.alarm_on:
|
||||
self.schedule()
|
||||
|
||||
def stop(self):
|
||||
self.__handle_stop(None)
|
||||
|
||||
|
||||
def create_skill():
|
||||
return AlarmSkill()
|
Binary file not shown.
|
@ -1,3 +0,0 @@
|
|||
the alarm has been turned off
|
||||
the alarm has been shut off
|
||||
the alarm has been stopped
|
|
@ -1 +0,0 @@
|
|||
Say, turn off alarm to end this alarm
|
|
@ -1,3 +0,0 @@
|
|||
Alarm set for {{datetime}}
|
||||
You have a new alarm on {{datetime}}
|
||||
A new alarm for {{datetime}} was added
|
|
@ -1,2 +0,0 @@
|
|||
Sorry, I didn't find a valid date and time to alarm you.
|
||||
It was not possible to set an alarm for the informed date and time.
|
|
@ -1,2 +0,0 @@
|
|||
There is no alarm to be removed
|
||||
You don't have any alarm to be deleted
|
|
@ -1,3 +0,0 @@
|
|||
{{amount}} alarms removed
|
||||
{{amount}} alarms were cancelled
|
||||
You have deleted {{amount}} alarms
|
|
@ -1,3 +0,0 @@
|
|||
One alarm removed
|
||||
An alarm was cancelled
|
||||
You have deleted one alarm
|
|
@ -1,2 +0,0 @@
|
|||
You have an alarm on {{datetime}}
|
||||
There is an alarm set for {{datetime}}
|
|
@ -1,2 +0,0 @@
|
|||
There is no alarm to be listed
|
||||
You don't have any alarm to be listed
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "alarm in 2 months",
|
||||
"intent_type": "AlarmSkillCreateIntent",
|
||||
"intent": {
|
||||
"AlarmSkillCreateVerb": "alarm"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "alarm on july 4th 2016 at 3pm",
|
||||
"intent_type": "AlarmSkillCreateIntent",
|
||||
"intent": {
|
||||
"AlarmSkillCreateVerb": "alarm"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "set a timer for tomorrow evening",
|
||||
"intent_type": "AlarmSkillCreateIntent",
|
||||
"intent": {
|
||||
"AlarmSkillCreateVerb": "timer"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "set an alarm to next saturday",
|
||||
"intent_type": "AlarmSkillCreateIntent",
|
||||
"intent": {
|
||||
"AlarmSkillCreateVerb": "alarm"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "list two alarms",
|
||||
"intent_type": "AlarmSkillListIntent",
|
||||
"intent": {
|
||||
"AlarmSkillListVerb": "list",
|
||||
"AlarmSkillAmount": "2",
|
||||
"AlarmSkillKeyword": "alarms"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "show all my timers",
|
||||
"intent_type": "AlarmSkillListIntent",
|
||||
"intent": {
|
||||
"AlarmSkillListVerb": "show",
|
||||
"AlarmSkillAmount": "all",
|
||||
"AlarmSkillKeyword": "timers"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "cancel one alarm",
|
||||
"intent_type": "AlarmSkillDeleteIntent",
|
||||
"intent": {
|
||||
"AlarmSkillDeleteVerb": "cancel",
|
||||
"AlarmSkillAmount": "1",
|
||||
"AlarmSkillKeyword": "alarm"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "erase the next alarm",
|
||||
"intent_type": "AlarmSkillDeleteIntent",
|
||||
"intent": {
|
||||
"AlarmSkillDeleteVerb": "erase",
|
||||
"AlarmSkillAmount": "the next",
|
||||
"AlarmSkillKeyword": "alarm"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
all|all my
|
||||
1|one
|
||||
2|two
|
||||
the next
|
||||
the following
|
|
@ -1,4 +0,0 @@
|
|||
timer
|
||||
timers
|
||||
alarm
|
||||
alarms
|
|
@ -1,4 +0,0 @@
|
|||
erase
|
||||
cancel
|
||||
delete
|
||||
remove
|
|
@ -1,4 +0,0 @@
|
|||
timer
|
||||
timers
|
||||
alarm
|
||||
alarms
|
|
@ -1,2 +0,0 @@
|
|||
show
|
||||
list
|
|
@ -1,3 +0,0 @@
|
|||
off
|
||||
end
|
||||
stop
|
|
@ -1,151 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import math
|
||||
import time
|
||||
|
||||
import psutil as psutil
|
||||
from adapt.intent import IntentBuilder
|
||||
from os.path import dirname
|
||||
|
||||
from mycroft.skills.scheduled_skills import ScheduledSkill
|
||||
from mycroft.util import record, play_wav
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
__author__ = 'jdorleans'
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class AudioRecordSkill(ScheduledSkill):
|
||||
def __init__(self):
|
||||
super(AudioRecordSkill, self).__init__("AudioRecordSkill")
|
||||
self.free_disk = self.config.get('free_disk')
|
||||
self.max_time = self.config.get('max_time')
|
||||
self.notify_delay = self.config.get('notify_delay')
|
||||
self.rate = self.config.get('rate')
|
||||
self.channels = self.config.get('channels')
|
||||
self.file_path = self.config.get('filename')
|
||||
self.duration = 0
|
||||
self.notify_time = None
|
||||
self.play_process = None
|
||||
self.record_process = None
|
||||
|
||||
def initialize(self):
|
||||
intent = IntentBuilder("AudioRecordSkillIntent").require(
|
||||
"AudioRecordSkillKeyword").build()
|
||||
self.register_intent(intent, self.handle_record)
|
||||
|
||||
intent = IntentBuilder('AudioRecordSkillStopIntent').require(
|
||||
'AudioRecordSkillStopVerb') \
|
||||
.require('AudioRecordSkillKeyword').build()
|
||||
self.register_intent(intent, self.handle_stop)
|
||||
|
||||
intent = IntentBuilder('AudioRecordSkillPlayIntent').require(
|
||||
'AudioRecordSkillPlayVerb') \
|
||||
.require('AudioRecordSkillKeyword').build()
|
||||
self.register_intent(intent, self.handle_play)
|
||||
|
||||
intent = IntentBuilder('AudioRecordSkillStopPlayIntent').require(
|
||||
'AudioRecordSkillStopVerb') \
|
||||
.require('AudioRecordSkillPlayVerb').require(
|
||||
'AudioRecordSkillKeyword').build()
|
||||
self.register_intent(intent, self.handle_stop_play)
|
||||
|
||||
def handle_record(self, message):
|
||||
utterance = message.data.get('utterance')
|
||||
date = self.get_utc_time(utterance)
|
||||
now = self.get_utc_time()
|
||||
self.duration = self.get_duration(date, now)
|
||||
if self.is_free_disk_space():
|
||||
self.notify_time = now
|
||||
self.feedback_start()
|
||||
time.sleep(3)
|
||||
self.record_process = record(
|
||||
self.file_path, self.duration, self.rate, self.channels)
|
||||
self.schedule()
|
||||
else:
|
||||
self.speak_dialog("audio.record.disk.full")
|
||||
|
||||
def get_duration(self, date, now):
|
||||
duration = math.ceil(date - now)
|
||||
if duration <= 0:
|
||||
duration = self.max_time
|
||||
return int(duration)
|
||||
|
||||
def is_free_disk_space(self):
|
||||
space = self.duration * self.channels * self.rate / 1024 / 1024
|
||||
free_mb = psutil.disk_usage('/')[2] / 1024 / 1024
|
||||
if free_mb - space > self.free_disk:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def feedback_start(self):
|
||||
if self.duration > 0:
|
||||
self.speak_dialog(
|
||||
'audio.record.start.duration', {'duration': self.duration})
|
||||
else:
|
||||
self.speak_dialog('audio.record.start')
|
||||
|
||||
def handle_stop(self, message):
|
||||
self.speak_dialog('audio.record.stop')
|
||||
if self.record_process:
|
||||
self.stop_process(self.record_process)
|
||||
self.record_process = None
|
||||
self.cancel()
|
||||
|
||||
@staticmethod
|
||||
def stop_process(process):
|
||||
if process.poll() is None:
|
||||
process.terminate()
|
||||
process.wait()
|
||||
|
||||
def get_times(self):
|
||||
return [self.notify_time]
|
||||
|
||||
def notify(self, timestamp):
|
||||
if self.record_process and self.record_process.poll() is None:
|
||||
if self.is_free_disk_space():
|
||||
LOGGER.info("Recording...")
|
||||
self.notify_time = self.get_utc_time() + self.notify_delay
|
||||
self.schedule()
|
||||
else:
|
||||
self.handle_stop(None)
|
||||
self.speak_dialog("audio.record.disk.full")
|
||||
else:
|
||||
self.handle_stop(None)
|
||||
|
||||
def handle_play(self, message):
|
||||
self.play_process = play_wav(self.file_path)
|
||||
|
||||
def handle_stop_play(self, message):
|
||||
self.speak_dialog('audio.record.stop.play')
|
||||
if self.play_process:
|
||||
self.stop()
|
||||
self.play_process = None
|
||||
|
||||
def stop(self):
|
||||
if self.play_process:
|
||||
self.stop_process(self.play_process)
|
||||
if self.record_process:
|
||||
self.stop_process(self.record_process)
|
||||
|
||||
|
||||
def create_skill():
|
||||
return AudioRecordSkill()
|
|
@ -1,3 +0,0 @@
|
|||
There is not enough free disk space to record an audio
|
||||
Audio recording is not possible. You have to free some disk usage
|
||||
You have reached the maximum disk usage. Free some disk space to record an audio
|
|
@ -1,3 +0,0 @@
|
|||
Recording audio
|
||||
Starting audio recording
|
||||
Audio recording initialized
|
|
@ -1,3 +0,0 @@
|
|||
Recording audio for {{duration}} seconds
|
||||
Starting audio recording for {{duration}} seconds
|
||||
Audio recording for {{duration}} seconds initialized
|
|
@ -1,3 +0,0 @@
|
|||
Audio recording ended
|
||||
Stopping audio recording
|
||||
Audio recording was stopped
|
|
@ -1,3 +0,0 @@
|
|||
Audio reproducing ended
|
||||
Stopping audio playing
|
||||
Audio playing was stopped
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "record",
|
||||
"intent_type": "AudioRecordSkillIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillKeyword": "record"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "start recording",
|
||||
"intent_type": "AudioRecordSkillIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillKeyword": "recording"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "record audio for 10 minutes",
|
||||
"intent_type": "AudioRecordSkillIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillKeyword": "record"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "stop recording",
|
||||
"intent_type": "AudioRecordSkillStopIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillStopVerb": "stop",
|
||||
"AudioRecordSkillKeyword": "recording"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "end record",
|
||||
"intent_type": "AudioRecordSkillStopIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillStopVerb": "end",
|
||||
"AudioRecordSkillKeyword": "record"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "cancel recording",
|
||||
"intent_type": "AudioRecordSkillStopIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillStopVerb": "cancel",
|
||||
"AudioRecordSkillKeyword": "recording"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "play record",
|
||||
"intent_type": "AudioRecordSkillPlayIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillPlayVerb": "play",
|
||||
"AudioRecordSkillKeyword": "record"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "stop playing record",
|
||||
"intent_type": "AudioRecordSkillStopPlayIntent",
|
||||
"intent": {
|
||||
"AudioRecordSkillStopVerb": "stop",
|
||||
"AudioRecordSkillPlayVerb": "playing",
|
||||
"AudioRecordSkillKeyword": "record"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
record
|
||||
recording
|
|
@ -1,9 +0,0 @@
|
|||
run
|
||||
play
|
||||
replay
|
||||
playback
|
||||
reproduce
|
||||
running
|
||||
playing
|
||||
replaying
|
||||
reproducing
|
|
@ -1,3 +0,0 @@
|
|||
end
|
||||
stop
|
||||
cancel
|
|
@ -1,78 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from adapt.intent import IntentBuilder
|
||||
from requests import HTTPError
|
||||
|
||||
from mycroft.api import DeviceApi
|
||||
from mycroft.messagebus.message import Message
|
||||
from mycroft.skills.scheduled_skills import ScheduledSkill
|
||||
|
||||
__author__ = 'augustnmonteiro'
|
||||
|
||||
|
||||
class ConfigurationSkill(ScheduledSkill):
|
||||
def __init__(self):
|
||||
super(ConfigurationSkill, self).__init__("ConfigurationSkill")
|
||||
self.max_delay = self.config.get('max_delay')
|
||||
self.api = DeviceApi()
|
||||
|
||||
def initialize(self):
|
||||
intent = IntentBuilder("UpdateConfigurationIntent") \
|
||||
.require("ConfigurationSkillKeyword") \
|
||||
.require("ConfigurationSkillUpdateVerb") \
|
||||
.build()
|
||||
self.register_intent(intent, self.handle_update_intent)
|
||||
self.schedule()
|
||||
|
||||
def handle_update_intent(self, message):
|
||||
try:
|
||||
self.update()
|
||||
self.speak_dialog("config.updated")
|
||||
except HTTPError as e:
|
||||
self.__api_error(e)
|
||||
|
||||
def notify(self, timestamp):
|
||||
try:
|
||||
self.update()
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 401:
|
||||
self.log.warn("Impossible to update configuration because "
|
||||
"device isn't paired")
|
||||
self.schedule()
|
||||
|
||||
def update(self):
|
||||
config = self.api.find_setting()
|
||||
location = self.api.find_location()
|
||||
if location:
|
||||
config["location"] = location
|
||||
self.emitter.emit(Message("configuration.updated", config))
|
||||
|
||||
def __api_error(self, e):
|
||||
if e.response.status_code == 401:
|
||||
self.emitter.emit(Message("mycroft.not.paired"))
|
||||
|
||||
def get_times(self):
|
||||
return [self.get_utc_time() + self.max_delay]
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_skill():
|
||||
return ConfigurationSkill()
|
|
@ -1,3 +0,0 @@
|
|||
Configuration updated.
|
||||
Your device configuration has been updated.
|
||||
Your device has been configured.
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "update configuration",
|
||||
"intent_type": "UpdateConfigurationIntent",
|
||||
"intent": {
|
||||
"UpdateConfigurationPhrase": "update configuration"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
config
|
||||
configuration
|
||||
setting
|
|
@ -1,4 +0,0 @@
|
|||
update
|
||||
reload
|
||||
sync
|
||||
synchronize
|
|
@ -23,7 +23,7 @@ from os.path import dirname, exists, isdir
|
|||
from mycroft.configuration import ConfigurationManager
|
||||
from mycroft.messagebus.client.ws import WebsocketClient
|
||||
from mycroft.skills.core import create_skill_descriptor, load_skill
|
||||
from mycroft.skills.intent import create_skill as create_intent_skill
|
||||
from mycroft.skills.intent import Intent
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
__author__ = 'seanfitz'
|
||||
|
@ -44,7 +44,7 @@ class SkillContainer(object):
|
|||
sys.path.append(params.dir)
|
||||
self.dir = params.dir
|
||||
|
||||
self.enable_intent_skill = params.enable_intent_skill
|
||||
self.enable_intent = params.enable_intent
|
||||
|
||||
self.__init_client(params)
|
||||
|
||||
|
@ -57,7 +57,7 @@ class SkillContainer(object):
|
|||
parser.add_argument("--host", default=None)
|
||||
parser.add_argument("--port", default=None)
|
||||
parser.add_argument("--use-ssl", action='store_true', default=False)
|
||||
parser.add_argument("--enable-intent-skill", action='store_true',
|
||||
parser.add_argument("--enable-intent", action='store_true',
|
||||
default=False)
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
@ -74,10 +74,9 @@ class SkillContainer(object):
|
|||
ssl=params.use_ssl)
|
||||
|
||||
def load_skill(self):
|
||||
if self.enable_intent_skill:
|
||||
intent_skill = create_intent_skill()
|
||||
intent_skill.bind(self.ws)
|
||||
intent_skill.initialize()
|
||||
if self.enable_intent:
|
||||
Intent(self.ws)
|
||||
|
||||
skill_descriptor = create_skill_descriptor(self.dir)
|
||||
self.skill = load_skill(skill_descriptor, self.ws)
|
||||
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
import abc
|
||||
import imp
|
||||
import time
|
||||
import signal
|
||||
|
||||
import os.path
|
||||
import re
|
||||
from adapt.intent import Intent
|
||||
import signal
|
||||
import time
|
||||
from os.path import join, dirname, splitext, isdir
|
||||
|
||||
from adapt.intent import Intent
|
||||
|
||||
from mycroft.client.enclosure.api import EnclosureAPI
|
||||
from mycroft.configuration import ConfigurationManager
|
||||
from mycroft.dialog import DialogLoader
|
||||
|
@ -37,12 +37,8 @@ __author__ = 'seanfitz'
|
|||
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||
|
||||
PRIMARY_SKILLS = ['intent', 'wake']
|
||||
BLACKLISTED_SKILLS = ["send_sms", "media"]
|
||||
SKILLS_BASEDIR = dirname(__file__)
|
||||
THIRD_PARTY_SKILLS_DIR = ["/opt/mycroft/third_party", "/opt/mycroft/skills"]
|
||||
# Note: /opt/mycroft/skills is recommended, /opt/mycroft/third_party
|
||||
# is for backwards compatibility
|
||||
SKILLS_DIR = "/opt/mycroft/skills"
|
||||
|
||||
MainModule = '__init__'
|
||||
|
||||
|
@ -151,18 +147,12 @@ def create_skill_descriptor(skill_folder):
|
|||
return {"name": os.path.basename(skill_folder), "info": info}
|
||||
|
||||
|
||||
def load_skills(emitter, skills_root=SKILLS_BASEDIR):
|
||||
def load_skills(emitter, skills_root=SKILLS_DIR):
|
||||
logger.info("Checking " + skills_root + " for new skills")
|
||||
skill_list = []
|
||||
skills = get_skills(skills_root)
|
||||
for skill in skills:
|
||||
if skill['name'] in PRIMARY_SKILLS:
|
||||
skill_list.append(load_skill(skill, emitter))
|
||||
for skill in get_skills(skills_root):
|
||||
skill_list.append(load_skill(skill, emitter))
|
||||
|
||||
for skill in skills:
|
||||
if (skill['name'] not in PRIMARY_SKILLS and
|
||||
skill['name'] not in BLACKLISTED_SKILLS):
|
||||
skill_list.append(load_skill(skill, emitter))
|
||||
return skill_list
|
||||
|
||||
|
||||
|
@ -187,6 +177,7 @@ class MycroftSkill(object):
|
|||
self.registered_intents = []
|
||||
self.log = getLogger(name)
|
||||
self.reload_skill = True
|
||||
self.events = []
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
|
@ -260,6 +251,7 @@ class MycroftSkill(object):
|
|||
|
||||
if handler:
|
||||
self.emitter.on(intent_parser.name, receive_handler)
|
||||
self.events.append((intent_parser.name, receive_handler))
|
||||
|
||||
def disable_intent(self, intent_name):
|
||||
"""Disable a registered intent"""
|
||||
|
@ -339,4 +331,11 @@ class MycroftSkill(object):
|
|||
process termination. The skill implementation must
|
||||
shutdown all processes and operations in execution.
|
||||
"""
|
||||
|
||||
# removing events
|
||||
for e, f in self.events:
|
||||
self.emitter.remove(e, f)
|
||||
|
||||
self.emitter.emit(
|
||||
Message("detach_skill", {"skill_name": self.name + ":"}))
|
||||
self.stop()
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import datetime
|
||||
from os.path import dirname
|
||||
|
||||
import tzlocal
|
||||
from adapt.intent import IntentBuilder
|
||||
from astral import Astral
|
||||
from pytz import timezone
|
||||
|
||||
from mycroft.skills.core import MycroftSkill
|
||||
|
||||
__author__ = 'ryanleesipes', 'jdorleans'
|
||||
|
||||
|
||||
# TODO - Localization
|
||||
class TimeSkill(MycroftSkill):
|
||||
def __init__(self):
|
||||
super(TimeSkill, self).__init__("TimeSkill")
|
||||
self.astral = Astral()
|
||||
self.init_format()
|
||||
|
||||
def init_format(self):
|
||||
if self.config_core.get('time_format') == 'full':
|
||||
self.format = "%H:%M"
|
||||
else:
|
||||
self.format = "%I:%M, %p"
|
||||
|
||||
def initialize(self):
|
||||
intent = IntentBuilder("TimeIntent").require("QueryKeyword") \
|
||||
.require("TimeKeyword").optionally("Location").build()
|
||||
self.register_intent(intent, self.handle_intent)
|
||||
|
||||
def get_timezone(self, locale):
|
||||
try:
|
||||
# This handles common city names, like "Dallas" or "Paris"
|
||||
return timezone(self.astral[locale].timezone)
|
||||
except:
|
||||
try:
|
||||
# This handles codes like "America/Los_Angeles"
|
||||
return timezone(locale)
|
||||
except:
|
||||
return None
|
||||
|
||||
def handle_intent(self, message):
|
||||
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
|
||||
|
||||
# Convert UTC to appropriate timezone and format
|
||||
time = nowUTC.astimezone(tz).strftime(self.format)
|
||||
self.speak_dialog("time.current", {"time": time})
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_skill():
|
||||
return TimeSkill()
|
|
@ -1,5 +0,0 @@
|
|||
{{time}}
|
||||
It is {{time}}
|
||||
{{time}}
|
||||
It's {{time}}
|
||||
Currently {{time}}
|
|
@ -1 +0,0 @@
|
|||
I could not find the timezone for {{location}}
|
|
@ -1 +0,0 @@
|
|||
(at|in) (?P<Location>.*)
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "what time is it",
|
||||
"intent_type": "TimeIntent",
|
||||
"intent": {
|
||||
"TimeKeyword": "what time is it"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "whats the time",
|
||||
"intent_type": "TimeIntent",
|
||||
"intent": {
|
||||
"TimeKeyword": "whats the time"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "time is it",
|
||||
"intent_type": "TimeIntent",
|
||||
"intent": {
|
||||
"TimeKeyword": "time is it"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
date
|
||||
day
|
|
@ -1,2 +0,0 @@
|
|||
what
|
||||
tell
|
|
@ -1 +0,0 @@
|
|||
time
|
|
@ -1,108 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import sys
|
||||
import urllib2
|
||||
import webbrowser
|
||||
|
||||
from adapt.intent import IntentBuilder
|
||||
from adapt.tools.text.tokenizer import EnglishTokenizer
|
||||
from os.path import dirname, join
|
||||
|
||||
from mycroft.skills.core import MycroftSkill
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
__author__ = 'seanfitz'
|
||||
|
||||
IFL_TEMPLATE = "http://www.google.com/search?&sourceid=navclient&btnI=I&q=%s"
|
||||
|
||||
|
||||
class DesktopLauncherSkill(MycroftSkill):
|
||||
def __init__(self):
|
||||
MycroftSkill.__init__(self, "DesktopLauncherSkill")
|
||||
self.appmap = {}
|
||||
|
||||
def initialize(self):
|
||||
try:
|
||||
import gio
|
||||
except:
|
||||
sys.path.append("/usr/lib/python2.7/dist-packages")
|
||||
try:
|
||||
import gio
|
||||
except:
|
||||
logger.error("Could not import gio")
|
||||
return
|
||||
|
||||
tokenizer = EnglishTokenizer()
|
||||
|
||||
for app in gio.app_info_get_all():
|
||||
name = app.get_name().lower()
|
||||
entry = [app]
|
||||
tokenized_name = tokenizer.tokenize(name)[0]
|
||||
|
||||
if name in self.appmap:
|
||||
self.appmap[name] += entry
|
||||
else:
|
||||
self.appmap[name] = entry
|
||||
|
||||
self.register_vocabulary(name, "Application")
|
||||
if name != tokenized_name:
|
||||
self.register_vocabulary(tokenized_name, "Application")
|
||||
if tokenized_name in self.appmap:
|
||||
self.appmap[tokenized_name] += entry
|
||||
else:
|
||||
self.appmap[tokenized_name] = entry
|
||||
|
||||
launch_intent = IntentBuilder(
|
||||
"LaunchDesktopApplicationIntent").require("LaunchKeyword").require(
|
||||
"Application").build()
|
||||
self.register_intent(launch_intent, self.handle_launch_desktop_app)
|
||||
|
||||
launch_website_intent = IntentBuilder(
|
||||
"LaunchWebsiteIntent").require("LaunchKeyword").require(
|
||||
"Website").build()
|
||||
self.register_intent(launch_website_intent, self.handle_launch_website)
|
||||
|
||||
search_website = IntentBuilder("SearchWebsiteIntent").require(
|
||||
"SearchKeyword").require("Website").require(
|
||||
"SearchTerms").build()
|
||||
self.register_intent(search_website, self.handle_search_website)
|
||||
|
||||
def handle_launch_desktop_app(self, message):
|
||||
app_name = message.data.get('Application')
|
||||
apps = self.appmap.get(app_name)
|
||||
if apps and len(apps) > 0:
|
||||
apps[0].launch()
|
||||
|
||||
def handle_launch_website(self, message):
|
||||
site = message.data.get("Website")
|
||||
webbrowser.open(IFL_TEMPLATE % (urllib2.quote(site)))
|
||||
|
||||
def handle_search_website(self, message):
|
||||
site = message.data.get("Website")
|
||||
search_terms = message.data.get("SearchTerms")
|
||||
search_str = site + " " + search_terms
|
||||
webbrowser.open(IFL_TEMPLATE % (urllib2.quote(search_str)))
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_skill():
|
||||
return DesktopLauncherSkill()
|
|
@ -1,3 +0,0 @@
|
|||
for (?P<SearchTerms>.*)
|
||||
for (?P<SearchTerms>.*) on
|
||||
(?P<SearchTerms>.*) on
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "search for kittens on youtube",
|
||||
"intent_type": "SearchWebsiteIntent",
|
||||
"intent": {
|
||||
"SearchKeyword": "search",
|
||||
"Website": "youtube",
|
||||
"SearchTerms": "for kittens"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"utterance": "find puppies on wikipedia",
|
||||
"intent_type": "SearchWebsiteIntent",
|
||||
"intent": {
|
||||
"SearchKeyword": "find",
|
||||
"Website": "wikipedia",
|
||||
"SearchTerms": "puppies"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "launch imgur",
|
||||
"intent_type": "LaunchWebsiteIntent",
|
||||
"intent": {
|
||||
"LaunchKeyword": "launch",
|
||||
"Website": "imgur"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "open tumblr",
|
||||
"intent_type": "LaunchWebsiteIntent",
|
||||
"intent": {
|
||||
"LaunchKeyword": "open",
|
||||
"Website": "tumblr"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
launch
|
||||
open
|
||||
run
|
|
@ -1,2 +0,0 @@
|
|||
search
|
||||
find
|
|
@ -1,25 +0,0 @@
|
|||
google
|
||||
facebook
|
||||
amazon
|
||||
youtube
|
||||
yahoo
|
||||
wikipedia
|
||||
ebay
|
||||
twitter
|
||||
go
|
||||
craigslist
|
||||
reddit
|
||||
linkedin
|
||||
netflix
|
||||
live
|
||||
bing
|
||||
pinterest
|
||||
espn
|
||||
imgur
|
||||
tumblr
|
||||
chase
|
||||
cnn
|
||||
paypal
|
||||
instagram
|
||||
blogspot
|
||||
apple
|
|
@ -1,80 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import subprocess
|
||||
|
||||
from adapt.intent import IntentBuilder
|
||||
from os.path import join, dirname
|
||||
|
||||
from mycroft.messagebus.message import Message
|
||||
from mycroft.skills.core import MycroftSkill
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
__author__ = 'jdorleans'
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class DialCallSkill(MycroftSkill):
|
||||
DBUS_CMD = [
|
||||
"dbus-send", "--print-reply",
|
||||
"--dest=com.canonical.TelephonyServiceHandler",
|
||||
"/com/canonical/TelephonyServiceHandler",
|
||||
"com.canonical.TelephonyServiceHandler.StartCall"
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super(DialCallSkill, self).__init__(name="DialCallSkill")
|
||||
self.contacts = {
|
||||
'jonathan': '12345678', 'ryan': '23456789',
|
||||
'sean': '34567890'} # TODO - Use API
|
||||
|
||||
def initialize(self):
|
||||
intent = IntentBuilder("DialCallIntent").require(
|
||||
"DialCallKeyword").require("Contact").build()
|
||||
self.register_intent(intent, self.handle_intent)
|
||||
|
||||
def handle_intent(self, message):
|
||||
try:
|
||||
contact = message.data.get("Contact").lower()
|
||||
|
||||
if contact in self.contacts:
|
||||
number = self.contacts.get(contact)
|
||||
self.__call(number)
|
||||
self.__notify(contact, number)
|
||||
|
||||
except Exception as e:
|
||||
LOGGER.error("Error: {0}".format(e))
|
||||
|
||||
def __call(self, number):
|
||||
cmd = list(self.DBUS_CMD)
|
||||
cmd.append("string:" + number)
|
||||
cmd.append("string:ofono/ofono/account0")
|
||||
subprocess.call(cmd)
|
||||
|
||||
def __notify(self, contact, number):
|
||||
self.emitter.emit(Message("dial_call", {
|
||||
'contact': contact, 'number': number
|
||||
}))
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_skill():
|
||||
return DialCallSkill()
|
|
@ -1 +0,0 @@
|
|||
(call|phone) (?P<Contact>.*)
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"utterance": "call mark",
|
||||
"intent_type": "DialCallIntent",
|
||||
"intent": {
|
||||
"DialCallKeyword": "call",
|
||||
"Contact": "mark"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
call
|
||||
phone
|
|
@ -1,63 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from os.path import dirname
|
||||
|
||||
from adapt.intent import IntentBuilder
|
||||
from mycroft.skills.core import MycroftSkill
|
||||
from mycroft.util.log import getLogger
|
||||
|
||||
__author__ = 'eward'
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
class HelloWorldSkill(MycroftSkill):
|
||||
|
||||
def __init__(self):
|
||||
super(HelloWorldSkill, self).__init__(name="HelloWorldSkill")
|
||||
|
||||
def initialize(self):
|
||||
thank_you_intent = IntentBuilder("ThankYouIntent").\
|
||||
require("ThankYouKeyword").build()
|
||||
self.register_intent(thank_you_intent, self.handle_thank_you_intent)
|
||||
|
||||
how_are_you_intent = IntentBuilder("HowAreYouIntent").\
|
||||
require("HowAreYouKeyword").build()
|
||||
self.register_intent(how_are_you_intent,
|
||||
self.handle_how_are_you_intent)
|
||||
|
||||
hello_world_intent = IntentBuilder("HelloWorldIntent").\
|
||||
require("HelloWorldKeyword").build()
|
||||
self.register_intent(hello_world_intent,
|
||||
self.handle_hello_world_intent)
|
||||
|
||||
def handle_thank_you_intent(self, message):
|
||||
self.speak_dialog("welcome")
|
||||
|
||||
def handle_how_are_you_intent(self, message):
|
||||
self.speak_dialog("how.are.you")
|
||||
|
||||
def handle_hello_world_intent(self, message):
|
||||
self.speak_dialog("hello.world")
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
def create_skill():
|
||||
return HelloWorldSkill()
|
|
@ -1,3 +0,0 @@
|
|||
Hello world
|
||||
Hello
|
||||
Hi to you too
|
|
@ -1,6 +0,0 @@
|
|||
I'm doing well
|
||||
Pretty well
|
||||
Not bad
|
||||
I'm doing excellent
|
||||
Could be better
|
||||
I'm doing very well
|
|
@ -1,6 +0,0 @@
|
|||
Any time.
|
||||
Glad to be of service.
|
||||
Glad to help.
|
||||
My Pleasure.
|
||||
No problem.
|
||||
You're welcome.
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "Thank you",
|
||||
"intent_type": "ThankYouIntent",
|
||||
"intent": {
|
||||
"ThankYouKeyword": "thank you"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "Thanks",
|
||||
"intent_type": "ThankYouIntent",
|
||||
"intent": {
|
||||
"ThankYouKeyword": "thanks"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "Hello world",
|
||||
"intent_type": "HelloWorldIntent",
|
||||
"intent": {
|
||||
"HelloWorldKeyword": "hello world"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "Greetings",
|
||||
"intent_type": "HelloWorldIntent",
|
||||
"intent": {
|
||||
"HelloWorldKeyword": "greetings"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "How are you doing",
|
||||
"intent_type": "HowAreYouIntent",
|
||||
"intent": {
|
||||
"HowAreYouKeyword": "how are you"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"utterance": "How has your day been",
|
||||
"intent_type": "HowAreYouIntent",
|
||||
"intent": {
|
||||
"HowAreYouKeyword": "how has your day been"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
hello world
|
||||
greetings
|
|
@ -1,3 +0,0 @@
|
|||
how are you
|
||||
how have you been
|
||||
how has your day been
|
|
@ -1,2 +0,0 @@
|
|||
thank you
|
||||
thanks
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue