Merge tag 'release/v0.8.9'

pull/632/head^2
Arron Atchison 2017-04-20 17:57:15 -07:00
commit 8bc55ac7f8
274 changed files with 1420 additions and 3859 deletions

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
*.pyc
*.swp
*~
msm
mimic
pocketsphinx-python
*.egg-info/

View File

@ -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.

View File

@ -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

121
msm/msm Executable file
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 = (

View File

@ -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()

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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
},

197
mycroft/lock/__init__.py Normal file
View File

@ -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

View File

@ -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")

View File

@ -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.

View File

@ -1,3 +0,0 @@
the alarm has been turned off
the alarm has been shut off
the alarm has been stopped

View File

@ -1 +0,0 @@
Say, turn off alarm to end this alarm

View File

@ -1,3 +0,0 @@
Alarm set for {{datetime}}
You have a new alarm on {{datetime}}
A new alarm for {{datetime}} was added

View File

@ -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.

View File

@ -1,2 +0,0 @@
There is no alarm to be removed
You don't have any alarm to be deleted

View File

@ -1,3 +0,0 @@
{{amount}} alarms removed
{{amount}} alarms were cancelled
You have deleted {{amount}} alarms

View File

@ -1,3 +0,0 @@
One alarm removed
An alarm was cancelled
You have deleted one alarm

View File

@ -1,2 +0,0 @@
You have an alarm on {{datetime}}
There is an alarm set for {{datetime}}

View File

@ -1,2 +0,0 @@
There is no alarm to be listed
You don't have any alarm to be listed

View File

@ -1,7 +0,0 @@
{
"utterance": "alarm in 2 months",
"intent_type": "AlarmSkillCreateIntent",
"intent": {
"AlarmSkillCreateVerb": "alarm"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "alarm on july 4th 2016 at 3pm",
"intent_type": "AlarmSkillCreateIntent",
"intent": {
"AlarmSkillCreateVerb": "alarm"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "set a timer for tomorrow evening",
"intent_type": "AlarmSkillCreateIntent",
"intent": {
"AlarmSkillCreateVerb": "timer"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "set an alarm to next saturday",
"intent_type": "AlarmSkillCreateIntent",
"intent": {
"AlarmSkillCreateVerb": "alarm"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "list two alarms",
"intent_type": "AlarmSkillListIntent",
"intent": {
"AlarmSkillListVerb": "list",
"AlarmSkillAmount": "2",
"AlarmSkillKeyword": "alarms"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "show all my timers",
"intent_type": "AlarmSkillListIntent",
"intent": {
"AlarmSkillListVerb": "show",
"AlarmSkillAmount": "all",
"AlarmSkillKeyword": "timers"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "cancel one alarm",
"intent_type": "AlarmSkillDeleteIntent",
"intent": {
"AlarmSkillDeleteVerb": "cancel",
"AlarmSkillAmount": "1",
"AlarmSkillKeyword": "alarm"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "erase the next alarm",
"intent_type": "AlarmSkillDeleteIntent",
"intent": {
"AlarmSkillDeleteVerb": "erase",
"AlarmSkillAmount": "the next",
"AlarmSkillKeyword": "alarm"
}
}

View File

@ -1,5 +0,0 @@
all|all my
1|one
2|two
the next
the following

View File

@ -1,4 +0,0 @@
timer
timers
alarm
alarms

View File

@ -1,4 +0,0 @@
erase
cancel
delete
remove

View File

@ -1,4 +0,0 @@
timer
timers
alarm
alarms

View File

@ -1,2 +0,0 @@
show
list

View File

@ -1,3 +0,0 @@
off
end
stop

View File

@ -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()

View File

@ -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

View File

@ -1,3 +0,0 @@
Recording audio
Starting audio recording
Audio recording initialized

View File

@ -1,3 +0,0 @@
Recording audio for {{duration}} seconds
Starting audio recording for {{duration}} seconds
Audio recording for {{duration}} seconds initialized

View File

@ -1,3 +0,0 @@
Audio recording ended
Stopping audio recording
Audio recording was stopped

View File

@ -1,3 +0,0 @@
Audio reproducing ended
Stopping audio playing
Audio playing was stopped

View File

@ -1,7 +0,0 @@
{
"utterance": "record",
"intent_type": "AudioRecordSkillIntent",
"intent": {
"AudioRecordSkillKeyword": "record"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "start recording",
"intent_type": "AudioRecordSkillIntent",
"intent": {
"AudioRecordSkillKeyword": "recording"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "record audio for 10 minutes",
"intent_type": "AudioRecordSkillIntent",
"intent": {
"AudioRecordSkillKeyword": "record"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "stop recording",
"intent_type": "AudioRecordSkillStopIntent",
"intent": {
"AudioRecordSkillStopVerb": "stop",
"AudioRecordSkillKeyword": "recording"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "end record",
"intent_type": "AudioRecordSkillStopIntent",
"intent": {
"AudioRecordSkillStopVerb": "end",
"AudioRecordSkillKeyword": "record"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "cancel recording",
"intent_type": "AudioRecordSkillStopIntent",
"intent": {
"AudioRecordSkillStopVerb": "cancel",
"AudioRecordSkillKeyword": "recording"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "play record",
"intent_type": "AudioRecordSkillPlayIntent",
"intent": {
"AudioRecordSkillPlayVerb": "play",
"AudioRecordSkillKeyword": "record"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "stop playing record",
"intent_type": "AudioRecordSkillStopPlayIntent",
"intent": {
"AudioRecordSkillStopVerb": "stop",
"AudioRecordSkillPlayVerb": "playing",
"AudioRecordSkillKeyword": "record"
}
}

View File

@ -1,2 +0,0 @@
record
recording

View File

@ -1,9 +0,0 @@
run
play
replay
playback
reproduce
running
playing
replaying
reproducing

View File

@ -1,3 +0,0 @@
end
stop
cancel

View File

@ -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()

View File

@ -1,3 +0,0 @@
Configuration updated.
Your device configuration has been updated.
Your device has been configured.

View File

@ -1,7 +0,0 @@
{
"utterance": "update configuration",
"intent_type": "UpdateConfigurationIntent",
"intent": {
"UpdateConfigurationPhrase": "update configuration"
}
}

View File

@ -1,3 +0,0 @@
config
configuration
setting

View File

@ -1,4 +0,0 @@
update
reload
sync
synchronize

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

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

View File

@ -1 +0,0 @@
I could not find the timezone for {{location}}

View File

@ -1 +0,0 @@
(at|in) (?P<Location>.*)

View File

@ -1,7 +0,0 @@
{
"utterance": "what time is it",
"intent_type": "TimeIntent",
"intent": {
"TimeKeyword": "what time is it"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "whats the time",
"intent_type": "TimeIntent",
"intent": {
"TimeKeyword": "whats the time"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "time is it",
"intent_type": "TimeIntent",
"intent": {
"TimeKeyword": "time is it"
}
}

View File

@ -1,2 +0,0 @@
date
day

View File

@ -1,2 +0,0 @@
what
tell

View File

@ -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()

View File

@ -1,3 +0,0 @@
for (?P<SearchTerms>.*)
for (?P<SearchTerms>.*) on
(?P<SearchTerms>.*) on

View File

@ -1,9 +0,0 @@
{
"utterance": "search for kittens on youtube",
"intent_type": "SearchWebsiteIntent",
"intent": {
"SearchKeyword": "search",
"Website": "youtube",
"SearchTerms": "for kittens"
}
}

View File

@ -1,9 +0,0 @@
{
"utterance": "find puppies on wikipedia",
"intent_type": "SearchWebsiteIntent",
"intent": {
"SearchKeyword": "find",
"Website": "wikipedia",
"SearchTerms": "puppies"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "launch imgur",
"intent_type": "LaunchWebsiteIntent",
"intent": {
"LaunchKeyword": "launch",
"Website": "imgur"
}
}

View File

@ -1,8 +0,0 @@
{
"utterance": "open tumblr",
"intent_type": "LaunchWebsiteIntent",
"intent": {
"LaunchKeyword": "open",
"Website": "tumblr"
}
}

View File

@ -1,3 +0,0 @@
launch
open
run

View File

@ -1,2 +0,0 @@
search
find

View File

@ -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

View File

@ -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()

View File

@ -1 +0,0 @@
(call|phone) (?P<Contact>.*)

View File

@ -1,8 +0,0 @@
{
"utterance": "call mark",
"intent_type": "DialCallIntent",
"intent": {
"DialCallKeyword": "call",
"Contact": "mark"
}
}

View File

@ -1,2 +0,0 @@
call
phone

View File

@ -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()

View File

@ -1,3 +0,0 @@
Hello world
Hello
Hi to you too

View File

@ -1,6 +0,0 @@
I'm doing well
Pretty well
Not bad
I'm doing excellent
Could be better
I'm doing very well

View File

@ -1,6 +0,0 @@
Any time.
Glad to be of service.
Glad to help.
My Pleasure.
No problem.
You're welcome.

View File

@ -1,7 +0,0 @@
{
"utterance": "Thank you",
"intent_type": "ThankYouIntent",
"intent": {
"ThankYouKeyword": "thank you"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "Thanks",
"intent_type": "ThankYouIntent",
"intent": {
"ThankYouKeyword": "thanks"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "Hello world",
"intent_type": "HelloWorldIntent",
"intent": {
"HelloWorldKeyword": "hello world"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "Greetings",
"intent_type": "HelloWorldIntent",
"intent": {
"HelloWorldKeyword": "greetings"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "How are you doing",
"intent_type": "HowAreYouIntent",
"intent": {
"HowAreYouKeyword": "how are you"
}
}

View File

@ -1,7 +0,0 @@
{
"utterance": "How has your day been",
"intent_type": "HowAreYouIntent",
"intent": {
"HowAreYouKeyword": "how has your day been"
}
}

View File

@ -1,2 +0,0 @@
hello world
greetings

View File

@ -1,3 +0,0 @@
how are you
how have you been
how has your day been

View File

@ -1,2 +0,0 @@
thank you
thanks

Some files were not shown because too many files have changed in this diff Show More