Merge branch 'dev' into patch-1
commit
5725359f16
23
dev_setup.sh
23
dev_setup.sh
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue