Exemple #1
0
class State(object):
    def __init__(self, bot):
        '''Config'''
        self.max_msg_line_length = 29
        self.max_reply_line_length = 28
        '''Initializations'''
        self.config = ConfigControls()
        self.mopidy = MopidyControls(self)
        self.mopidy.refresh_token()
        self.alarm = AlarmControls(self)
        self.cal_check = CalendarCheck(self)
        self.font = AsciiFont()
        self.bot = bot
        self.default_volume = self.get_default_control("volume")
        self.mopidy.set_volume_sys(self.default_volume)
        self.set_reply_text('')
        '''General variables'''
        self.state = 'music'
        self.dashboard_type = 'music'
        self.keyboard_type = 'home home'
        self.music_status = self.mopidy.get_status()
        self.last_dash_msg = [False]
        self.last_reply_msg = [False]
        self.chat_id_list = []
        self.chat_password = self.get_password()
        self.auto_refresh_enabled = True
        self.week_events = [[], [], [], [], [], [], []]
        self.set_alarm_day = 0
        self.snooze_counts = 0
        self.set_config_name = ' '
        self.alarm_to_edit = [['manual', 'disabled'],
                              datetime.time(hour=0, minute=0)]
        '''Search stored variables'''
        self.search_type = False
        self.search_query = False
        self.options_list = False
        self.uri_list = False
        self.uri = False

    def __call__(self, state=None):
        if state is None:
            return self.state
        else:
            self.state = state
            logger.info('State changed to ' + state)

    def get_password(self):
        '''Returns the password from the config file'''
        password = self.config.get_section('Password')
        if len(password) > 1:
            logger.warning('Only first chat password is accepted')
        return password[0].strip(' ')

    def get_default_control(self, control):
        '''Returns the desired default control from the config file'''
        text = self.config.get_section('Default Controls')
        for line in text:
            if control in line:
                default_control = line.split(" = ")[1]
                if default_control.isdigit():
                    default_control = int(default_control)
                return default_control
        return False

    def set_default_control(self, control, value):
        '''Changesthe desired default control in the config file'''
        text = self.config.get_section('Default Controls')
        new_text = []
        for line in text:
            if control in line:
                new_text.append(control + ' = ' + str(value))
            else:
                new_text.append(line)
        self.config.set_section('Default Controls', new_text)
        return True

    def dashboard(self):
        '''Selects a dashboard to output'''
        dashboard_type = self.dashboard_type
        if 'music' in dashboard_type:
            status = self.music_status
            if status.random:
                random_indicator = u'\U00002714 \U0001F500'
            else:
                random_indicator = u'\U0000274C \U0001F500'
            if status.repeat:
                repeat_indicator = u'\U00002714 \U0001F501'
            else:
                repeat_indicator = u'\U0000274C \U0001F501'
            if status.playing:
                play_indicator = u'\U000023F8'
            else:
                play_indicator = u'\U000025B6'
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text=u'\U000023EE',
                                         callback_data='/prev'),
                    InlineKeyboardButton(text=play_indicator,
                                         callback_data='/play'),
                    InlineKeyboardButton(text=u'\U000023ED',
                                         callback_data='/next')
                ],
                [
                    InlineKeyboardButton(text=u'\U000023EA',
                                         callback_data='/step_prev'),
                    InlineKeyboardButton(text=u'\U000023F9',
                                         callback_data='/stop'),
                    InlineKeyboardButton(text=u'\U000023E9',
                                         callback_data='/step_next')
                ],
                [
                    InlineKeyboardButton(text=random_indicator,
                                         callback_data='/random_mode'),
                    InlineKeyboardButton(text=u'\U0001F508 \U0001F53B',
                                         callback_data='/volume_down'),
                    InlineKeyboardButton(text=u'\U0001F50A \U0001F53A',
                                         callback_data='/volume_up'),
                    InlineKeyboardButton(text=repeat_indicator,
                                         callback_data='/repeat_mode')
                ],
            ])
        elif 'ring' in dashboard_type:
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text=u'\U0001F634' + '/snooze' +
                                         u'\U0001F634',
                                         callback_data='/snooze')
                ],
            ])
        elif 'snooze' in dashboard_type:
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text='refresh time counter',
                                         callback_data='/time')
                ],
            ])
        elif 'alarm view' in dashboard_type:
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:0'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:1'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:2'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:3'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:4'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:5'),
                    InlineKeyboardButton(text=u'\U0001F527',
                                         callback_data='/set_alarm:6'),
                    InlineKeyboardButton(text=u'\U00002699',
                                         callback_data='/change_config'),
                ],
            ])
        elif 'alarm config' in dashboard_type:
            inline_keyboard_array = []
            config_list = self.alarm.get_config()
            for name in config_list._fields:
                if not 'ring_music' in name:
                    value = getattr(config_list, name)
                    units = self.alarm.get_config_units(name)
                    if isinstance(value, int):
                        value = str(value)
                    button_text = u'\U00002699' + name + ' = ' + value + units
                    inline_keyboard_array.append([
                        InlineKeyboardButton(text=button_text,
                                             callback_data=name)
                    ])
            keyboard = InlineKeyboardMarkup(
                inline_keyboard=inline_keyboard_array)
        elif 'general_config' in dashboard_type:
            keyboard = InlineKeyboardMarkup(inline_keyboard=[
                [
                    InlineKeyboardButton(text='Password',
                                         callback_data='set_password')
                ],
                [
                    InlineKeyboardButton(text='Add calendar',
                                         callback_data='add_calendard')
                ],
                [
                    InlineKeyboardButton(text='Remove calendar',
                                         callback_data='remove_calendar')
                ],
                [
                    InlineKeyboardButton(text='Default Volume = ' +
                                         str(self.default_volume),
                                         callback_data='default_volume')
                ],
            ])
        else:
            keyboard = False
        return keyboard

    def keyboard(self):
        '''Selects a reply keyboard to output'''
        keyboard_type = self.keyboard_type
        if 'home home' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text='menu')],
                ],
                resize_keyboard=True,
                #on_time_keyboard = True, (not supported by current telepot version)
            )
        elif 'home menu' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text=u'\U000023EF' + 'music')],
                    [KeyboardButton(text=u'\U000023F0' + 'alarm')],
                    [KeyboardButton(text=u'\U0001F529' + 'configuration')],
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'music' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [
                        KeyboardButton(text=u'\U0001F3A7 \U0001F4DD' +
                                       'playlists')
                    ],
                    [KeyboardButton(text=u'\U0001F50E' + 'search')],
                    [KeyboardButton(text=u'\U0001F534' + 'youtube help')],
                    [
                        KeyboardButton(text=u'\U0001F4FB' +
                                       'spotify device not found?')
                    ],
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'search type' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text=u'\U0000270F \U0001F3B5' + 'track')],
                    [KeyboardButton(text=u'\U0001F919 \U0001F399' + 'artist')],
                    [KeyboardButton(text=u'\U0001F3B6 \U0001F4BF' + 'album')],
                    [
                        KeyboardButton(text=u'\U0001F3A7 \U0001F4DD' +
                                       'playlist')
                    ],
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'options list' in keyboard_type:
            if self.options_list:
                keyboard_buttons_list = []
                for option in self.options_list:
                    keyboard_buttons_list.append([KeyboardButton(text=option)])
                keyboard_buttons_list.append(
                    [KeyboardButton(text=u'\U0001F519' + '/return')])
                keyboard = ReplyKeyboardMarkup(
                    keyboard=keyboard_buttons_list,
                    resize_keyboard=False,
                )
            else:
                logger.warning('Options_list is empty')
                keyboard = ReplyKeyboardMarkup(
                    keyboard=[
                        [KeyboardButton(text=u'\U0001F519' + '/return')],
                    ],
                    resize_keyboard=True,
                )
        elif 'action' in keyboard_type:
            if self.uri:
                action_list = [u'\U000023E9' + 'play', u'\U000023CF' + 'add']
                if ('artist' in self.uri or 'playlist' in self.uri
                        or 'album' in self.uri):
                    action_list.append(u'\U0001F500' + 'shuffle')
                    action_list.append(u'\U0000270F \U0001F3B5' + 'tracks')
                if 'artist' in self.uri:
                    action_list.append(u'\U0001F3B6 \U0001F4BF' + 'albums')
                if 'playlist' in self.uri:
                    action_list.append(u'\U0001F634' + 'wake up sound' +
                                       u'\U0001F918')
                keyboard_buttons_list = []
                for option in action_list:
                    keyboard_buttons_list.append([KeyboardButton(text=option)])
                keyboard_buttons_list.append(
                    [KeyboardButton(text=u'\U0001F519' + '/return')])
                keyboard = ReplyKeyboardMarkup(
                    keyboard=keyboard_buttons_list,
                    resize_keyboard=False,
                )
            else:
                keyboard = ReplyKeyboardMarkup(
                    keyboard=[
                        [KeyboardButton(text=u'\U0001F519' + '/return')],
                    ],
                    resize_keyboard=True,
                )
        elif 'alarm menu' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text=u'\U000023F1' + 'set_alarm')],
                    [KeyboardButton(text=u'\U000023F3' + 'temporizer')],
                    [
                        KeyboardButton(text=u'\U0001F501' +
                                       'refresh auto alarms')
                    ],
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'alarm set on_day' in keyboard_type:
            keyboard_buttons_list = []
            for event in self.week_events[self.set_alarm_day]:
                event = (event - datetime.timedelta(
                    minutes=int(self.alarm.c.other_event_time_before)))
                keyboard_buttons_list.append([
                    KeyboardButton(text=event.strftime('set ' + u'\U0001F5D3' +
                                                       u'\U0001F519' +
                                                       "-%H:%M"))
                ])
            week_alarms = self.alarm.get_week_alarms_localtime()
            _type = 0
            _time = 1
            for alarm_info in week_alarms[self.set_alarm_day]:
                if 'auto' in alarm_info[_type][0]:
                    a_type0 = u'\U0001F3E7 '
                else:
                    a_type0 = u'\U000024C2 '
                if 'disabled' in alarm_info[_type][1]:
                    a_type1 = u'\U0001F4F4 '
                else:
                    a_type1 = u'\U0001F51B '
                keyboard_buttons_list.append([
                    KeyboardButton(
                        text=alarm_info[_time].strftime("edit " + a_type0 +
                                                        a_type1 +
                                                        u'\U000023F0' +
                                                        "-%H:%M"))
                ])
            keyboard_buttons_list.append([
                KeyboardButton(text=u'\U000023F1' + 'add new' + u'\U0001F195')
            ])
            keyboard_buttons_list.append(
                [KeyboardButton(text=u'\U0001F519' + '/return')])
            keyboard = ReplyKeyboardMarkup(
                keyboard=keyboard_buttons_list,
                resize_keyboard=False,
            )
        elif 'alarm edit' in keyboard_type:
            if 'enabled' in self.alarm_to_edit[0][1]:
                toggle_text = u'\U0001F4F4' + 'disable'
            else:
                toggle_text = u'\U0001F51B' + 'enable'
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text=toggle_text)],
                    [KeyboardButton(text=u'\U0001F527' + 'change')],
                    [KeyboardButton(text=u'\U00002620' + 'delete')],
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'ringing' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [
                        KeyboardButton(text=u'\U0001F634' + '/snooze' +
                                       u'\U0001F634')
                    ],
                ],
                resize_keyboard=True,
            )
        elif 'return' in keyboard_type:
            keyboard = ReplyKeyboardMarkup(
                keyboard=[
                    [KeyboardButton(text=u'\U0001F519' + '/return')],
                ],
                resize_keyboard=True,
            )
        elif 'nothing' in keyboard_type:
            keyboard = ReplyKeyboardRemove(remove_keyboard=True)
        else:
            keyboard = False
        return keyboard

    def refresh_dashboard(self):
        '''Deletes last message and sends a new one'''
        msg_text = self.get_msg_text()
        tries = 0
        chat_count = 0
        for chat_id in self.chat_id_list:
            '''Sends the message with dashboard and the message with custom keyboard
               to every allowed chat id
               '''
            while tries < 2:
                try:
                    dashboard = self.dashboard()
                    keyboard = self.keyboard()
                    dash_msg_id = self.bot.sendMessage(chat_id,
                                                       msg_text,
                                                       parse_mode='Markdown',
                                                       reply_markup=dashboard)
                    reply_msg_id = self.bot.sendMessage(chat_id,
                                                        self.reply_text,
                                                        parse_mode='Markdown',
                                                        reply_markup=keyboard)
                    self.delete_last_msg(self.bot, chat_count)
                    self.set_reply_text('')
                    self.store_dash_msg(
                        chat_count, telepot.message_identifier(dash_msg_id))
                    self.store_reply_msg(
                        chat_count, telepot.message_identifier(reply_msg_id))
                    logger.info('Refresh')
                    break
                except Exception as error:
                    tries = tries + 1
                    logger.error("Could not refresh: {}\n {}".format(
                        sys._getframe().f_code.co_name, error))
            chat_count += 1
        return

    def get_msg_text(self):
        if 'music' in self.state:
            self.music_status = self.mopidy.get_status()
            status = self.music_status
            if status.playing:
                playing_indicator = u'\U0001F918'
            else:
                playing_indicator = u'\U0000270B'
            if len(status.song) > self.max_msg_line_length:
                status.song = ''.join(
                    [status.song[:self.max_msg_line_length - 3], '...'])
            if len(status.artist) > self.max_msg_line_length:
                status.artist = ''.join(
                    [status.artist[:self.max_msg_line_length - 2], '...'])
            msg_text = ''.join([
                '`' + (self.max_msg_line_length - 2) * '-' + '`' + '\n',
                playing_indicator, '*', status.song, '*', playing_indicator,
                '\n', u'\U0001F3A4', '_', status.artist, '_', u'\U0001F3B8',
                '\n', u'\U0001F3B6', '`', status.song_number, '` ',
                u'\U000023F3', '_', status.song_time, '_ ', u'\U0001F509', '`',
                status.volume, '`'
            ])
            self.dashboard_type = 'music'
        elif 'alarm' in self.state:
            if 'change_config' in self.state:
                text = 'Alarm Configuration'
                msg_text = (
                    '`' + int(
                        (self.max_reply_line_length - len(text)) / 2) * '-' +
                    text + int(
                        (self.max_reply_line_length + 1 - len(text)) / 2) * '-'
                    + '`')
                self.dashboard_type = 'alarm config'
            else:
                msg_text = self.alarm.build_msg_text_from_alarms()
                self.dashboard_type = 'alarm view'
        elif 'ringing' in self.state:
            now = self.alarm.to_localtime(datetime.datetime.utcnow())
            hour = now.strftime("%H")
            minu = now.strftime("%M")
            msg_list = []
            hour0 = self.font(int(hour[0]))
            hour1 = self.font(int(hour[1]))
            for index in range(0, len(hour0)):
                msg_list.append('`' + '  ' + hour0[index] + ' ' +
                                hour1[index] + '`')
            msg_list.append(' ')
            minu0 = self.font(int(minu[0]))
            minu1 = self.font(int(minu[1]))
            point = ['  ', '  ', '# ', '  ', '# ', '  ', '  ']
            for index in range(0, len(minu0)):
                msg_list.append('`' + point[index] + minu0[index] + ' ' +
                                minu1[index] + '`')
            msg_text = '\n'.join(msg_list)
            self.dashboard_type = 'ring'
        elif 'snooze' in self.state:
            t = self.alarm.alarm_check()
            seconds = str(int(t % 60))
            if len(seconds) < 2:
                seconds = '0' + seconds
            minutes = str(int(t / 60) % 60)
            if len(minutes) < 2:
                minutes = '0' + minutes
            msg_text = (u'\U0001F634' + u'\U0001F634' + u'\U000023F3' +
                        u'\U000027A1' + '` `' + minutes[0] + u'\U000020E3' +
                        minutes[1] + u'\U000020E3' + '*:*' + seconds[0] +
                        u'\U000020E3' + seconds[1] + u'\U000020E3' +
                        u'\U0001F634' + u'\U0001F634')
            self.dashboard_type = 'snooze'
        elif 'general_config' in self.state:
            text = 'General Configuration'
            msg_text = (
                '`' + int((self.max_reply_line_length - len(text)) / 2) * '-' +
                text + int(
                    (self.max_reply_line_length + 1 - len(text)) / 2) * '-' +
                '`')
            self.dashboard_type = 'general_config'
        else:
            msg_text = '***WIP***'
            self.dashboard_type = ' '
        return msg_text

    def set_reply_text(self, text=None, dash_join=True):
        if text is None:
            return self.reply_text
        else:
            if dash_join:
                text = '-'.join(text.strip(' ').split(' '))
            else:
                text = ' '.join(text.strip(' ').split(' '))
            self.reply_text = (
                '`' + int((self.max_reply_line_length - len(text)) / 2) * '-' +
                text + int(
                    (self.max_reply_line_length + 1 - len(text)) / 2) * '-' +
                '`')

    def auto_refresh_dashboard(self):
        ''' Process performed in parallel with the 'mpc idle' command
            that waits until something changes in the music player
            '''
        while True:
            try:
                subprocess.check_output(['mpc', 'idle'])
                if self.auto_refresh_enabled:
                    logger.info('Auto')
                    self.refresh_dashboard()
                else:
                    logger.info('Auto not enabled')
            except Exception as error:
                logger.warning('Mopidy not enabled \n' + error)
                self.send_chat_action('upload_audio')
                sleep(2)  # HUGE BLOCK

    def auto_refresh(self, enabled=None):
        '''Enables or disables auto refresh process'''
        if enabled is None:
            return self.auto_refresh_enabled
        else:
            self.auto_refresh_enabled = enabled

    def chat_ids(self, new_chat_id=None, command=None):
        '''Returns the chat ids allowed or adds a new chat id allowed
           if the password given is correct
           '''
        if new_chat_id is None:
            return self.chat_id_list
        else:
            is_new = True
            for chat_id in self.chat_id_list:
                if new_chat_id == chat_id:
                    if command is not None:
                        if '/exit' in command:
                            self.chat_id_list.remove(new_chat_id)
                            self.bot.sendMessage(new_chat_id, 'Goodbye')
                            logger.info('Allowed chat ids:')
                            logger.debug(self.chat_id_list)
                            return False
                        else:
                            is_new = False
                    else:
                        is_new = False
            if is_new:
                if self.chat_password in command:
                    self.chat_id_list.append(new_chat_id)
                    logger.info('Allowed chat ids:')
                    logger.debug(self.chat_id_list)
                    self.set_reply_text('Welcome')
                    self.refresh_dashboard()
                else:
                    self.bot.sendMessage(new_chat_id, 'Type correct password')
                return False
            else:
                return True

    def store_dash_msg(self, chat_count, msg=None):
        '''Returns and stores the last message id to
           delete the message afterwards
           '''
        if len(self.last_dash_msg) > chat_count:
            if msg is None:
                return self.last_dash_msg[chat_count]
            else:
                self.last_dash_msg[chat_count] = msg
        elif len(self.last_dash_msg) == chat_count:
            if msg is None:
                return False
            else:
                self.last_dash_msg.append(msg)
        else:
            logger.error("Store last msg: {}".format(
                sys._getframe().f_code.co_name))
            return False

    def store_reply_msg(self, chat_count, msg=None):
        '''Returns and stores the last message id to
           delete the message afterwards
           '''
        if len(self.last_reply_msg) > chat_count:
            if msg is None:
                return self.last_reply_msg[chat_count]
            else:
                self.last_reply_msg[chat_count] = msg
        elif len(self.last_reply_msg) == chat_count:
            if msg is None:
                return False
            else:
                self.last_reply_msg.append(msg)
        else:
            logger.error("Store last msg: {}".format(
                sys._getframe().f_code.co_name))
            return False

    def delete_last_msg(self, bot, chat_count):
        '''Deletes last message sent'''
        try:
            bot.deleteMessage(self.last_dash_msg[chat_count])
            bot.deleteMessage(self.last_reply_msg[chat_count])
        except Exception:
            logger.warning('First msg?')

    def send_msg(self, message):
        for chat_id in self.chat_id_list:
            self.bot.sendMessage(chat_id, message)

    def send_chat_action(self, action):
        '''Valid actions: upload_audio, typing
           '''
        for chat_id in self.chat_id_list:
            self.bot.sendChatAction(chat_id, action)
Exemple #2
0
class AlarmControls:
    def __init__(self, state):
        self.state = state
        self.cal_check = CalendarCheck(self.state)
        self.config = ConfigControls()
        '''Config'''
        self.c = self.get_config()
        self.RING_TIME_THRESHOLD = 15  #[seconds]

    def ring(self):
        '''Plays the alarm music'''
        self.c = self.get_config(
        )  # probably is better to refresh config in main
        while self.state.auto_refresh_enabled:
            self.state.auto_refresh_enabled = False
        self.state.music_status.mopidy_playing = True
        self.state.mopidy.clear()
        self.state.mopidy.repeat('on')
        self.state.mopidy.random('on')
        if not self.state.mopidy.load(self.c.ring_music, 'uri'):
            logger.warning('Local file loaded for ringing')
            self.state.mopidy.load_local_song()
        self.state.mopidy.shuffle()
        self.state.mopidy.play()
        self.state.mopidy.set_volume_sys(int(self.c.ring_start_volume))
        self.state('ringing')
        self.state.keyboard_type = 'ringing'
        self.state.refresh_dashboard()

    def set_ring_volume(self, ringing_time):
        '''Sets the ring volume according to two linear relationships, one for the first
           half volume augment and the other until the maximum ringing volume'''
        if ringing_time <= self.c.half_ring_volume_sec:
            vol_slope = ((
                (self.c.max_ringing_volume - self.c.ring_start_volume) / 2) /
                         self.c.half_ring_volume_sec)
            volume = int(self.c.ring_start_volume + vol_slope * ringing_time)
        elif ringing_time <= self.c.max_ring_volume_sec:
            vol_slope = (
                ((self.c.max_ringing_volume - self.c.ring_start_volume) / 2) /
                (self.c.max_ring_volume_sec - self.c.half_ring_volume_sec))
            volume = int(
                (self.c.max_ringing_volume + self.c.ring_start_volume) / 2 +
                vol_slope * (ringing_time - self.c.half_ring_volume_sec))
        else:
            volume = int(self.c.max_ringing_volume)
        volume = min(volume, int(self.c.max_ringing_volume))
        self.state.mopidy.set_volume_sys(volume)

    def get_alarms(self):
        '''Gets the alarm string array from the saved file'''
        file = open(os.path.join(self.config.path, "saved_alarms.py"), "rb")
        alarms = pickle.load(file)
        file.close()
        return alarms

    def get_config(self):
        try:
            text_config = self.config.get_section('Alarm Controls')
            config_name_list = []
            for line in text_config:
                config_name_list.append(
                    line.split('=')[0].split('(')[0].strip())
            config_list = namedtuple('config_list', ' '.join(config_name_list))
            for line in text_config:
                for config_name in config_list._fields:
                    if config_name in line:
                        config_value = line.split(' = ')[1].strip()
                        if config_value.isdigit():
                            config_value = int(config_value)
                        setattr(config_list, config_name, config_value)
            return config_list
        except:
            # Put here the default control values
            logger.error(
                "Bad AlarmControls config, default config applied: {}".format(
                    sys._getframe().f_code.co_name))
            return config_list(
                10, 30, 70, 14, 35, 30, 60, 10, 6, 10, "I am awake",
                'spotify:user:2155eprgg73n7x46ybpzpycra:playlist:64xSAgfPBl8HjIKJBi3CIi'
            )

    def get_config_units(self, config_name):
        if 'time' in config_name or 'min' in config_name:
            units = '(minutes)'
        elif 'sec' in config_name:
            units = '(seconds)'
        elif 'volume' in config_name:
            units = '(%)'
        elif 'counts' in config_name:
            units = '(-)'
        else:
            units = ''
        return units

    def set_alarms(self, alarms):
        '''Saves the alarm string array to the file'''
        file = open(os.path.join(self.config.path, "saved_alarms.py"), "wb")
        pickle.dump(alarms, file)
        file.close()
        return

    def set_config(self, config_name, config_value):
        '''Saves the config parameter in the text file and checks the value'''
        if 'ring_music' in config_name:
            if config_value.isdigit():
                return 'Bad URI format'
        elif 'alarm_stop_password' in config_name:
            if len(config_value) > 100:
                return "Don't be silly"
            elif '/' in config_value:
                return 'invalid key "/"'
        else:
            if not config_value.strip().isdigit():
                return 'Must be a positive number'
            else:
                if ('snooze_min' in config_name
                        or 'max_snooze_counts' in config_name):
                    if int(config_value) == 0:
                        return 'Must be greater than 0'
                    if 'snooze_min' in config_name:
                        if int(config_value) > 30:
                            return 'Snooze up to 30 min'
                    elif 'max_snooze_counts' in config_name:
                        if int(config_value) > 1000:
                            return "Don't be silly"
                elif ('ring_start_volume' in config_name
                      or 'max_ringing_volume' in config_name):
                    if int(config_value) > 100:
                        return 'Up to 100%'
                    if 'ring_start_volume' in config_name:
                        if int(config_value) > int(self.c.max_ringing_volume):
                            return "Start Vol(<)Max Vol"
                    elif 'max_ringing_volume' in config_name:
                        if int(config_value) < int(self.c.ring_start_volume):
                            return "Max Vol(>)Start Vol"
                elif 'max_ringing_time' in config_name:
                    if int(config_value) < 5 or int(config_value) > 60:
                        return 'Between 5 and 60'
                elif 'event_time_before' in config_name:
                    #first_event_time_before and other_event_time_before
                    if int(config_value) > 360:
                        return 'Up to 6 hours before'
                elif 'ring_volume_sec' in config_name:
                    #max_ring_volume_time and half_ring_volume_time
                    if int(config_value) > 180:
                        return 'Up to 3 minutes'
                    elif 'max' in config_name:
                        if int(config_value) < int(
                                self.c.half_ring_volume_sec):
                            return 'Max Time(>)Half Time'
                    elif 'half' in config_name:
                        if int(config_value) > int(self.c.max_ring_volume_sec):
                            return 'Half Time(<)Max Time'
                elif 'ring_refresh_sec' in config_name:
                    if int(config_value) > 60 or int(config_value < 5):
                        return 'Betweeen 5 and 60 seconds'
                else:
                    return 'Config name not recognized!'
        unit = self.get_config_units(config_name)
        if unit == '':
            setattr(self.c, config_name, str(config_value))
        else:
            setattr(self.c, config_name, int(config_value))
        config_list = []
        for name in self.c._fields:
            value = getattr(self.c, name)
            units = self.get_config_units(name)
            if isinstance(value, int):
                value = str(value)
            config_list.append(name + ' ' + units + ' = ' + value)
        self.config.set_section('Alarm Controls', config_list)
        return 'Your wishes are orders'

    def is_config(self, config_name):
        '''Says if a name is a configuration name'''
        for name in self.c._fields:
            if config_name in name:
                return True
        return False

    def config_options_list(self, config_name):
        options_list = []
        if 'ring_music' in config_name:
            return False
        else:
            if 'snooze_min' in config_name:
                options_list = list(range(5, 35, 5))
            elif 'max_snooze_counts' in config_name:
                options_list = list(range(1, 11, 1))
            elif 'ring_start_volume' in config_name:
                options_list = list(range(0, int(self.c.max_ringing_volume),
                                          5))
            elif 'max_ringing_volume' in config_name:
                options_list = list(
                    range(int(self.c.ring_start_volume), 105, 5))
            elif 'max_ringing_time' in config_name:
                options_list = list(range(5, 35, 5))
            elif 'event_time_before' in config_name:
                #first_event_time_before and other_event_time_before
                options_list = list(range(0, 135, 15))
            elif 'alarm_stop_password' in config_name:
                options_list = ["I am awake"]
            elif 'half_ring_volume_sec' in config_name:
                options_list = list(
                    range(0, int(self.c.max_ring_volume_sec), 2))
            elif 'max_ring_volume_sec' in config_name:
                options_list = list(
                    range(int(self.c.half_ring_volume_sec), 60, 5))
            elif 'ring_refresh_sec' in config_name:
                options_list = list(range(10, 70, 10))
            else:
                return False
        return options_list

    def temporizer_options_list(self):
        temp_list = list(range(5, 30, 5))
        temp_list.extend(list(range(30, 75, 15)))
        return temp_list

    def alarm_str2time(self, alarm_str):
        ''' Format the alarm string to time '''
        separator = alarm_str.find('-')
        if separator >= 0:
            alarm_type = alarm_str[:separator]
            alarm_type = alarm_type.split(' ')
            alarm_time = alarm_str[(separator + 1):]
            alarm_time = datetime.datetime.strptime(alarm_time, '%c')
            return [alarm_type, alarm_time]
        else:
            logging.error("Bad alarm format: {} ".format(
                sys._getframe().f_code.co_name))
            return [False, False]

    def alarm_time2str(self, alarm_type, alarm_time):
        ''' Format the alarm time to string '''
        alarm_type_str = ' '.join(alarm_type)
        alarm_time_str = alarm_time.strftime('%c')
        alarm_str = '-'.join([alarm_type_str, alarm_time_str])
        return alarm_str

    def to_localtime(self, d):
        '''Localtime defined by the raspberry localtime'''
        utcoffset = datetime.datetime.now(
            datetime.timezone.utc).astimezone().tzinfo.utcoffset(None)
        if d is not None:
            return d + utcoffset
        else:
            return None

    def to_utc(self, d):
        utcoffset = datetime.datetime.now(
            datetime.timezone.utc).astimezone().tzinfo.utcoffset(None)
        if d is not None:
            return d - utcoffset
        else:
            return None

    def set_auto_alarms(self, week_events):
        '''Sets the alarms acording to the first event of the day'''
        alarms = self.get_alarms()
        now_local = self.to_localtime(datetime.datetime.utcnow())
        today = now_local.date()
        new_alarms = []
        first_event_time_before = datetime.timedelta(
            minutes=int(self.c.first_event_time_before))
        auto_alarm_disabled = [False, False, False, False, False, False, False]
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            if alarm_time.date() - today >= datetime.timedelta(0):
                if 'auto' in alarm_type[0]:
                    if 'enabled' in alarm_type[1]:
                        pass  # We delete enabled auto alarms and update them later
                    else:  #We mark the disabled auto alarms to chekc update
                        days_to_go = (self.to_localtime(alarm_time).weekday() -
                                      today.weekday())
                        if days_to_go < 0:
                            days_to_go += 7
                        auto_alarm_disabled[days_to_go] = alarm_time
                else:
                    # We keep all manual alarms not outdated
                    new_alarm = self.alarm_time2str(alarm_type, alarm_time)
                    new_alarms.append(new_alarm)
            else:
                pass  #if the alarm is outdated we delete it
        first_events_localtime = self.cal_check.get_day_first_events_localtime(
            week_events)
        first_events = [self.to_utc(item) for item in first_events_localtime]
        for day, event in enumerate(first_events):
            if event is not None:  # if there is an event we update the alarm
                if auto_alarm_disabled[day]:
                    if event - first_event_time_before < auto_alarm_disabled[
                            day]:
                        # if the event is earlier than the diabled event we enable it
                        new_alarm = self.alarm_time2str(
                            ['auto', 'enabled'],
                            event - first_event_time_before)
                        new_alarms.append(new_alarm)
                    else:
                        # if the event is later than the disabled event we keep it disabled
                        new_alarm = self.alarm_time2str(
                            ['auto', 'disabled'],
                            event - first_event_time_before)
                        new_alarms.append(new_alarm)
                else:  # if the event was enabled or the event is new we keep it enabled
                    new_alarm = self.alarm_time2str(
                        ['auto', 'enabled'], event - first_event_time_before)
                    new_alarms.append(new_alarm)
            else:  #if there is no event now we delete the auto alarm
                pass
        new_alarms = list(set(new_alarms))  #This removes the duplicates
        self.set_alarms(new_alarms)
        return True

    def build_temporizer_timedelta(self, temp_minutes):
        now = datetime.datetime.utcnow()
        return datetime.timedelta(0,
                                  hours=now.hour,
                                  minutes=now.minute + temp_minutes)

    def set_manual_alarm(self, set_datetime):
        '''Sets a manual alarm some days after at a desired time
           set_datetime = datetime.timedelta(day_increase,set_time)
           '''
        today = datetime.datetime.utcnow().date()
        new_alarm = (datetime.datetime.combine(today, datetime.time(0)) +
                     set_datetime)
        repeated = False
        alarms = self.get_alarms()
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            if abs((alarm_time - new_alarm).total_seconds()) <= 60:
                repeated = True
        if not repeated:
            new_alarm_str = self.alarm_time2str(['manual', 'enabled'],
                                                new_alarm)
            alarms.append(new_alarm_str)
            self.set_alarms(alarms)
            return
        else:
            return

    def set_manual_alarm_localtime(self, set_datetime):
        '''Sets a manual alarm some days after at a desired time on the local
           timezone
           set_datetime = datetime.timedelta(day_increase,set_time)
           '''
        utcoffset = datetime.datetime.now(
            datetime.timezone.utc).astimezone().tzinfo.utcoffset(None)
        today = (datetime.datetime.utcnow() + utcoffset).date()
        new_alarm = (datetime.datetime.combine(today, datetime.time(0)) +
                     set_datetime - utcoffset)
        repeated = False
        alarms = self.get_alarms()
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            if abs((alarm_time - new_alarm).total_seconds()) <= 60:
                repeated = True
        if not repeated:
            new_alarm_str = self.alarm_time2str(['manual', 'enabled'],
                                                new_alarm)
            alarms.append(new_alarm_str)
            self.set_alarms(alarms)
            return
        else:
            return

    def alarm_check(self):
        '''Checks if an alarm time has been reached and rings acordingly'''
        alarms = self.get_alarms()
        now = datetime.datetime.utcnow()
        min_time_to_ring = 24 * 60 * 60
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            ''' Check only enabled alarms '''
            if 'enabled' in alarm_type[1]:
                time_to_ring = (alarm_time - now).total_seconds()
                if (time_to_ring <= self.RING_TIME_THRESHOLD
                        and (now - alarm_time).total_seconds() <= 20 * 60):
                    self.ring()
                    return 0
                if time_to_ring < 0:
                    time_to_ring = min_time_to_ring
                min_time_to_ring = min(min_time_to_ring, time_to_ring)
        return min_time_to_ring

    def edit_closest_alarm(self, action):
        '''Edits the closest alarm acording to the input action,
           useful for disable the alarm that is ringing now or activate snooze mode
           '''
        alarms = self.get_alarms()
        now = datetime.datetime.utcnow()
        success = False
        for index, alarm in enumerate(alarms):
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            ''' Check only enabled alarms '''
            if 'enabled' in alarm_type[1]:
                if 'disable' in action:
                    if ((alarm_time - now).total_seconds() <= 11 * 60
                            and (now - alarm_time).total_seconds() <= 20 * 60):
                        alarm_type[1] = 'disabled'
                        success = True
                        logger.info('Alarm disabled')
                elif 'snooze' in action:
                    if ((alarm_time - now).total_seconds() <= 60
                            and (now - alarm_time).total_seconds() <= 20 * 60):
                        if 'manual' in alarm_type[0]:
                            alarm_time = alarm_time + datetime.timedelta(
                                minutes=self.c.snooze_min)
                            logger.info('Manual alarm snooze')
                        else:  # Disable auto alarm and set manual alarm
                            alarm_type[1] = 'disabled'
                            new_alarm_time = alarm_time + datetime.timedelta(
                                minutes=self.c.snooze_min)
                            new_alarm_type = ['manual', 'enabled']
                            alarms.append(
                                self.alarm_time2str(new_alarm_type,
                                                    new_alarm_time))
                            logger.info('Auto alarm snooze')
                        success = True
                alarms[index] = self.alarm_time2str(alarm_type, alarm_time)
        self.set_alarms(alarms)
        return success

    def edit_alarm(self, days_to_go, alarm_time, action, change_time=None):
        '''Edits an alarm configuration acording to the input action'''
        today = self.to_localtime(datetime.datetime.utcnow())
        edit_alarm_time = datetime.datetime.combine(
            today + datetime.timedelta(days=days_to_go), alarm_time)
        alarms = self.get_alarms()
        new_alarms = []
        success = False
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            if abs((alarm_time - edit_alarm_time).total_seconds()) < 60:
                if 'toggle' in action:
                    if 'disabled' in alarm_type[1]:
                        alarm_type[1] = 'enabled'
                    else:
                        alarm_type[1] = 'disabled'
                elif 'change' in action:
                    if change_time is None:
                        logger.error("Bad imput: {}".format(
                            sys._getframe().f_code.co_name))
                        success = False
                        return success
                    else:
                        if 'manual' in alarm_type[0]:
                            alarm_time = datetime.datetime.combine(
                                today + datetime.timedelta(days=days_to_go),
                                change_time)
                        else:  #When changing auto alarms, disable auto and create new manual
                            alarm_type[1] = 'disabled'
                            new_alarm_time = datetime.datetime.combine(
                                today + datetime.timedelta(days=days_to_go),
                                change_time)
                            new_alarms.append(
                                self.alarm_time2str(['manual', 'enabled'],
                                                    new_alarm_time))
                if not 'delete' in action:
                    new_alarms.append(
                        self.alarm_time2str(alarm_type, alarm_time))
                success = True
            else:
                new_alarms.append(self.alarm_time2str(alarm_type, alarm_time))
        self.set_alarms(new_alarms)
        return success

    def get_week_alarms_localtime(self):
        '''Gets a list of alarms ordered by day, and time,
           today alarms are on week_alarms[0], today first alarm is on
           week_alarms[0][0], tomorrow is on week_alarms[1], etc.
           '''
        today = self.to_localtime(datetime.datetime.utcnow())
        alarms = self.get_alarms()
        week_alarms = [[], [], [], [], [], [], []]
        for alarm in alarms:
            [alarm_type, alarm_time] = self.alarm_str2time(alarm)
            alarm_time = self.to_localtime(alarm_time)
            '''Check within a week'''
            if ((alarm_time.date() - today.date() >= datetime.timedelta(0)) and
                (alarm_time.date() - today.date() < datetime.timedelta(7))):
                weekday = alarm_time.weekday()
                week_alarms[weekday].append([alarm_type, alarm_time.time()])
        sorted_alarms = [[], [], [], [], [], [], []]
        for weekday, day in enumerate(week_alarms):
            sorted_alarms[weekday] = sorted(day, key=lambda day: day[1])
        week_alarms = numpy.roll(sorted_alarms, -today.weekday()).tolist()
        return week_alarms

    def build_msg_text_from_alarms(self):
        '''Builds a msg_txt from the alarms set on the week
           in a very specific format, shows also the alarm type
           '''
        today = self.to_localtime(datetime.datetime.utcnow())
        today_weekday = today.weekday()
        week_alarms = self.get_week_alarms_localtime()
        _type = 0
        _time = 1
        max_length = 0
        for day in week_alarms:
            if len(day) > max_length:
                max_length = len(day)
        alarm_list = []
        for x in range(0, max_length):
            hours_list = []
            minut_list = []
            type_list = []
            for day in week_alarms:
                if x < len(day):
                    if 'disabled' in day[x][_type][1]:
                        hours_list.append(day[x][_time].strftime("x%H|"))
                    else:
                        hours_list.append(day[x][_time].strftime(" %H|"))
                    minut_list.append(day[x][_time].strftime(":%M|"))
                    if 'auto' in day[x][_type][0]:
                        type_list.append('-A-|')
                    else:
                        type_list.append('---|')
                else:
                    hours_list.append('   |')
                    minut_list.append('   |')
                    type_list.append('---|')
            hours_str = ''.join(hours_list)
            minut_str = ''.join(minut_list)
            type_str = ''.join(type_list)
            alarm_list.append('`' + type_str.strip('|') + '`')
            alarm_list.append('`' + hours_str.strip('|') + '`')
            alarm_list.append('`' + minut_str.strip('|') + '`')
        num_list = []
        month_list = []
        for x in range(0, 7):
            day = today + datetime.timedelta(x)
            month_list.append(day.strftime('%B')[:3] + '|')
            num_list.append(day.strftime(' %d|'))
        name_list = ['Mon|', 'Tue|', 'Wed|', 'Thu|', 'Fri|', 'Sat|', 'Sun|']
        name_list = numpy.roll(name_list, -today_weekday).tolist()
        msg_text = ''.join([
            '`' + ''.join(name_list).strip('|') + '`' + '\n',
            '\n'.join(alarm_list) + '\n',
            '`' + ''.join(month_list).strip('|') + '`' + '\n',
            '`' + ''.join(num_list).strip('|') + '`'
        ])
        t = self.alarm_check()
        hours = str(int(t / 3600) % 24)
        if len(hours) < 2:
            hours = '0' + hours
        minutes = str(int(t / 60) % 60)
        if len(minutes) < 2:
            minutes = '0' + minutes
        last_line = ('`--`' + u'\U0001F567' + u'\U0001F552' + u'\U000023F3' +
                     u'\U000027A1' + '` `' + hours[0] + u'\U000020E3' +
                     hours[1] + u'\U000020E3' + '*:*' + minutes[0] +
                     u'\U000020E3' + minutes[1] + u'\U000020E3' +
                     u'\U0001F555' + u'\U0001F55B' + '`--`')
        msg_text = '\n'.join([msg_text, last_line])
        return msg_text
Exemple #3
0
class CalendarCheck:
    def __init__(self, state):
        self.state = state
        self.config = ConfigControls()

    def get_cal_urls(self):
        '''Gets the calendars urls from the config.txt'''
        urls = self.config.get_section('Calendar')
        return urls

    def add_cal_url(self, url):
        '''Adds a url to the config file'''
        urls = self.get_cal_urls()
        urls.append(url)
        self.config.set_section('Calendar', urls)
        return True

    def remove_cal_url(self, url):
        urls = self.get_cal_urls()
        try:
            urls.remove(url)
            self.config.set_section('Calendar', urls)
            return True
        except:
            return False

    def get_week_cal(self):
        '''Extracts the weekly calendar from the ical urls on the config file'''
        logger.info('Getting week calendar')
        today = datetime.datetime.utcnow().date()
        next_week = today + datetime.timedelta(7)
        week_cal = Calendar()
        week_cal.add('x-wr-calname', "Weekly Combined Calendar")
        urls = self.get_cal_urls()
        for url in urls:
            logger.debug(url)
            try:
                # Error avoider: Tries to read the calendar several times
                attempts = 0
                while attempts < 5:
                    if attempts < 4:
                        try:
                            req = requests.get(url)
                            attempts = 5
                        except:
                            attempts += 1
                    else:
                        req = requests.get(url)
                        attempts = 5
                # Error avoider finished
                if req.status_code != 200:
                    logger.error("Error {} fetching {}: {}".format(
                        url, req.status_code, req.text))
                    continue
                cal = Calendar.from_ical(req.text)
                for event in cal.walk("VEVENT"):
                    end = event.get('dtend')
                    if end:
                        WEEK_EVENT = False
                        if hasattr(end.dt, 'date'):
                            date = end.dt.date()
                        else:
                            date = end.dt
                        if date >= today and date < next_week:
                            WEEK_EVENT = True
                        elif 'RRULE' in event:
                            try:
                                rrule = event['RRULE']
                                until = rrule.get('until')[0]
                                if today < until.date():
                                    # Only weekly repeated events are supported
                                    if 'WEEKLY' in rrule.get('freq')[0]:
                                        WEEK_EVENT = True
                            except Exception as error:
                                logger.error(("{} rrule\n" + error).format(
                                    sys._getframe().f_code.co_name))
                        if WEEK_EVENT:
                            copied_event = Event()
                            for attr in event:
                                if type(event[attr]) is list:
                                    for element in event[attr]:
                                        copied_event.add(attr, element)
                                else:
                                    copied_event.add(attr, event[attr])
                            week_cal.add_component(copied_event)
            except Exception as error:
                # Add counter to avoid false errors
                logger.warning('Invalid calendar, removing\n' + url + '\n' +
                               error)
                self.remove_cal_url(url)
                for chat_id in self.state.chat_id_list:
                    self.state.bot.sendMessage(
                        chat_id, 'Removed invalid calendar url:\n' + url)
        logger.info('Got calendars')
        return week_cal

    def get_week_events_local_times(self):
        '''Gets the starting times of the next week events and returns them
           in a list ordered in days remaining
           events = [[datetime.datetime,...], [...], ...]
           '''
        week_cal = self.get_week_cal()
        now = datetime.datetime.utcnow()
        utcoffset = datetime.datetime.now(
            datetime.timezone.utc).astimezone().tzinfo.utcoffset(None)
        today_local = (datetime.datetime.utcnow() + utcoffset).date()
        events = [[], [], [], [], [], [], []]
        for event in week_cal.walk("VEVENT"):
            if 'RRULE' in event:
                # Only weekly repeated events are supported
                try:
                    ref_start = event.get('dtstart')
                    if isinstance(ref_start.dt, datetime.datetime
                                  ):  # Check is not a full day event
                        reference = ref_start.dt.replace(tzinfo=None)
                        rrule = event['RRULE']
                        if 'BYDAY' in rrule:
                            day_list = rrule.get('byday')
                        elif 'WKST' in rrule:
                            day_list = rrule.get('wkst')
                        for day in day_list:
                            for index, day_string in enumerate(
                                ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']):
                                if day in day_string:
                                    reference_weekday = index
                                    break
                            event_add_day = reference_weekday - now.weekday()
                            if event_add_day < 0:
                                event_add_day += 7
                            event_day = now.day + event_add_day
                            event_start = datetime.datetime(
                                now.year, now.month, event_day, reference.hour,
                                reference.minute)
                            #Check for excluded dates
                            is_excluded = False
                            if 'EXDATE' in event:
                                exclusions = event.get('exdate')
                                if not isinstance(exclusions, list):
                                    exclusions = [exclusions]
                                for ex_list in exclusions:
                                    for ex_day in ex_list.dts:
                                        if ex_day.dt.replace(
                                                tzinfo=None
                                        ) - event_start == datetime.timedelta(
                                                0):
                                            is_excluded = True
                                            break
                            if not is_excluded:
                                event_start_local = event_start  # + utcoffset '''I don't know why it is already local'''
                                days_to_go = event_start_local.weekday(
                                ) - today_local.weekday()
                                if days_to_go < 0:
                                    days_to_go += 7
                                events[days_to_go].append(event_start_local)
                except Exception as error:
                    logger.error(" {}: rrule\n{}".format(
                        sys._getframe().f_code.co_name, error))
            else:
                start = event.get('dtstart')
                if start:
                    try:
                        if isinstance(start.dt, datetime.datetime
                                      ):  # Check is not a full day event
                            event_start = start.dt.replace(tzinfo=None)
                            event_start_local = event_start + utcoffset
                            days_to_go = event_start_local.weekday(
                            ) - today_local.weekday()
                            if days_to_go < 0:
                                days_to_go += 7
                            events[days_to_go].append(event_start_local)
                    except Exception as error:
                        logger.error(" {}: not rrule\n{}".format(
                            sys._getframe().f_code.co_name, error))
        sorted_events = [[], [], [], [], [], [], []]
        for weekday, day in enumerate(events):
            sorted_events[weekday] = sorted(day)
        return sorted_events

    def get_day_first_events_localtime(self, week_events):
        '''Gets a list with the first events of each weekday
           first_events = [datetime.time,...]
           '''
        first_events = [None, None, None, None, None, None, None]
        for days_to_go, event in enumerate(week_events):
            if len(event) > 0:
                first_events[days_to_go] = event[0]
        return first_events