From 1225e046da3d2d9312f129a5c299f21aa500d3e0 Mon Sep 17 00:00:00 2001 From: builderjer Date: Sat, 8 Mar 2025 16:26:54 -0700 Subject: [PATCH 1/8] Better default color handling A couple more/better intents --- __init__.py | 213 ++++++++++++++---- locale/en-us/color.entity | 160 ------------- locale/en-us/color.voc | 2 + locale/en-us/default.eye.color.current.intent | 4 + locale/en-us/default.eye.color.intent | 3 + locale/en-us/default.voc | 1 + locale/en-us/enclosure.voc | 2 +- locale/en-us/set.current.eye.color.dialog | 1 + 8 files changed, 183 insertions(+), 203 deletions(-) delete mode 100644 locale/en-us/color.entity create mode 100644 locale/en-us/color.voc create mode 100644 locale/en-us/default.eye.color.current.intent create mode 100644 locale/en-us/default.eye.color.intent create mode 100644 locale/en-us/default.voc create mode 100644 locale/en-us/set.current.eye.color.dialog diff --git a/__init__.py b/__init__.py index f3d6e8c..384083b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,13 +1,17 @@ import random import time +import datetime +from pathlib import Path from ast import literal_eval as parse_tuple from ovos_color_parser import color_from_description, sRGBAColor +from ovos_color_parser.matching import lookup_name from ovos_utils import create_daemon, classproperty from ovos_utils.log import LOG from ovos_utils.process_utils import RuntimeRequirements -from ovos_workshop.decorators import intent_handler +from ovos_workshop.decorators import intent_handler, resting_screen_handler from ovos_workshop.intents import IntentBuilder from ovos_workshop.skills import OVOSSkill +from ovos_workshop.skills.api import SkillApi from ovos_bus_client.message import Message from ovos_i2c_detection import is_mark_1 from threading import Thread @@ -29,17 +33,40 @@ def _hex_to_rgb(_hex): return (r, g, b) except Exception: return None + +def verify_mark_1(): + # Check for output from ovos-i2csound + i2c = Path("/etc/OpenVoiceOS/i2c_platform") + error = "" + if i2c.exists(): + with open(i2c, 'r') as f: + device = f.read().rstrip() + if not device == "MARK1": + LOG.error = f"ovos-i2cdetect detected device {device}." + else: + return True + # Check one more time just in case initialization was not complete + if not is_mark_1(): + LOG.error("This device is not a Mark 1.") + else: + return True + return False + class EnclosureControlSkill(OVOSSkill): def __init__(self, *args, **kwargs): - if not is_mark_1(): - LOG.error("This device is not a Mark 1. It is suggested to uninstall this skill") + if not verify_mark_1(): raise NotImplementedError("Purposeful exception because not on a Mark 1 device") super().__init__(*args, **kwargs) + # self.datetime_api = None self.thread = None self.playing = False self.animations = [] - self.add_event('mycroft.eyes.default', self.handle_default_eyes) + if not self.settings.get("defaults"): + self._create_defaults() + self._load_defaults() + # callback_time = now_local() + datetime.timedelta(seconds=60) + # self.schedule_repeating_event(self.update_dt, callback_time, 10) self.add_event('mycroft.ready', self.handle_default_eyes) @classproperty @@ -84,7 +111,6 @@ def up_down_animation(self): return [ self.animate(2, 6, self.enclosure.eyes_look, "d"), self.animate(4, 6, self.enclosure.eyes_look, "u"), - ] @property @@ -92,9 +118,12 @@ def left_right_animation(self): return [ self.animate(2, 6, self.enclosure.eyes_look, "l"), self.animate(4, 6, self.enclosure.eyes_look, "r"), - ] - + + # @property + # def datetime_skill_id(self): + # return self.settings.get("datetime_skill", None) + @staticmethod def animate(t, often, func, *args): ''' @@ -113,10 +142,65 @@ def animate(t, often, func, *args): "args": args } - @staticmethod - def _get_time(often, t): - return often - t % often - +# @staticmethod +# def _get_time(often, t): +# return often - t % often +# +# def _load_skill_apis(self): +# """ +# Loads date/time skill API +# """ +# # Import Date Time Skill As Date Time Provider if configured (default LF) +# try: +# if not self.datetime_api and self.datetime_skill_id: +# self.datetime_api = SkillApi.get(self.datetime_skill_id) +# assert self.datetime_api.get_display_time is not None +# assert self.datetime_api.get_display_date is not None +# assert self.datetime_api.get_weekday is not None +# assert self.datetime_api.get_year is not None +# except AssertionError as e: +# LOG.error(f"missing API method: {e}") +# self.datetime_api = None +# except Exception as e: +# LOG.error(f"Failed to import DateTime Skill: {e}") +# self.datetime_api = None + + def _load_defaults(self): + defaults = self.settings.get("defaults").get("default_eye_color") + (r, g, b) = defaults.get("rgb") + self.default_eye_color = sRGBAColor(r, g, b) + self.set_eye_color(self.default_eye_color) + # TODO: Eye position? + # TODO: Mouth position? + self.enclosure.mouth_reset() + + def _create_defaults(self): + LOG.info("creating defaults") + # Using "mycroft blue" as a default color + # TODO: figure out naming conventions + + # color = color_from_description("mycroft blue") This does not return the correct color + # https://github.com/OpenVoiceOS/ovos-color-parser/issues/26 + # It works when getting the color from a hex code + color = sRGBAColor.from_hex_str("#22A7F0", name="Mycroft blue", description="blue") + + self.settings["defaults"] = {"default_eye_color": {"rgb": [color.r, color.g, color.b], + "name": color.name}} + + # def update_dt(self): + # """ + # Loads or updates date/time via the datetime_api. + # """ + # if not self.datetime_api and self.datetime_skill_id: + # LOG.debug("Requested update before datetime API loaded") + # self._load_skill_apis() + # if self.datetime_api: + # try: + # self._update_datetime_from_api() + # return + # except Exception as e: + # LOG.exception(f"Skill API error: {e}") + def run(self): """ animation thread while performing speedtest @@ -222,16 +306,14 @@ def handle_spin_eyes(self, message): .require("narrow").require("eyes") .optionally("enclosure")) def handle_narrow_eyes(self, message): - self.speak("this is my evil face") + # self.speak("this is my evil face") self.enclosure.eyes_narrow() - self.enclosure.eyes_color(255, 0, 0) + # self.enclosure.eyes_color(255, 0, 0) @intent_handler(IntentBuilder("EnclosureReset") .require("reset").require("enclosure")) def handle_enclosure_reset(self, message): self.handle_default_eyes() - self.enclosure.eyes_reset() - self.enclosure.mouth_reset() self.speak("this was fun") @intent_handler(IntentBuilder("EnclosureMouthSmile") @@ -266,15 +348,14 @@ def handle_enclosure_crazy_eyes(self, message): ##################################################################### # Color interactions - def set_eye_color(self, color=None, rgb=None, speak=True, make_default=False): + def set_eye_color(self, color, speak=True):#=None, rgb=None, speak=True):# name=None, speak=True): """ Change the eye color on the faceplate, update saved setting """ if color is not None: color_rgb = self._parse_to_rgb(color) if color_rgb is not None: - (r, g, b) = color_rgb - elif rgb is not None: - (r, g, b) = rgb + (r, g, b) = (color_rgb.r, color_rgb.g, color_rgb.b) + name = color_rgb.name else: return # no color provided! @@ -283,9 +364,8 @@ def set_eye_color(self, color=None, rgb=None, speak=True, make_default=False): if speak: self.speak_dialog('set.color.success') # Update saved color - self.settings['current_eye_color'] = [r, g, b] - if make_default: - self.settings['default_eye_color'] = [r, g, b] + self.settings['current_eye_color']["rgb"] = [r, g, b] + self.settings['current_eye_color']["name"] = name except Exception: self.log.debug('Bad color code: ' + str(color)) if speak: @@ -318,12 +398,9 @@ def is_byte(utt): if not b: return # cancelled - custom_rgb = [r, g, b] + custom_rgb = sRGBAColor(r, g, b) - default = False - if self.ask_yesno('set.default.eye.color') == 'yes': - default = True - self.set_eye_color(rgb=custom_rgb, make_default=default) + self.set_eye_color(custom_rgb) @intent_handler('eye.color.intent') def handle_eye_color(self, message): @@ -337,13 +414,42 @@ def handle_eye_color(self, message): if color_str: match = color_from_description(color_str) if match is not None: - default = False - if self.ask_yesno('set.default.eye.color') == 'yes': - default = True - self.set_eye_color(color=match, make_default=default) + self.set_eye_color(color=match) else: self.speak_dialog('color.not.exist') + @intent_handler('default.eye.color.intent') + def handle_default_eye_color(self, message): + """ Callback to set the default eye color from list + + Args: + message (dict): messagebus message from intent parser + """ + LOG.info("setting the default eye color") + color_str = (message.data.get('color', None) or + self.get_response('color.need')) + if color_str: + match = color_from_description(color_str) + if match is not None: + color_rgb = self._parse_to_rgb(match) + if color_rgb is not None: + (r, g, b) = color_rgb.r, color_rgb.g, color_rgb.b + self.settings["defaults"]["default_eye_color"] = [r, g, b] + if self.settings.get("current_eye_color") != self.settings["defaults"]["default_eye_color"]: + if self.ask_yesno('set.current.eye.color').lower() == "yes": + LOG.info("said change color") + self.set_eye_color(match) + + @intent_handler('default.eye.color.current.intent') + def handle_default_eye_color_current(self, message): + """ Callback to set the default eye color from the current color + + Args: + message (dict): messagebus message from intent parser + """ + self.settings["defaults"]["default_eye_color"] = self.settings.get("current_eye_color") + self.speak("I set the default color") + def _parse_to_rgb(self, color): """ Convert color descriptor to RGB @@ -351,20 +457,19 @@ def _parse_to_rgb(self, color): '(0,0,128)' to an RGB tuple. Args: - color (str): RGB, Hex, or color from color_dict + color (str): RGB, Hex, or color from color_from_description Returns: (r, g, b) (tuple): Tuple of rgb values (0-255) or None """ if not color: return None - - # # check if named color is valid + + # check if named color is valid try: if isinstance(color, sRGBAColor): - if 0 <= color.r <= 255 and 0 <= color.g <= 255 and 0 <= color.b <= 255: - return (color.r, color.g, color.b) - else: - return None + return color + else: + return None except Exception: pass @@ -372,18 +477,42 @@ def _parse_to_rgb(self, color): try: (r, g, b) = parse_tuple(color) if 0 <= r <= 255 and 0 <= g <= 255 and 0 <= b <= 255: - return (r, g, b) + return sRGBAColor(r, g, b) else: return None except Exception: pass # Finally check if color is hex, like '#0000cc' or '0000cc' - return _hex_to_rgb(color) + try: + return sRGBAColor(_hex_to_rgb(color)) + except Exception: + return None + + return None def handle_default_eyes(self): - if self.settings.get('default_eye_color'): - self.set_eye_color(rgb=self.settings['default_eye_color'], speak=False) + settings = self.settings.get("defaults") + if settings: + # Handle default eyes + if settings.get("default_eye_color"): + try: + (r, g, b) = settings.get("rgb") + name = settings.get("name", None) + color = sRGBAColor(r, g, b, name=name) + self.set_eye_color(color, speak=False) + except ValueError: + self._create_defaults() + self._load_defaults() + if settings.get("default_eye_position"): + LOG.info("Not implemented yet") + else: + self.enclosure.eyes_reset() + + else: + self._create_defaults() + self._load_defaults() + ##################################################################### # Brightness intent interaction diff --git a/locale/en-us/color.entity b/locale/en-us/color.entity deleted file mode 100644 index d221217..0000000 --- a/locale/en-us/color.entity +++ /dev/null @@ -1,160 +0,0 @@ -default -alice blue -aliceblue -antique white -aqua -aquamarine -azure -beige -bisque -black -blanched almond -blue -blue violet -brown -burly wood -burlywood -cadet blue -chartreuse -chocolate -coral -cornflower blue -corn flower blue -cornsilk -corn silk -crimson -cyan -dark blue -dark cyan -dark golden rod -dark goldenrod -dark gray -dark grey -dark green -dark khaki -dark magenta -dark olive green -dark orange -dark orchid -dark red -dark salmon -dark sea green -dark slate blue -dark slate gray -dark slate grey -dark turquoise -dark violet -deep pink -deep sky blue -dim gray -dim grey -dodger blue -fire brick -firebrick -floral white -forest green -fuchsia -fuschia -gainsboro -ghost white -gold -golden rod -gray -grey -green -green yellow -honey dew -honeydew -hot pink -indian red -indigo -ivory -khaki -lavender -lavender blush -lawn green -lemon chiffon -light blue -light coral -light cyan -light golden rod yellow -light goldenrod yellow -light gray -light grey -light green -light pink -light salmon -light sea green -light sky blue -light slate gray -light slate grey -light steel blue -light yellow -lime -lime green -linen -magenta -maroon -medium aqua marine -medium blue -medium orchid -medium purple -medium sea green -medium slate blue -medium spring green -medium turquoise -medium violet red -midnight blue -mint cream -misty rose -moccasin -navajo white -navy -old lace -olive -olive drab -orange -orange red -orchid -pale golden rod -pale goldenrod -pale green -pale turquoise -pale violet red -papaya whip -peach puff -peru -pink -plum -powder blue -purple -rebecca purple -red -rosy brown -royal blue -saddle brown -salmon -sandy brown -sea green -sea shell -seashell -sienna -silver -sky blue -slate blue -slate gray -slate grey -snow -spring green -steel blue -tan -teal -thistle -tomato -turquoise -violet -wheat -white -white smoke -yellow -yellow green diff --git a/locale/en-us/color.voc b/locale/en-us/color.voc new file mode 100644 index 0000000..9d1b936 --- /dev/null +++ b/locale/en-us/color.voc @@ -0,0 +1,2 @@ +color +colors diff --git a/locale/en-us/default.eye.color.current.intent b/locale/en-us/default.eye.color.current.intent new file mode 100644 index 0000000..2518231 --- /dev/null +++ b/locale/en-us/default.eye.color.current.intent @@ -0,0 +1,4 @@ +(change|set|make) (the|your|) default (eye|eyes) (color|colors|) (to|) (the|) current (color|) +(change|set|make) (eye|eyes) default (color|colors|) (the|) current (color|) +(change|set|make) default eye (color|colors) (to|) (the|) current (color|) +(change|set|make) (the|) current (eye|eyes|) (color|colors) default diff --git a/locale/en-us/default.eye.color.intent b/locale/en-us/default.eye.color.intent new file mode 100644 index 0000000..7c4dc9c --- /dev/null +++ b/locale/en-us/default.eye.color.intent @@ -0,0 +1,3 @@ +(change|set) (the|your|) default (eye|eyes) (color|colors|) to {color} +(change|set) (eye|eyes) default (color|colors|) to {color} +(change|set) default eye (color|colors) diff --git a/locale/en-us/default.voc b/locale/en-us/default.voc new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/locale/en-us/default.voc @@ -0,0 +1 @@ +default diff --git a/locale/en-us/enclosure.voc b/locale/en-us/enclosure.voc index 74cbff0..a86e742 100644 --- a/locale/en-us/enclosure.voc +++ b/locale/en-us/enclosure.voc @@ -1,4 +1,4 @@ enclosure -your body +your face mark 1 mark one diff --git a/locale/en-us/set.current.eye.color.dialog b/locale/en-us/set.current.eye.color.dialog new file mode 100644 index 0000000..eac68ad --- /dev/null +++ b/locale/en-us/set.current.eye.color.dialog @@ -0,0 +1 @@ +Would you like to change to this color now From 915dc76f6b114b4e80361a659eba346e040ad125 Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 9 Mar 2025 06:14:55 -0600 Subject: [PATCH 2/8] bad value --- __init__.py | 17 ++++++++--------- locale/en-us/eye.color.intent | 1 + locale/en-us/narrow.voc | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/__init__.py b/__init__.py index 384083b..4c3178a 100644 --- a/__init__.py +++ b/__init__.py @@ -495,15 +495,14 @@ def handle_default_eyes(self): settings = self.settings.get("defaults") if settings: # Handle default eyes - if settings.get("default_eye_color"): - try: - (r, g, b) = settings.get("rgb") - name = settings.get("name", None) - color = sRGBAColor(r, g, b, name=name) - self.set_eye_color(color, speak=False) - except ValueError: - self._create_defaults() - self._load_defaults() + try: + (r, g, b) = settings.get("default_eye_color").get("rgb") + name = settings.get("default_eye_color").get("name", None) + color = sRGBAColor(r, g, b, name=name) + self.set_eye_color(color, speak=False) + except ValueError: + self._create_defaults() + self._load_defaults() if settings.get("default_eye_position"): LOG.info("Not implemented yet") else: diff --git a/locale/en-us/eye.color.intent b/locale/en-us/eye.color.intent index ac1c610..8b9c2b2 100644 --- a/locale/en-us/eye.color.intent +++ b/locale/en-us/eye.color.intent @@ -4,3 +4,4 @@ (change|set) (your|) eyes to (a|an|) {color} (color|) (change|set) (eye|eyes) to (a|an) {color} color (change|set) eye (color|colors) +make (the|your) (eye|eyes) {color} diff --git a/locale/en-us/narrow.voc b/locale/en-us/narrow.voc index d7dced3..337bfbe 100644 --- a/locale/en-us/narrow.voc +++ b/locale/en-us/narrow.voc @@ -1 +1,2 @@ -narrow \ No newline at end of file +narrow +squint From 6536a39fafa75a72eb84fcb60111f90465975fe0 Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 9 Mar 2025 06:44:11 -0600 Subject: [PATCH 3/8] whoops --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 4c3178a..9c530a5 100644 --- a/__init__.py +++ b/__init__.py @@ -142,9 +142,9 @@ def animate(t, often, func, *args): "args": args } -# @staticmethod -# def _get_time(often, t): -# return often - t % often + @staticmethod + def _get_time(often, t): + return often - t % often # # def _load_skill_apis(self): # """ From e33763dc8e6aa3d8e9a4c00ad84b28c374cfedcd Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 9 Mar 2025 12:30:27 -0600 Subject: [PATCH 4/8] bug fixes coderabbit suggestions --- __init__.py | 123 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 39 deletions(-) diff --git a/__init__.py b/__init__.py index 9c530a5..3ef7352 100644 --- a/__init__.py +++ b/__init__.py @@ -41,7 +41,7 @@ def verify_mark_1(): if i2c.exists(): with open(i2c, 'r') as f: device = f.read().rstrip() - if not device == "MARK1": + if device != "MARK1": LOG.error = f"ovos-i2cdetect detected device {device}." else: return True @@ -63,7 +63,8 @@ def __init__(self, *args, **kwargs): self.playing = False self.animations = [] if not self.settings.get("defaults"): - self._create_defaults() + LOG.info("No default settings, creating them now") + self._create_settings() self._load_defaults() # callback_time = now_local() + datetime.timedelta(seconds=60) # self.schedule_repeating_event(self.update_dt, callback_time, 10) @@ -166,16 +167,27 @@ def _get_time(often, t): # self.datetime_api = None def _load_defaults(self): - defaults = self.settings.get("defaults").get("default_eye_color") - (r, g, b) = defaults.get("rgb") - self.default_eye_color = sRGBAColor(r, g, b) - self.set_eye_color(self.default_eye_color) - # TODO: Eye position? - # TODO: Mouth position? - self.enclosure.mouth_reset() - - def _create_defaults(self): - LOG.info("creating defaults") + try: + defaults = self.settings.get("defaults", {}).get("default_eye_color", {}) + rgb = defaults.get("rgb") + if rgb and len(rgb) == 3: + (r, g, b) = rgb + self.set_eye_color(sRGBAColor(r, g, b)) + else: + LOG.warning("Invalid default eye color, recreating defaults") + self._create_settings() + self._load_defaults() + # TODO: Eye position? + # TODO: Mouth position? + self.enclosure.mouth_reset() + except Exception as e: + LOG.error(f"error loading defaults {e}") + self._create_settings() + self._load_defaults() + + + def _create_settings(self): + LOG.info("creating settings") # Using "mycroft blue" as a default color # TODO: figure out naming conventions @@ -185,7 +197,9 @@ def _create_defaults(self): color = sRGBAColor.from_hex_str("#22A7F0", name="Mycroft blue", description="blue") self.settings["defaults"] = {"default_eye_color": {"rgb": [color.r, color.g, color.b], - "name": color.name}} + "name": color.name}} + self.settings["current_eye_color"] = {"rgb": [color.r, color.g, color.b], + "name": color.name} # def update_dt(self): # """ @@ -203,7 +217,7 @@ def _create_defaults(self): def run(self): """ - animation thread while performing speedtest + animation thread """ @@ -356,20 +370,23 @@ def set_eye_color(self, color, speak=True):#=None, rgb=None, speak=True):# name= if color_rgb is not None: (r, g, b) = (color_rgb.r, color_rgb.g, color_rgb.b) name = color_rgb.name + try: + self.enclosure.eyes_color(r, g, b) + if speak: + self.speak_dialog('set.color.success') + # Update saved color + if "current_eye_color" not in self.settings: + self.settings["current_eye_color"] = {} + self.settings["current_eye_color"] = { + "rgb": [r, g, b], + "name": name} + + except Exception: + LOG.debug('Bad color code: ' + str(color)) + if speak: + self.speak_dialog('error.set.color') else: - return # no color provided! - - try: - self.enclosure.eyes_color(r, g, b) - if speak: - self.speak_dialog('set.color.success') - # Update saved color - self.settings['current_eye_color']["rgb"] = [r, g, b] - self.settings['current_eye_color']["name"] = name - except Exception: - self.log.debug('Bad color code: ' + str(color)) - if speak: - self.speak_dialog('error.set.color') + return @intent_handler('custom.eye.color.intent') def handle_custom_eye_color(self, message): @@ -434,12 +451,25 @@ def handle_default_eye_color(self, message): color_rgb = self._parse_to_rgb(match) if color_rgb is not None: (r, g, b) = color_rgb.r, color_rgb.g, color_rgb.b - self.settings["defaults"]["default_eye_color"] = [r, g, b] - if self.settings.get("current_eye_color") != self.settings["defaults"]["default_eye_color"]: + if "defaults" not in self.settings: + self.settings["defaults"] = {} + if "default_eye_color" not in self.settings["defaults"]: + self.settings["defaults"]["default_eye_color"] = {} + # Save the default settings + self.settings["defaults"]["default_eye_color"] = { + "rgb": [r, g, b], + "name": color_rgb.name} + current_rgb = self.settings.get("current_eye_color", {}).get("rgb", []) + default_rgb = [r, g, b] + if current_rgb != default_rgb: if self.ask_yesno('set.current.eye.color').lower() == "yes": - LOG.info("said change color") self.set_eye_color(match) - + else: + self.speak("Default color is set") + else: + LOG.error(f"Coluld not parse color {match}") + else: + self.speak_dialog("color.not.exist") @intent_handler('default.eye.color.current.intent') def handle_default_eye_color_current(self, message): """ Callback to set the default eye color from the current color @@ -447,7 +477,14 @@ def handle_default_eye_color_current(self, message): Args: message (dict): messagebus message from intent parser """ - self.settings["defaults"]["default_eye_color"] = self.settings.get("current_eye_color") + current_eye_color = self.settings.get("current_eye_color") + if current_eye_color: + if "defaults" not in self.settings: + self.settings["defaults"] = {} + self.settings["defaults"] = current_eye_color + else: + LOG.error("Could not get a current eye color") + self.speak("I set the default color") def _parse_to_rgb(self, color): @@ -496,20 +533,28 @@ def handle_default_eyes(self): if settings: # Handle default eyes try: - (r, g, b) = settings.get("default_eye_color").get("rgb") - name = settings.get("default_eye_color").get("name", None) - color = sRGBAColor(r, g, b, name=name) - self.set_eye_color(color, speak=False) + default_eye_color = settings.get("default_eye_color", {}) + if default_eye_color and "rgb" in default_eye_color: + (r, g, b) = default_eye_color.get("rgb") + name = default_eye_color.get("name", None) + color = sRGBAColor(r, g, b, name=name) + self.set_eye_color(color, speak=False) except ValueError: - self._create_defaults() + LOG.error("Invalid default eye color settings") + self._create_settings() + self._load_defaults() + except Exception as e: + LOG.error(f"Could not load default eye color. {e}") + self._create_settings() self._load_defaults() if settings.get("default_eye_position"): LOG.info("Not implemented yet") - else: + # else: self.enclosure.eyes_reset() else: - self._create_defaults() + LOG.info("No default eye color found, creating them") + self._create_settings() self._load_defaults() From fc1e80bb6b4ee87642c85c317211c20ce952899d Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 9 Mar 2025 16:38:30 -0600 Subject: [PATCH 5/8] fix crazy eyes --- __init__.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/__init__.py b/__init__.py index 3ef7352..127ab65 100644 --- a/__init__.py +++ b/__init__.py @@ -88,13 +88,13 @@ def crazy_eyes_animation(self): (self.enclosure.eyes_look, "u"), (self.enclosure.eyes_look, "l"), (self.enclosure.eyes_look, "r"), - (self.enclosure.eyes_color, (255, 0, 0)), - (self.enclosure.eyes_color, (255, 0, 255)), - (self.enclosure.eyes_color, (255, 255, 255)), - (self.enclosure.eyes_color, (0, 0, 255)), - (self.enclosure.eyes_color, (0, 255, 0)), - (self.enclosure.eyes_color, (255, 255, 0)), - (self.enclosure.eyes_color, (0, 255, 255)), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 0, 0))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 0, 255))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 255, 255))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 0, 255))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 255, 0))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 255, 0))), + (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 255, 255))), (self.enclosure.eyes_spin, None), (self.enclosure.eyes_narrow, None), (self.enclosure.eyes_on, None), @@ -240,7 +240,7 @@ def run(self): self.thread = None self.enclosure.activate_mouth_events() self.enclosure.mouth_reset() - self.enclosure.eyes_reset() + # self.handle_default_eyes() def play_animation(self, animation=None): animation = animation or self.up_down_animation @@ -358,7 +358,7 @@ def handle_enclosure_crazy_eyes(self, message): self.speak("artificial intelligence performing artificial " "stupidity, you don't see this every day") self.play_animation(self.crazy_eyes_animation) - self.enclosure.eyes_reset() + self.handle_default_eyes() ##################################################################### # Color interactions @@ -549,8 +549,6 @@ def handle_default_eyes(self): self._load_defaults() if settings.get("default_eye_position"): LOG.info("Not implemented yet") - # else: - self.enclosure.eyes_reset() else: LOG.info("No default eye color found, creating them") From aeb2dcf921346855a136da8684061373693cd4d9 Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 23 Mar 2025 15:20:33 -0600 Subject: [PATCH 6/8] fixed crazy eyes better default color handling --- __init__.py | 143 ++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/__init__.py b/__init__.py index 127ab65..3294726 100644 --- a/__init__.py +++ b/__init__.py @@ -5,13 +5,12 @@ from ast import literal_eval as parse_tuple from ovos_color_parser import color_from_description, sRGBAColor from ovos_color_parser.matching import lookup_name -from ovos_utils import create_daemon, classproperty +from ovos_utils import create_daemon, classproperty, create_killable_daemon from ovos_utils.log import LOG from ovos_utils.process_utils import RuntimeRequirements -from ovos_workshop.decorators import intent_handler, resting_screen_handler +from ovos_workshop.decorators import intent_handler from ovos_workshop.intents import IntentBuilder from ovos_workshop.skills import OVOSSkill -from ovos_workshop.skills.api import SkillApi from ovos_bus_client.message import Message from ovos_i2c_detection import is_mark_1 from threading import Thread @@ -66,8 +65,6 @@ def __init__(self, *args, **kwargs): LOG.info("No default settings, creating them now") self._create_settings() self._load_defaults() - # callback_time = now_local() + datetime.timedelta(seconds=60) - # self.schedule_repeating_event(self.update_dt, callback_time, 10) self.add_event('mycroft.ready', self.handle_default_eyes) @classproperty @@ -88,23 +85,23 @@ def crazy_eyes_animation(self): (self.enclosure.eyes_look, "u"), (self.enclosure.eyes_look, "l"), (self.enclosure.eyes_look, "r"), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 0, 0))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 0, 255))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 255, 255))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 0, 255))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 255, 0))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(255, 255, 0))), - (self.enclosure.eyes_color, self.set_eye_color(sRGBAColor(0, 255, 255))), + (self.set_eye_color, sRGBAColor(255, 0, 0)), + (self.set_eye_color, sRGBAColor(255, 0, 255)), + (self.set_eye_color, sRGBAColor(255, 255, 255)), + (self.set_eye_color, sRGBAColor(0, 0, 255)), + (self.set_eye_color, sRGBAColor(0, 255, 0)), + (self.set_eye_color, sRGBAColor(255, 255, 0)), + (self.set_eye_color, sRGBAColor(0, 255, 255)), (self.enclosure.eyes_spin, None), (self.enclosure.eyes_narrow, None), (self.enclosure.eyes_on, None), (self.enclosure.eyes_off, None), - (self.enclosure.eyes_blink, None)] + (self.enclosure.eyes_blink, "b")] anim = [] for i in range(0, 10): frame = random.choice(choices) - anim.append(self.animate(i, 3, frame[0], frame[1])) + anim.append(self.animate(i, 1, frame[0], frame[1])) return anim @property @@ -120,11 +117,7 @@ def left_right_animation(self): self.animate(2, 6, self.enclosure.eyes_look, "l"), self.animate(4, 6, self.enclosure.eyes_look, "r"), ] - - # @property - # def datetime_skill_id(self): - # return self.settings.get("datetime_skill", None) - + @staticmethod def animate(t, often, func, *args): ''' @@ -145,26 +138,7 @@ def animate(t, often, func, *args): @staticmethod def _get_time(often, t): - return often - t % often -# -# def _load_skill_apis(self): -# """ -# Loads date/time skill API -# """ -# # Import Date Time Skill As Date Time Provider if configured (default LF) -# try: -# if not self.datetime_api and self.datetime_skill_id: -# self.datetime_api = SkillApi.get(self.datetime_skill_id) -# assert self.datetime_api.get_display_time is not None -# assert self.datetime_api.get_display_date is not None -# assert self.datetime_api.get_weekday is not None -# assert self.datetime_api.get_year is not None -# except AssertionError as e: -# LOG.error(f"missing API method: {e}") -# self.datetime_api = None -# except Exception as e: -# LOG.error(f"Failed to import DateTime Skill: {e}") -# self.datetime_api = None + return often - t % often def _load_defaults(self): try: @@ -187,7 +161,7 @@ def _load_defaults(self): def _create_settings(self): - LOG.info("creating settings") + LOG.default("Creating default settings") # Using "mycroft blue" as a default color # TODO: figure out naming conventions @@ -200,48 +174,46 @@ def _create_settings(self): "name": color.name}} self.settings["current_eye_color"] = {"rgb": [color.r, color.g, color.b], "name": color.name} - - # def update_dt(self): - # """ - # Loads or updates date/time via the datetime_api. - # """ - # if not self.datetime_api and self.datetime_skill_id: - # LOG.debug("Requested update before datetime API loaded") - # self._load_skill_apis() - # if self.datetime_api: - # try: - # self._update_datetime_from_api() - # return - # except Exception as e: - # LOG.exception(f"Skill API error: {e}") - + def run(self): - """ - animation thread - - """ - - while self.playing: - for animation in self.animations: - if animation["time"] <= time.time(): - # Execute animation action - animation["func"](*animation["args"]) - - # Adjust time for next loop - if type(animation["often"]) is int: - animation["time"] = time.time() + animation["often"] - else: - often = int(animation["often"]) - t = animation["time"] - animation["time"] = time.time() + self._get_time( - often, t) - time.sleep(0.1) - - self.thread = None + remaining_frames = len(self.animations) # Track how many frames need to execute + executed_frames = set() # Track which frames have been executed by index + current_eye_color = self.settings.get("current_eye_color", {"rgb": [34, 167, 240], "name": "Mycroft blue"}) + + while self.playing and remaining_frames > 0: + for i, animation in enumerate(self.animations): + if animation["time"] <= time.time() and i not in executed_frames: + try: + if animation["args"] and animation["args"] == (None,): + animation["func"]() + else: + animation["func"](*animation["args"]) + executed_frames.add(i) # Mark this frame as done + remaining_frames -= 1 # Decrement remaining count + except Exception as e: + LOG.error(f"Animation failed: {e}") + executed_frames.add(i) # Still mark as done to avoid stalling + remaining_frames -= 1 + # Do not reschedule; let it run once + time.sleep(0.1) # Prevent tight CPU loop + + self.playing = False # Explicitly stop the loop + self.thread = None # Thread ends naturally self.enclosure.activate_mouth_events() self.enclosure.mouth_reset() # self.handle_default_eyes() - + # Restore initial current_eye_color if different from default color + if current_eye_color["rgb"] != self.settings.get("defaults", {}).get("default_eye_color")["rgb"]: + (r, g, b) = current_eye_color["rgb"] + try: + self.enclosure.eyes_color(r, g, b) + LOG.info(f"Restored eye color to {current_eye_color}") + except Exception as e: + LOG.error(f"Failed to restore eye color: {e}") + self.handle_default_eyes() + else: + self.handle_default_eyes() + def play_animation(self, animation=None): animation = animation or self.up_down_animation if not self.thread: @@ -320,9 +292,8 @@ def handle_spin_eyes(self, message): .require("narrow").require("eyes") .optionally("enclosure")) def handle_narrow_eyes(self, message): - # self.speak("this is my evil face") + self.speak("this is my evil face") self.enclosure.eyes_narrow() - # self.enclosure.eyes_color(255, 0, 0) @intent_handler(IntentBuilder("EnclosureReset") .require("reset").require("enclosure")) @@ -358,11 +329,10 @@ def handle_enclosure_crazy_eyes(self, message): self.speak("artificial intelligence performing artificial " "stupidity, you don't see this every day") self.play_animation(self.crazy_eyes_animation) - self.handle_default_eyes() ##################################################################### # Color interactions - def set_eye_color(self, color, speak=True):#=None, rgb=None, speak=True):# name=None, speak=True): + def set_eye_color(self, color, speak=True): """ Change the eye color on the faceplate, update saved setting """ if color is not None: @@ -442,7 +412,6 @@ def handle_default_eye_color(self, message): Args: message (dict): messagebus message from intent parser """ - LOG.info("setting the default eye color") color_str = (message.data.get('color', None) or self.get_response('color.need')) if color_str: @@ -470,6 +439,7 @@ def handle_default_eye_color(self, message): LOG.error(f"Coluld not parse color {match}") else: self.speak_dialog("color.not.exist") + @intent_handler('default.eye.color.current.intent') def handle_default_eye_color_current(self, message): """ Callback to set the default eye color from the current color @@ -506,7 +476,11 @@ def _parse_to_rgb(self, color): if isinstance(color, sRGBAColor): return color else: - return None + try: + c = color_from_description(color) + return c + except Exception as e: + LOG.info(e) except Exception: pass @@ -529,6 +503,7 @@ def _parse_to_rgb(self, color): return None def handle_default_eyes(self): + LOG.info("setting default eyes") settings = self.settings.get("defaults") if settings: # Handle default eyes From c8bcef3571484e83a97910486f08a78cc9a45060 Mon Sep 17 00:00:00 2001 From: builderjer Date: Sun, 23 Mar 2025 15:29:20 -0600 Subject: [PATCH 7/8] added back color.entity --- locale/en-us/color.entity | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 locale/en-us/color.entity diff --git a/locale/en-us/color.entity b/locale/en-us/color.entity new file mode 100644 index 0000000..d221217 --- /dev/null +++ b/locale/en-us/color.entity @@ -0,0 +1,160 @@ +default +alice blue +aliceblue +antique white +aqua +aquamarine +azure +beige +bisque +black +blanched almond +blue +blue violet +brown +burly wood +burlywood +cadet blue +chartreuse +chocolate +coral +cornflower blue +corn flower blue +cornsilk +corn silk +crimson +cyan +dark blue +dark cyan +dark golden rod +dark goldenrod +dark gray +dark grey +dark green +dark khaki +dark magenta +dark olive green +dark orange +dark orchid +dark red +dark salmon +dark sea green +dark slate blue +dark slate gray +dark slate grey +dark turquoise +dark violet +deep pink +deep sky blue +dim gray +dim grey +dodger blue +fire brick +firebrick +floral white +forest green +fuchsia +fuschia +gainsboro +ghost white +gold +golden rod +gray +grey +green +green yellow +honey dew +honeydew +hot pink +indian red +indigo +ivory +khaki +lavender +lavender blush +lawn green +lemon chiffon +light blue +light coral +light cyan +light golden rod yellow +light goldenrod yellow +light gray +light grey +light green +light pink +light salmon +light sea green +light sky blue +light slate gray +light slate grey +light steel blue +light yellow +lime +lime green +linen +magenta +maroon +medium aqua marine +medium blue +medium orchid +medium purple +medium sea green +medium slate blue +medium spring green +medium turquoise +medium violet red +midnight blue +mint cream +misty rose +moccasin +navajo white +navy +old lace +olive +olive drab +orange +orange red +orchid +pale golden rod +pale goldenrod +pale green +pale turquoise +pale violet red +papaya whip +peach puff +peru +pink +plum +powder blue +purple +rebecca purple +red +rosy brown +royal blue +saddle brown +salmon +sandy brown +sea green +sea shell +seashell +sienna +silver +sky blue +slate blue +slate gray +slate grey +snow +spring green +steel blue +tan +teal +thistle +tomato +turquoise +violet +wheat +white +white smoke +yellow +yellow green From 7b841b8ed0a2b1ed5576b859a81bafcd2b976a3b Mon Sep 17 00:00:00 2001 From: builderjer Date: Sat, 29 Mar 2025 07:24:46 -0600 Subject: [PATCH 8/8] Removed crazy eyes animation This animation is unreliable and will be added back when another solution is found --- __init__.py | 63 ++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/__init__.py b/__init__.py index 3294726..8fb4fcf 100644 --- a/__init__.py +++ b/__init__.py @@ -79,30 +79,30 @@ def runtime_requirements(self): no_network_fallback=True, no_gui_fallback=True) - @property - def crazy_eyes_animation(self): - choices = [(self.enclosure.eyes_look, "d"), - (self.enclosure.eyes_look, "u"), - (self.enclosure.eyes_look, "l"), - (self.enclosure.eyes_look, "r"), - (self.set_eye_color, sRGBAColor(255, 0, 0)), - (self.set_eye_color, sRGBAColor(255, 0, 255)), - (self.set_eye_color, sRGBAColor(255, 255, 255)), - (self.set_eye_color, sRGBAColor(0, 0, 255)), - (self.set_eye_color, sRGBAColor(0, 255, 0)), - (self.set_eye_color, sRGBAColor(255, 255, 0)), - (self.set_eye_color, sRGBAColor(0, 255, 255)), - (self.enclosure.eyes_spin, None), - (self.enclosure.eyes_narrow, None), - (self.enclosure.eyes_on, None), - (self.enclosure.eyes_off, None), - (self.enclosure.eyes_blink, "b")] - - anim = [] - for i in range(0, 10): - frame = random.choice(choices) - anim.append(self.animate(i, 1, frame[0], frame[1])) - return anim + # @property + # def crazy_eyes_animation(self): + # choices = [(self.enclosure.eyes_look, "d"), + # (self.enclosure.eyes_look, "u"), + # (self.enclosure.eyes_look, "l"), + # (self.enclosure.eyes_look, "r"), + # (self.set_eye_color, sRGBAColor(255, 0, 0)), + # (self.set_eye_color, sRGBAColor(255, 0, 255)), + # (self.set_eye_color, sRGBAColor(255, 255, 255)), + # (self.set_eye_color, sRGBAColor(0, 0, 255)), + # (self.set_eye_color, sRGBAColor(0, 255, 0)), + # (self.set_eye_color, sRGBAColor(255, 255, 0)), + # (self.set_eye_color, sRGBAColor(0, 255, 255)), + # (self.enclosure.eyes_spin, None), + # (self.enclosure.eyes_narrow, None), + # (self.enclosure.eyes_on, None), + # (self.enclosure.eyes_off, None), + # (self.enclosure.eyes_blink, "b")] + # + # anim = [] + # for i in range(0, 10): + # frame = random.choice(choices) + # anim.append(self.animate(i, 1, frame[0], frame[1])) + # return anim @property def up_down_animation(self): @@ -201,7 +201,6 @@ def run(self): self.thread = None # Thread ends naturally self.enclosure.activate_mouth_events() self.enclosure.mouth_reset() - # self.handle_default_eyes() # Restore initial current_eye_color if different from default color if current_eye_color["rgb"] != self.settings.get("defaults", {}).get("default_eye_color")["rgb"]: (r, g, b) = current_eye_color["rgb"] @@ -322,13 +321,13 @@ def handle_enclosure_think(self, message): self.speak("i love thinking") self.enclosure.mouth_think() - @intent_handler(IntentBuilder("EnclosureCrazyEyes") - .require("eyes").optionally("animation").require("crazy") - .optionally("enclosure")) - def handle_enclosure_crazy_eyes(self, message): - self.speak("artificial intelligence performing artificial " - "stupidity, you don't see this every day") - self.play_animation(self.crazy_eyes_animation) + # @intent_handler(IntentBuilder("EnclosureCrazyEyes") + # .require("eyes").optionally("animation").require("crazy") + # .optionally("enclosure")) + # def handle_enclosure_crazy_eyes(self, message): + # self.speak("artificial intelligence performing artificial " + # "stupidity, you don't see this every day") + # self.play_animation(self.crazy_eyes_animation) ##################################################################### # Color interactions