def __update_volume(self, level=0): mixer = Mixer() volume = mixer.getvolume()[0] code = self.get_volume_code(volume) + level code = self.fix_code(code) if code in self.VOLUMES: volume = self.VOLUMES[code] mixer.setvolume(volume) return code, volume
def __update_volume(self, change=0): """ Tries to change volume level :param change: +1 or -1; the step to change by :return: new code (0..11), whether volume 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 set_volume(request, username, value): global volume_who, volume_direction m = Mixer() if value > m.getvolume()[0]: volume_direction = "up" volume_who = username elif value < m.getvolume()[0]: volume_direction = "down" else: return volume() # no change, quit volume_who = username m.setvolume(value) return volume()
def __update_volume(self, level=0): """ Tries to change volume level :param level: +1 or -1; the step to change by :return: new code (0..11), whether volume changed """ mixer = Mixer() volume = mixer.getvolume()[0] old_code = self.get_volume_code(volume) new_code = self.fix_code(old_code + level) if new_code in self.VOLUMES: volume = self.VOLUMES[new_code] mixer.setvolume(volume) return new_code, new_code != old_code
def __init__(self, mplfifo, ser, mpout, beep=True): self.playing = False self.mixer = Mixer(control='PCM') self.fifo = mplfifo self.ser = ser self.mpout = mpout self.beep = beep
def __init__(self, player): self.player = player self._alsa_mixer = Mixer(control="PCM") self.is_pressed = False GPIO.setmode(GPIO.BOARD) input_buttons = [k for k in self.buttons.keys()] GPIO.setup(input_buttons, GPIO.IN, pull_up_down=GPIO.PUD_UP)
class RaspberryGPIOPlugin: buttons = { 12: "volume_up", 11: "volume_down", 8: "skip", 10: "play_pause", } def __init__(self, player): self.player = player self._alsa_mixer = Mixer(control="PCM") self.is_pressed = False GPIO.setmode(GPIO.BOARD) input_buttons = [k for k in self.buttons.keys()] GPIO.setup(input_buttons, GPIO.IN, pull_up_down=GPIO.PUD_UP) def __call__(self): for port_number, callback_name in self.buttons.items(): if GPIO.input(port_number) == 0: getattr(self, callback_name)() return else: self.is_pressed = False def _change_volume(self, change): new_volume = self.current_volume + change self._alsa_mixer.setvolume(new_volume) @property def current_volume(self): return self._alsa_mixer.getvolume()[0] @debounced def skip(self): self.player.skip() @debounced def play_pause(self): self.player.play_pause() def volume_up(self): self._change_volume(1) def volume_down(self): self._change_volume(-1)
def __init__(self, mplfifo, ser, mpout, beep=True, override = [], blacklist = []): self.playing = False self.mixer = Mixer(control='PCM') self.fifo = mplfifo self.ser = ser self.mpout = mpout self.beep = beep self.override = override self.blacklist = blacklist self.current_user = None
def notify(self, timestamp): with self.LOCK: if self.data.__contains__(timestamp): volume = None self.alarm_on = True delay = self.__calculate_delay(self.max_delay) while self.alarm_on and datetime.now() < delay: play_mp3(self.file_path).communicate() self.speak_dialog('alarm.stop') time.sleep(self.repeat_time + 2) if not volume and datetime.now() >= delay: mixer = Mixer() volume = mixer.getvolume()[0] mixer.setvolume(100) delay = self.__calculate_delay(self.extended_delay) if volume: Mixer().setvolume(volume) self.remove(timestamp) self.alarm_on = False self.save()
def process(self, data): self.client.emit(Message(data)) if "mycroft.stop" in data: self.client.emit(Message("mycroft.stop")) if "volume.up" in data: self.client.emit( Message("IncreaseVolumeIntent", metadata={'play_sound': True})) if "volume.down" in data: self.client.emit( Message("DecreaseVolumeIntent", metadata={'play_sound': True})) if "system.test.begin" in data: self.client.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.client.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.client.emit(Message("speak", metadata={ 'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav") time.sleep(3.5) # Pause between tests so it's not so fast # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True)
def process(self, data): self.client.emit(Message(data)) if "mycroft.stop" in data: self.client.emit(Message("mycroft.stop")) if "volume.up" in data: self.client.emit( Message("IncreaseVolumeIntent", metadata={'play_sound': True})) if "volume.down" in data: self.client.emit( Message("DecreaseVolumeIntent", metadata={'play_sound': True})) if "system.test.begin" in data: self.client.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.client.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.client.emit(Message("speak", metadata={ 'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav") time.sleep(3.5) # Pause between tests so it's not so fast # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True) if "unit.shutdown" in data: self.client.emit( Message("enclosure.eyes.timedspin", metadata={'length': 12000})) self.client.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl poweroff -i', shell=True) if "unit.reboot" in data: self.client.emit( Message("enclosure.eyes.spin")) self.client.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True) if "unit.setwifi" in data: self.client.emit(Message("wifisetup.start")) if "unit.factory-reset" in data: subprocess.call( 'rm ~/.mycroft/identity/identity.json', shell=True) self.client.emit( Message("enclosure.eyes.spin")) self.client.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True)
def process(self, data): self.client.emit(Message(data)) if "mycroft.stop" in data: self.client.emit(Message("mycroft.stop")) if "volume.up" in data: self.client.emit( Message("IncreaseVolumeIntent", metadata={'play_sound': True})) if "volume.down" in data: self.client.emit( Message("DecreaseVolumeIntent", metadata={'play_sound': True})) if "system.test.begin" in data: self.client.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.client.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.client.emit(Message("speak", metadata={ 'utterance': "I am testing one two three"})) record("/tmp/test.wav", 3.5) play_wav("/tmp/test.wav") # Test audio muting on arduino self.client.emit(Message("speak", metadata={ 'utterance': "LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG"})) mixer.setvolume(prev_vol)
def notify(self, timestamp): with self.LOCK: if self.data.__contains__(timestamp): volume = None self.reminder_on = True delay = self.__calculate_delay(self.max_delay) while self.reminder_on and datetime.now() < delay: self.speak_dialog( 'reminder.notify', data=self.build_feedback_payload(timestamp)) time.sleep(1) self.speak_dialog('reminder.stop') time.sleep(self.repeat_time) if not volume and datetime.now() >= delay: mixer = Mixer() volume = mixer.getvolume()[0] mixer.setvolume(100) delay = self.__calculate_delay(self.extended_delay) if volume: Mixer().setvolume(volume) self.remove(timestamp) self.reminder_on = False self.save()
def run(self, commandlist): mixer = Mixer() if len(commandlist) == 1: if mixer.getmute(): mixer.setmute(0) else: mixer.setmute(1) else: command = {'mode':'set'} for i in xrange(0, len(commandlist)-1): pass
def __init__(self): self.logged_in = threading.Event() self.end_of_track = threading.Event() self.queue_playlist = [] self.prev_playlist = [] self.min_volume = 70 config = spotify.Config() config.load_application_key_file(filename='/home/pi/django_projects/mysite/spotify_app/spotify_appkey.key') self.session = spotify.Session(config=config) self.loop = spotify.EventLoop(self.session) self.loop.start() self.current_track = None # Connect an audio sink self.audio = spotify.AlsaSink(self.session) self.mixer = Mixer('PCM', 0) self.session.on(spotify.SessionEvent.CONNECTION_STATE_UPDATED, self.on_connection_state_updated) self.session.on(spotify.SessionEvent.END_OF_TRACK, self.on_end_of_track)
def create_mixer(self): self.alsamixer = Mixer(control=self.mixer, id=self.mixer_id, cardindex=self.card)
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})
def get_volume(): id = alsaaudio.mixers(SOUND_CARD).index(SOUND_MIXER) mixer = Mixer(SOUND_MIXER, id, SOUND_CARD) return mixer.getvolume()[0]
class ALSA(IntervalModule): """ Shows volume of ALSA mixer. You can also use this for inputs, btw. Requires pyalsaaudio Available formatters: * `{volume}` — the current volume in percent * `{muted}` — the value of one of the `muted` or `unmuted` settings * `{card}` — the associated soundcard * `{mixer}` — the associated ALSA mixer """ interval = 1 settings = ( "format", ("format_muted", "optional format string to use when muted"), ("mixer", "ALSA mixer"), ("mixer_id", "ALSA mixer id"), ("card", "ALSA sound card"), "muted", "unmuted", "color_muted", "color", "channel" ) muted = "M" unmuted = "" color_muted = "#AAAAAA" color = "#FFFFFF" format = "♪: {volume}" format_muted = None mixer = "Master" mixer_id = 0 card = 0 channel = 0 alsamixer = None has_mute = True def init(self): self.create_mixer() try: self.alsamixer.getmute() except ALSAAudioError: self.has_mute = False self.fdict = { "card": self.alsamixer.cardname(), "mixer": self.mixer, } def create_mixer(self): self.alsamixer = Mixer( control=self.mixer, id=self.mixer_id, cardindex=self.card) def run(self): self.create_mixer() muted = False if self.has_mute: muted = self.alsamixer.getmute()[self.channel] == 1 self.fdict["volume"] = self.alsamixer.getvolume()[self.channel] self.fdict["muted"] = self.muted if muted else self.unmuted if muted and self.format_muted is not None: output_format = self.format_muted else: output_format = self.format self.output = { "full_text": output_format.format(**self.fdict), "color": self.color_muted if muted else self.color, }
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # [email protected] import sys from alsaaudio import Mixer m = Mixer() if __name__ == '__main__': output = ["{} {}".format(u'\uf001', m.getvolume()[0])] color = False if m.getmute()[0]: color = '#666666' output += output if color: output.append(color) print('\n'.join(output))
def handle_reset_volume(self, message): Mixer().setvolume(self.default_volume) self.speak_dialog('reset.volume', data={'volume': self.get_volume_code(self.default_volume)})
def process(self, data): # TODO: Look into removing this emit altogether. # We need to check if any other serial bus messages # are handled by other parts of the code if "mycroft.stop" not in data: self.ws.emit(Message(data)) if "Command: system.version" in data: # This happens in response to the "system.version" message # sent during the construction of Enclosure() self.ws.emit(Message("enclosure.started")) if "mycroft.stop" in data: if has_been_paired(): create_signal('buttonPress') self.ws.emit(Message("mycroft.stop")) if "volume.up" in data: self.ws.emit( Message("VolumeSkill:IncreaseVolumeIntent", {'play_sound': True})) if "volume.down" in data: self.ws.emit( Message("VolumeSkill:DecreaseVolumeIntent", {'play_sound': True})) if "system.test.begin" in data: self.ws.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.ws.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.ws.emit(Message("speak", { 'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav").communicate() # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True) if "unit.shutdown" in data: self.ws.emit( Message("enclosure.eyes.timedspin", {'length': 12000})) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl poweroff -i', shell=True) if "unit.reboot" in data: self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True) if "unit.setwifi" in data: self.ws.emit(Message("mycroft.wifi.start")) if "unit.factory-reset" in data: self.ws.emit(Message("enclosure.eyes.spin")) subprocess.call( 'rm ~/.mycroft/identity/identity2.json', shell=True) self.ws.emit(Message("mycroft.wifi.reset")) self.ws.emit(Message("mycroft.disable.ssh")) self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("reset to factory defaults")})) wait_while_speaking() self.ws.emit(Message("enclosure.mouth.reset")) self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True) if "unit.enable-ssh" in data: # This is handled by the wifi client self.ws.emit(Message("mycroft.enable.ssh")) self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("ssh enabled")})) if "unit.disable-ssh" in data: # This is handled by the wifi client self.ws.emit(Message("mycroft.disable.ssh")) self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("ssh disabled")}))
class ALSA(): def init(self): self.muted = "M" self.unmuted = "" self.color_muted = "#AAAAAA" self.color = "#FFFFFF" self.format = "♪: {volume}" self.format_muted = None self.mixer = "Master" self.mixer_id = 0 self.card = 0 self.channel = 0 self.increment = 5 self.map_volume = False self.alsamixer = None self.has_mute = True self.create_mixer() try: self.alsamixer.getmute() except ALSAAudioError: self.has_mute = False self.fdict = { "card": self.alsamixer.cardname(), "mixer": self.mixer, } self.dbRng = self.alsamixer.getrange() self.dbMin = self.dbRng[0] self.dbMax = self.dbRng[1] def create_mixer(self): self.alsamixer = Mixer( control=self.mixer, id=self.mixer_id, cardindex=self.card) def run(self): self.create_mixer() muted = False if self.has_mute: muted = self.alsamixer.getmute()[self.channel] == 1 self.fdict["volume"] = self.get_cur_volume() self.fdict["muted"] = self.muted if muted else self.unmuted self.fdict["db"] = self.get_db() if muted and self.format_muted is not None: output_format = self.format_muted else: output_format = self.format self.output = { "full_text": output_format.format(**self.fdict), "color": self.color_muted if muted else self.color, } def switch_mute(self): if self.has_mute: muted = self.alsamixer.getmute()[self.channel] self.alsamixer.setmute(not muted) def get_cur_volume(self): if self.map_volume: dbCur = self.get_db() * 100.0 dbMin = self.dbMin * 100.0 dbMax = self.dbMax * 100.0 dbCur_norm = self.exp10((dbCur - dbMax) / 6000.0) dbMin_norm = self.exp10((dbMin - dbMax) / 6000.0) vol = (dbCur_norm - dbMin_norm) / (1 - dbMin_norm) vol = int(round(vol * 100, 0)) return vol else: return self.alsamixer.getvolume()[self.channel] def get_new_volume(self, direction): if direction == "inc": volume = (self.fdict["volume"] + 1) / 100 elif direction == "dec": volume = (self.fdict["volume"] - 1) / 100 dbMin = self.dbMin * 100 dbMax = self.dbMax * 100 dbMin_norm = self.exp10((dbMin - dbMax) / 6000.0) vol = volume * (1 - dbMin_norm) + dbMin_norm if direction == "inc": dbNew = min(self.dbMax, ceil(((6000.0 * log10(vol)) + dbMax) / 100)) elif direction == "dec": dbNew = max(self.dbMin, floor(((6000.0 * log10(vol)) + dbMax) / 100)) volNew = int(round(self.map_db(dbNew, self.dbMin, self.dbMax, 0, 100), 0)) return volNew def increase_volume(self, delta=None): if self.map_volume: vol = self.get_new_volume("inc") self.alsamixer.setvolume(vol) else: vol = self.alsamixer.getvolume()[self.channel] self.alsamixer.setvolume(min(100, vol + (delta if delta else self.increment))) def decrease_volume(self, delta=None): if self.map_volume: vol = self.get_new_volume("dec") self.alsamixer.setvolume(vol) else: vol = self.alsamixer.getvolume()[self.channel] self.alsamixer.setvolume(max(0, vol - (delta if delta else self.increment))) def get_db(self): db = (((self.dbMax - self.dbMin) / 100) * self.alsamixer.getvolume()[self.channel]) + self.dbMin db = int(round(db, 0)) return db def map_db(self, value, dbMin, dbMax, volMin, volMax): dbRange = dbMax - dbMin volRange = volMax - volMin volScaled = float(value - dbMin) / float(dbRange) return volMin + (volScaled * volRange) def exp10(self, x): return exp(x * log(10))
def process(self, data): # TODO: Look into removing this emit altogether. # We need to check if any other serial bus messages # are handled by other parts of the code if "mycroft.stop" not in data: self.bus.emit(Message(data)) if "Command: system.version" in data: # This happens in response to the "system.version" message # sent during the construction of Enclosure() self.bus.emit(Message("enclosure.started")) if "mycroft.stop" in data: if has_been_paired(): create_signal('buttonPress') self.bus.emit(Message("mycroft.stop")) if "volume.up" in data: self.bus.emit( Message("mycroft.volume.increase", {'play_sound': True})) if "volume.down" in data: self.bus.emit( Message("mycroft.volume.decrease", {'play_sound': True})) if "system.test.begin" in data: self.bus.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.bus.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.bus.emit( Message("speak", {'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav").communicate() # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True) if "unit.shutdown" in data: # Eyes to soft gray on shutdown self.bus.emit( Message("enclosure.eyes.color", { 'r': 70, 'g': 65, 'b': 69 })) self.bus.emit( Message("enclosure.eyes.timedspin", {'length': 12000})) self.bus.emit(Message("enclosure.mouth.reset")) time.sleep(0.5) # give the system time to pass the message self.bus.emit(Message("system.shutdown")) if "unit.reboot" in data: # Eyes to soft gray on reboot self.bus.emit( Message("enclosure.eyes.color", { 'r': 70, 'g': 65, 'b': 69 })) self.bus.emit(Message("enclosure.eyes.spin")) self.bus.emit(Message("enclosure.mouth.reset")) time.sleep(0.5) # give the system time to pass the message self.bus.emit(Message("system.reboot")) if "unit.setwifi" in data: self.bus.emit(Message("system.wifi.setup", {'lang': self.lang})) if "unit.factory-reset" in data: self.bus.emit( Message("speak", { 'utterance': mycroft.dialog.get("reset to factory defaults") })) subprocess.call('rm ~/.mycroft/identity/identity2.json', shell=True) self.bus.emit(Message("system.wifi.reset")) self.bus.emit(Message("system.ssh.disable")) wait_while_speaking() self.bus.emit(Message("enclosure.mouth.reset")) self.bus.emit(Message("enclosure.eyes.spin")) self.bus.emit(Message("enclosure.mouth.reset")) time.sleep(5) # give the system time to process all messages self.bus.emit(Message("system.reboot")) if "unit.enable-ssh" in data: # This is handled by the wifi client self.bus.emit(Message("system.ssh.enable")) self.bus.emit( Message("speak", {'utterance': mycroft.dialog.get("ssh enabled")})) if "unit.disable-ssh" in data: # This is handled by the wifi client self.bus.emit(Message("system.ssh.disable")) self.bus.emit( Message("speak", {'utterance': mycroft.dialog.get("ssh disabled")})) if "unit.enable-learning" in data or "unit.disable-learning" in data: enable = 'enable' in data word = 'enabled' if enable else 'disabled' LOG.info("Setting opt_in to: " + word) new_config = {'opt_in': enable} user_config = LocalConf(USER_CONFIG) user_config.merge(new_config) user_config.store() self.bus.emit( Message("speak", {'utterance': mycroft.dialog.get("learning " + word)}))
class Harold(object): def __init__(self, mplfifo, ser, mpout, beep=True): self.playing = False self.mixer = Mixer(control='PCM') self.fifo = mplfifo self.ser = ser self.mpout = mpout self.beep = beep def write(self, *args, **kwargs): delay = kwargs.pop("delay", 0.5) kws = {"file": self.fifo} kws.update(kwargs) print(*args, **kws) time.sleep(delay) def __call__(self): if not self.playing: userlog = open("/home/pi/logs/user_log.csv", "a") # Lower the volume during quiet hours... Don't piss off the RA! self.mixer.setvolume(85 if quiet_hours() else 100) varID = self.ser.readline() print(varID) # mplayer will play any files sent to the FIFO file. if self.beep: self.write("loadfile", DING_SONG) if "ready" not in varID: # Turn the LEDs off LED.on(False) print("Turning off LEDs") # Get the username from the ibutton print("Getting User Data") uid, homedir = read_ibutton(varID) # Print the user's name (Super handy for debugging...) print("User: '******'\n") song = get_user_song(homedir, uid) print("Now playing '" + song + "'...\n") varID = varID[:-2] userlog.write("\n" + time.strftime('%Y/%m/%d %H:%M:%S') + "," + uid + "," + song) self.write("loadfile '" + song.replace("'", "\\'") + "'\nget_time_length", delay=0.0) line = self.mpout.readline().strip() # timeout = time.time() + 5 # Five second time out to wait for song time. while not line.startswith("ANS_LENGTH="): line = self.mpout.readline().strip() if line.startswith("Playing"): # Check if mplayer can play file. if self.mpout.readline().strip() == "": self.starttime = time.time() self.endtime = time.time() self.playing = True break elif line.startswith("ANS_LENGTH="): duration = float(line.strip().split("=")[-1]) self.starttime = time.time() self.endtime = time.time() + min(30, duration) self.playing = True else: pass userlog.close() elif time.time() >= self.endtime: self.write("stop") self.playing = False self.ser.flushInput() LED.on(True) print("Stopped\n") elif time.time() >= self.starttime+28: # Fade out the music at the end. vol = int(self.mixer.getvolume()[0]) while vol > 60: vol -= 1 + (100 - vol)/30. self.mixer.setvolume(int(vol)) time.sleep(0.1)
class ALSA(IntervalModule): """ Shows volume of ALSA mixer. You can also use this for inputs, btw. Requires pyalsaaudio Available formatters: * `{volume}` — the current volume in percent * `{muted}` — the value of one of the `muted` or `unmuted` settings * `{card}` — the associated soundcard * `{mixer}` — the associated ALSA mixer """ interval = 1 settings = ("format", ("mixer", "ALSA mixer"), ("mixer_id", "ALSA mixer id"), ("card", "ALSA sound card"), "muted", "unmuted", "color_muted", "color", "channel") muted = "M" unmuted = "" color_muted = "#AAAAAA" color = "#FFFFFF" format = "♪: {volume}" mixer = "Master" mixer_id = 0 card = 0 channel = 0 alsamixer = None has_mute = True def init(self): self.create_mixer() try: self.alsamixer.getmute() except ALSAAudioError: self.has_mute = False self.fdict = { "card": self.alsamixer.cardname(), "mixer": self.mixer, } def create_mixer(self): self.alsamixer = Mixer(control=self.mixer, id=self.mixer_id, cardindex=self.card) def run(self): self.create_mixer() muted = False if self.has_mute: muted = self.alsamixer.getmute()[self.channel] == 1 self.fdict["volume"] = self.alsamixer.getvolume()[self.channel] self.fdict["muted"] = self.muted if muted else self.unmuted self.output = { "full_text": self.format.format(**self.fdict), "color": self.color_muted if muted else self.color, }
class TelegramSpamSkill(MycroftSkill): def __init__(self): super(TelegramSpamSkill, self).__init__(name="TelegramSpamSkill") def initialize(self): self.mute = str(self.settings.get('MuteIt', '')) if (self.mute == 'True') or (self.mute == 'true'): try: self.mixer = Mixer() msg = "Telegram Messages will temporary Mute Mycroft" logger.info(msg) except: msg = "There is a problem with alsa audio, mute is not working!" logger.info( "There is a problem with alsaaudio, mute is not working!") self.sendMycroftSay(msg) self.mute = 'false' else: logger.info("Telegram: Muting is off") self.mute = "false" self.add_event('recognizer_loop:utterance', self.utteranceHandler) self.add_event('telegram-skill:response', self.sendHandler) self.add_event('speak', self.responseHandler) user_id1 = self.settings.get('TeleID1', '') self.chat_id = user_id1 self.chat_whitelist = [user_id1] # Get Bot Token from settings.json UnitName = DeviceApi().get()['name'] MyCroftDevice1 = self.settings.get('MDevice1', '') self.bottoken = "" if MyCroftDevice1 == UnitName: logger.debug("Found MyCroft Unit 1: " + UnitName) self.bottoken = self.settings.get('TeleToken1', '') else: msg = ( "No or incorrect Device Name specified! Your DeviceName is: " + UnitName) logger.info(msg) self.sendMycroftSay(msg) # Connection to Telegram API self.telegram_updater = Updater( token=self.bottoken) # get telegram Updates self.telegram_dispatcher = self.telegram_updater.dispatcher receive_handler = MessageHandler( Filters.text, self.TelegramMessages ) # TODO: Make audio Files as Input possible: Filters.text | Filters.audio self.telegram_dispatcher.add_handler(receive_handler) self.telegram_updater.start_polling( clean=True) # start clean and look for messages wbot = telegram.Bot(token=self.bottoken) global loaded # get global variable if loaded == 0: # check if bot is just started loaded = 1 # make sure that users gets this message only once bot is newly loaded if self.mute == "false": msg = "Telegram Skill is loaded" self.sendMycroftSay(msg) loadedmessage = "Telegram-Skill on Mycroft Unit \"" + UnitName + "\" is loaded and ready to use!" # give User a nice message try: wbot.send_message( chat_id=user_id1, text=loadedmessage) # send welcome message to user 1 except: pass def TelegramMessages(self, bot, update): msg = update.message.text if self.chat_id in self.chat_whitelist: logger.info("Telegram-Message from User: "******"', '\\\"').replace( '(', ' ').replace(')', ' ').replace('{', ' ').replace('}', ' ') msg = msg.casefold( ) # some skills need lowercase (eg. the cows list) self.add_event('recognizer_loop:audio_output_start', self.muteHandler) self.sendMycroftUtt(msg) else: logger.info("Chat ID " + self.chat_id + " is not whitelisted, i don't process it") nowhite = ("This is your ChatID: " + self.chat_id) bot.send_message(chat_id=self.chat_id, text=nowhite) def sendMycroftUtt(self, msg): uri = 'ws://localhost:8181/core' ws = create_connection(uri) utt = '{"context": null, "type": "recognizer_loop:utterance", "data": {"lang": "' + self.lang + '", "utterances": ["' + msg + '"]}}' ws.send(utt) ws.close() def sendMycroftSay(self, msg): uri = 'ws://localhost:8181/core' ws = create_connection(uri) msg = "say " + msg utt = '{"context": null, "type": "recognizer_loop:utterance", "data": {"lang": "' + self.lang + '", "utterances": ["' + msg + '"]}}' ws.send(utt) ws.close() def responseHandler(self, message): response = message.data.get("utterance") self.bus.emit( Message("telegram-skill:response", { "intent_name": "telegram-response", "utterance": response })) def utteranceHandler(self, message): response = str(message.data.get("utterances")) self.bus.emit( Message("telegram-skill:response", { "intent_name": "telegram-response", "utterance": response })) def sendHandler(self, message): sendData = message.data.get("utterance") logger.info("Sending to Telegram-User: " + sendData) sendbot = telegram.Bot(token=self.bottoken) sendbot.send_message(chat_id=self.chat_id, text=sendData) def muteHandler(self, message): if (self.mute == 'true') or (self.mute == 'True'): self.mixer.setmute(1) wait_while_speaking() self.mixer.setmute(0) self.remove_event('recognizer_loop:audio_output_start') def shutdown(self): # shutdown routine self.telegram_updater.stop() # will stop update and dispatcher self.telegram_updater.is_idle = False super(TelegramSpamSkill, self).shutdown() def stop(self): pass
def handle_set_volume(self, message): mixer = Mixer() code, volume = self.get_volume(message, mixer.getvolume()[0]) mixer.setvolume(volume) self.speak_dialog('set.volume', data={'volume': code})
class ALSA(IntervalModule): """ Shows volume of ALSA mixer. You can also use this for inputs, btw. Requires pyalsaaudio .. rubric:: Available formatters * `{volume}` — the current volume in percent * `{muted}` — the value of one of the `muted` or `unmuted` settings * `{card}` — the associated soundcard * `{mixer}` — the associated ALSA mixer """ interval = 1 settings = ( "format", ("format_muted", "optional format string to use when muted"), ("mixer", "ALSA mixer"), ("mixer_id", "ALSA mixer id"), ("card", "ALSA sound card"), ("increment", "integer percentage of max volume to in/decrement volume on mousewheel"), "muted", "unmuted", "color_muted", "color", "channel", ("map_volume", "volume display/setting as in AlsaMixer. increment option is ignored then.") ) muted = "M" unmuted = "" color_muted = "#AAAAAA" color = "#FFFFFF" format = "♪: {volume}" format_muted = None mixer = "Master" mixer_id = 0 card = 0 channel = 0 increment = 5 map_volume = False alsamixer = None has_mute = True on_upscroll = "increase_volume" on_downscroll = "decrease_volume" on_leftclick = "switch_mute" on_rightclick = on_leftclick def init(self): self.create_mixer() try: self.alsamixer.getmute() except ALSAAudioError: self.has_mute = False self.fdict = { "card": self.alsamixer.cardname(), "mixer": self.mixer, } self.dbRng = self.alsamixer.getrange() self.dbMin = self.dbRng[0] self.dbMax = self.dbRng[1] def create_mixer(self): self.alsamixer = Mixer( control=self.mixer, id=self.mixer_id, cardindex=self.card) def run(self): self.create_mixer() muted = False if self.has_mute: muted = self.alsamixer.getmute()[self.channel] == 1 self.fdict["volume"] = self.get_cur_volume() self.fdict["muted"] = self.muted if muted else self.unmuted self.fdict["db"] = self.get_db() if muted and self.format_muted is not None: output_format = self.format_muted else: output_format = self.format self.data = self.fdict self.output = { "full_text": output_format.format(**self.fdict), "color": self.color_muted if muted else self.color, } def switch_mute(self): if self.has_mute: muted = self.alsamixer.getmute()[self.channel] self.alsamixer.setmute(not muted) def get_cur_volume(self): if self.map_volume: dbCur = self.get_db() * 100.0 dbMin = self.dbMin * 100.0 dbMax = self.dbMax * 100.0 dbCur_norm = self.exp10((dbCur - dbMax) / 6000.0) dbMin_norm = self.exp10((dbMin - dbMax) / 6000.0) vol = (dbCur_norm - dbMin_norm) / (1 - dbMin_norm) vol = int(round(vol * 100, 0)) return vol else: return self.alsamixer.getvolume()[self.channel] def get_new_volume(self, direction): if direction == "inc": volume = (self.fdict["volume"] + 1) / 100 elif direction == "dec": volume = (self.fdict["volume"] - 1) / 100 dbMin = self.dbMin * 100 dbMax = self.dbMax * 100 dbMin_norm = self.exp10((dbMin - dbMax) / 6000.0) vol = volume * (1 - dbMin_norm) + dbMin_norm if direction == "inc": dbNew = min(self.dbMax, ceil(((6000.0 * log10(vol)) + dbMax) / 100)) elif direction == "dec": dbNew = max(self.dbMin, floor(((6000.0 * log10(vol)) + dbMax) / 100)) volNew = int(round(self.map_db(dbNew, self.dbMin, self.dbMax, 0, 100), 0)) return volNew def increase_volume(self, delta=None): if self.map_volume: vol = self.get_new_volume("inc") self.alsamixer.setvolume(vol) else: vol = self.alsamixer.getvolume()[self.channel] self.alsamixer.setvolume(min(100, vol + (delta if delta else self.increment))) def decrease_volume(self, delta=None): if self.map_volume: vol = self.get_new_volume("dec") self.alsamixer.setvolume(vol) else: vol = self.alsamixer.getvolume()[self.channel] self.alsamixer.setvolume(max(0, vol - (delta if delta else self.increment))) def get_db(self): db = (((self.dbMax - self.dbMin) / 100) * self.alsamixer.getvolume()[self.channel]) + self.dbMin db = int(round(db, 0)) return db def map_db(self, value, dbMin, dbMax, volMin, volMax): dbRange = dbMax - dbMin volRange = volMax - volMin volScaled = float(value - dbMin) / float(dbRange) return volMin + (volScaled * volRange) def exp10(self, x): return exp(x * log(10))
def handle_mute_volume(self, message): self.speak_dialog('mute.volume') time.sleep(2) Mixer().setvolume(0)
background.blit(image_weather, image_weather.get_rect()) render.render(datetime.today().strftime("%H:%M"), font, background, hex_to_rgb(conf["general"]["front_color"]), 0, 120, 320, 120) table.update([{"image": image_temp, "data": str(round(data_forecast["list"][0]["temp"]["day"], 1))}, {"image": image_rise, "data": datetime.fromtimestamp(data["sys"]["sunrise"]).strftime("%H:%M")}, {"image": image_rise, "data": datetime.fromtimestamp(data["sys"]["sunset"]).strftime("%H:%M")}, {"image": image_mail, "data": str(unread[0])}, {"image": image_news, "data": str(news)}, {"image": image_cal, "data": str(cal[0])}]) screen.blit(background, (0, 0)) display.flip() else : render.render(get_ip_address('eth0'), font, background, hex_to_rgb(conf["general"]["front_color"]), 0, 0, 320, 240) screen.blit(background, (0, 0)) display.flip() # Boucle de rafraichissement mixer = Mixer(control="PCM") i = 0 while True : sleep(0.1) update() i+=1 if i >= 20 : conf_file = open("../conf/wake.json") conf = jload(conf_file) s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect("mastersocket") s.send(jdumps({"request": "get_delta"}))
def create_mixer(self): self.alsamixer = Mixer( control=self.mixer, id=self.mixer_id, cardindex=self.card)
def process(self, data): # TODO: Look into removing this emit altogether. # We need to check if any other serial bus messages # are handled by other parts of the code if "mycroft.stop" not in data: self.ws.emit(Message(data)) if "Command: system.version" in data: # This happens in response to the "system.version" message # sent during the construction of Enclosure() self.ws.emit(Message("enclosure.started")) if "mycroft.stop" in data: if has_been_paired(): create_signal('buttonPress') self.ws.emit(Message("mycroft.stop")) if "volume.up" in data: self.ws.emit(Message("mycroft.volume.increase", {'play_sound': True})) if "volume.down" in data: self.ws.emit(Message("mycroft.volume.decrease", {'play_sound': True})) if "system.test.begin" in data: self.ws.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.ws.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.ws.emit(Message("speak", { 'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav").communicate() # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True) if "unit.shutdown" in data: # Eyes to soft gray on shutdown self.ws.emit(Message("enclosure.eyes.color", {'r': 70, 'g': 65, 'b': 69})) self.ws.emit( Message("enclosure.eyes.timedspin", {'length': 12000})) self.ws.emit(Message("enclosure.mouth.reset")) time.sleep(0.5) # give the system time to pass the message self.ws.emit(Message("system.shutdown")) if "unit.reboot" in data: # Eyes to soft gray on reboot self.ws.emit(Message("enclosure.eyes.color", {'r': 70, 'g': 65, 'b': 69})) self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) time.sleep(0.5) # give the system time to pass the message self.ws.emit(Message("system.reboot")) if "unit.setwifi" in data: self.ws.emit(Message("system.wifi.setup", {'lang': self.lang})) if "unit.factory-reset" in data: self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("reset to factory defaults")})) subprocess.call( 'rm ~/.mycroft/identity/identity2.json', shell=True) self.ws.emit(Message("system.wifi.reset")) self.ws.emit(Message("system.ssh.disable")) wait_while_speaking() self.ws.emit(Message("enclosure.mouth.reset")) self.ws.emit(Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) time.sleep(5) # give the system time to process all messages self.ws.emit(Message("system.reboot")) if "unit.enable-ssh" in data: # This is handled by the wifi client self.ws.emit(Message("system.ssh.enable")) self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("ssh enabled")})) if "unit.disable-ssh" in data: # This is handled by the wifi client self.ws.emit(Message("system.ssh.disable")) self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("ssh disabled")})) if "unit.enable-learning" in data or "unit.disable-learning" in data: enable = 'enable' in data word = 'enabled' if enable else 'disabled' LOG.info("Setting opt_in to: " + word) new_config = {'opt_in': enable} user_config = LocalConf(USER_CONFIG) user_config.merge(new_config) user_config.store() self.ws.emit(Message("speak", { 'utterance': mycroft.dialog.get("learning " + word)}))
def set_volume(val): id = alsaaudio.mixers(SOUND_CARD).index(SOUND_MIXER) mixer = Mixer(SOUND_MIXER, id, SOUND_CARD) mixer.setvolume(val)
def process(self, data): self.ws.emit(Message(data)) if "Command: system.version" in data: self.ws.emit(Message("enclosure.start")) if "mycroft.stop" in data: create_signal('buttonPress') # FIXME - Must use WS instead self.ws.emit(Message("mycroft.stop")) if "volume.up" in data: self.ws.emit( Message("IncreaseVolumeIntent", {'play_sound': True})) if "volume.down" in data: self.ws.emit( Message("DecreaseVolumeIntent", {'play_sound': True})) if "system.test.begin" in data: self.ws.emit(Message('recognizer_loop:sleep')) if "system.test.end" in data: self.ws.emit(Message('recognizer_loop:wake_up')) if "mic.test" in data: mixer = Mixer() prev_vol = mixer.getvolume()[0] mixer.setvolume(35) self.ws.emit(Message("speak", { 'utterance': "I am testing one two three"})) time.sleep(0.5) # Prevents recording the loud button press record("/tmp/test.wav", 3.0) mixer.setvolume(prev_vol) play_wav("/tmp/test.wav").communicate() # Test audio muting on arduino subprocess.call('speaker-test -P 10 -l 0 -s 1', shell=True) if "unit.shutdown" in data: self.ws.emit( Message("enclosure.eyes.timedspin", {'length': 12000})) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl poweroff -i', shell=True) if "unit.reboot" in data: self.ws.emit( Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True) if "unit.setwifi" in data: self.ws.emit(Message("mycroft.wifi.start")) if "unit.factory-reset" in data: subprocess.call( 'rm ~/.mycroft/identity/identity2.json', shell=True) self.ws.emit( Message("enclosure.eyes.spin")) self.ws.emit(Message("enclosure.mouth.reset")) subprocess.call('systemctl reboot -i', shell=True)
class Player: def __init__(self): self.logged_in = threading.Event() self.end_of_track = threading.Event() self.queue_playlist = [] self.prev_playlist = [] self.min_volume = 70 config = spotify.Config() config.load_application_key_file(filename='/home/pi/django_projects/mysite/spotify_app/spotify_appkey.key') self.session = spotify.Session(config=config) self.loop = spotify.EventLoop(self.session) self.loop.start() self.current_track = None # Connect an audio sink self.audio = spotify.AlsaSink(self.session) self.mixer = Mixer('PCM', 0) self.session.on(spotify.SessionEvent.CONNECTION_STATE_UPDATED, self.on_connection_state_updated) self.session.on(spotify.SessionEvent.END_OF_TRACK, self.on_end_of_track) def login(self, username, password): self.session.login(username, password) self.logged_in.wait() self.player = self.session.player def logout(self): self.session.logout() def play(self, track_uri): track = self.session.get_track(track_uri).load() self.current_track = track self.player.load(track) self.player.play() def queue(self, track_uri): track = self.session.get_track(track_uri).load() self.queue_playlist.append(track) def pause(self): self.player.pause() def stop(self): self.current_track = None self.player.unload() def play_prev(self): if len(self.prev_playlist) == 0: self.play(self.current_track.link.uri) else: self.queue_playlist.insert(0, self.current_track) self.play(self.prev_playlist.pop(0).link.uri) def play_next(self): self.prev_playlist.insert(0, self.current_track) if len(self.queue_playlist) == 0: self.current_track = None else: self.play(self.queue_playlist.pop(0).link.uri); def set_audio_output(self, output): subprocess.call(["amixer", "cset", "numid=3", str(output)]) def search(self, query): search = self.session.search(query); search.load() return search.tracks def get_playlist(self): return self.queue_playlist def is_playing(self): if self.current_track == None: return False else: return True def remove_from_queue(self, index): self.queue_playlist.pop(int(index)) def set_volume(self, volume): if int(volume) == self.min_volume: volume = 0 self.mixer.setvolume(int(volume)) def get_volume(self): return self.mixer.getvolume()[0] def on_connection_state_updated(self, session): if session.connection.state is spotify.ConnectionState.LOGGED_IN: self.logged_in.set() else: self.logged_in.clear() def on_end_of_track(self, self2): self.play_next()
class AlarmSkill(MycroftSkill): # seconds between end of a beep and the start of next # must be bigger than the max listening time (10 sec) beep_gap = 15 default_sound = "constant_beep" threshold = 0.7 # Threshold for word fuzzy matching def __init__(self): super(AlarmSkill, self).__init__() self.beep_process = None self.beep_start_time = None self.flash_state = 0 # Seconds of gap between sound repeats. # The value name must match an option from the 'sound' value of the # settingmeta.json, which also corresponds to the name of an mp3 # file in the skill's sounds/ folder. E.g. <skill>/sounds/bell.mp3 # self.sounds = { "bell": 5.0, "escalate": 32.0, "constant_beep": 5.0, "beep4": 4.0, "chimes": 22.0 } #initialize alarm settings self.init_settings() try: self.mixer = Mixer() except Exception: # Retry instanciating the mixer try: self.mixer = Mixer() except Exception as e: self.log.error('Couldn\'t allocate mixer, {}'.format(repr(e))) self.mixer = None self.saved_volume = None # Alarm list format [{ # "timestamp": float, # "repeat_rule": str, # "name": str, # "snooze": float # }, ...] # where: # timestamp is a POSIX timestamp float assumed to # be in the utc timezone. # # repeat_rule is for generating the next in a series. Valid # repeat_rules include None for a one-shot alarm or any other # iCalendar rule from RFC <https://tools.ietf.org/html/rfc5545>. # # name is the name of the alarm. There can be multiple alarms of the # same name. # # snooze is the POSIX timestamp float of the Snooze assumed to # be in the utc timezone. # # NOTE: Using list instead of tuple because of serialization def init_settings(self): """Add any missing default settings.""" # default sound is 'constant_beep' self.settings.setdefault('max_alarm_secs', 10 * 60) # Beep for 10 min. self.settings.setdefault('sound', AlarmSkill.default_sound) self.settings.setdefault('start_quiet', True) self.settings.setdefault('alarm', []) def dump_alarms(self, tag=""): # Useful when debugging dump = "\n" + "=" * 30 + " ALARMS " + tag + " " + "=" * 30 + "\n" dump += "raw = " + str(self.settings["alarm"]) + "\n\n" now_ts = to_utc(now_utc()).timestamp() dt = datetime.fromtimestamp(now_ts) dump += "now = {} ({})\n".format( nice_time(self.get_alarm_local(timestamp=now_ts), speech=False, use_ampm=True), now_ts) dump += " U{} L{}\n".format(to_utc(dt), to_local(dt)) dump += "\n\n" idx = 0 for alarm in self.settings["alarm"]: dt = self.get_alarm_local(alarm) dump += "alarm[{}] - {} \n".format(idx, alarm) dump += " Next: {} {}\n".format( nice_time(dt, speech=False, use_ampm=True), nice_date(dt, now=now_local())) dump += " U{} L{}\n".format(dt, to_local(dt)) if 'snooze' in alarm: dtOrig = self.get_alarm_local(timestamp=alarm['snooze']) dump += " Orig: {} {}\n".format( nice_time(dtOrig, speech=False, use_ampm=True), nice_date(dtOrig, now=now_local())) idx += 1 dump += "=" * 75 self.log.info(dump) def initialize(self): self.register_entity_file('daytype.entity') # TODO: Keep? self.recurrence_dict = self.translate_namedvalues('recurring') # Time is the first value, so this will sort alarms by time self.settings["alarm"] = sorted(self.settings["alarm"], key=lambda a: a["timestamp"]) # This will reschedule alarms which have expired within the last # 5 minutes, and cull anything older. self._curate_alarms(5 * 60) self._schedule() # Support query for active alarms from other skills self.add_event('private.mycroftai.has_alarm', self.on_has_alarm) def on_has_alarm(self, message): # Reply to requests for alarm on/off status total = len(self.settings["alarm"]) self.bus.emit(message.response(data={"active_alarms": total})) def get_alarm_local(self, alarm=None, timestamp=None): if timestamp: ts = timestamp else: ts = alarm["timestamp"] return datetime.fromtimestamp(ts, default_timezone()) def set_alarm(self, when, name=None, repeat=None): time = when.replace(second=0, microsecond=0) if repeat: alarm = self._create_recurring_alarm(time, repeat) alarm = { "timestamp": alarm["timestamp"], "repeat_rule": alarm["repeat_rule"], "name": name or "" } else: alarm = { "timestamp": to_utc(time).timestamp(), "repeat_rule": "", "name": name or "" } for existing in self.settings["alarm"]: if alarm == existing: self.speak_dialog("alarm.already.exists") return None self.settings["alarm"].append(alarm) self._schedule() return alarm def _schedule(self): # cancel any existing timed event self.cancel_scheduled_event('NextAlarm') self._curate_alarms() # set timed event for next alarm (if it exists) if self.settings["alarm"]: dt = self.get_alarm_local(self.settings["alarm"][0]) self.schedule_event(self._alarm_expired, to_system(dt), name='NextAlarm') def _curate_alarms(self, curation_limit=1): """[summary] curation_limit (int, optional): Seconds past expired at which to remove the alarm """ alarms = [] now_ts = to_utc(now_utc()).timestamp() for alarm in self.settings["alarm"]: # Alarm format == [timestamp, repeat_rule[, orig_alarm_timestamp]] if alarm["timestamp"] < now_ts: if alarm["timestamp"] < (now_ts - curation_limit): # skip playing an old alarm if alarm["repeat_rule"]: # reschedule in future if repeat rule exists alarms.append(self._next_repeat(alarm)) else: # schedule for right now, with the # third entry as the original base time base = alarm["name"] if alarm["name"] == ''\ else alarm["timestamp"] alarms.append({ "timestamp": now_ts + 1, "repeat_rule": alarm["repeat_rule"], "name": alarm["name"], "snooze": base }) else: alarms.append(alarm) alarms = sorted(alarms, key=lambda a: a["timestamp"]) self.settings["alarm"] = alarms def _next_repeat(self, alarm): # evaluate recurrence to the next instance if 'snooze' in alarm: # repeat from original time (it was snoozed) ref = datetime.fromtimestamp(alarm["repeat_rule"]) else: ref = datetime.fromtimestamp(alarm["timestamp"]) # Create a repeat rule and get the next alarm occurrance after that start = to_utc(ref) rr = rrulestr("RRULE:" + alarm["repeat_rule"], dtstart=start) now = to_utc(now_utc()) next = rr.after(now) self.log.debug(" Now={}".format(now)) self.log.debug("Original={}".format(start)) self.log.debug(" Next={}".format(next)) return { "timestamp": to_utc(next).timestamp(), "repeat_rule": alarm["repeat_rule"], "name": alarm["name"], } def _create_recurring_alarm(self, when, recur): # 'recur' is a set of day index strings, e.g. {"3", "4"} # convert rule into an iCal rrule # TODO: Support more complex alarms, e.g. first monday, monthly, etc rule = "" abbr = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"] days = [] for day in recur: days.append(abbr[int(day)]) if days: rule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=" + ",".join(days) if when and rule: when = to_utc(when) # Create a repeating rule that starts in the past, enough days # back that it encompasses any repeat. past = when + timedelta(days=-45) rr = rrulestr("RRULE:" + rule, dtstart=past) now = to_utc(now_utc()) # Get the first repeat that happens after right now next = rr.after(now) return { "timestamp": to_utc(next).timestamp(), "repeat_rule": rule, } else: return { "timestamp": None, "repeat_rule": rule, } def has_expired_alarm(self): # True is an alarm should be 'going off' now. Snoozed alarms don't # count until they are triggered again. if not self.settings["alarm"]: return False now_ts = to_utc(now_utc()).timestamp() for alarm in self.settings["alarm"]: if alarm['timestamp'] <= now_ts: return True return False # Wake me on ... (hard to match with Adapt entities) @intent_handler( IntentBuilder("").require("WakeMe").optionally("Recurring").optionally( "Recurrence")) def handle_wake_me(self, message): self.handle_set_alarm(message) def _create_day_set(self, phrase): recur = set() for r in self.recurrence_dict: if r in phrase: for day in self.recurrence_dict[r].split(): recur.add(day) return recur def _recur_desc(self, recur): # Create a textual description of the recur set day_list = list(recur) day_list.sort() days = " ".join(day_list) for r in self.recurrence_dict: if self.recurrence_dict[r] == days: return r # accept the first perfect match # Assemble a long desc, e.g. "Monday and Wednesday" day_names = [] for day in days.split(" "): for r in self.recurrence_dict: if self.recurrence_dict[r] is day: day_names.append(r) break return join_list(day_names, self.translate('and')) @intent_handler( IntentBuilder("").require("Set").require("Alarm").optionally( "Recurring").optionally("Recurrence")) def handle_set_alarm(self, message): """Handler for "set an alarm for...""" utt = message.data.get('utterance').lower() recur = None if message.data.get('Recurring'): # Just ignoring the 'Recurrence' now, we support more complex stuff # recurrence = message.data.get('Recurrence') recur = self._create_day_set(utt) # TODO: remove days following an "except" in the utt while not recur: r = self.get_response('query.recurrence', num_retries=1) if not r: return recur = self._create_day_set(r) if self.voc_match(utt, "Except"): # TODO: Support exceptions self.speak_dialog("no.exceptions.yet") return # Get the time when, utt_no_datetime = extract_datetime(utt) or (None, utt) # Get name from leftover string from extract_datetime name = self._get_alarm_name(utt_no_datetime) # Will return dt of unmatched string today = extract_datetime("today") today = today[0] # Check the time if it's midnight. This is to check if the user # said a recurring alarm with only the Day or if the user did # specify to set an alarm on midnight. If it's confirmed that # it's for a day only, then get another response from the user # to clarify what time on that day the recurring alarm is. is_midnight = self._check_if_utt_has_midnight(utt, when, self.threshold) if (when is None or when.time() == today.time()) and not is_midnight: r = self.get_response('query.for.when', validator=extract_datetime) if not r: self.speak_dialog("alarm.schedule.cancelled") return when_temp = extract_datetime(r) if when_temp is not None: when_temp = when_temp[0] # TODO add check for midnight # is_midnight = self._check_if_utt_has_midnight(r, when_temp, # self.threshold) when = when_temp if when is None \ else datetime(tzinfo=when.tzinfo, year=when.year, month=when.month, day=when.day, hour=when_temp.hour, minute=when_temp.minute) else: when = None # Verify time alarm_time = when confirmed_time = False while (not when or when == today) and not confirmed_time: if recur: t = nice_time(alarm_time, use_ampm=True) conf = self.ask_yesno('confirm.recurring.alarm', data={ 'time': t, 'recurrence': self._recur_desc(recur) }) else: t = nice_date_time(alarm_time, now=today, use_ampm=True) conf = self.ask_yesno('confirm.alarm', data={'time': t}) if not conf: return if conf == 'yes': when = [alarm_time] confirmed_time = True else: # check if a new (corrected) time was given when = extract_datetime(conf) if when is not None: when = when[0] if not when or when == today: # Not a confirmation and no date/time in statement, quit return alarm_time = when when = None # reverify alarm = {} if not recur: alarm_time_ts = to_utc(alarm_time).timestamp() now_ts = now_utc().timestamp() if alarm_time_ts > now_ts: alarm = self.set_alarm(alarm_time, name) else: if ('today' in utt) or ('tonight' in utt): self.speak_dialog('alarm.past') return else: # Set the alarm to find the next 24 hour time slot while alarm_time_ts < now_ts: alarm_time_ts += 86400.0 alarm_time = datetime.utcfromtimestamp(alarm_time_ts) alarm = self.set_alarm(alarm_time, name) else: alarm = self.set_alarm(alarm_time, name, repeat=recur) if not alarm: # none set, it was a duplicate return # Don't want to hide the animation self.enclosure.deactivate_mouth_events() if confirmed_time: self.speak_dialog("alarm.scheduled") else: t = self._describe(alarm) reltime = nice_relative_time(self.get_alarm_local(alarm)) if recur: self.speak_dialog("recurring.alarm.scheduled.for.time", data={ "time": t, "rel": reltime }) else: self.speak_dialog("alarm.scheduled.for.time", data={ "time": t, "rel": reltime }) self._show_alarm_anim(alarm_time) self.enclosure.activate_mouth_events() def _get_alarm_name(self, utt): """Get the alarm name using regex on an utterance.""" self.log.debug("Utterance being searched: " + utt) invalid_names = self.translate_list('invalid_names') rx_file = self.find_resource('name.rx', 'regex') if utt and rx_file: patterns = [] with open(rx_file) as f: patterns = [ p.strip() for p in f.readlines() if not p.strip().startswith('#') ] for pat in patterns: self.log.debug("Regex pattern: {}".format(pat)) res = re.search(pat, utt) if res: try: name = res.group("Name").strip() self.log.debug('Regex name extracted: '.format(name)) if (name and len(name.strip()) > 0 and name not in invalid_names): return name.lower() except IndexError: pass return '' def _check_if_utt_has_midnight(self, utt, init_time, threshold): matched = False if init_time is None: return matched if init_time.time() == datetime(1970, 1, 1, 0, 0, 0).time(): for word in self.translate_list('midnight'): matched = self._fuzzy_match(word, utt, threshold) if matched: return matched return matched @property def use_24hour(self): return self.config_core.get('time_format') == 'full' def _describe(self, alarm): if alarm["repeat_rule"]: # Describe repeating alarms if alarm["repeat_rule"].startswith( "FREQ=WEEKLY;INTERVAL=1;BYDAY="): days = alarm["repeat_rule"][29:] # e.g. "SU,WE" days = (days.replace("SU", "0").replace("MO", "1").replace( "TU", "2").replace("WE", "3").replace("TH", "4").replace( "FR", "5").replace("SA", "6").replace(",", " ")) # now "0 3" recur = set() for day in days.split(): recur.add(day) desc = self._recur_desc(recur) else: desc = self.translate('repeats') dt = self.get_alarm_local(alarm) dialog = 'recurring.alarm' if alarm["name"] is not "": dialog = dialog + '.named' return self.translate(dialog, data={ 'time': nice_time(dt, use_ampm=True), 'recurrence': desc, 'name': alarm["name"] }) else: dt = self.get_alarm_local(alarm) dt_string = nice_date_time(dt, now=now_local(), use_ampm=True) if alarm["name"]: return self.translate('alarm.named', data={ 'datetime': dt_string, 'name': alarm["name"] }) else: return dt_string @intent_handler( IntentBuilder("").require("Query").optionally("Next").require( "Alarm").optionally("Recurring")) def handle_status(self, message): utt = message.data.get("utterance") if not len(self.settings["alarm"]): self.speak_dialog("alarms.list.empty") return status, alarms = self._get_alarm_matches(utt, alarm=self.settings["alarm"], max_results=3, dialog='ask.which.alarm', is_response=False) total = None desc = [] if alarms: total = len(alarms) for alarm in alarms: desc.append(self._describe(alarm)) items_string = '' if desc: items_string = join_list(desc, self.translate('and')) if status == 'No Match Found': self.speak_dialog('alarm.not.found') elif status == 'User Cancelled': return elif status == 'Next': reltime = nice_relative_time(self.get_alarm_local(alarms[0])) self.speak_dialog("next.alarm", data={ "when": self._describe(alarms[0]), "duration": reltime }) else: if total == 1: reltime = nice_relative_time(self.get_alarm_local(alarms[0])) self.speak_dialog('alarms.list.single', data={ 'item': desc[0], 'duration': reltime }) else: self.speak_dialog('alarms.list.multi', data={ 'count': total, 'items': items_string }) def _get_alarm_matches(self, utt, alarm=None, max_results=1, dialog='ask.which.alarm', is_response=False): """Get list of alarms that match based on a user utterance. Arguments: utt (str): string spoken by the user alarm (list): list of alarm to match against max_results (int): max number of results desired dialog (str): name of dialog file used for disambiguation is_response (bool): is this being called by get_response Returns: (str): ["All", "Matched", "No Match Found", or "User Cancelled"] (list): list of matched alarm """ alarms = alarm or self.settings['alarm'] all_words = self.translate_list('all') next_words = self.translate_list('next') status = ["All", "Matched", "No Match Found", "User Cancelled", "Next"] # No alarms if alarms is None or len(alarms) == 0: self.log.error("Cannot get match. No active alarms.") return (status[2], None) # Extract Alarm Time when, utt_no_datetime = extract_datetime(utt) or (None, None) # Will return dt of unmatched string today = extract_datetime("today") today = today[0] # Check the time if it's midnight. This is to check if the user # said a recurring alarm with only the Day or if the user did # specify to set an alarm on midnight. If it's confirmed that # it's for a day only, then get another response from the user # to clarify what time on that day the recurring alarm is. is_midnight = self._check_if_utt_has_midnight(utt, when, self.threshold) if when == today and not is_midnight: when = None time_matches = None time_alarm = None if when: time_alarm = to_utc(when).timestamp() if is_midnight: time_alarm = time_alarm + 86400.0 time_matches = [ a for a in alarms if abs(a["timestamp"] - time_alarm) <= 60 ] # Extract Recurrence recur = None recurrence_matches = None for word in self.recurrence_dict: is_match = self._fuzzy_match(word, utt.lower(), self.threshold) if is_match: recur = self._create_day_set(utt) alarm_recur = self._create_recurring_alarm(when, recur) recurrence_matches = [ a for a in alarms if a["repeat_rule"] == alarm_recur["repeat_rule"] ] break utt = utt_no_datetime or utt # Extract Ordinal/Cardinal Numbers number = extract_number(utt, ordinals=True) if number and number > 0: number = int(number) else: number = None # Extract Name name_matches = [ a for a in alarms if a["name"] and self._fuzzy_match(a["name"], utt, self.threshold) ] # Match Everything alarm_to_match = None if when: if recur: alarm_to_match = alarm_recur else: alarm_to_match = {"timestamp": time_alarm, "repeat_rule": ""} # Find the Intersection of the Alarms list and all the matched alarms orig_count = len(alarms) if when and time_matches: alarms = [a for a in alarms if a in time_matches] if recur and recurrence_matches: alarms = [a for a in alarms if a in recurrence_matches] if name_matches: alarms = [a for a in alarms if a in name_matches] # Utterance refers to all alarms if utt and any(self._fuzzy_match(i, utt, 1) for i in all_words): return (status[0], alarms) # Utterance refers to the next alarm to go off elif utt and any(self._fuzzy_match(i, utt, 1) for i in next_words): return (status[4], [alarms[0]]) # Given something to match but no match found if ((number and number > len(alarms)) or (recur and not recurrence_matches) or (when and not time_matches)): return (status[2], None) # If number of alarms filtered were the same, assume user asked for # All alarms if (len(alarms) == orig_count and max_results > 1 and not number and not when and not recur): return (status[0], alarms) # Return immediately if there is ordinal if number and number <= len(alarms): return (status[1], [alarms[number - 1]]) # Return immediately if within maximum results elif alarms and len(alarms) <= max_results: return (status[1], alarms) # Ask for reply from user and iterate the function elif alarms and len(alarms) > max_results: desc = [] for alarm in alarms: desc.append(self._describe(alarm)) items_string = '' if desc: items_string = join_list(desc, self.translate('and')) reply = self.get_response(dialog, data={ 'number': len(alarms), 'list': items_string, }, num_retries=1) if reply: return self._get_alarm_matches(reply, alarm=alarms, max_results=max_results, dialog=dialog, is_response=True) else: return (status[3], None) # No matches found return (status[2], None) def _fuzzy_match(self, word, phrase, threshold): """ Search a phrase to another phrase using fuzzy_match. Matches on a per word basis, and will not match if word is a subword. Args: word (str): string to be searched on a phrase phrase (str): string to be matched against the word threshold (int): minimum fuzzy matching score to be considered a match Returns: (boolean): True if word is found in phrase. False if not. """ matched = False score = 0 phrase_split = phrase.split(' ') word_split_len = len(word.split(' ')) for i in range(len(phrase_split) - word_split_len, -1, -1): phrase_comp = ' '.join(phrase_split[i:i + word_split_len]) score_curr = fuzzy_match(phrase_comp, word.lower()) if score_curr > score and score_curr >= threshold: score = score_curr matched = True return matched @intent_handler( IntentBuilder("").require("Delete").require("Alarm").optionally( "Recurring").optionally("Recurrence")) def handle_delete(self, message): if self.has_expired_alarm(): self._stop_expired_alarm() return total = len(self.settings["alarm"]) if not total: self.speak_dialog("alarms.list.empty") return utt = message.data.get("utterance") or "" status, alarms = self._get_alarm_matches( utt, alarm=self.settings["alarm"], max_results=1, dialog='ask.which.alarm.delete', is_response=False) if alarms: total = len(alarms) else: total = None if total == 1: desc = self._describe(alarms[0]) recurring = ".recurring" if alarms[0]["repeat_rule"] else "" if self.ask_yesno('ask.cancel.desc.alarm' + recurring, data={'desc': desc}) == 'yes': del self.settings["alarm"][self.settings["alarm"].index( alarms[0])] self._schedule() self.speak_dialog("alarm.cancelled.desc" + recurring, data={'desc': desc}) return else: self.speak_dialog("alarm.delete.cancelled") # As the user did not confirm to delete # return True to skip all the remaining conditions return elif status in ['Next', 'All', 'Matched']: if self.ask_yesno('ask.cancel.alarm.plural', data={"count": total}) == 'yes': self.settings['alarm'] = [ a for a in self.settings['alarm'] if a not in alarms ] self._schedule() self.speak_dialog('alarm.cancelled.multi', data={"count": total}) return elif not total: # Failed to delete self.speak_dialog("alarm.not.found") return @intent_file_handler('snooze.intent') def snooze_alarm(self, message): if not self.has_expired_alarm(): return self.__end_beep() self.__end_flash() utt = message.data.get('utterance') or "" snooze_for = extract_number(utt) if not snooze_for or snooze_for < 1: snooze_for = 9 # default to 9 minutes # Snooze always applies the the first alarm in the sorted array alarm = self.settings["alarm"][0] dt = self.get_alarm_local(alarm) snooze = to_utc(dt) + timedelta(minutes=snooze_for) if "snooze" in alarm: # already snoozed original_time = alarm["snooze"] else: original_time = alarm["timestamp"] # Fill schedule with a snoozed entry -- 3 items: # snooze_expire_timestamp, repeat_rule, original_timestamp self.settings["alarm"][0] = { "timestamp": snooze.timestamp(), "repeat_rule": alarm["repeat_rule"], "name": alarm["name"], "snooze": original_time } self._schedule() @intent_file_handler('change.alarm.sound.intent') def handle_change_alarm(self, message): self.speak_dialog("alarm.change.sound") ########################################################################## # Audio and Device Feedback def converse(self, utterances, lang="en-us"): if self.has_expired_alarm(): if utterances and self.voc_match(utterances[0], "StopBeeping"): self._stop_expired_alarm() return True # and consume this phrase def stop(self, message=None): if self.has_expired_alarm(): self._stop_expired_alarm() return True # Stop signal handled no need to listen else: return False def _play_beep(self, message=None): """ Play alarm sound file """ now = now_local() if not self.beep_start_time: self.beep_start_time = now elif ((now - self.beep_start_time).total_seconds() > self.settings["max_alarm_secs"]): # alarm has been running long enough, auto-quiet it self.log.info("Automatically quieted alarm after 10 minutes") self._stop_expired_alarm() return # Validate user-selected alarm sound file alarm_file = join(abspath(dirname(__file__)), 'sounds', self.sound_name + ".mp3") if not os.path.isfile(alarm_file): # Couldn't find the required sound file self.sound_name = AlarmSkill.default_sound alarm_file = join(abspath(dirname(__file__)), 'sounds', self.sound_name + ".mp3") beep_duration = self.sounds[self.sound_name] repeat_interval = beep_duration + AlarmSkill.beep_gap next_beep = now + timedelta(seconds=repeat_interval) self.cancel_scheduled_event('Beep') self.schedule_event(self._play_beep, to_system(next_beep), name='Beep') # Increase volume each pass until fully on if self.saved_volume: if self.volume < 90: self.volume += 10 self.mixer.setvolume(self.volume) try: self.beep_process = play_mp3(alarm_file) except Exception: self.beep_process = None def _while_beeping(self, message): if self.flash_state < 3: if self.flash_state == 0: alarm_timestamp = message.data["alarm_time"] dt = self.get_alarm_local(timestamp=alarm_timestamp) self._render_time(dt) self.flash_state += 1 else: self.enclosure.mouth_reset() self.flash_state = 0 # Check if the WAV is still playing if self.beep_process: self.beep_process.poll() if self.beep_process.returncode: # The playback has ended self.beep_process = None def __end_beep(self): self.cancel_scheduled_event('Beep') self.beep_start_time = None if self.beep_process: try: if self.beep_process.poll() is None: # still running self.beep_process.kill() except Exception: pass self.beep_process = None self._restore_volume() self._restore_listen_beep() def _restore_listen_beep(self): if 'user_beep_setting' in self.settings: # Wipe from local config new_conf_values = {"confirm_listening": False} user_config = LocalConf(USER_CONFIG) if (self.settings["user_beep_setting"] is None and "confirm_listening" in user_config): del user_config["confirm_listening"] else: user_config.merge( {"confirm_listening": self.settings["user_beep_setting"]}) user_config.store() # Notify all processes to update their loaded configs self.bus.emit(Message('configuration.updated')) del self.settings["user_beep_setting"] def _stop_expired_alarm(self): if self.has_expired_alarm(): self.__end_beep() self.__end_flash() self.cancel_scheduled_event('NextAlarm') self._curate_alarms(0) # end any expired alarm self._schedule() return True else: return False def _restore_volume(self): # Return global volume to the appropriate level if we've messed with it if self.saved_volume: self.mixer.setvolume(self.saved_volume[0]) self.saved_volume = None def _disable_listen_beep(self): user_config = LocalConf(USER_CONFIG) if 'user_beep_setting' not in self.settings: # Save any current local config setting self.settings['user_beep_setting'] = (user_config.get( "confirm_listening", None)) # Disable in local config user_config.merge({"confirm_listening": False}) user_config.store() # Notify all processes to update their loaded configs self.bus.emit(Message('configuration.updated')) def _alarm_expired(self): self.sound_name = self.settings["sound"] # user-selected alarm sound if not self.sound_name or self.sound_name not in self.sounds: # invalid sound name, use the default self.sound_name = AlarmSkill.default_sound if self.settings['start_quiet'] and self.mixer: if not self.saved_volume: # don't overwrite if already saved! self.saved_volume = self.mixer.getvolume() self.volume = 0 # increase by 10% each pass else: self.saved_volume = None self._disable_listen_beep() self._play_beep() # Once a second Flash the alarm and auto-listen self.flash_state = 0 self.enclosure.deactivate_mouth_events() alarm = self.settings["alarm"][0] self.schedule_repeating_event(self._while_beeping, 0, 1, name='Flash', data={"alarm_time": alarm["timestamp"]}) def __end_flash(self): self.cancel_scheduled_event('Flash') self.enclosure.mouth_reset() self.enclosure.activate_mouth_events() def _show_alarm_anim(self, dt): # Animated confirmation of the alarm self.enclosure.mouth_reset() self._render_time(dt) time.sleep(2) self.enclosure.mouth_reset() # Show an animation # TODO: mouth_display_png() is choking images > 8x8 # (likely on the enclosure side) for i in range(1, 16): png = join(abspath(dirname(__file__)), "anim", "Alarm-" + str(int(i)) + "-1.png") # self.enclosure.mouth_display_png(png, x=0, y=0, refresh=False, # invert=True) png = join(abspath(dirname(__file__)), "anim", "Alarm-" + str(int(i)) + "-2.png") if i < 8: self.enclosure.mouth_display_png(png, x=8, y=0, refresh=False, invert=True) png = join(abspath(dirname(__file__)), "anim", "Alarm-" + str(int(i)) + "-3.png") self.enclosure.mouth_display_png(png, x=16, y=0, refresh=False, invert=True) png = join(abspath(dirname(__file__)), "anim", "Alarm-" + str(int(i)) + "-4.png") self.enclosure.mouth_display_png(png, x=24, y=0, refresh=False, invert=True) if i == 4: time.sleep(1) else: time.sleep(0.15) self.enclosure.mouth_reset() def _render_time(self, datetime): # Show the time in numbers "8:00 AM" timestr = nice_time(datetime, speech=False, use_ampm=True, use_24hour=self.use_24hour) x = 16 - ((len(timestr) * 4) // 2) # centers on display if not self.use_24hour: x += 1 # account for wider letters P and M, offset by the colon # draw on the display for ch in timestr: if ch == ":": png = "colon.png" w = 2 elif ch == " ": png = "blank.png" w = 2 elif ch == 'A' or ch == 'P' or ch == 'M': png = ch + ".png" w = 5 else: png = ch + ".png" w = 4 png = join(abspath(dirname(__file__)), "anim", png) self.enclosure.mouth_display_png(png, x=x, y=2, refresh=False) x += w