Merge branch 'dev' into patch-1

pull/2166/head
MichaIng 2019-06-24 22:53:41 +02:00 committed by GitHub
commit 5725359f16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 26 deletions

View File

@ -257,6 +257,10 @@ function os_is() {
[[ $(grep "^ID=" /etc/os-release | awk -F'=' '/^ID/ {print $2}' | sed 's/\"//g') == $1 ]]
}
function os_is_like() {
[[ $(grep "^ID_LIKE=" /etc/os-release | awk -F'=' '/^ID_LIKE/ {print $2}' | sed 's/\"//g') == $1 ]]
}
function redhat_common_install() {
$SUDO yum install -y cmake gcc-c++ git python34 python34-devel libtool libffi-devel openssl-devel autoconf automake bison swig portaudio-devel mpg123 flac curl libicu-devel python34-pkgconfig libjpeg-devel fann-devel python34-libs pulseaudio
git clone https://github.com/libfann/fann.git
@ -273,24 +277,33 @@ function install_deps() {
echo 'Installing packages...'
if found_exe zypper ; then
# OpenSUSE
echo ${GREEN} Installing packages for OpenSUSE...${RESET}
$SUDO zypper install -y git python3 python3-devel libtool libffi-devel libopenssl-devel autoconf automake bison swig portaudio-devel mpg123 flac curl libicu-devel pkg-config libjpeg-devel libfann-devel python3-curses pulseaudio
$SUDO zypper install -y -t pattern devel_C_C++
elif found_exe yum && os_is centos ; then
# CentOS
echo ${GREEN} Installing packages for Centos...${RESET}
$SUDO yum install epel-release
redhat_common_install
elif found_exe yum && os_is rhel ; then
# Redhat Enterprise Linux
echo ${GREEN} Installing packages for Red Hat...${RESET}
$SUDO yum install -y wget
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$SUDO yum install -y epel-release-latest-7.noarch.rpm
rm epel-release-latest-7.noarch.rpm
redhat_common_install
elif found_exe apt-get ; then
elif os_is_like debian ; then
# Debian / Ubuntu
echo ${GREEN} Installing packages for Debian/Ubuntu...${RESET}
$SUDO apt-get install -y git python3 python3-dev python-setuptools python-gobject-2-dev libtool libffi-dev libssl-dev autoconf automake bison swig libglib2.0-dev portaudio19-dev mpg123 screen flac curl libicu-dev pkg-config automake libjpeg-dev libfann-dev build-essential jq
elif os_is_like fedora ; then
echo ${GREEN} Installing packages for Fedora...${RESET}
# Fedora
$SUDO dnf install -y git python3 python3-devel python3-pip python3-setuptools python3-virtualenv pygobject3-devel libtool libffi-devel openssl-devel autoconf bison swig glib2-devel portaudio-devel mpg123 mpg123-plugins-pulseaudio screen curl pkgconfig libicu-devel automake libjpeg-turbo-devel fann-devel gcc-c++ redhat-rpm-config jq
elif found_exe pacman; then
# Arch Linux
echo ${GREEN} Installing packages for Arch...${RESET}
$SUDO pacman -S --needed --noconfirm git python python-pip python-setuptools python-virtualenv python-gobject python-virtualenvwrapper libffi swig portaudio mpg123 screen flac curl icu libjpeg-turbo base-devel jq pulseaudio pulseaudio-alsa
pacman -Qs '^fann$' &> /dev/null || (
git clone https://aur.archlinux.org/fann.git
@ -299,12 +312,10 @@ function install_deps() {
cd ..
rm -rf fann
)
elif found_exe dnf ; then
# Fedora
$SUDO dnf install -y git python3 python3-devel python3-pip python3-setuptools python3-virtualenv pygobject3-devel libtool libffi-devel openssl-devel autoconf bison swig glib2-devel portaudio-devel mpg123 mpg123-plugins-pulseaudio screen curl pkgconfig libicu-devel automake libjpeg-turbo-devel fann-devel gcc-c++ redhat-rpm-config jq
else
echo -e "\n${GREEN}Could not find package manager
${GREEN}Make sure to manually install:$BLUE git python 2 python-setuptools python-virtualenv pygobject virtualenvwrapper libtool libffi openssl autoconf bison swig glib2.0 portaudio19 mpg123 flac curl fann g++\n$RESET"
echo
echo -e "${YELLOW}Could not find package manager
${YELLOW}Make sure to manually install:$BLUE git python3 python-setuptools python-venv pygobject libtool libffi libjpg openssl autoconf bison swig glib2.0 portaudio19 mpg123 flac curl fann g++ jq\n$RESET"
fi
}

View File

@ -23,7 +23,7 @@ from mycroft.lock import Lock as PIDLock # Create/Support PID locking file
from mycroft.messagebus.client.ws import WebsocketClient
from mycroft.messagebus.message import Message
from mycroft.util import create_daemon, wait_for_exit_signal, \
reset_sigint_handler
reset_sigint_handler, create_echo_function
from mycroft.util.log import LOG
bus = None # Mycroft messagebus connection
@ -175,6 +175,7 @@ def main():
bus.on('recognizer_loop:audio_output_start', handle_audio_start)
bus.on('recognizer_loop:audio_output_end', handle_audio_end)
bus.on('mycroft.stop', handle_stop)
bus.on('message', create_echo_function('VOICE'))
create_daemon(bus.run_forever)
create_daemon(loop.run)

View File

@ -889,11 +889,15 @@ for s in help_struct:
help_longest = max(help_longest, len(ent[0]))
HEADER_SIZE = 2
HEADER_FOOTER_SIZE = 4
def num_help_pages():
lines = 0
for section in help_struct:
lines += 2 + len(section[1])
return ceil(lines / (curses.LINES - 4))
lines += 3 + len(section[1])
return ceil(lines / (curses.LINES - HEADER_FOOTER_SIZE))
def do_draw_help(scr):
@ -914,11 +918,12 @@ def do_draw_help(scr):
scr.erase()
render_header()
y = 2
y = HEADER_SIZE
page = subscreen + 1
first = subscreen * (curses.LINES - 7) # account for header
last = first + (curses.LINES - 7) # account for header/footer
# Find first and last taking into account the header and footer
first = subscreen * (curses.LINES - HEADER_FOOTER_SIZE)
last = first + (curses.LINES - HEADER_FOOTER_SIZE)
i = 0
for section in help_struct:
y = render_help(section[0], y, i, first, last, CLR_HEADING)

View File

@ -19,8 +19,9 @@
# BE WARNED THAT THE CLASSES, FUNCTIONS, ETC MAY CHANGE WITHOUT WARNING.
from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum, unique
from functools import total_ordering
from functools import total_ordering, wraps
from itertools import count
from mycroft import MycroftSkill
@ -29,6 +30,7 @@ from mycroft.messagebus.message import Message
ENTITY = "ENTITY"
SCENE = "SCENE"
IOT_REQUEST_ID = "iot_request_id" # TODO make the id a property of the request
_counter = count()
@ -57,8 +59,14 @@ class _BusKeys():
RUN = BASE + ":run." # Will have skill_id appened
REGISTER = BASE + "register"
CALL_FOR_REGISTRATION = REGISTER + ".request"
SPEAK = BASE + ":speak"
####################################################################
# When adding a new Thing, Attribute, etc, be sure to also add the #
# corresponding voc files to the skill-iot-control. #
####################################################################
@unique
class Thing(Enum):
"""
@ -81,13 +89,35 @@ class Thing(Enum):
class Attribute(Enum):
"""
This class represents 'Attributes' of 'Things'.
This may also grow to encompass states, e.g.
'locked' or 'unlocked'.
"""
BRIGHTNESS = auto()
COLOR = auto()
COLOR_TEMPERATURE = auto()
TEMPERATURE = auto()
@unique
class State(Enum):
"""
This class represents 'States' of 'Things'.
These are generally intended to handle binary
queries, such as "is the door locked?" or
"is the heat on?" where 'locked' and 'on'
are the state values. The special value
'STATE' can be used for more general queries
capable of providing more detailed in formation,
for example, "what is the state of the lamp?"
could produce state information that includes
brightness or color.
"""
STATE = auto()
POWERED = auto()
UNPOWERED = auto()
LOCKED = auto()
UNLOCKED = auto()
OCCUPIED = auto()
UNOCCUPIED = auto()
@unique
@ -106,6 +136,11 @@ class Action(Enum):
INCREASE = auto()
DECREASE = auto()
TRIGGER = auto()
BINARY_QUERY = auto() # yes/no answer
INFORMATION_QUERY = auto() # detailed answer
LOCATE = auto()
LOCK = auto()
UNLOCK = auto()
@total_ordering
@ -135,12 +170,14 @@ class IoTRequestVersion(Enum):
V1 = {'action', 'thing', 'attribute', 'entity', 'scene'}
V2 = V1 | {'value'}
V3 = V2 | {'state'}
"""
def __lt__(self, other):
return self.name < other.name
V1 = {'action', 'thing', 'attribute', 'entity', 'scene'}
V2 = V1 | {'value'}
V3 = V2 | {'state'}
class IoTRequest():
@ -151,11 +188,13 @@ class IoTRequest():
a user's request. The information is supplied as properties
on the request. At present, those properties are:
action (see the Action enum above)
thing (see the Thing enum above)
action (see the Action enum)
thing (see the Thing enum)
state (see the State enum)
attribute (see the Attribute enum)
value
entity
scene
value
The 'action' is mandatory, and will always be not None. The
other fields may be None.
@ -186,7 +225,8 @@ class IoTRequest():
attribute: Attribute = None,
entity: str = None,
scene: str = None,
value: int = None):
value: int = None,
state: State = None):
if not thing and not entity and not scene:
raise Exception("At least one of thing,"
@ -198,6 +238,7 @@ class IoTRequest():
self.entity = entity
self.scene = scene
self.value = value
self.state = state
def __repr__(self):
template = ('IoTRequest('
@ -206,19 +247,26 @@ class IoTRequest():
' attribute={attribute},'
' entity={entity},'
' scene={scene},'
' value={value}'
' value={value},'
' state={state}'
')')
entity = '"{}"'.format(self.entity) if self.entity else None
scene = '"{}"'.format(self.scene) if self.scene else None
value = '"{}"'.format(self.value) if self.value is not None else None
return template.format(
action=self.action,
thing=self.thing,
attribute=self.attribute,
entity='"{}"'.format(self.entity) if self.entity else None,
scene='"{}"'.format(self.scene) if self.scene else None,
value='"{}"'.format(self.value) if self.value is not None else None
entity=entity,
scene=scene,
value=value,
state=self.state
)
@property
def version(self):
if self.state is not None:
return IoTRequestVersion.V3
if self.value is not None:
return IoTRequestVersion.V2
return IoTRequestVersion.V1
@ -230,7 +278,8 @@ class IoTRequest():
'attribute': self.attribute.name if self.attribute else None,
'entity': self.entity,
'scene': self.scene,
'value': self.value
'value': self.value,
'state': self.state.name if self.state else None
}
@classmethod
@ -241,10 +290,39 @@ class IoTRequest():
data['thing'] = Thing[data['thing']]
if data.get('attribute') not in (None, ''):
data['attribute'] = Attribute[data['attribute']]
if data.get('state') not in (None, ''):
data['state'] = State[data['state']]
return cls(**data)
def _track_request(func):
"""
Used within the CommonIoT skill to track IoT requests.
The primary purpose of tracking the reqeust is determining
if the skill is currently handling an IoT request, or is
running a standard intent. While running IoT requests, certain
methods defined on MycroftSkill should behave differently than
under normal circumstances. In particular, speech related methods
should not actually trigger speech, but instead pass the message
to the IoT control skill, which will handle deconfliction (in the
event multiple skills want to respond verbally to the same request).
Args:
func: Callable
Returns:
Callable
"""
@wraps(func)
def tracking_function(self, message: Message):
with self._current_request(message.data.get(IOT_REQUEST_ID)):
func(self, message)
return tracking_function
class CommonIoTSkill(MycroftSkill, ABC):
"""
Skills that want to work with the CommonIoT system should
@ -270,6 +348,11 @@ class CommonIoTSkill(MycroftSkill, ABC):
step on each other.
"""
@wraps(MycroftSkill.__init__)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._current_iot_request = None
def bind(self, bus):
"""
Overrides MycroftSkill.bind.
@ -277,6 +360,9 @@ class CommonIoTSkill(MycroftSkill, ABC):
This is called automatically during setup, and
need not otherwise be used.
Subclasses that override this method must call this
via super in their implementation.
Args:
bus:
"""
@ -287,6 +373,17 @@ class CommonIoTSkill(MycroftSkill, ABC):
self.add_event(_BusKeys.CALL_FOR_REGISTRATION,
self._handle_call_for_registration)
@contextmanager
def _current_request(self, id: str):
# Multiple simultaneous requests may interfere with each other as they
# would overwrite this value, however, this seems unlikely to cause
# any real world issues and tracking multiple requests seems as
# likely to cause issues as to solve them.
self._current_iot_request = id
yield id
self._current_iot_request = None
@_track_request
def _handle_trigger(self, message: Message):
"""
Given a message, determines if this skill can
@ -308,6 +405,7 @@ class CommonIoTSkill(MycroftSkill, ABC):
"callback_data": callback_data})
self.bus.emit(message.response(data))
@_track_request
def _run_request(self, message: Message):
"""
Given a message, extracts the IoTRequest and
@ -321,6 +419,18 @@ class CommonIoTSkill(MycroftSkill, ABC):
callback_data = message.data["callback_data"]
self.run_request(request, callback_data)
def speak(self, utterance, *args, **kwargs):
if self._current_iot_request:
self.bus.emit(Message(_BusKeys.SPEAK,
data={"skill_id": self.skill_id,
IOT_REQUEST_ID:
self._current_iot_request,
"speak_args": args,
"speak_kwargs": kwargs,
"speak": utterance}))
else:
super().speak(utterance, *args, **kwargs)
def _handle_call_for_registration(self, _: Message):
"""
Register this skill's scenes and entities when requested.

View File

@ -457,7 +457,8 @@ class SkillTest(object):
# Messages that should not print debug info
HIDDEN_MESSAGES = ['skill.converse.request', 'skill.converse.response']
HIDDEN_MESSAGES = ['skill.converse.request', 'skill.converse.response',
'gui.page.show', 'gui.value.set']
def load_dialog_list(skill, dialog):