def initialize(self): self.load_data_files(dirname(__file__)) welcome_intent = IntentBuilder("WelcIntent").require("WelcKey").build() self.register_intent(welcome_intent, self.handle_welcome_intent)
class MeshSkill(MycroftSkill): # The constructor of the skill, which calls Mycroft Skill's constructor def __init__(self): super(MeshSkill, self).__init__(name="MeshSkill") # Initialize settings values self._is_setup = False self.notifier_bool = True self.deviceUUID = '' # This is the unique ID based on the Mac of this unit self.targetDevice = '' # This is the targed device_id obtained through mycroft dialog self.base_topic = '' self.MQTT_Enabled = '' self.broker_address = '' self.broker_port = '' self.broker_uname = '' self.broker_pass = '' self.location_id = '' # ToDo This will be retrieved from The the DeviceApi self.response_location = '' def on_connect(self, mqttc, obj, flags, rc): LOG.info("Connection Verified") LOG.info("This device location is: " + DeviceApi().get()["description"]) mqtt_path = self.base_topic + "/RemoteDevices/" + str(self.location_id) qos = 0 mqttc.subscribe(mqtt_path, qos) LOG.info('Mesh-Skill Subscribing to: ' + mqtt_path) def on_disconnect(self, mqttc, obj, flags, rc): self._is_setup = False LOG.info("MQTT has Dis-Connected") def on_message(self, mqttc, obj, msg): # called when a new MQTT message is received # Sample Payload {"source":"basement", "message":"is dinner ready yet"} LOG.info('message received for location id: ' + str(self.location_id)) LOG.info("This device location is: " + DeviceApi().get()["description"]) try: mqtt_message = msg.payload.decode('utf-8') LOG.info(msg.topic + " " + str(msg.qos) + ", " + mqtt_message) new_message = json.loads(mqtt_message) if "command" in new_message: # example: {"source":"kitchen", "command":"what time is it"} LOG.info('Command Received! - ' + new_message["command"] + ', From: ' + new_message["source"]) self.response_location = new_message["source"] self.send_message(new_message["command"]) elif "message" in new_message: # example: {"source":"kitchen", "message":"is dinner ready yet"} self.response_location = '' LOG.info('Message Received! - ' + new_message["message"] + ', From: ' + new_message["source"]) self.speak_dialog('location', data={"result": new_message["source"]}, expect_response=False) wait_while_speaking() self.speak_dialog('message', data={"result": new_message["message"]}, expect_response=False) else: LOG.info('Unable to decode the MQTT Message') except Exception as e: LOG.error('Error: {0}'.format(e)) # This method loads the files needed for the skill's functioning, and # creates and registers each intent that the skill uses def initialize(self): self.load_data_files(dirname(__file__)) # Check and then monitor for credential changes self.settings.set_changed_callback(self.on_websettings_changed) self.on_websettings_changed() self.deviceUUID = self.get_mac_address() self.add_event('recognizer_loop:utterance', self.handle_utterances) # should be "utterances" self.add_event('speak', self.handle_speak) # should be "utterance" mqttc.on_connect = self.on_connect mqttc.on_message = self.on_message mqttc.on_disconnect = self.on_disconnect if self._is_setup: self.mqtt_init() def on_websettings_changed(self): # called when updating mycroft home page self._is_setup = False self.MQTT_Enabled = self.settings.get( "MQTT_Enabled", False) # used to enable / disable mqtt self.broker_address = self.settings.get("broker_address", "127.0.0.1") self.base_topic = self.settings.get("base_topic", "Mycroft") self.broker_port = self.settings.get("broker_port", 1883) self.broker_uname = self.settings.get("broker_uname", "") self.broker_pass = self.settings.get("broker_pass", "") # self.location_id = self.settings.get("location_id", "basement") # This is the device_id of this device this_location_id = str(DeviceApi().get()["description"]) self.location_id = this_location_id.lower() LOG.info("This device location is: " + str(self.location_id)) try: mqttc LOG.info('Client exist') mqttc.loop_stop() mqttc.disconnect() LOG.info('Stopped old client loop') except NameError: mqttc = mqtt.Client() LOG.info('Client re-created') LOG.info("Websettings Changed! " + self.broker_address + ", " + str(self.broker_port)) self.mqtt_init() self._is_setup = True def mqtt_init( self ): # initializes the MQTT configuration and subscribes to its own topic if self.MQTT_Enabled: LOG.info('MQTT Is Enabled') try: LOG.info("Connecting to host: " + self.broker_address + ", on port: " + str(self.broker_port)) if self.broker_uname and self.broker_pass: LOG.info("Using MQTT Authentication") mqttc.username_pw_set(username=self.broker_uname, password=self.broker_pass) mqttc.connect_async(self.broker_address, self.broker_port, 60) mqttc.loop_start() LOG.info("MQTT Loop Started Successfully") # LOG.info("This device location is: " + DeviceApi().get()["description"]) except Exception as e: LOG.error('Error: {0}'.format(e)) def id_generator(self, size=6, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) def get_mac_address( self): #used to create a unique UUID for this device that node = uuid.getnode() mac = uuid.UUID(int=node).hex[-12:] LOG.info("MQTT using UUID: " + mac) return mac # utterance event used for notifications ***This is what the user requests*** def handle_utterances(self, message): mqtt_path = self.base_topic + "/RemoteDevices/" + self.deviceUUID + "/request" # LOG.info(mqtt_path) voice_payload = str(message.data.get('utterances')[0]) if self.notifier_bool: try: # LOG.info(voice_payload) self.send_MQTT(mqtt_path, voice_payload) except Exception as e: LOG.error(e) self.on_websettings_changed() # mycroft speaking event used for notificatons ***This is what mycroft says*** def handle_speak(self, message): mqtt_path = self.base_topic + "/RemoteDevices/" + self.deviceUUID + "/response" # LOG.info(mqtt_path) voice_payload = message.data.get('utterance') if self.notifier_bool: try: self.send_MQTT(mqtt_path, voice_payload) LOG.info("Response Location Length: " + str(len(self.response_location))) if len(self.response_location) == 0: self.response_location = '' else: reply_payload = { "source": str(self.location_id), "message": voice_payload } reply_path = self.base_topic + "/RemoteDevices/" + self.response_location self.response_location = '' self.send_MQTT(reply_path, reply_payload) except Exception as e: LOG.error(e) self.on_websettings_changed() def send_MQTT(self, my_topic, my_message): # Sends MQTT Message # LOG.info("This device location is: " + DeviceApi().get()["description"]) if self.MQTT_Enabled and self._is_setup: LOG.info("MQTT: " + my_topic + ", " + json.dumps(my_message)) # myID = self.id_generator() LOG.info("address: " + self.broker_address + ", Port: " + str(self.broker_port)) publish.single(my_topic, json.dumps(my_message), hostname=self.broker_address) else: LOG.info( "MQTT has been disabled in the websettings at https://home.mycroft.ai" ) def send_message( self, message): # Sends the remote received commands to the messagebus LOG.info("Sending a command to the message bus: " + message) payload = json.dumps({ "type": "recognizer_loop:utterance", "context": "", "data": { "utterances": [message] } }) uri = 'ws://localhost:8181/core' ws = create_connection(uri) ws.send(payload) ws.close() # First step in the dialog is to receive the initial request to "send a message/command" @intent_handler( IntentBuilder("SendMessageIntent").require("SendKeyword").require( "MessageTypeKeyword").optionally("RemoteKeyword").build()) def handle_send_message_intent(self, message): message_json = {} # create json object message_json['source'] = str(self.location_id) # LOG.info("This device location is: " + DeviceApi().get()["description"]) msg_type = message.data.get("MessageTypeKeyword") if self.config_core.get('lang') == 'nl-nl': if message.data.get("MessageTypeKeyword") == 'commando': msg_type = 'command' elif message.data.get("MessageTypeKeyword") == 'bericht': msg_type = 'message' self.targetDevice = self.get_response('request.location', data={"result": msg_type}) message_json[msg_type] = self.get_response('request.details', data={"result": msg_type}) LOG.info("Preparing to Send a message to " + self.targetDevice) self.speak_dialog('sending.message', data={ "message": msg_type, "location": self.targetDevice }, expect_response=False) mqtt_path = self.base_topic + "/RemoteDevices/" + str( self.targetDevice).lower() self.send_MQTT(mqtt_path, message_json) def stop(self): pass
def initialize(self): """ Mycroft Google Calendar Intents """ self.load_data_files(dirname(__file__)) self.load_regex_files(join(dirname(__file__), 'regex', 'en-us')) self.emitter.on(self.name + '.google_calendar',self.google_calendar) self.emitter.emit(Message(self.name + '.google_calendar')) intent = IntentBuilder('WhenEventsFutureIntent')\ .require('WhenKeyword') \ .require('IsKeyword') \ .require('Ask') \ .build() self.register_intent(intent, self.handle_when_event_future) """ intent = IntentBuilder('WhenEventsFutureIntent')\ .require('DateKeyword') \ .require('OfKeyword') \ .require('TheKeyword') \ .build() self.register_intent(intent, self.handle_when_event_future) """ # ***************************************************** # Add event Intent to Calendar Section # ***************************************************** // .optionally('OnKeyword') \ intent = IntentBuilder('AddMonthDayIntent')\ .require('ScheduleKeyword') \ .optionally('CalendarGroupKeyword') \ .require('EventKeyword') \ .require('MonthKeyword') \ .require('XDaysKeyword') \ .require('AtKeyword') \ .require('HoursKeyword') \ .optionally('AMPMKeyword') \ .optionally('Description') \ .build() self.register_intent(intent, self.handle_add_month_day_event) intent = IntentBuilder('AddEventIntent')\ .require('SetKeyword') \ .optionally('CalendarGroupKeyword') \ .require('EventKeyword') \ .optionally('DateEventKeyword') \ .require('AtKeyword') \ .require('HoursKeyword') \ .optionally('AMPMKeyword') \ .optionally('Description') \ .build() self.register_intent(intent, self.handle_add_event) intent = IntentBuilder('AddMonthDayStartEndIntent')\ .require('ScheduleKeyword') \ .optionally('CalendarGroupKeyword') \ .require('EventKeyword') \ .optionally('OnKeyword') \ .require('MonthKeyword') \ .require('XDaysKeyword') \ .require('FromKeyword') \ .require('HoursKeyword') \ .optionally('AMPMKeyword') \ .require('ToKeyword') \ .require('HoursEndKeyword') \ .optionally('AMPMEndKeyword') \ .optionally('Description') \ .build() self.register_intent(intent, self.handle_add_month_start_end_event) intent = IntentBuilder('AddEventStartEndIntent')\ .require('SetKeyword') \ .optionally('CalendarGroupKeyword') \ .require('EventKeyword') \ .optionally('DateEventKeyword') \ .require('FromKeyword') \ .require('HoursKeyword') \ .optionally('AMPMKeyword') \ .require('HoursEndKeyword') \ .optionally('AMPMEndKeyword') \ .optionally('Description') \ .build() self.register_intent(intent, self.handle_add_start_end_event) # ***************************************************** # Get events Intent from Calendar Section # ***************************************************** intent = IntentBuilder('NextEventIntent')\ .require('NextKeyword') \ .require('EventKeyword') \ .optionally('WhereKeyword') \ .build() self.register_intent(intent, self.handle_next_event) intent = IntentBuilder('WithEventIntent')\ .require('EventKeyword') \ .require('Person') \ .build() self.register_intent(intent, self.handle_with_event) intent = IntentBuilder('TodaysEventsIntent')\ .require('ForKeyword') \ .require('TodayKeyword') \ .require('EventKeyword') \ .build() self.register_intent(intent, self.handle_today_events) intent = IntentBuilder('TomorrowEventsIntent')\ .require('ForKeyword') \ .require('TomorrowKeyword') \ .require('EventKeyword') \ .build() self.register_intent(intent, self.handle_tomorrow_events) intent = IntentBuilder('UntilTomorrowEventsIntent')\ .require('UntilTomorrowKeyword') \ .require('EventKeyword') \ .build() self.register_intent(intent, self.handle_until_tomorrow_events) intent = IntentBuilder('EventsForWeekDayIntent')\ .require('ForKeyword') \ .require('WeekdayKeyword') \ .require('EventKeyword') \ .build() self.register_intent(intent, self.handle_weekday_events) intent = IntentBuilder('EventsForXDaysIntent')\ .require('FollowingsKeyword') \ .require('EventKeyword') \ .require('XDaysKeyword') \ .build() self.register_intent(intent, self.handle_xdays_events)
def _connect(self, message): url = "http://localhost:6680" try: self.mopidy = Mopidy(url) except: logger.info('Could not connect to server, retrying in 10 sec') time.sleep(10) self.emitter.emit(Message(self.name + '.connect')) return self.albums = {} self.artists = {} self.genres = {} self.playlists = {} self.radios = {} logger.info('Loading content') self.albums['gmusic'] = self.mopidy.get_gmusic_albums() self.artists['gmusic'] = self.mopidy.get_gmusic_artists() self.genres['gmusic'] = self.mopidy.get_gmusic_radio() self.playlists['gmusic'] = {} self.albums['local'] = self.mopidy.get_local_albums() self.artists['local'] = self.mopidy.get_local_artists() self.genres['local'] = self.mopidy.get_local_genres() self.playlists['local'] = self.mopidy.get_local_playlists() self.albums['spotify'] = {} self.artists['spotify'] = {} self.genres['spotify'] = {} self.playlists['spotify'] = self.mopidy.get_spotify_playlists() self.playlist = {} for loc in ['local', 'gmusic', 'spotify']: logger.info(loc) self.playlist.update(self.playlists[loc]) logger.info(loc) self.playlist.update(self.genres[loc]) logger.info(loc) self.playlist.update(self.artists[loc]) logger.info(loc) self.playlist.update(self.albums[loc]) self.register_vocabulary(self.name, 'NameKeyword') for p in self.playlist.keys(): logger.debug("Playlist: " + p) self.register_vocabulary(p, 'PlaylistKeyword' + self.name) intent = IntentBuilder('PlayPlaylistIntent' + self.name)\ .require('PlayKeyword')\ .require('PlaylistKeyword' + self.name)\ .build() self.register_intent(intent, self.handle_play_playlist) intent = IntentBuilder('PlayFromIntent' + self.name)\ .require('PlayKeyword')\ .require('PlaylistKeyword')\ .require('NameKeyword')\ .build() self.register_intent(intent, self.handle_play_playlist) self.register_regex("for (?P<Source>.*)") intent = IntentBuilder('SearchSpotifyIntent' + self.name)\ .require('SearchKeyword')\ .optionally('Source')\ .require('SpotifyKeyword')\ .build() self.register_intent(intent, self.search_spotify)
stop_keywords = [ 'stop', 'terminate', 'end', 'quit', 'disable', 'unenroll', ] for enroll_keyword in enroll_keywords: engine.register_entity(enroll_keyword, "EnrollKeywords") for stop_keyword in stop_keywords: engine.register_entity(stop_keyword, "StopKeywords") enroll_intent = IntentBuilder( MessageIntent.ENROLL.value).require("EnrollKeywords").build() stop_intent = IntentBuilder( MessageIntent.STOP.value).require("StopKeywords").build() engine.register_intent_parser(enroll_intent) engine.register_intent_parser(stop_intent) def identify_message(message: str) -> MessageIntent: for intent in engine.determine_intent(utterance=message): if intent and intent.get('confidence') > 0: return MessageIntent(intent.get('intent_type')) return MessageIntent.OTHER
class PlaybackControlSkill(MycroftSkill): def __init__(self): super(PlaybackControlSkill, self).__init__('Playback Control Skill') self.query_replies = {} # cache of received replies self.query_extensions = {} # maintains query timeout extensions self.has_played = False self.lock = Lock() # TODO: Make this an option for voc_match()? Only difference is the # comparison using "==" instead of "in" def voc_match_exact(self, utt, voc_filename, lang=None): """ Determine if the given utterance contains the vocabulary provided Checks for vocabulary match in the utterance instead of the other way around to allow the user to say things like "yes, please" and still match against "Yes.voc" containing only "yes". The method first checks in the current skill's .voc files and secondly the "res/text" folder of mycroft-core. The result is cached to avoid hitting the disk each time the method is called. Args: utt (str): Utterance to be tested voc_filename (str): Name of vocabulary file (e.g. 'yes' for 'res/text/en-us/yes.voc') lang (str): Language code, defaults to self.long Returns: bool: True if the utterance has the given vocabulary it """ lang = lang or self.lang cache_key = lang + voc_filename if cache_key not in self.voc_match_cache: # Check for both skill resources and mycroft-core resources voc = self.find_resource(voc_filename + '.voc', 'vocab') if not voc: voc = self.resolve_resource_file( join('text', lang, voc_filename + '.voc')) if not voc or not exists(voc): raise FileNotFoundError( 'Could not find {}.voc file'.format(voc_filename)) with open(voc) as f: self.voc_match_cache[cache_key] = f.read().splitlines() # Check for exact match if utt and any(i.strip() == utt for i in self.voc_match_cache[cache_key]): return True return False def initialize(self): self.audio_service = AudioService(self.bus) self.add_event('play:query.response', self.handle_play_query_response) self.add_event('play:status', self.handle_song_info) self.gui.register_handler('next', self.handle_next) self.gui.register_handler('prev', self.handle_prev) self.clear_gui_info() # Handle common audio intents. 'Audio' skills should listen for the # common messages: # self.add_event('mycroft.audio.service.next', SKILL_HANDLER) # self.add_event('mycroft.audio.service.prev', SKILL_HANDLER) # self.add_event('mycroft.audio.service.pause', SKILL_HANDLER) # self.add_event('mycroft.audio.service.resume', SKILL_HANDLER) def clear_gui_info(self): """Clear the gui variable list.""" # Initialize track info variables for k in STATUS_KEYS: self.gui[k] = '' @intent_handler(IntentBuilder('').require('Next').require("Track")) def handle_next(self, message): self.audio_service.next() @intent_handler(IntentBuilder('').require('Prev').require("Track")) def handle_prev(self, message): self.audio_service.prev() @intent_handler(IntentBuilder('').require('Pause')) def handle_pause(self, message): self.audio_service.pause() @intent_handler(IntentBuilder('').one_of('PlayResume', 'Resume')) def handle_play(self, message): """Resume playback if paused""" self.audio_service.resume() def stop(self, message=None): self.clear_gui_info() self.log.info('Audio service status: ' '{}'.format(self.audio_service.track_info())) if self.audio_service.is_playing: self.audio_service.stop() return True else: return False def converse(self, utterances, lang="en-us"): if (utterances and self.has_played and self.voc_match_exact(utterances[0], "converse_resume")): # NOTE: voc_match() will overmatch (e.g. it'll catch "play next # song" or "play Some Artist") self.audio_service.resume() return True else: return False @intent_handler(IntentBuilder('').require('Play').require('Phrase')) def play(self, message): self.speak_dialog("just.one.moment") # Remove everything up to and including "Play" # NOTE: This requires a Play.voc which holds any synomyms for 'Play' # and a .rx that contains each of those synonyms. E.g. # Play.voc # play # bork # phrase.rx # play (?P<Phrase>.*) # bork (?P<Phrase>.*) # This really just hacks around limitations of the Adapt regex system, # which will only return the first word of the target phrase utt = message.data.get('utterance') phrase = re.sub('^.*?' + message.data['Play'], '', utt).strip() self.log.info("Resolving Player for: " + phrase) wait_while_speaking() self.enclosure.mouth_think() # Now we place a query on the messsagebus for anyone who wants to # attempt to service a 'play.request' message. E.g.: # { # "type": "play.query", # "phrase": "the news" / "tom waits" / "madonna on Pandora" # } # # One or more skills can reply with a 'play.request.reply', e.g.: # { # "type": "play.request.response", # "target": "the news", # "skill_id": "<self.skill_id>", # "conf": "0.7", # "callback_data": "<optional data>" # } # This means the skill has a 70% confidence they can handle that # request. The "callback_data" is optional, but can provide data # that eliminates the need to re-parse if this reply is chosen. # self.query_replies[phrase] = [] self.query_extensions[phrase] = [] self.bus.emit(message.forward('play:query', data={"phrase": phrase})) self.schedule_event(self._play_query_timeout, 1, data={"phrase": phrase}, name='PlayQueryTimeout') def handle_play_query_response(self, message): with self.lock: search_phrase = message.data["phrase"] if ("searching" in message.data and search_phrase in self.query_extensions): # Manage requests for time to complete searches skill_id = message.data["skill_id"] if message.data["searching"]: # extend the timeout by 5 seconds self.cancel_scheduled_event("PlayQueryTimeout") self.schedule_event(self._play_query_timeout, 5, data={"phrase": search_phrase}, name='PlayQueryTimeout') # TODO: Perhaps block multiple extensions? if skill_id not in self.query_extensions[search_phrase]: self.query_extensions[search_phrase].append(skill_id) else: # Search complete, don't wait on this skill any longer if skill_id in self.query_extensions[search_phrase]: self.query_extensions[search_phrase].remove(skill_id) if not self.query_extensions[search_phrase]: self.cancel_scheduled_event("PlayQueryTimeout") self.schedule_event(self._play_query_timeout, 0, data={"phrase": search_phrase}, name='PlayQueryTimeout') elif search_phrase in self.query_replies: # Collect all replies until the timeout self.query_replies[message.data["phrase"]].append(message.data) def _play_query_timeout(self, message): with self.lock: # Prevent any late-comers from retriggering this query handler search_phrase = message.data["phrase"] self.query_extensions[search_phrase] = [] self.enclosure.mouth_reset() # Look at any replies that arrived before the timeout # Find response(s) with the highest confidence best = None ties = [] self.log.debug("CommonPlay Resolution: {}".format(search_phrase)) for handler in self.query_replies[search_phrase]: self.log.debug(" {} using {}".format( handler["conf"], handler["skill_id"])) if not best or handler["conf"] > best["conf"]: best = handler ties = [] elif handler["conf"] == best["conf"]: ties.append(handler) if best: if ties: # TODO: Ask user to pick between ties or do it # automagically pass # invoke best match self.gui.show_page("controls.qml", override_idle=True) self.log.info("Playing with: {}".format(best["skill_id"])) start_data = { "skill_id": best["skill_id"], "phrase": search_phrase, "callback_data": best.get("callback_data") } self.bus.emit(message.forward('play:start', start_data)) self.has_played = True elif self.voc_match(search_phrase, "Music"): self.speak_dialog("setup.hints") else: self.log.info(" No matches") self.speak_dialog("cant.play", data={"phrase": search_phrase}) if search_phrase in self.query_replies: del self.query_replies[search_phrase] if search_phrase in self.query_extensions: del self.query_extensions[search_phrase] def handle_song_info(self, message): changed = False for key in STATUS_KEYS: val = message.data.get(key, '') changed = changed or self.gui[key] != val self.gui[key] = val if changed: self.log.info('\n-->Track: {}\n-->Artist: {}\n-->Image: {}' ''.format(self.gui['track'], self.gui['artist'], self.gui['image']))
class PianobarSkill(MycroftSkill): def __init__(self): super(PianobarSkill, self).__init__(name="PianobarSkill") self.process = None self.piano_bar_state = None # 'playing', 'paused', 'autopause' self.current_station = None self._is_setup = False self.vocabs = [] # keep a list of vocabulary words self.pianobar_path = expanduser('~/.config/pianobar') self._pianobar_initated = False self.debug_mode = False self.idle_count = 0 # Initialize settings values self.settings["email"] = "" self.settings["password"] = "" self.settings["song_artist"] = "" self.settings["song_title"] = "" self.settings["song_album"] = "" self.settings["station_name"] = "" self.settings["station_count"] = 0 self.settings["stations"] = [] self.settings["last_played"] = None self.settings['first_init'] = True # True = first run ever subprocess.call(["killall", "-9", "pianobar"]) def initialize(self): self._load_vocab_files() # Check and then monitor for credential changes self.settings.set_changed_callback(self.on_websettings_changed) self.on_websettings_changed() self.add_event('mycroft.stop', self.stop) ###################################################################### # 'Auto ducking' - pause playback when Mycroft wakes def handle_listener_started(self, message): if self.piano_bar_state == "playing": self.handle_pause() self.piano_bar_state = "autopause" def handle_listener_ended(self, message): if self.piano_bar_state == "autopause": # Start idle check self.idle_count = 0 self.cancel_scheduled_event('IdleCheck') self.schedule_repeating_event(self.check_for_idle, None, 1, name='IdleCheck') def check_for_idle(self): if not self.piano_bar_state == "autopause": self.cancel_scheduled_event('IdleCheck') return active = DisplayManager.get_active() # LOG.info("active: " + str(active)) if active == "PianobarSkill" or not active: # No activity, start to fall asleep self.idle_count += 1 if self.idle_count >= 2: # Resume playback after 2 seconds of being idle self.cancel_scheduled_event('IdleCheck') self.handle_resume_song() else: self.idle_count = 0 ###################################################################### def _register_all_intents(self): """ Intents should only be registered once settings are inputed to avoid conflicts with other music skills """ next_station_intent = IntentBuilder("PandoraNextStationIntent"). \ require("Next").require("Station").build() self.register_intent(next_station_intent, self.handle_next_station) list_stations_intent = IntentBuilder("PandoraListStationIntent"). \ optionally("Pandora").require("Query").require("Station").build() self.register_intent(list_stations_intent, self.handle_list) play_stations_intent = IntentBuilder("PandoraChangeStationIntent"). \ require("Change").require("Station").build() self.register_intent(play_stations_intent, self.play_station) # Messages from the skill-playback-control / common Audio service self.add_event('mycroft.audio.service.pause', self.handle_pause) self.add_event('mycroft.audio.service.resume', self.handle_resume_song) self.add_event('mycroft.audio.service.next', self.handle_next_song) def on_websettings_changed(self): if not self._is_setup: email = self.settings.get("email", "") password = self.settings.get("password", "") try: if email and password: self._configure_pianobar() self._init_pianobar() self._register_all_intents() self._is_setup = True except Exception as e: LOG.error(e) def _configure_pianobar(self): # Initialize the Pianobar configuration file if not exists(self.pianobar_path): makedirs(self.pianobar_path) config_path = join(self.pianobar_path, 'config') with open(config_path, 'w+') as f: # grabs the tls_key needed tls_key = subprocess.check_output( "openssl s_client -connect tuner.pandora.com:443 \ < /dev/null 2> /dev/null | openssl x509 -noout \ -fingerprint | tr -d ':' | cut -d'=' -f2", shell=True) config = 'audio_quality = medium\n' + \ 'tls_fingerprint = {}\n' + \ 'user = {}\n' + \ 'password = {}\n' + \ 'event_command = {}' f.write( config.format(tls_key, self.settings["email"], self.settings["password"], self._dir + '/event_command.py')) # Raspbian requires adjustments to audio output to use PulseAudio platform = self.config_core['enclosure'].get('platform') if platform == 'picroft' or platform == 'mycroft_mark_1': libao_path = expanduser('~/.libao') if not isfile(libao_path): with open(libao_path, 'w') as f: f.write('dev=0\ndefault_driver=pulse') self.speak_dialog("configured.please.reboot") wait_while_speaking() self.emitter.emit(Message('system.reboot')) def _load_vocab_files(self): # Keep a list of all the vocabulary words for this skill. Later # these words will be removed from utterances as part of the station # name. vocab_dir = join(dirname(__file__), 'vocab', self.lang) if path.exists(vocab_dir): for vocab_type in listdir(vocab_dir): if vocab_type.endswith(".voc"): with open(join(vocab_dir, vocab_type), 'r') as voc_file: for line in voc_file: parts = line.strip().split("|") vocab = parts[0] self.vocabs.append(vocab) else: LOG.error('No vocab loaded, ' + vocab_dir + ' does not exist') def start_monitor(self): # Clear any existing event self.stop_monitor() # Schedule a new one every second to monitor/update display self.schedule_repeating_event(self._poll_for_pianobar_update, None, 1, name='MonitorPianobar') self.add_event('recognizer_loop:record_begin', self.handle_listener_started) self.add_event('recognizer_loop:utterance', self.handle_listener_ended) def stop_monitor(self): # Clear any existing event self.cancel_scheduled_event('MonitorPianobar') def _poll_for_pianobar_update(self, message): # Checks once a second for feedback from Pianobar # 'info_ready' file is created by the event_command.py # script when Pianobar sends new track information. info_ready_path = join(self.pianobar_path, 'info_ready') if isfile(info_ready_path): self._load_current_info() try: remove(info_ready_path) except Exception as e: LOG.error(e) # Update the "Now Playing song" LOG.info("State: " + str(self.piano_bar_state)) if self.piano_bar_state == "playing": self.enclosure.mouth_text(self.settings["song_artist"] + ": " + self.settings["song_title"]) def cmd(self, s): self.process.stdin.write(s.encode()) self.process.stdin.flush() def _init_pianobar(self): if self.settings.get('first_init') is False: return # Run this exactly one time to prepare pianobar for usage # by Mycroft. try: LOG.info("INIT PIANOBAR") subprocess.call(["killall", "-9", "pianobar"]) self.process = subprocess.Popen(["pianobar"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) time.sleep(3) self.cmd("0\n") self.cmd("S") time.sleep(0.5) self.process.kill() self.settings['first_init'] = False self._load_current_info() except Exception as e: LOG.exception('Failed to connect to Pandora') self.speak_dialog('wrong.credentials') self.process = None def _load_current_info(self): # Load the 'info' file created by Pianobar when changing tracks info_path = join(self.pianobar_path, 'info') # this is a hack to remove the info_path # if it's a directory. An earlier version # of code may sometimes create info_path as # a directory instead of a file path # date: 02-18 if isdir(info_path): shutil.rmtree(info_path) if not exists(info_path): with open(info_path, 'w+'): pass with open(info_path, 'r') as f: info = json.load(f) # Save the song info for later display self.settings["song_artist"] = info["artist"] self.settings["song_title"] = info["title"] self.settings["song_album"] = info["album"] self.settings["station_name"] = info["stationName"] if self.debug_mode: LOG.info("Station name: " + str(self.settings['station_name'])) self.settings["station_count"] = int(info["stationCount"]) self.settings["stations"] = [] for index in range(self.settings["station_count"]): station = "station" + str(index) self.settings["stations"].append( (info[station].replace("Radio", ""), index)) if self.debug_mode: LOG.info("Stations: " + str(self.settings["stations"])) # self.settings.store() def _launch_pianobar_process(self): try: LOG.info("Starting Pianobar process") subprocess.call(["killall", "-9", "pianobar"]) time.sleep(1) # start pandora if self.debug_mode: self.process = \ subprocess.Popen(["pianobar"], stdin=subprocess.PIPE) else: self.process = subprocess.Popen(["pianobar"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) self.current_station = "0" self.cmd("0\n") self.handle_pause() time.sleep(2) self._load_current_info() LOG.info("Pianobar process initialized") except Exception: LOG.exception('Failed to connect to Pandora') self.speak_dialog('wrong.credentials') self.process = None def _extract_station(self, utterance): """ parse the utterance for station names and return station with highest probability """ try: # TODO: Internationalize common_words = [" to ", " on ", " pandora", " play"] for vocab in self.vocabs: utterance = utterance.replace(vocab, "") # strip out other non important words for words in common_words: utterance = utterance.replace(words, "") utterance.lstrip() stations = [station[0] for station in self.settings["stations"]] probabilities = fuzz_process.extractOne(utterance, stations, scorer=fuzz.ratio) if self.debug_mode: LOG.info("Probabilities: " + str(probabilities)) if probabilities[1] > 70: station = probabilities[0] return station else: return None except Exception as e: LOG.info(e) return None def _play_station(self, station, dialog=None): LOG.info("Starting: " + str(station)) self._launch_pianobar_process() if not self.process: return if dialog: self.speak_dialog(dialog, {"station": station}) else: if station: self.speak_dialog("playing.station", {"station": station}) self.enclosure.mouth_think() if station: for channel in self.settings.get("stations"): if station == channel[0]: self.cmd("s") self.current_station = str(channel[1]) station_number = str(channel[1]) + "\n" self.cmd(station_number) self.piano_bar_state = "playing" self.settings["last_played"] = channel self.start_monitor() else: time.sleep(2) # wait for pianobar to loading if self.debug_mode: LOG.info(self.settings.get('stations')) # try catch block because some systems # may not load pianobar info in time try: channel = self.settings.get("stations")[0] if self.debug_mode: LOG.info(channel) if channel: self.speak_dialog("playing.station", {"station": channel[0]}) station_number = str(channel[1]) + "\n" if self.debug_mode: LOG.info(station_number) self.cmd(station_number) self.settings["last_played"] = channel else: raise ValueError except Exception as e: LOG.info(e) self.speak_dialog("playing.station", {"station": "pandora"}) self.current_station = "0" self.cmd("0\n") self.handle_resume_song() self.piano_bar_state = "playing" self.start_monitor() @intent_handler(IntentBuilder("").require("Play").require("Pandora")) def play_pandora(self, message=None): if self._is_setup: # Examine the whole utterance to see if the user requested a # station by name station = self._extract_station(message.data["utterance"]) if self.debug_mode: LOG.info("Station request:" + str(station)) dialog = None if not station: last_played = self.settings.get("last_played") if last_played: station = last_played[0] dialog = "resuming.last.station" else: # default to the first station in the list if self.settings.get("stations"): station = self.settings["stations"][0][0] # Play specified station self._play_station(station, dialog) else: # Lead user to setup for Pandora self.speak_dialog("please.register.pandora") def handle_next_song(self, message=None): if self.process: self.enclosure.mouth_think() self.cmd("n") self.piano_bar_state = "playing" self.start_monitor() def handle_next_station(self, message=None): if self.process and self.settings.get("stations"): new_station = int(self.current_station) + 1 if new_station >= int(self.settings.get("station_count", 0)): new_station = 0 new_station = self.settings["stations"][new_station][0] self._play_station(new_station) def handle_pause(self, message=None): if self.process: self.cmd("S") self.process.stdin.flush() self.piano_bar_state = "paused" self.stop_monitor() def handle_resume_song(self, message=None): if self.process: self.cmd("P") self.piano_bar_state = "playing" self.start_monitor() def play_station(self, message=None): if self._is_setup: # Examine the whole utterance to see if the user requested a # station by name station = self._extract_station(message.data["utterance"]) if station is not None: self._play_station(station) else: self.speak_dialog("no.matching.station") else: # Lead user to setup for Pandora self.speak_dialog("please.register.pandora") def handle_list(self, message=None): is_playing = self.piano_bar_state == "playing" if is_playing: self.handle_pause() # build the list of stations l = [] for station in self.settings.get("stations"): l.append(station[0]) # [0] = name if len(l) == 0: self.speak_dialog("no.stations") return # read the list if len(l) > 1: list = ', '.join(l[:-1]) + " " + \ self.translate("and") + " " + l[-1] else: list = str(l) self.speak_dialog("subscribed.to.stations", {"stations": list}) if is_playing: wait_while_speaking() self.handle_resume_song() @intent_handler( IntentBuilder("PandoraLoveSongIntent").require("Love").require("Song")) def handle_love(self, message=None): if self.process: utterance = message.data["utterance"] if "don't" in utterance or "not" in utterance: self.handle_ban(message) return self.cmd("+") self.speak_dialog("love.song") # if self.piano_bar_state != "playing": # self.handle_resume_song() @intent_handler( IntentBuilder("PandoraBanSongIntent").require("Ban").require("Song")) def handle_ban(self, message=None): if self.process: self.cmd("-") self.speak_dialog("ban.song") @intent_handler( IntentBuilder("PandoraTiredSongIntent").require("Tired").require( "Song")) def handle_tired(self, message=None): if self.process: self.cmd("t") self.speak_dialog("tired.song") @intent_handler( IntentBuilder("PandoraVolumeRaiseIntent").require("Raise").require( "Pandora").require("Volume")) def handle_volume_raise(self, message=None): if self.process: self.cmd(")") self.speak_dialog("raised.pandora.volume") @intent_handler( IntentBuilder("PandoraVolumeLowerIntent").require("Lower").require( "Pandora").require("Volume")) def handle_volume_lower(self, message=None): if self.process: self.cmd("(") self.speak_dialog("lowered.pandora.volume") @intent_handler( IntentBuilder("PandoraVolumeResetIntent").require("Reset").require( "Pandora").require("Volume")) def handle_volume_reset(self, message=None): if self.process: self.cmd("^") self.speak_dialog("reset.pandora.volume") @intent_handler( IntentBuilder("PandoraTrackInfo").require("Song").require("Playing")) def handle_track_info(self, message=None): if self.process: song = self.settings["song_title"] artist = self.settings["song_artist"] album = self.settings["song_album"] self.speak_dialog("track.info", { "song": song, "artist": artist, "album": album }) def stop(self): LOG.info('STOPPING PANDORA') if not self.piano_bar_state == "paused": LOG.info('YES') self.handle_pause() self.enclosure.mouth_reset() return True @intent_handler( IntentBuilder("").require("Pandora").require("Debug").require("On")) def debug_on_intent(self, message=None): if not self.debug_mode: self.debug_mode = True self.speak_dialog("entering.debug.mode") @intent_handler( IntentBuilder("").require("Pandora").require("Debug").require("Off")) def debug_off_intent(self, message=None): if self.debug_mode: self.debug_mode = False self.speak_dialog("leaving.debug.mode") def shutdown(self): self.stop_monitor() # Clean up before shutting down the skill if self.piano_bar_state == "playing": self.enclosure.mouth_reset() if self.process: self.cmd("q") super(PianobarSkill, self).shutdown()
def initialize(self): what_my_heart_rate = IntentBuilder("HeartMonitor"). \ require("HeartMonitor").build() self.register_intent(what_my_heart_rate, self.handle_what_my_heart_rate)
def initialize(self): self.load_data_files(dirname(__file__)) next_launch_intent = IntentBuilder("NextLaunchIntent").\ require("NextLaunchKeyword").build() self.register_intent(next_launch_intent, self.handle_next_launch_intent)
class YoutubeMpvSkill(MycroftSkill): def __init__(self): super(YoutubeMpvSkill, self).__init__(name="YoutubeMpvSkill") self.search = "" self.url = "" self.volume = 80 self.mpv_start = "mpv --volume={}\ --input-ipc-server=/tmp/mpvsocket {} &" self.pause_state = "true" self.mpv_process = "ps cax | grep mpv" # if you want add more options use the mpv manpages self.mpv_echo = "echo '{}' | socat - /tmp/mpvsocket" # setter for unix socket self.mpv_pause = '"command": ["set_property", "pause", {}]' self.mpv_volume = '"command": ["set_property", "volume", {}]' self.mpv_speed = '"command": ["set_property", "speed", {}]' self.mpv_seek = '"command": [ "seek", "{}" ]' # getter from unix socket self.mpv_duration = '"command": ["get_property", "duration"]' self.mpv_time_pos = '"command": ["get_property", "time-pos"]' self.mpv_time_remaining = '"command": ["get_property", \ "time-remaining"]' self.mpv_get_volume = '"command": ["get_property", "volume"]' self.mpv_get_speed = '"command": ["get_property", "speed"]' self.mpv_stop = "killall mpv" def getResults(self, search, pos=0): query = urllib.parse.quote(search) link = "https://www.youtube.com/results?search_query=" + query response = urllib.request.urlopen(link) html = response.read() soup = BeautifulSoup(html, 'html.parser') vids = soup.findAll(attrs={'class': 'yt-uix-tile-link'}) url = 'https://www.youtube.com' + vids[pos]['href'] return url def mpvExists(self): cmd = subprocess.Popen("command -v mpv", stdout=subprocess.PIPE, shell=True).stdout.read() cmd = cmd.decode("utf-8") if (cmd != ''): return True else: return False def isMpvRunning(self): exists = subprocess.Popen(self.mpv_process, stdout=subprocess.PIPE, shell=True).stdout.read() exists = exists.decode("utf-8") if (exists == '' or exists is None): return False else: return True def mpvStart(self): if (not self.isMpvRunning()): subprocess.call(self.mpv_start.format(self.volume, self.url), shell=True) self.speak_dialog("ytmpv.start", data={"search": self.search}) else: self.speak_dialog("ytmpv.running") def mpvPause(self, state): self.pause_state = state pause = self.mpv_pause.format(self.pause_state) cmd = self.mpv_echo.format("{" + pause + "}") subprocess.call(cmd, shell=True) def mpvStop(self): subprocess.call("killall mpv", shell=True) subprocess.call("rm /tmp/mpvsocket", shell=True) self.speak_dialog("ytmpv.stop", data={"search": self.search}) self.pause_state = "true" def mpvChangeVol(self, volume): if (volume <= 0): volume = 0 elif (volume >= 100): volume = 100 self.volume = volume cmd = self.mpv_echo.format("{" + self.mpv_volume.format(volume) + "}") subprocess.call(cmd, shell=True) def mpvChangeSpeed(self, speed): if (speed <= 0): speed = 0 elif (speed >= 5): speed = 5 self.speed = speed cmd = self.mpv_echo.format("{" + self.mpv_speed.format(speed) + "}") subprocess.call(cmd, shell=True) def mpvSeek(self, secs): cmd = self.mpv_echo.format("{" + self.mpv_seek.format(secs) + "}") subprocess.call(cmd, shell=True) def getDuration(self): if (self.isMpvRunning()): cmd = self.mpv_echo.format("{" + self.mpv_duration + "}") data = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read() j = json.loads(data) secs = int(str(j["data"]).split(".")[0]) m = int(secs / 60) rest = secs % 60 self.speak_dialog("ytmpv.duration", data={"min": m, "sec": rest}) def getPosition(self): cmd = self.mpv_echo.format("{" + self.mpv_time_pos + "}") data = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read() j = json.loads(data) secs = int(str(j["data"]).split(".")[0]) m = int(secs / 60) rest = secs % 60 self.speak_dialog("ytmpv.position", data={"min": m, "sec": rest}) def getRemaining(self): cmd = self.mpv_echo.format("{" + self.mpv_time_remaining + "}") data = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read() j = json.loads(data) secs = int(str(j["data"]).split(".")[0]) m = int(secs / 60) rest = secs % 60 self.speak_dialog("ytmpv.remaining", data={"min": m, "sec": rest}) def getVolume(self): cmd = self.mpv_echo.format('{' + self.mpv_get_volume + '}') data = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read() j = json.loads(data) vol = str(j["data"]).split('.')[0] self.speak_dialog("ytmpv.volume", data={"vol": vol}) def getSpeed(self): cmd = self.mpv_echo.format('{' + self.mpv_get_speed + '}') data = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read() j = json.loads(data) num = str(j["data"]).split(".")[0] point = str(j["data"]).split(".")[1] self.speak_dialog("ytmpv.get.speed", data={"num": num, "point": point}) @intent_handler(IntentBuilder("").require("Start")) def handle_youtubempv_intent(self, message): try: cmd = str(message.data.get('Start')) msg = str(message.data.get('utterance')).replace(cmd + " ", "", 1) if (self.mpvExists()): # TODO adding translations in voc self.search = msg self.url = self.getResults(self.search) self.mpvStart() else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Pause")) def handle_youtubempv_pause_intent(self, message): try: if (self.mpvExists()): self.mpvPause("true") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Resume")) def handle_youtubempv_play_intent(self, message): try: if (self.mpvExists()): self.mpvPause("false") else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Exit")) def handle_youtubempv_exit_intent(self, message): try: if (self.mpvExists()): self.mpvStop() else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Volume")) def handle_youtubempv_volume_intent(self, message): try: if (self.mpvExists()): msg = str(message.data.get("utterance")).split(" ")[2] if (msg != ''): num = int(msg) self.mpvChangeVol(num) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("VolumeDown")) def handle_youtubempv_volume_down_intent(self, message): try: if (self.mpvExists()): self.mpvChangeVol(self.volume - 10) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("VolumeUp")) def handle_youtubempv_volume_up_intent(self, message): try: if (self.mpvExists()): self.mpvChangeVol(self.volume + 10) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Speed")) def handle_youtubempv_speed_intent(self, message): try: if (self.mpvExists()): msg = str(message.data.get("utterance")).split(" ")[2] if (msg != ''): num = float(msg) self.mpvChangeSpeed(num) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("SpeedDown")) def handle_youtubempv_speed_down_intent(self, message): try: if (self.mpvExists()): self.mpvChangeSpeed(self.speed - 0.2) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("SpeedUp")) def handle_youtubempv_speed_up_intent(self, message): try: if (self.mpvExists()): self.mpvChangeSpeed(self.volume + 0.2) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("Seek")) def handle_youtubempv_seek_intent(self, message): try: if (self.mpvExists()): msg = str(message.data.get("utterance")).split(" ")[2] if (msg != ''): secs = int(msg) self.mpvSeek(secs) # self.mpvChangeSpeed(self.volume+0.2) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("SeekForward")) def handle_youtubempv_seek_forward_intent(self, message): try: if (self.mpvExists()): msg = str(message.data.get("utterance")).split(" ")[3] if (msg != ''): secs = int(msg) self.mpvSeek(secs) else: self.mpvSeek(20) # self.mpvChangeSpeed(self.volume+0.2) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("SeekBackward")) def handle_youtubempv_seek_backward_intent(self, message): try: if (self.mpvExists()): msg = str(message.data.get("utterance")).split(" ")[3] if (msg != ''): secs = int(msg) * -1 self.mpvSeek(secs) else: self.mpvSeek(-20) # self.mpvChangeSpeed(self.volume+0.2) else: self.speak_dialog("ytmpv.not.exists") except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("GetDuration")) def handle_youtubempv_get_duration_intent(self, message): try: if (self.mpvExists()): self.getDuration() except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("GetPosition")) def handle_youtubempv_get_position_intent(self, message): try: if (self.mpvExists()): self.getPosition() except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("GetRemaining")) def handle_youtubempv_get_remaining_intent(self, message): try: if (self.mpvExists()): self.getRemaining() except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("GetSpeed")) def handle_youtubempv_get_speed_intent(self, message): try: if (self.mpvExists()): self.getSpeed() except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error") @intent_handler(IntentBuilder("").require("GetVolume")) def handle_youtubempv_get_volume_intent(self, message): try: if (self.mpvExists()): self.getVolume() except Exception as e: LOG.exception("YoutubeMpv Error: " + e.message) self.speak_dialog("ytmpv.error")
def initialize(self): intent = IntentBuilder("QuoteIntent"). \ require("QuoteKeyword").build() self.register_intent(intent, self.handle_intent)
class VolumeSkill(MycroftSkill): """ Control the audio volume for the Mycroft system Terminology: "Level" = Mycroft volume levels, from 0 to 11 "Volume" = ALSA mixer setting, from 0 to 100 """ MIN_LEVEL = 0 MAX_LEVEL = 11 # TODO: Translation layer (have to match word in Level.voc) VOLUME_WORDS = {'loud': 9, 'normal': 6, 'quiet': 3} def __init__(self): super(VolumeSkill, self).__init__("VolumeSkill") self.default_level = self.config.get('default_level') self.min_volume = self.config.get('min_volume') self.max_volume = self.config.get('max_volume') self.volume_sound = join(dirname(__file__), "blop-mark-diangelo.wav") try: Mixer() except: self.log.warning('HACK: first access to Mixer() error workaround') def initialize(self): self.log.info("********** Reeg handlers") intent = IntentBuilder("IncreaseVolume").require("Volume").require( "Increase").build() self.register_intent(intent, self.handle_increase_volume) intent = IntentBuilder("DecreaseVolume").require("Volume").require( "Decrease").build() self.register_intent(intent, self.handle_decrease_volume) intent = IntentBuilder("MuteVolume").require("Volume").require( "Mute").build() self.register_intent(intent, self.handle_mute_volume) intent = IntentBuilder("UnmuteVolume").require("Volume").require( "Unmute").build() self.register_intent(intent, self.handle_unmute_volume) try: # Register handlers for messagebus events self.add_event('mycroft.volume.increase', self.handle_increase_volume) self.add_event('mycroft.volume.decrease', self.handle_decrease_volume) self.add_event('mycroft.volume.mute', self.handle_mute_volume) self.add_event('mycroft.volume.unmute', self.handle_unmute_volume) self.log.info("********** Handlers registered") except: pass @intent_handler( IntentBuilder("SetVolume").require("Volume").require("Level")) def handle_set_volume(self, message): mixer = Mixer() level = self.__get_volume_level(message, mixer.getvolume()[0]) mixer.setvolume(self.__level_to_volume(level)) self.speak_dialog('set.volume', data={'volume': level}) @intent_handler( IntentBuilder("QueryVolume").require("Volume").require("Query")) def handle_query_volume(self, message): level = self.__get_volume_level(message, mixer.getvolume()[0]) self.speak_dialog('volume.is', data={'volume': level}) def __communicate_volume_change(self, message, dialog, code, changed): play_sound = message.data.get('play_sound', False) if play_sound: if changed: play_wav(self.volume_sound) else: if not changed: dialog = 'already.max.volume' self.speak_dialog(dialog, data={'volume': code}) # @intent_handler(IntentBuilder("IncreaseVolume").require( # "Volume").require("Increase")) def handle_increase_volume(self, message): self.__communicate_volume_change(message, 'increase.volume', *self.__update_volume(+1)) # @intent_handler(IntentBuilder("DecreaseVolume").require( # "Volume").require("Decrease")) def handle_decrease_volume(self, message): self.__communicate_volume_change(message, 'decrease.volume', *self.__update_volume(-1)) # @intent_handler(IntentBuilder("MuteVolume").require( # "Volume").require("Mute")) def handle_mute_volume(self, message): speak_message = message.data.get('speak_message', True) if speak_message: self.speak_dialog('mute.volume') wait_while_speaking() Mixer().setvolume(0) # @intent_handler(IntentBuilder("UnmuteVolume").require( # "Volume").require("Unmute")) def handle_unmute_volume(self, message): Mixer().setvolume(self.__level_to_volume(self.default_level)) speak_message = message.data.get('speak_message', True) if speak_message: self.speak_dialog('reset.volume', data={'volume': self.default_level}) def __volume_to_level(self, volume): """ Convert a 'volume' to a 'level' Args: volume (int): min_volume..max_volume Returns: int: the equivalent level """ range = self.MAX_LEVEL - self.MIN_LEVEL prop = float(volume - self.min_volume) / self.max_volume level = int(round(self.MIN_LEVEL + range * prop)) if level > self.MAX_LEVEL: level = self.MAX_LEVEL elif level < self.MIN_LEVEL: level = self.MIN_LEVEL return level def __level_to_volume(self, level): """ Convert a 'level' to a 'volume' Args: level (int): 0..MAX_LEVEL Returns: int: the equivalent volume """ range = self.max_volume - self.min_volume prop = float(level) / self.MAX_LEVEL volume = int(round(self.min_volume + int(range) * prop)) return volume @staticmethod def __bound_level(level): if level > VolumeSkill.MAX_LEVEL: level = VolumeSkill.MAX_LEVEL elif level < VolumeSkill.MIN_LEVEL: level = VolumeSkill.MIN_LEVEL return level def __update_volume(self, change=0): """ Attempt to change audio level Args: change (int): +1 or -1; the step to change by Returns: int: new level code (0..11) bool: whether level changed """ mixer = Mixer() old_level = self.__volume_to_level(mixer.getvolume()[0]) new_level = self.__bound_level(old_level + change) self.enclosure.eyes_volume(new_level) mixer.setvolume(self.__level_to_volume(new_level)) return new_level, new_level != old_level def __get_volume_level(self, message, default=None): level_str = message.data.get('Level', default) level = self.default_level try: level = self.VOLUME_WORDS[level_str] except KeyError: try: level = int(level_str) if (level > self.MAX_LEVEL): # Guess that the user said something like 100 percent # so convert that into a level value level = self.MAX_LEVEL * level / 100 except ValueError: pass level = self.__bound_level(level) return level
def initialize(self): self.load_data_files(dirname(__file__)) Door_command_intent = IntentBuilder("DoorCommandIntent").require("DoorKeyword").require("Action").build() self.register_intent(Door_command_intent, self.handle_Door_command_intent)
class SoundcloudSkill(MycroftSkill): # The constructor of the skill, which calls MycroftSkill's constructor def __init__(self): super(SoundcloudSkill, self).__init__(name="SoundcloudSkill") # Initialize working variables used within the skill. self.count = 0 self.player = SoundCloudPlayer() # The "handle_xxxx_intent" function is triggered by Mycroft when the # skill's intent is matched. The intent is defined by the IntentBuilder() # pieces, and is triggered when the user's utterance matches the pattern # defined by the keywords. In this case, the match occurs when one word # is found from each of the files: # vocab/en-us/Hello.voc # vocab/en-us/World.voc # In this example that means it would match on utterances like: # 'Hello world' # 'Howdy you great big world' # 'Greetings planet earth' @intent_handler(IntentBuilder("").require("Play").require("Soundcloud")) def handle_soundcloud_intent(self, message): try: # In this case, respond by simply speaking a canned response. # Mycroft will randomly speak one of the lines from the file # dialogs/en-us/hello.world.dialog utterance = message.data['utterance'] LOGGER.info("utterance is " + utterance) play = message.data.get("Play") soundcloud = message.data.get("Soundcloud") to_word = ' ' + self.translate('To') on_word = ' ' + self.translate('On') query = utterance.replace(play, "") query = query.replace(play.lower(), "") query = query.replace(soundcloud, "") query = query.replace(soundcloud.lower(), "") query = query.replace(to_word, "") query = query.replace(to_word.lower(), "") query = query.replace(on_word, "") query = query.replace(on_word.lower(), "") trackName = query.strip() LOGGER.info("Finding some tracks for " + trackName) message.data['track'] = trackName self.play_song(message) except Exception as e: LOGGER.error("Error: {0}".format(e)) @intent_handler(IntentBuilder("").require("Stop").require("Soundcloud")) def handle_soundcloud_stop_intent(self, message): self.stop() # The "stop" method defines what Mycroft does when told to stop during # the skill's execution. In this case, since the skill's functionality # is extremely simple, there is no need to override it. If you DO # need to implement stop, you should return True to indicate you handled # it. # def stop(self): self.player.stop() return True def play_song(self, message): """ When the user wants to hear a song, optionally with artist and/or album information attached. play the song <song> play the song <song> by <artist> play the song <song> off <album> play <song> by <artist> off the album <album> etc. Args: message (Dict): The utterance as interpreted by Padatious """ song = message.data.get('track') # create a client object with your app credentials client = soundcloud.Client( client_id='bK2tF3BXmEa5vVQCI1xTZTA9NSwA5NPv') # find all sounds tracks = client.get('/tracks', q=song) urls = [] name = tracks[0].title LOGGER.info("First track to be played is " + name) for track in tracks: urls.append( client.get(tracks[0].stream_url, allow_redirects=False).url) self.speak_dialog("play.soundcloud", data={"track": name}) self.player.play(urls)
def build_intent_delete(self): return IntentBuilder( self.name + 'DeleteIntent').require(self.name + 'DeleteVerb') \ .optionally(self.name + 'Amount').require(self.name + 'Keyword')
if match: skills = match.group(1) else: skills = "" # read the list for followup self.speak_dialog("choose", data={'skills': ", ".join(skills)}) elif rc == 202: # Not found self.speak_dialog("not.found", data={'skill': name}) else: # Other installation error, just read code self.speak_dialog("installation.error", data={'skill': name, 'error': rc}) @intent_handler(IntentBuilder("UninstallIntent").require("Uninstall")) def uninstall(self, message): utterance = message.data.get('utterance').lower() name = utterance.replace(message.data.get('Uninstall'), '') self.speak_dialog("removing") # Invoke MSM to perform removal try: cmd = ' '.join([BIN, 'remove', '"' + name.strip() + '"']) output = subprocess.check_output(cmd, shell=True) self.log.info("MSM output: " + str(output)) rc = 0 except subprocess.CalledProcessError, e: output = e.output rc = e.returncode
def initialize(self): self.load_data_files(dirname(__file__)) intent = IntentBuilder("PairingIntent") \ .require("PairingKeyword").require("DeviceKeyword").build() self.register_intent(intent, self.handle_pairing) self.emitter.on("mycroft.not.paired", self.not_paired)
def initialize(self): super(AlarmSkill, self).initialize() intent = IntentBuilder( 'AlarmSkillStopIntent').require('AlarmSkillStopVerb') \ .require('AlarmSkillKeyword').build() self.register_intent(intent, self.__handle_stop)
class GetEvent(MycroftSkill): def __init__(self): MycroftSkill.__init__(self) @intent_handler(IntentBuilder("").require("event.by.name")) def handle_get_event_by_name(self): # Getting credentials for Google Calendar API creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Query to get the event by name title = self.get_response('what\'s the name of the event') events_result = service.events().list(calendarId='primary', maxResults=1, singleEvents=True, orderBy='startTime', q=title).execute() events = events_result.get('items', []) if not events: self.speak('event not found') for event in events: start = event['start'].get('dateTime', event['start'].get('date')) location = event['location'] description = event['description'] self.speak_dialog('get.event', data={ 'start': start, 'title': title, 'location': location, 'description': description }) @intent_handler(IntentBuilder("").require("attendees.by.event")) def handle_get_attendees_by_event(self): # Getting credentials for Google Calendar API creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Query to get the event by name title = self.get_response('what\'s the name of the event') events_result = service.events().list(calendarId='primary', maxResults=1, singleEvents=True, orderBy='startTime', q=title).execute() events = events_result.get('items', []) attendemail = [] attendname = [] attendstatus = [] i = 0 if not events: print('event not found.') for event in events: start = event['start'].get('dateTime', event['start'].get('date')) eventid = event['id'] attendees = event['attendees'] l = len(attendees) while i != l: attendemail.append(attendees[i]['email']) attendstatus.append(attendees[i]['responseStatus']) attendname.append(attendees[i].get('displayName')) i = i + 1 self.speak_dialog('attendees.list', data={'att': attendname}) @intent_handler( IntentBuilder("").require("attendees.status").require('status')) def handle_get__attendees_status_by_event(self, message): # Getting credentials for Google Calendar API creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Query to get the event by name title = self.get_response('what\'s the name of the event') events_result = service.events().list(calendarId='primary', maxResults=1, singleEvents=True, orderBy='startTime', q=title).execute() events = events_result.get('items', []) attendemail = [] attendname = [] attendstatus = [] confattend = [] notyetattend = [] declattend = [] tentattend = [] i = 0 j = 0 k = 0 if not events: print('event not found.') for event in events: start = event['start'].get('dateTime', event['start'].get('date')) eventid = event['id'] attendees = event['attendees'] l = len(attendees) while i != l: attendemail.append(attendees[i]['email']) attendstatus.append(attendees[i]['responseStatus']) attendname.append(attendees[i].get('displayName')) i = i + 1 while k != l: if attendname[k] is None: attendname[k] = attendemail[k] k = k + 1 while j != l: if attendees[j]['responseStatus'] == 'accepted': confattend.append(attendees[j].get('displayName')) elif attendees[j]['responseStatus'] == 'needsAction': notyetattend.append(attendees[j].get('displayName')) elif attendees[j]['responseStatus'] == 'declined': declattend.append(attendees[j].get('displayName')) elif attendees[j]['responseStatus'] == 'tentative': tentattend.append(attendees[j].get('displayName')) j = j + 1 if message.data["status"] == "confirmed": self.speak_dialog('confirmed.attendees.list', data={'att': confattend}) elif message.data["status"] == "did not take action": self.speak_dialog('notyet.attendees.list', data={'att': notyetattend}) elif message.data["status"] == "decline": self.speak_dialog('declined.attendees.list', data={'att': declattend}) elif message.data["status"] == "are tentative": self.speak_dialog('tentative.attendees.list', data={'att': tentattend}) @intent_handler(IntentBuilder("").require("first.event")) def handle_get_first_event(self): creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Call the Calendar API now = datetime.datetime.utcnow().isoformat( ) + 'Z' # 'Z' indicates UTC time self.speak('Getting the first upcoming event') events_result = service.events().list(calendarId='primary', timeMin=now, maxResults=1, singleEvents=True, orderBy='startTime').execute() events = events_result.get('items', []) if not events: self.speak('No upcoming event found.') for event in events: start = event['start'].get('dateTime', event['start'].get('date')) title = event['summary'] self.speak_dialog("ten.upcoming.events", data={ "title": title, 'start': start }) @intent_handler(IntentBuilder("").require("upcoming.events")) def handle_get_upcoming_ten_events(self): """Shows basic usage of the Google Calendar API. Prints the start and name of the next 10 events on the user's calendar. """ creds = None # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. if os.path.exists('token.pickle'): with open('token.pickle', 'rb') as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server() # Save the credentials for the next run with open('token.pickle', 'wb') as token: pickle.dump(creds, token) service = build('calendar', 'v3', credentials=creds) # Call the Calendar API now = datetime.datetime.utcnow().isoformat( ) + 'Z' # 'Z' indicates UTC time self.speak('Getting the upcoming events') events_result = service.events().list(calendarId='primary', timeMin=now, maxResults=10, singleEvents=True, orderBy='startTime').execute() events = events_result.get('items', []) if not events: self.speak('No upcoming events found.') for event in events: start = event['start'].get('dateTime', event['start'].get('date')) # end = event['end'].get('dateTime', event['end'].get('date')) # description = event['description'] title = event['summary'] self.speak_dialog("ten.upcoming.events", data={ "title": title, 'start': start })
class DictionarySkill(MycroftSkill): def __init__(self): super(DictionarySkill, self).__init__(name="DictionarySkill") @intent_handler(IntentBuilder("DefineIntent").require("Define") .require("Word")) def handle_define_intent(self, message): try: config = self.config_core.get("DictionarySkill", {}) if not config == {}: base_url = str(config.get("base_url")) language = str(config.get("language")) app_id = str(config.get("app_id")) app_key = str(config.get("app_key")) else: base_url = str(self.settings.get("base_url")) language = str(self.settings.get("language")) app_id = str(self.settings.get("app_id")) app_key = str(self.settings.get("app_key")) if not base_url or not language or not app_id or not app_key: raise Exception("None found.") except Exception as e: self.speak_dialog("settings.error") self.log.error(e) return word = message.data.get("Word") if not word: return api_url = "{0}/entries/{1}/{2}".format(base_url, language, word) api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'app_id': app_id, 'app_key': app_key} try: response = requests.get(api_url, headers=api_headers) except Exception as e: self.speak_dialog("connection.error") self.log.error(e) return if response.status_code == 200: try: definition = response.json()['results'][0][ 'lexicalEntries'][0][ 'entries'][0]['senses'][0][ 'definitions'][0] self.speak_dialog("definition", {"word": word, "definition": definition}) except KeyError: definition = response.json()['results'][0][ 'lexicalEntries'][0][ 'entries'][0]['senses'][0][ 'crossReferenceMarkers' ][0] self.speak_dialog("grammer", {"word": word, "definition": definition}) elif response.status_code == 404: self.speak_dialog("invalid", {"word": word}) def stop(self): pass
class NPROneSkill(CommonPlaySkill): def __init__(self): super(NPROneSkill, self).__init__(name="NPROneSkill") self.curl = None self.now_playing = None self.last_message = None self.STREAM = '{}/stream'.format(get_cache_directory('NPROneSkill')) # read api key for NPR One from settings and pass to api managing cnstructor self.apiKey = self.settings.get('api_key') self.nprone = NPROne(self.apiKey) # change setting if modified on web self.settings_change_callback = self.websettings_callback def CPS_match_query_phrase(self, phrase): """ check if skill can play input phrase Returns: tuple (matched phrase(str), match level(CPSMatchLevel), optional data(dict)) or None if no match was found. """ matched_feed = { 'key': None, 'conf': 0.0} # Remove "the" as it matches too well will "other" search_phrase = phrase.lower().replace('the', '') # Catch any short explicit phrases eg "play the news" npr1_phrases = self.translate_list("PlayNPROne") or [] if search_phrase.strip() in npr1_phrases: station_key = self.settings.get("station", "not_set") if station_key == "not_set": station_key = self.get_default_station() matched_feed = { 'key': station_key, 'conf': 1.0 } # could add func here for finding specific shows/sources from pre-defined list # if no specific show match but utterance contains npr one, return low-ish confidence if matched_feed['conf'] == 0.0 and self.voc_match(search_phrase, "NPR one"): matched_feed = {'key': None, 'conf': 0.5 } match_level = CPSMatchLevel.CATEGORY else: match_level = None return match_level return (None, match_level, None ) def CPS_start(self, phrase, data): """ starts playback of npr one""" # use default npr one news feed self.handle_latest_news() pass def websettings_callback(self): self.apiKey = self.settings.get('api_key') self.log.info('NPR One skill api set to ' + self.apiKey) self.nprone.setApiKey(self.apiKey) def setApiKey(self,key): self.apiKey = key @intent_file_handler("PlayNPROne.intent") def handle_npr_one_alt(self, message): """ capture alternatives for request """" utt = message.data["utterance"] match = self.CPS_match_query_phrase(utt) # feed them to skill if valid if match and len(match) > 2: feed = match[2]["feed"] else: feed = None self.handle_latest_news(message, feed) @intent_handler(IntentBuilder("").one_of("Give", "Latest").require("News")) def handle_latest_news(self, message=None, feed=None): try: self.stop() # speak intro while downloading feed self.speak_dialog('npr1', data={}) # TODO: get news feed # Show news title if exists wait_while_speaking() # Begin the news stream self.log.info('Feed: {}'.format(feed)) self.CPS_play(('file://' + self.STREAM, mime)) self.CPS_send_status(image=image or image_path('generic.png'), track=self.now_playing) self.last_message = (True, message) self.enable_intent('restart_playback') except Exception as e: self.log.error("Error: {0}".format(e)) self.log.info("Traceback: {}".format(traceback.format_exc())) self.speak_dialog("could.not.start.the.news.feed") @intent_handler(IntentBuilder('').require('Restart')) def restart_playback(self, message): self.log.debug('Restarting last message') if self.last_message: self.handle_latest_news(self.last_message[1]) def stop(self): # Disable restarting when stopped if self.last_message: self.disable_intent('restart_playback') self.last_message = None # Stop download process if it's running. if self.curl: try: self.curl.kill() self.curl.communicate() except Exception as e: self.log.error('Could not stop curl: {}'.format(repr(e))) finally: self.curl = None self.CPS_send_status() return True def CPS_send_status(self, artist='', track='', image=''): data = {'skill': self.name, 'artist': artist, 'track': track, 'image': image, 'status': None # TODO Add status system } self.bus.emit(Message('play:status', data))
class SkillGuiExample(MycroftSkill): """ Example Skill Showcasing All Delegates """ def __init__(self): super().__init__("SkillGuiExample") self.html_resources = "file://" + dirname(__file__) + '/res/' def initialize(self): # Handle Menu and Navigation self.gui.register_handler('SkillGuiExample.simpleText', self.handle_gui_example_simpleText_intent) self.gui.register_handler('SkillGuiExample.simpleImage', self.handle_gui_example_simpleImage_intent) self.gui.register_handler('SkillGuiExample.paginatedText', self.handle_gui_example_paginatedText_intent) self.gui.register_handler('SkillGuiExample.slidingImage', self.handle_gui_example_slidingImage_intent) self.gui.register_handler( 'SkillGuiExample.proportionalDelegate', self.handle_gui_example_proportionalDelegate_intent) self.gui.register_handler( 'SkillGuiExample.proportionalDelegateWrapText', self.handle_gui_example_proportionalDelegateWrapText_intent) self.gui.register_handler('SkillGuiExample.listView', self.handle_gui_example_listView_intent) self.gui.register_handler('SkillGuiExample.eventsExample', self.handle_gui_example_events_intent) self.gui.register_handler('SkillGuiExample.audioDelegateExample', self.handle_gui_example_audioDelegate_intent) self.gui.register_handler('SkillGuiExample.htmlUrlExample', self.handle_gui_example_showHTMLUrl_intent) self.gui.register_handler('SkillGuiExample.htmlRawExample', self.handle_gui_example_showHTMLRaw_intent) self.gui.register_handler('SkillGuiExample.menu', self.handle_gui_example_menu_intent) # Handle example events self.gui.register_handler('SkillGuiExample.colorChange', self.change_color_event) @intent_handler( IntentBuilder('handle_gui_example_simpleText_intent').require( 'gui.example.one')) def handle_gui_example_simpleText_intent(self, message): """ Example Intent Showcasing Basic UI Text """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui.show_text("Lorem ipsum dolor sit amet booom baka kakakaka.") @intent_handler( IntentBuilder('handle_gui_example_simpleImage_intent').require( 'gui.example.two')) def handle_gui_example_simpleImage_intent(self, message): """ Example Intent Showcasing Basic UI Image """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui.show_image( "https://source.unsplash.com/1920x1080/?+random", "Example Long Caption That Needs Wrapping Very Long Long Text Text Example That Is", "Example Title") @intent_handler( IntentBuilder('handle_gui_example_paginatedText_intent').require( 'gui.example.three')) def handle_gui_example_paginatedText_intent(self, message): """ Example Intent Showcasing Paginated UI Text """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui[ 'sampleText'] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. Aliquam sem et tortor consequat id porta nibh. Amet est placerat in egestas erat imperdiet sed. Ut ornare lectus sit amet est placerat in egestas erat. Iaculis eu non diam phasellus vestibulum lorem sed risus ultricies. Hac habitasse platea dictumst vestibulum rhoncus est pellentesque. Vulputate eu scelerisque felis imperdiet proin fermentum. Neque convallis a cras semper auctor neque. Pharetra magna ac placerat vestibulum lectus mauris ultrices eros in. Phasellus faucibus scelerisque eleifend donec pretium vulputate. Malesuada bibendum arcu vitae elementum curabitur vitae nunc. Tellus id interdum velit laoreet id donec. Diam donec adipiscing tristique risus nec. Nisi lacus sed viverra tellus in hac habitasse platea. Amet venenatis urna cursus eget nunc scelerisque viverra mauris in. Sit amet nisl suscipit adipiscing bibendum est ultricies. Nec ultrices dui sapien eget mi proin sed. Egestas dui id ornare arcu odio ut sem nulla. Rhoncus aenean vel elit scelerisque. Neque gravida in fermentum et sollicitudin. Pellentesque massa placerat duis ultricies lacus sed. Nunc id cursus metus aliquam eleifend mi. Eu feugiat pretium nibh ipsum consequat nisl. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae. Est ante in nibh mauris cursus mattis. Sagittis eu volutpat odio facilisis mauris sit amet. At consectetur lorem donec massa sapien faucibus. Odio facilisis mauris sit amet. Quis ipsum suspendisse ultrices gravida dictum fusce. Sagittis nisl rhoncus mattis rhoncus urna neque viverra justo nec. Eget mi proin sed libero enim sed faucibus. Interdum velit euismod in pellentesque massa. Et netus et malesuada fames. Velit aliquet sagittis id consectetur purus. Condimentum lacinia quis vel eros donec ac odio tempor orci. Amet consectetur adipiscing elit pellentesque habitant. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Nisi porta lorem mollis aliquam ut porttitor leo a diam. Egestas integer eget aliquet nibh praesent tristique. Velit scelerisque in dictum non. Id volutpat lacus laoreet non curabitur gravida arcu ac. Suspendisse interdum consectetur libero id faucibus nisl tincidunt eget. Ipsum a arcu cursus vitae congue mauris. Duis at consectetur lorem donec massa. Orci sagittis eu volutpat odio facilisis mauris. Eget mauris pharetra et ultrices neque ornare. Commodo nulla facilisi nullam vehicula ipsum a. Arcu risus quis varius quam quisque. Gravida in fermentum et sollicitudin. Lacus laoreet non curabitur gravida arcu ac tortor dignissim. Netus et malesuada fames ac turpis. Ipsum dolor sit amet consectetur adipiscing. Tellus elementum sagittis vitae et leo duis ut diam quam. Vitae et leo duis ut diam quam nulla. Risus pretium quam vulputate dignissim. Justo laoreet sit amet cursus sit amet dictum sit. Blandit libero volutpat sed cras. Lacus sed viverra tellus in. Ornare lectus sit amet est placerat in egestas erat. Tortor dignissim convallis aenean et tortor at. Tempus quam pellentesque nec nam aliquam. Nisi scelerisque eu ultrices vitae auctor eu augue ut lectus. Consequat id porta nibh venenatis cras sed felis eget. Massa enim nec dui nunc mattis enim ut. Dignissim enim sit amet venenatis urna. Ac tincidunt vitae semper quis lectus nulla at. Sed felis eget velit aliquet sagittis. Vel turpis nunc eget lorem dolor sed viverra. Non consectetur a erat nam at lectus. Iaculis eu non diam phasellus vestibulum. Dolor sit amet consectetur adipiscing elit ut aliquam purus sit. Libero justo laoreet sit amet cursus sit. Tellus pellentesque eu tincidunt tortor. Maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Semper risus in hendrerit gravida rutrum quisque non tellus orci. Diam in arcu cursus euismod quis viverra nibh cras pulvinar. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Elit ut aliquam purus sit." self.gui.show_page("paginationExample.qml") @intent_handler( IntentBuilder('handle_gui_example_slidingImage_intent').require( 'gui.example.four')) def handle_gui_example_slidingImage_intent(self, message): """ Example Intent Showcasing Sliding Image UI """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui[ 'sampleImage'] = "https://source.unsplash.com/1920x1080/?+random" self.gui.show_page("slidingExample.qml") @intent_handler( IntentBuilder('handle_gui_example_proportionalDelegate_intent'). require('gui.example.five')) def handle_gui_example_proportionalDelegate_intent(self, message): """ Example Intent Showcasing Proportional Delegate and Autofit Label """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui['sampleText'] = "Loading.." self.gui.show_page("proportionalDelegateExample.qml") @intent_handler( IntentBuilder('handle_gui_example_proportionalDelegateWrapText_intent' ).require('gui.example.five.wrapText')) def handle_gui_example_proportionalDelegateWrapText_intent(self, message): """ Example Intent Showcasing Proportional Delegate and Autofit Label """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui['sampleText'] = "Incomprehensibilities" self.gui.show_page("proportionalDelegateWrapExample.qml") @intent_handler( IntentBuilder('handle_gui_example_listView_intent').require( 'gui.example.six')) def handle_gui_example_listView_intent(self, message): """ Example Intent Showcasing Advanced QML Skills with List and JSON Models """ self.gui.clear() self.enclosure.display_manager.remove_active() sampleObject = {} sampleList = [{ "text": "Praesent id leo felis", "image": "https://c1.staticflickr.com/8/7246/13792463963_817450e973_b.jpg" }, { "text": "Cras egestas tempus tempus", "image": "https://c1.staticflickr.com/8/7246/13792463963_817450e973_b.jpg" }, { "text": "Habitasse platea dictumst", "image": "https://c1.staticflickr.com/8/7246/13792463963_817450e973_b.jpg" }] sampleObject['lorem'] = sampleList self.gui['sampleBlob'] = sampleObject self.gui[ 'background'] = "https://source.unsplash.com/1920x1080/?+random" self.gui.show_page("listViewExample.qml") @intent_handler( IntentBuilder('handle_gui_example_events_intent').require( 'gui.example.seven')) def handle_gui_example_events_intent(self, message): """ Example Intent Showcasing Events Between Skill and Display """ self.gui.clear() self.enclosure.display_manager.remove_active() self.gui.show_page("eventsExample.qml") def change_color_event(self, message): """ Change Color Event """ self.gui['fooColor'] = message.data['color'] self.gui.show_page("eventsExample.qml") @intent_handler( IntentBuilder('handle_gui_example_audioDelegate_intent').require( 'gui.example.eight')) def handle_gui_example_audioDelegate_intent(self, message): self.gui.clear() self.enclosure.display_manager.remove_active() self.gui[ "audioSource"] = "https://www.free-stock-music.com/music/serge-narcissoff-background-theme.mp3" self.gui["audioTitle"] = "Background Theme by Serge Narcissoff " self.gui[ "audioThumb"] = "https://www.free-stock-music.com/thumbnails/serge-narcissoff-background-theme.jpg" self.gui.show_page("audioPlayerExample.qml") @intent_handler( IntentBuilder('handle_gui_example_showHTMLUrl_intent').require( 'gui.example.nine')) def handle_gui_example_showHTMLUrl_intent(self, message): self.gui.clear() self.enclosure.display_manager.remove_active() self.gui.show_url("https://mycroft.ai/", override_idle=True) @intent_handler( IntentBuilder('handle_gui_example_showHTMLRaw_intent').require( 'gui.example.ten')) def handle_gui_example_showHTMLRaw_intent(self, message): self.gui.clear() self.enclosure.display_manager.remove_active() rawhtmlexample = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Untitled Document</title> <body> <h1> HTML Example </h1> <img src="apple.png" width=150px height=100px> <p> This is an example of an HTML webpage. </p> <p> <b>Tags</b> can be wrapped <i>inside other tags!</i> </p> <p> HTML doesn't care about extra spaces, tabs or newlines, so we can use indentation and spacing to keep everything lined up nicely. </p> <ul> <li> This is how you create a bulleted list! </li> <li> Item 2 </li> <li> Item 3 </li> </ul> </body> </html> """ self.gui.show_html(rawhtmlexample, resource_url=self.html_resources, override_idle=True) @intent_handler( IntentBuilder('handle_gui_example_menu_intent').require( 'gui.example.menu')) def handle_gui_example_menu_intent(self, message): """ Build and Show Skill Example Menu To Run Test """ self.gui.clear() self.enclosure.display_manager.remove_active() menuObject = {} menuList = [{ "exampleIcon": "beamerblock", "exampleLabel": "Simple Text Example", "exampleEvent": "SkillGuiExample.simpleText" }, { "exampleIcon": "beamerblock", "exampleLabel": "Simple Image Example", "exampleEvent": "SkillGuiExample.simpleImage" }, { "exampleIcon": "beamerblock", "exampleLabel": "Paginated Text Example", "exampleEvent": "SkillGuiExample.paginatedText" }, { "exampleIcon": "beamerblock", "exampleLabel": "Sliding Image Example", "exampleEvent": "SkillGuiExample.slidingImage" }, { "exampleIcon": "beamerblock", "exampleLabel": "Proportion Delegate & Autofit Label", "exampleEvent": "SkillGuiExample.proportionalDelegate" }, { "exampleIcon": "beamerblock", "exampleLabel": "Proportion Delegate Word Wrap", "exampleEvent": "SkillGuiExample.proportionalDelegateWrapText" }, { "exampleIcon": "beamerblock", "exampleLabel": "Cards ListView", "exampleEvent": "SkillGuiExample.listView" }, { "exampleIcon": "beamerblock", "exampleLabel": "Events Example", "exampleEvent": "SkillGuiExample.eventsExample" }, { "exampleIcon": "beamerblock", "exampleLabel": "Audio Player Example", "exampleEvent": "SkillGuiExample.audioDelegateExample" }, { "exampleIcon": "beamerblock", "exampleLabel": "Html Url Example", "exampleEvent": "SkillGuiExample.htmlUrlExample" }, { "exampleIcon": "beamerblock", "exampleLabel": "Html Raw Example", "exampleEvent": "SkillGuiExample.htmlRawExample" }] menuObject['menuItems'] = menuList self.gui['menuBlob'] = menuObject self.gui.show_page("exampleMenu.qml") def stop(self): pass
class YoutubeSkill( CommonPlaySkill ): # info: https://mycroft.ai/documentation/skills/common-play-framework/ def __init__(self): super(YoutubeSkill, self).__init__(name="YoutubeAudioAndVideo") self.process = None self.current_video_info = None self.past_videos = [] self.next_videos = [] self.is_playing = False """True if the current process is playing. If you want to know if something is playing, you should also make sure self.process is active. It's recommended to use self._get_process()""" self.is_auto_paused = False self.is_stopped = False """A bool that when True, stops automatic playing of the next song""" self.is_monitoring = False self.ignore_stop = False self.was_play_success = False # initialize settings values self.settings["show_video_by_default"] = True def _voc_match(self, utt_or_message, voc_filename, lang=None): if not isinstance(utt_or_message, str): return utt_or_message.data.get(voc_filename) utt_or_message = utt_or_message.lower() lang = lang or self.lang cache_key = lang + voc_filename self.voc_match(utt_or_message, voc_filename, lang) if utt_or_message: # Check for matches against complete words return next((i for i in self.voc_match_cache[cache_key] if re.match(r'.*\b' + i + r'\b.*', utt_or_message)), None) else: return None def start_monitor(self): if self.is_monitoring: return self.is_monitoring = True # useful: https://mycroft.ai/documentation/message-bus/ self.add_event("recognizer_loop:record_begin", self.auto_pause_begin) self.add_event("recognizer_loop:record_end", self.auto_play_end) self.add_event("recognizer_loop:audio_output_start", self.auto_pause_begin) self.add_event("recognizer_loop:audio_output_end", self.auto_play_end) self.add_event("mycroft.audio.service.play", self.play) self.add_event("mycroft.audio.service.resume", self.play) self.add_event( "mycroft.audio.service.pause", lambda: self.pause()) # in lambda so unwanted args aren't passed self.add_event("mycroft.audio.service.next", lambda: self.next(say_no_videos=True)) self.add_event("mycroft.audio.service.prev", lambda: self.previous()) self.schedule_repeating_event(self.periodic_execute, datetime.datetime.now(), 1, name="repeating_event") def stop_monitor(self): if not self.is_monitoring: return self.is_monitoring = False for event in [ "recognizer_loop:record_begin", "recognizer_loop:record_end", "recognizer_loop:audio_output_start", "recognizer_loop:audio_output_end", "mycroft.audio.service.play", "mycroft.audio.service.resume", "mycroft.audio.service.pause", "mycroft.audio.service.next", "mycroft.audio.service.prev" ]: self.cancel_scheduled_event(event) self.cancel_scheduled_event("repeating_event") def auto_pause_begin(self): if self.is_playing: self.pause(auto_paused=True) def auto_play_end(self): if self.is_auto_paused: self.play() def play(self): if not self.is_playing: self.toggle_pause() self.is_auto_paused = False def pause(self, auto_paused=False): if self.is_playing: self.toggle_pause() self.is_auto_paused = auto_paused def _get_process(self): process = self.process if process and process.poll() is None: return process return None def _replace_process(self, process, video_info=None): """ :param process: The new Process to replace the old one or None :return: True if there was a previous process, False otherwise """ r = False if self.process: self.process.terminate() self.process.wait() r = True self.process = process self.current_video_info = video_info if process: self.start_monitor() self.is_playing = True self.is_stopped = False return r def toggle_pause(self): process = self._get_process() if process: toggle_pause(process) self.is_playing = not self.is_playing return True return False def skip_10_seconds(self, number_of_skips, forward=True): process = self._get_process() if process: skip_10_seconds(process, number_of_skips, forward=forward) self.is_playing = True return True return False def play_video(self, video_info): self._replace_process(create_player( video_info.path, show_video=video_info.show_video, start_fullscreen=video_info.start_fullscreen), video_info=video_info) self.speak_dialog("playing") def next(self, say_no_videos=True): if self.next_videos: video_info = self.current_video_info if video_info: self.past_videos.append(video_info) next_video = self.next_videos.pop(0) self.play_video(next_video) elif say_no_videos: self.speak_dialog("no.next.videos") def previous(self): if self.past_videos: video_info = self.current_video_info if video_info: self.next_videos.insert(0, video_info) new_video = self.past_videos.pop(-1) self.play_video(new_video) else: self.speak_dialog("no.past.videos") def periodic_execute(self): if not self.is_stopped and not self._get_process(): self.next(say_no_videos=False) def do_video_search_and_play(self, search, schedule_next, show_video, start_fullscreen): def success(path, info): video_info = VideoInfo(path, info, show_video, start_fullscreen) self.is_stopped = False if schedule_next: self.next_videos.append(video_info) self.speak_dialog("downloaded") else: current_video = self.current_video_info if current_video: self.past_videos.append(current_video) self.play_video(video_info) def fail(): self.speak_dialog("failed") self.enclosure.mouth_text("Failed to Download") self._replace_process(None) LOG.info("using: " + search) self.speak_dialog("downloading") self.enclosure.mouth_text("Downloading...") path_str = join(self.file_system.path, ".download-cache") download(search, success, fail, path_str=path_str) def get_query_data(self, utt_or_message): video_supported = bool(os.environ.get("DESKTOP_SESSION")) youtube_word = self._voc_match(utt_or_message, "Youtube") next_word = self._voc_match(utt_or_message, "Next") is_next = bool(next_word) without_video = self._voc_match(utt_or_message, "WithoutVideo") is_without_video = bool(without_video) with_video = self._voc_match(utt_or_message, "WithVideo") is_with_video = bool(with_video) start_fullscreen = self._voc_match(utt_or_message, "StartFullscreen") is_fullscreen = bool(start_fullscreen) and not without_video search = utt_or_message if isinstance( utt_or_message, str) else utt_or_message.data["utterance"] if youtube_word: search = search.replace(youtube_word, "") if is_next: search = search.replace(next_word, "") if is_without_video: search = search.replace(without_video, "") is_fullscreen = False elif is_with_video: search = search.replace(with_video, "") if is_fullscreen: search = search.replace(start_fullscreen, "") # search, schedule next, show video, start full screen return (search, is_next, ((not is_without_video and self.settings.get("show_video_by_default")) or with_video) and video_supported, is_fullscreen and video_supported) def on_start(self, data): """ :param data: The data from get_query_data() """ self.is_stopped = False self.start_monitor() self.do_video_search_and_play(data[0], *data[1:]) def CPS_match_query_phrase(self, phrase): self.ignore_stop = True # self.stop() will be called because of a stop broadcast from CommonPlaySkill self.was_play_success = False data = self.get_query_data(phrase) if self.voc_match(phrase, "Youtube"): return phrase, CPSMatchLevel.MULTI_KEY, data else: return phrase, CPSMatchLevel.GENERIC, data def CPS_start(self, phrase, data): self.was_play_success = True self.on_start(data) @intent_handler( IntentBuilder("YoutubeIntent").require("Youtube").optionally( "WithoutVideo").optionally("WithVideo").optionally( "StartFullscreen").optionally("Next")) def handle_youtube(self, message): data = self.get_query_data(message) self.on_start(data) def handle_skip(self, message, forward): is_minute = bool(message.data.get("Minute")) number = extract_number(message.data["utterance"], self.lang) if is_minute: number *= 60 if number <= 5: self.speak_dialog("must.choose.multiple.of.ten") else: amount = int(round(number / 10.0)) if not self.skip_10_seconds(amount, forward=forward): self.speak_dialog("no.song.playing") @intent_handler( IntentBuilder("YoutubeSkipForwardIntent").require( "SkipForward").optionally("Second").optionally("Minute")) def handle_skip_forward(self, message): self.handle_skip(message, True) @intent_handler( IntentBuilder("YoutubeSkipBackwardIntent").require( "SkipBackward").optionally("Second").optionally("Minute")) def handle_skip_backward(self, message): self.handle_skip(message, False) @intent_handler( IntentBuilder("YoutubeVideoInfo").require("Youtube").require("Info")) def handle_video_info(self, message): current_video = self.current_video_info if not current_video: if self.is_playing and self._get_process(): # no info available self.speak_dialog("no.info.available") else: # nothing playing self.speak_dialog("no.song.playing") return info = current_video.info self.speak_dialog("currently.playing", { "title": get_track(info), "artist": get_artist(info) }) @intent_handler( IntentBuilder("YoutubeFullscreen").require("Youtube").require( "ToggleFullscreen")) def handle_toggle_fullscreen(self, message): process = self._get_process() if process: toggle_fullscreen(process) else: self.speak_dialog("no.song.playing") def stop(self, force_stop=False): if not isinstance(force_stop, bool): raise ValueError("force_stop is: {}".format(force_stop)) if self.ignore_stop and not force_stop: self.ignore_stop = False if not self.was_play_success: LOG.info("scheduling event") self.schedule_event( lambda: self.stop() if not self.was_play_success else None, datetime.datetime.now() + datetime.timedelta(seconds=1)) else: LOG.info("Not scheduling") return False self.was_play_success = False LOG.info("youtube skill received stop") self.is_stopped = True self.stop_monitor() return self._replace_process(None) def shutdown(self): super(YoutubeSkill, self).shutdown() self.stop(force_stop=True)
import json import socket import sys from xml.dom.minidom import parse from adapt.intent import IntentBuilder from adapt.engine import IntentDeterminationEngine engine = IntentDeterminationEngine() DOMTree = parse('config.xml') for intent in DOMTree.getElementsByTagName("intent"): builder = IntentBuilder(intent.attributes["name"].value) for node in intent.getElementsByTagName("keyword"): keyword = node.attributes["name"].value required = node.attributes["required"].value ktype = node.attributes["type"].value if ktype == 'normal': for child in node.getElementsByTagName("item"): engine.register_entity(child.childNodes[0].nodeValue, keyword) else: engine.register_regex_entity(node.childNodes[0].nodeValue) if required == 'true': builder.require(keyword) else: builder.optionally(keyword) engine.register_intent_parser(builder.build()) # create and register weather vocabulary '''weather_keyword = [ "weather" ]
class WebLauncher(MycroftSkill): def __init__(self): MycroftSkill.__init__(self) self.sites = self.translate_namedvalues("sites") def initialize(self): self.settings_change_callback = self.on_settings_changed self.on_settings_changed() if self.gui.connected: self.register_intent("CloseSite.intent", self.handle_close_site) def on_settings_changed(self): """Load all custom site settings and register names as vocab. If expanding number of custom url settings available remember to increment range stop. """ for num in range(1, 6): name = self.settings.get("name_" + str(num)) url = self.settings.get("url_" + str(num)) if name and url: self.sites[name] = url self.register_sites_as_vocab() def register_sites_as_vocab(self): """Registers site names as vocab for Adapt intents.""" for site_name in self.sites: self.register_vocabulary(site_name.lower(), "SiteName") @intent_handler( IntentBuilder("LaunchSite").require("SiteName").require("Launch")) def handle_launch_site(self, message): """Launch known site using xdg-open url. Primary intent handler for this Skill. Uses Adapt so that only utterances that include a known site are triggered. Prevents conflicts with other intents that also use vocabularly like "launch" or "open". """ requested_site = message.data.get("SiteName") if requested_site in self.sites: site_url = self.sites[requested_site] if self.gui.connected: self.gui.show_url(site_url, override_idle=True) else: args = ["xdg-open", site_url] current_process = subprocess.Popen(args) self.bus.emit( Message( "skill.weblauncher.opening", { "site_name": requested_site, "site_url": site_url }, )) else: # This should never actually be triggered self.speak_dialog("not.found") def handle_close_site(self, message): """Close the website if opened on a Mycroft based GUI.""" self.gui.release() self.acknowledge()
def build_intent_create(self): return IntentBuilder( self.name + 'CreateIntent').require(self.name + 'CreateVerb')
class MeatThermometerSkill(MycroftSkill): # The constructor of the skill, which calls MycroftSkill's constructor def __init__(self): super(MeatThermometerSkill, self).__init__(name="MeatThermometerSkill") @intent_handler( IntentBuilder("").require("Temperature").require("Cook").require( "Meat").optionally("Modifier")) def handle_count_intent(self, message): meat = message.data["Meat"] if message.data.get("Modifier") is None: modifier = "" else: modifier = message.data.get("Modifier") if (meat == "turkey") or \ (meat == "chicken") or \ (meat == "poultry"): temperature = 165 self.speak_dialog("cooking.temperature.is", data={ "temperature": temperature, "meat": meat, "modifier": modifier }) return elif (meat == "beef") or \ (meat == "steak"): if modifier is "": temperature = 145 elif modifier == "ground": temperature = 160 elif modifier == "rare": temperature = 135 elif modifier == "medium rare": temperature = 140 elif modifier == "medium": temperature = 155 elif modifier == "well done": temperature = 165 self.speak_dialog("cooking.temperature.with.rest", data={ "temperature": temperature, "meat": meat, "modifier": modifier }) return elif (meat == "fish") or \ (meat == "tuna") or \ (meat == "salmon") or \ (meat == "shell fish") or \ (meat == "clams") or \ (meat == "scallop") or \ (meat == "crab"): temperature = 145 self.speak_dialog("cooking.temperature.is", data={ "temperature": temperature, "meat": meat, "modifier": "" }) return else: self.speak_dialog("do.not.know.cooking.temperature", data={"meat": meat}) return
def build_intent_list(self): return IntentBuilder( self.name + 'ListIntent').require(self.name + 'ListVerb') \ .optionally(self.name + 'Amount').require(self.name + 'Keyword')
def initialize(self): i = IntentBuilder('a').require('Keyword').build() self.register_intent(i, self.handler)
class MbtaBusTracking(MycroftSkill): def __init__(self): MycroftSkill.__init__(self) super(MbtaBusTracking, self).__init__(name="MbtaBusTracking") self.apiKey = None # using api key? self.useownkey = self.settings.get('useownkey') # yes, read it from settings if self.useownkey: self.apiKey = self.settings.get('api_key') # create MBTA object to handle api calls self.t = MBTA(self.apiKey, self.settings.get('maxTrack', 3)) self.routeName = None # bus route self.requestTracking = False # True => last request was for tracking, not arrivals self.directions = None # direction name, terminus tuple for route self.stopName = None # bus stop self.dirName = None # direction of travel self.destName = None # terminus for direction self.savedRoutes = dict() # routes save to disk self.trackingInterval = max(30, (self.settings.get( 'trackingUpateFreq', 30))) # enforce min tracking updates # watch for changes on HOME self.settings.set_changed_callback(self.on_websettings_changed) def initialize(self): # try to read saved routes try: with self.file_system.open(ROUTE_FILE, 'rb') as f: self.savedRoutes = pickle.load(f) # make a vocabulary from saved routes if self.savedRoutes: for s in self.savedRoutes: self.register_vocabulary(s, 'SavedRouteNames') except: pass # handle change of setting on home def on_websettings_changed(self): # using api key? self.useownkey = self.settings.get('useownkey') # yes, read it from settings if self.useownkey: self.apiKey = self.settings.get('api_key') LOGGER.info('MBTA skill API key set to ' + self.apiKey) else: self.apiKey = None LOGGER.info('MBTA skill not use an API key') # update MBTA object with new settings self.t.updateSettings(self.apiKey, self.settings.get('maxTrack', 3)) # get tracking interval self.trackingInterval = max(30, (self.settings.get( 'trackingUpateFreq', 30))) # enforce min tracking updates # speak list of passed arrival times def announceArrivals(self, eta): # get current datetime for east coast without timecode currentTime = datetime.datetime.now( (timezone('America/New_York'))).replace(tzinfo=None) # build datetime objets from strings in predection list # strip timezone first since we cannot count on fromisoformat() being available (3.7+) eta = [ datetime.datetime.strptime(x[0:TZ_STR_IDX], '%Y-%m-%dT%H:%M:%S') for x in eta ] # calculate waiting times wt = [x - currentTime for x in eta if x > currentTime] arrivalCount = len(wt) # speak prefix if necessary if arrivalCount > 0: # add stop to prefix string prefix = {'stop': self.stopName} # might be updating arrivals while Mycroft is speaking, so wait wait_while_speaking() self.speak_dialog("Bus.Arrival.Prefix", prefix) for waitTime in wt: # wait for previous arrival announcement to finish wait_while_speaking() # calculate hour and minutes from datetime timedelta seconds arrival = { 'hour': waitTime.seconds // 3600, 'minutes': (waitTime.seconds % 3600) // 60 } # one hour or longer if (arrival['hour'] > 0): # round down to one hour if only one minute over if (arrival['minutes'] < 2): self.speak_dialog("Arriving.Hour", arrival) else: # arrives in 1 hour and some minutes self.speak_dialog("Arriving.Hour.Minutes", arrival) elif arrival['minutes'] > 1: # arrives in more than one minute self.speak_dialog("Arriving.Minutes", arrival) elif arrival['minutes'] == 1: # arrives in one minute self.speak_dialog("Arriving.Minute", arrival) else: # arrives in less than a minute self.speak_dialog("Arriving.Now") return arrivalCount # call to stop tracking def endTracking(self): # stop updates self.cancel_scheduled_event('BusTracker') # tell T object that we are no longer tracking self.t.stopTracking() # calllback for tracking updates def updateTracking(self): # get predictions eta = self.t.updateTracking() # if any arrivals predicted for our stop if eta != None: # speak times self.announceArrivals(eta) else: # last tracked bus has passed the stop, end updates self.endTracking() # call to start tracking arrivals # afer route, direction and stop have been set def startTracking(self): # get predictions eta = self.t.startTracking() # if any arrivals predicted for our stop if eta != None: # speak times self.announceArrivals(eta) # schedule updates self.schedule_repeating_event(self.updateTracking, None, self.trackingInterval, name='BusTracker') else: # no busses running stopInfo = {'name': self.stopName} self.speak_dialog("No.Busses.Found", stopInfo) # call when an arrival route, direction and # stop have been set def getArrivals(self): # ask API for arrival times eta = self.t.getArrivals() # begin arrival announcments announcement = {'route': self.routeName, 'dest': self.destName} self.speak_dialog("Service.Announcement", announcement) # speak arrival times if we got any if eta != None: self.announceArrivals(eta) else: stopInfo = {'name': self.stopName} self.speak_dialog("No.Busses.Found", stopInfo) # write route to file def writeRoutes(self): # open file in /home/pi/.mycroft/skills with self.file_system.open(ROUTE_FILE, 'wb') as f: # serialize dictionary pickle.dump(self.savedRoutes, f, pickle.HIGHEST_PROTOCOL) # save the current route as a shortcut def saveRoute(self, name): # add current route to saved routes dictionary self.savedRoutes[name] = self.t.getRouteSettings() # wrtie it to disk self.writeRoutes() # add to vocabulary self.register_vocabulary(name, 'SavedRouteNames') # remove saved route from file def removeRoute(self, name): # if route is in dict, remove and save if self.savedRoutes.pop(name, None) != None: self.writeRoutes() # try to restore route with passed name, return True if successful def restoreRoute(self, name): retVal = False # look up route associated with this name restoredRoute = self.savedRoutes.get(name) # if we got one if restoredRoute != None: # set up API class with copy of route object self.routeName = self.t.restoreRoute(copy.deepcopy(restoredRoute)) # set class variables self.stopName = self.t.getStopName() self.dirName, self.destName = self.t.getDirDest() retVal = True return retVal # set proper route name based on utterance # and get directions for route def setRouteAndDirection(self, routeName): # Silver Line and Crosstown must be abreviated for API calls routeName = routeName.replace('crosstown ', 'CT') routeName = routeName.replace('silverline ', 'SL') # quirks fround in testing routeName = routeName.replace('to', '2') routeName = routeName.replace('for', '4') # convert any alpha characters to uppercase routeName = routeName.upper() # tell API which route we are riding self.routeName = self.t.setRoute(routeName) # read directions for this route if self.routeName: self.directions = self.t.getDirections() return self.routeName # prompt for direction and set context def setDirectionContext(self): # possible directions have already been set dirChoices = { 'dir1': self.directions[0][0], 'dest1': self.directions[0][1], 'dir2': self.directions[1][0], 'dest2': self.directions[1][1] } # prompt and set context self.speak_dialog('Which.Direction', dirChoices, expect_response=True) self.set_context('DirectionNeededContex') # prompt for direction and set context def setStopContext(self): self.speak_dialog('Which.Stop', expect_response=True) self.set_context('StopNeededContext') # remove all contexts we may have set def removeContexts(self): self.remove_context('RouteNeededContex') self.remove_context('DirectionNeededContex') self.remove_context('StopNeededContex') # process request for arrivals or tracking def processRequest(self, message, tracking): # may have set context in previous call self.removeContexts() # may already be tracking self.endTracking() # init class variables self.routeName = None self.dirName = None self.destName = None self.stopName = None self.requestTracking = tracking # get fields from utterance routeName = message.data.get("Route.Name", None) direction = message.data.get("Direction", None) stop = message.data.get("Stop", None) # set up for API call if routeName: # set route name routeName = self.setRouteAndDirection(routeName) # check for error if self.t.callError() is True: # server error self.speak_dialog("Error.Calling.Server") # clear route name routeName = None # only accept direction if we have a route if routeName and direction: # set direction from utterance self.dirName, self.destName = self.t.setDirection(direction) #print('Direction set to {}'.format(self.dirName)) # only accept stop name if we have route and direction if routeName and direction and stop: self.stopName = self.t.setStop(stop) #print('Direction set to {} toward {} at {}'.format(self.dirName,self.destName,self.stopName)) # if we got all the info needed,get arrivals if (self.routeName and self.dirName and self.stopName): # good to go - list arrivals or start tracking if self.requestTracking: self.startTracking() else: self.getArrivals() elif (self.routeName and self.dirName): # got route and direction, need stop self.setStopContext() #print('got route {} and direction {}'.format(self.routeName, self.dirName)) elif self.routeName: # got route,need direction #print('got route {} '.format(self.routeName)) # now we nedd a direction self.setDirectionContext() else: # if we are here it is because no route was in the # utterance, the route in the utterance was not valid # or there was an error calling the API # # set context to get a valid route as long as there was # not an error calling the API # if self.t.callError() is False: # need route name self.speak_dialog('Which.Route', expect_response=True) self.set_context('RouteNeededContex') # set route based on context @intent_handler( IntentBuilder('').require('RouteNeededContex').optionally( 'Route').require('Route.Name').build()) def handle_route_context_intent(self, message): # done with this context self.remove_context('RouteNeededContex') # pull route from message routeName = message.data.get("Route.Name", None) # set route name and direction routeName = self.setRouteAndDirection(routeName) # check for server error if self.t.callError() is True: # server error self.speak_dialog("Error.Calling.Server") else: # now we nedd a direction self.setDirectionContext() # set direction based on context @intent_handler(IntentBuilder('').require('DirectionNeededContex').build()) def handle_direction_context_intent(self, message): # done with this context self.remove_context('DirectionNeededContex') # set direction from utterance self.dirName, self.destName = self.t.setDirection( message.data.get('utterance')) #print('Direction set to {}'.format(self.dirName)) # now we need a stop self.setStopContext() # set stop based on context @intent_handler(IntentBuilder('').require('StopNeededContext').build()) def handle_stop_context_intent(self, message): # done with this context self.remove_context('StopNeededContext') # set stop from utterance self.stopName = self.t.setStop(message.data.get('utterance')) # good to go - list arrivals or start tracking if self.requestTracking: self.startTracking() else: self.getArrivals() # arrivals for bus route @intent_handler( IntentBuilder('').require('T.Bus').require('Arrivals').optionally( 'Route').optionally('Route.Name').optionally( 'Direction').optionally('Stop').build()) def handle_arrivals_intent(self, message): # process arrivals request self.processRequest(message, False) # tracking bus route @intent_handler( IntentBuilder('').require('T.Bus').require('Tracking').optionally( 'Route').optionally('Route.Name').optionally( 'Direction').optionally('Stop').build()) def handle_tracking_intent(self, message): # process tracking request self.processRequest(message, True) # save shortcut @intent_handler( IntentBuilder('').require('Save').require('T.Bus').require( 'Shortcut').build()) def handle_save_route_intent(self, message): # need full route information to save if (self.routeName and self.dirName and self.stopName): # get name for shortcut shortcutName = self.get_response("Shortcut.Prompt") if shortcutName: # save route under passed name self.saveRoute(shortcutName) shortcutInfo = {'shortcut': shortcutName} self.speak_dialog("Save.Complete", shortcutInfo) else: # don't have complete info self.speak_dialog("Not.Enough.Info") # remove shortcut @intent_handler( IntentBuilder('').require('Remove').require('T.Bus').optionally( 'Shortcut').require('SavedRouteNames').build()) def handle_remove_route_intent(self, message): # extract short cut shortcutNmae = message.data.get("SavedRouteNames", None) # remove it self.removeRoute(shortcutNmae) shortcutInfo = {'shortcut': shortcutNmae} self.speak_dialog("Delete.Complete", shortcutInfo) # list shortcuts @intent_handler( IntentBuilder('').require('List').require('T.Bus').require( 'Shortcuts').build()) def handle_list_saved_route_intent(self, message): # build list of rotue names routeList = [s for s in self.savedRoutes] if len(routeList) > 0: # speak the names routes = {'routes': ' '.join(routeList)} self.speak_dialog('List.Saved', routes) else: self.speak_dialog('No.Saved.Routes') # tracking for a saved route @intent_handler( IntentBuilder('').require('T.Bus').require('Tracking').optionally( 'Route').require('SavedRouteNames').build()) def handle_saved_tracking_intent(self, message): # may already be tracking self.endTracking() # restore named route and start tracking routeName = message.data.get("SavedRouteNames", None) self.restoreRoute(routeName) self.startTracking() # arrivals for a saved route @intent_handler( IntentBuilder('').require('T.Bus').require('Arrivals').optionally( 'Route').require('SavedRouteNames').build()) def handle_saved_arrivals_intent(self, message): # pull shortcut name from intent shortCut = message.data.get("SavedRouteNames", None) # if shortcut has been deleted it will still be # in vocablulary until restart if self.savedRoutes.get(shortCut, None): # may already be tracking self.endTracking() # restore route and list arrivals self.restoreRoute(shortCut) self.getArrivals() # stop tracking @intent_handler( IntentBuilder('').require('T.Bus').require('Shutdown').build()) def handle_shutdown_intent(self, message): # stop tracking self.endTracking() # reset contexts self.removeContexts() self.speak_dialog('Shutdown.Message')