Skip to content

Commit

Permalink
refactor/improve_enclosure_api
Browse files Browse the repository at this point in the history
update enclosure API to match mycroft-core (add missing methods/args)

ensure skill_id is always added to message.context
  • Loading branch information
JarbasAl committed Nov 16, 2021
1 parent 7620990 commit 837ad46
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 58 deletions.
194 changes: 137 additions & 57 deletions ovos_utils/enclosure/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ovos_utils.messagebus import Message, get_mycroft_bus
from ovos_utils.messagebus import Message


class EnclosureAPI:
Expand All @@ -14,53 +14,90 @@ class EnclosureAPI:
where there is no face at all.
"""

def __init__(self, bus=None):
self.bus = bus or get_mycroft_bus()
def __init__(self, bus=None, skill_id=""):
self.bus = bus
self.skill_id = skill_id

def set_bus(self, bus):
self.bus = bus

def set_id(self, skill_id):
self.skill_id = skill_id

def register(self, skill_id=""):
"""Registers a skill as active. Used for speak() and speak_dialog()
to 'patch' a previous implementation. Somewhat hacky.
"""
skill_id = skill_id or self.skill_id
self.bus.emit(
Message("enclosure.active_skill",
data={"skill_id": skill_id},
context={"destination": ["enclosure"],
"skill_id": skill_id}))

def reset(self):
"""The enclosure should restore itself to a started state.
Typically this would be represented by the eyes being 'open'
and the mouth reset to its default (smile or blank).
"""
self.bus.emit(Message("enclosure.reset"))
self.bus.emit(Message("enclosure.reset",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def system_reset(self):
"""The enclosure hardware should reset any CPUs, etc."""
self.bus.emit(Message("enclosure.system.reset"))
self.bus.emit(Message("enclosure.system.reset",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def system_mute(self):
"""Mute (turn off) the system speaker."""
self.bus.emit(Message("enclosure.system.mute"))
self.bus.emit(Message("enclosure.system.mute",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def system_unmute(self):
"""Unmute (turn on) the system speaker."""
self.bus.emit(Message("enclosure.system.unmute"))
self.bus.emit(Message("enclosure.system.unmute",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def system_blink(self, times):
"""The 'eyes' should blink the given number of times.
Args:
times (int): number of times to blink
"""
self.bus.emit(Message("enclosure.system.blink", {'times': times}))
self.bus.emit(Message("enclosure.system.blink",
data={'times': times},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_on(self):
"""Illuminate or show the eyes."""
self.bus.emit(Message("enclosure.eyes.on"))
self.bus.emit(Message("enclosure.eyes.on",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_off(self):
"""Turn off or hide the eyes."""
self.bus.emit(Message("enclosure.eyes.off"))
self.bus.emit(Message("enclosure.eyes.off",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_blink(self, side):
"""Make the eyes blink
Args:
side (str): 'r', 'l', or 'b' for 'right', 'left' or 'both'
"""
self.bus.emit(Message("enclosure.eyes.blink", {'side': side}))
self.bus.emit(Message("enclosure.eyes.blink", {'side': side},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_narrow(self):
"""Make the eyes look narrow, like a squint"""
self.bus.emit(Message("enclosure.eyes.narrow"))
self.bus.emit(Message("enclosure.eyes.narrow",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_look(self, side):
"""Make the eyes look to the given side
Expand All @@ -71,7 +108,10 @@ def eyes_look(self, side):
'd' for down
'c' for crossed
"""
self.bus.emit(Message("enclosure.eyes.look", {'side': side}))
self.bus.emit(Message("enclosure.eyes.look",
data={'side': side},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_color(self, r=255, g=255, b=255):
"""Change the eye color to the given RGB color
Expand All @@ -81,7 +121,9 @@ def eyes_color(self, r=255, g=255, b=255):
b (int): 0-255, blue value
"""
self.bus.emit(Message("enclosure.eyes.color",
{'r': r, 'g': g, 'b': b}))
data={'r': r, 'g': g, 'b': b},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_setpixel(self, idx, r=255, g=255, b=255):
"""Set individual pixels of the Mark 1 neopixel eyes
Expand All @@ -92,44 +134,55 @@ def eyes_setpixel(self, idx, r=255, g=255, b=255):
b (int): The blue value to apply
"""
if idx < 0 or idx > 23:
raise ValueError('row ({}) must be between 0-23'.format(str(idx)))
raise ValueError(f'idx ({idx}) must be between 0-23')
self.bus.emit(Message("enclosure.eyes.setpixel",
{'row': idx, 'r': r, 'g': g, 'b': b}))
data={'idx': idx, 'r': r, 'g': g, 'b': b},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_fill(self, percentage):
"""Use the eyes as a type of progress meter
Args:
amount (int): 0-49 fills the right eye, 50-100 also covers left
percentage (int): 0-49 fills the right eye, 50-100 also covers left
"""
if percentage < 0 or percentage > 100:
raise ValueError('percentage ({}) must be between 0-100'.
format(str(percentage)))
raise ValueError(f'percentage ({percentage}) must be between 0-100')
self.bus.emit(Message("enclosure.eyes.fill",
{'percentage': percentage}))
data={'percentage': percentage},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_brightness(self, level=30):
"""Set the brightness of the eyes in the display.
Args:
level (int): 1-30, bigger numbers being brighter
"""
self.bus.emit(Message("enclosure.eyes.level", {'level': level}))
self.bus.emit(Message("enclosure.eyes.level", {'level': level},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_reset(self):
"""Restore the eyes to their default (ready) state."""
self.bus.emit(Message("enclosure.eyes.reset"))
self.bus.emit(Message("enclosure.eyes.reset",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_spin(self):
"""Make the eyes 'roll'
"""
self.bus.emit(Message("enclosure.eyes.spin"))
self.bus.emit(Message("enclosure.eyes.spin",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_timed_spin(self, length):
"""Make the eyes 'roll' for the given time.
Args:
length (int): duration in milliseconds of roll, None = forever
"""
self.bus.emit(Message("enclosure.eyes.timedspin",
{'length': length}))
data={'length': length},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def eyes_volume(self, volume):
"""Indicate the volume using the eyes
Expand All @@ -139,32 +192,45 @@ def eyes_volume(self, volume):
if volume < 0 or volume > 11:
raise ValueError('volume ({}) must be between 0-11'.
format(str(volume)))
self.bus.emit(Message("enclosure.eyes.volume", {'volume': volume}))
self.bus.emit(Message("enclosure.eyes.volume",
data={'volume': volume},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_reset(self):
"""Restore the mouth display to normal (blank)"""
self.bus.emit(Message("enclosure.mouth.reset"))
self.bus.emit(Message("enclosure.mouth.reset",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_talk(self):
"""Show a generic 'talking' animation for non-synched speech"""
self.bus.emit(Message("enclosure.mouth.talk"))
self.bus.emit(Message("enclosure.mouth.talk",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_think(self):
"""Show a 'thinking' image or animation"""
self.bus.emit(Message("enclosure.mouth.think"))
self.bus.emit(Message("enclosure.mouth.think",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_listen(self):
"""Show a 'thinking' image or animation"""
self.bus.emit(Message("enclosure.mouth.listen"))
self.bus.emit(Message("enclosure.mouth.listen",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_smile(self):
"""Show a 'smile' image or animation"""
self.bus.emit(Message("enclosure.mouth.smile"))
self.bus.emit(Message("enclosure.mouth.smile",
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_viseme(self, start, viseme_pairs):
""" Send mouth visemes as a list in a single message.
Arguments:
Args:
start (int): Timestamp for start of speech
viseme_pairs: Pairs of viseme id and cumulative end times
(code, end time)
Expand All @@ -179,17 +245,25 @@ def mouth_viseme(self, start, viseme_pairs):
6 = shape for sounds like 'oy' or 'ao'
"""
self.bus.emit(Message("enclosure.mouth.viseme_list",
{"start": start, "visemes": viseme_pairs}))
data={"start": start,
"visemes": viseme_pairs},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_text(self, text=""):
"""Display text (scrolling as needed)
Args:
text (str): text string to display
"""
self.bus.emit(Message("enclosure.mouth.text", {'text': text}))
self.bus.emit(Message("enclosure.mouth.text",
data={'text': text},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_display(self, img_code="", x=0, y=0, refresh=True):
"""Display images on faceplate.
"""Display images on faceplate. Currently supports images up to 16x8,
or half the face. You can use the 'x' parameter to cover the other
half of the faceplate.
Args:
img_code (str): text string that encodes a black and white image
x (int): x offset for image
Expand All @@ -200,10 +274,12 @@ def mouth_display(self, img_code="", x=0, y=0, refresh=True):
on the faceplate at once.
"""
self.bus.emit(Message('enclosure.mouth.display',
{'img_code': img_code,
'xOffset': x,
'yOffset': y,
'clearPrev': refresh}))
data={'img_code': img_code,
'xOffset': x,
'yOffset': y,
'clearPrev': refresh},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def mouth_display_png(self, image_absolute_path,
invert=False, x=0, y=0, refresh=True):
Expand All @@ -220,11 +296,13 @@ def mouth_display_png(self, image_absolute_path,
on the faceplate at once.
"""
self.bus.emit(Message("enclosure.mouth.display_image",
{'img_path': image_absolute_path,
'xOffset': x,
'yOffset': y,
'invert': invert,
'clearPrev': refresh}))
data={'img_path': image_absolute_path,
'xOffset': x,
'yOffset': y,
'invert': invert,
'clearPrev': refresh},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def weather_display(self, img_code, temp):
"""Show a the temperature and a weather icon
Expand All @@ -242,22 +320,26 @@ def weather_display(self, img_code, temp):
temp (int): the temperature (either C or F, not indicated)
"""
self.bus.emit(Message("enclosure.weather.display",
{'img_code': img_code, 'temp': temp}))
data={'img_code': img_code, 'temp': temp},
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def activate_mouth_events(self):
"""Enable movement of the mouth with speech"""
self.bus.emit(Message('enclosure.mouth.events.activate'))
self.bus.emit(Message('enclosure.mouth.events.activate',
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def deactivate_mouth_events(self):
"""Disable movement of the mouth with speech"""
self.bus.emit(Message('enclosure.mouth.events.deactivate'))
self.bus.emit(Message('enclosure.mouth.events.deactivate',
context={"destination": ["enclosure"],
"skill_id": self.skill_id}))

def get_eyes_color(self):
"""
Get the eye RGB color for all pixels
:returns pixels (list) - list of (r,g,b) tuples for each eye pixel
"""Get the eye RGB color for all pixels
Returns:
(list) pixels - list of (r,g,b) tuples for each eye pixel
"""
message = Message("enclosure.eyes.rgb.get",
context={"source": "enclosure_api",
Expand All @@ -268,12 +350,10 @@ def get_eyes_color(self):
raise TimeoutError("Enclosure took too long to respond")

def get_eyes_pixel_color(self, idx):
"""
Get the RGB color for a specific eye pixel
:returns (r,g,b) tuples for selected pixel
"""Get the RGB color for a specific eye pixel
Returns:
(r,g,b) tuples for selected pixel
"""
if idx < 0 or idx > 23:
raise ValueError('row ({}) must be between 0-23'.format(str(idx)))
raise ValueError(f'idx ({idx}) must be between 0-23')
return self.get_eyes_color()[idx]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='ovos_utils',
version='0.0.13',
version='0.0.14a1',
packages=['ovos_utils',
'ovos_utils.intents',
'ovos_utils.sound',
Expand Down

0 comments on commit 837ad46

Please sign in to comment.