Beispiel #1
0
class LightBot(Plugin):
    # Which lights should be targeted if no light specifying parameter is provided?
    all_lights = [0]

    def __init__(self, name=None, slack_client=None, plugin_config=None):
        super(LightBot, self).__init__(name=name, slack_client=slack_client, plugin_config=plugin_config)

        bridge_address = plugin_config.get('HUE_BRIDGE_ADDRESS', None)

        self.allowed_light_control_channel_ids = plugin_config.get('CHANNELS', None)
        self.allowed_light_control_user_ids = plugin_config.get('USERS', None)
        self.wootric_bot_id = plugin_config.get('WOOTRIC_BOT', None)
        self.wigwag_color = self.xy_from_color_string(plugin_config.get('WIGWAG_COLOR', str(DEFAULT_WIGWAG_COLOR)))
        self.whirl_color = self.xy_from_color_string(plugin_config.get('WHIRL_COLOR', str(DEFAULT_WHIRL_COLOR)))
        self.slow_pulse_color = self.xy_from_color_string(
            plugin_config.get('SLOW_PULSE_COLOR', str(DEFAULT_SLOW_PULSE_COLOR)))
        self.slow_pulse_lights = plugin_config.get('SLOW_PULSE_LIGHTS', None)

        config_lights = plugin_config.get('LIGHTS', None)

        if config_lights is not None:
            self.all_lights = config_lights

        if not bridge_address:
            raise ValueError("Please add HUE_BRIDGE_ADDRESS under LightBot in your config file.")

        self.bridge = Bridge(bridge_address)
        self.bridge.connect()

        if self.debug:
            print dumps(self.bridge.get_api())

        lights_on_bridge = self.bridge.lights

        if self.all_lights == [0]:
            # The magic 0 light ID does not work for most light settings we will use
            self.all_lights = []
            for light in lights_on_bridge:
                self.all_lights.append(light.light_id)

        config_wig_wag_groups = plugin_config.get('WIGWAG_GROUPS', None)
        if config_wig_wag_groups is not None and len(config_wig_wag_groups) == 2 \
                and len(config_wig_wag_groups[0]) > 0 and len(config_wig_wag_groups[1]):
            self.wigwag_groups = config_wig_wag_groups
        else:
            self.wigwag_groups = None

        if self.slow_pulse_lights is None:
            self.slow_pulse_lights = self.all_lights

        # Check for whirl lights, either as an array of IDs or an array of arrays of IDs.
        config_whirl_lights = plugin_config.get('WHIRL_LIGHTS', None)
        self.whirl_lights = []

        if config_whirl_lights is None:
            # Default to all lights
            for light_id in self.all_lights:
                # Put each light ID into an array since whirl_lights is an array of arrays of IDs.
                self.whirl_lights.append([light_id])
        else:
            for whirl_group in config_whirl_lights:
                if isinstance(whirl_group, list):
                    # The user gave us an array of arrays, just what we wanted! :x
                    self.whirl_lights.append(whirl_group)
                elif whirl_group is not None:
                    # It's not an array and not None.  This is probably a single light ID scalar.
                    self.whirl_lights.append([whirl_group])

        if self.wigwag_groups is None:
            # We do not have configuration-specified wig wag groups.  Use all odd and even lights.
            even_lights = []
            odd_lights = []

            for light in lights_on_bridge:
                if light.light_id % 2 == 0:
                    even_lights.append(light.light_id)
                else:
                    odd_lights.append(light.light_id)

            self.wigwag_groups = [odd_lights, even_lights]

    def process_message(self, data):
        print dumps(data)

        is_wootric_bot = ('subtype' in data and data['subtype'] == 'bot_message'
                          and 'bot_id' in data and data['bot_id'] == self.wootric_bot_id)
        user_impersonating_bot = self.debug and 'user' in data and data['user'] in self.allowed_light_control_user_ids

        light_control_regex = r"(?i)^lights?\s+(\S+.*)$"

        # Direct light control
        if self.message_allows_light_control(data):
            # Match any command beginning with "lights" and containing any other command(s)
            pattern = re.compile(light_control_regex)
            match = pattern.match(data['text'])

            if match is not None:
                light_command = match.group(1)

                if light_command is not None:
                    self.process_lights_command(light_command, data)

        # NPS scores
        if is_wootric_bot or user_impersonating_bot:
            pattern = re.compile(r"[*_]*New NPS rating:\s+(\d+).*")

            if user_impersonating_bot:
                match = pattern.match(data['text'])
            elif is_wootric_bot and 'attachments' in data:
                match = pattern.match(data['attachments'][0]['text'])
            else:
                match = None

            if match is not None:
                nps_score = match.group(1)

                if nps_score is not None:
                    self.process_nps_score(nps_score)

    def process_lights_command(self, args, data=None):
        pattern = re.compile(r"(?i)^((\d+\s+)+)?([#\S]+.*%?)$")
        match = pattern.match(args)

        if match is not None and match.group(1) is not None:
            target_lights = match.group(1).split()
        else:
            target_lights = self.all_lights

        command = match.group(3)

        if 'debug' in command.lower():
            self.handle_debug_command(args, data)
            return

        if command.lower() == 'whirl':
            self.whirl()
            return

        if command.lower() == 'wigwag':
            self.wigwag()
            return

        if command.lower() == 'pulsate':
            self.pulsate()
            return

        if command.lower() == 'on':
            self.lights_on_or_off(True, target_lights)
            return
        elif command.lower() == 'off':
            self.lights_on_or_off(False, target_lights)
            return

        if command.lower() == 'dance party':
            self.dance_party(target_lights)
            return

        # Check for a color
        try:
            xy = self.xy_from_color_string(command)

            if xy is not None:
                self.color_change(xy, target_lights)
                return
        except ValueError:
            pass

        # Check for brightness
        pattern = re.compile(r"(?i)^bri(ghtness)?\s+(\d+(%?|(\.\d+)?))$")
        match = pattern.match(command)

        if match is not None:
            brightness = match.group(2)

            if brightness is not None:
                self.brightness_change(brightness, target_lights)
                return

        # Check for a scene after updating Hue API
        scene_id = self.scene_id_matching_string(command)

        if scene_id is not None:
            self.bridge.activate_scene(0, scene_id)
            return

    def handle_debug_command(self, command, incoming_data=None):
        if command == 'debug rules':
            data_type = 'rules'
            data = self.bridge.request('GET', '/api/' + self.bridge.username + '/rules')
        elif command == 'debug schedules':
            data_type = 'schedules'
            data = self.bridge.get_schedule()
        elif command == 'debug lights':
            data_type = 'lights'
            data = self.bridge.get_light()
        elif command == 'debug sensors':
            data_type = 'sensors'
            data = self.bridge.get_sensor()
        else:
            data_type = 'bridge objects of all types'
            data = self.bridge.get_api()

        pretty_data_string = dumps(data, sort_keys=True, indent=4, separators=(',',':'))

        message_attachments = [{
            'fallback': '%d %s:' % (len(data), data_type),
            'title': '%d %s:' % (len(data), data_type),
            'text': pretty_data_string
        }]

        self.slack_client.api_call('chat.postMessage', as_user=True, channel=incoming_data['channel'],
                                   attachments=message_attachments, type='message')

    def scene_id_matching_string(self, scene_name):
        name = scene_name.lower()

        for scene_id, scene in self.bridge.get_scene().iteritems():
            if scene['name'].lower() == name:
                return scene_id

        return None

    # Disables all enabled schedules for the time period specified
    def disable_schedules_for_time(self, seconds):
        if seconds < 1:
            seconds = 1

        seconds = int(ceil(seconds))
        minutes = seconds / 60
        seconds %= 60
        hours = minutes / 60
        minutes %= 60

        time_string = 'PT%02d:%02d:%02d' % (hours, minutes, seconds)

        all_schedules = self.bridge.get_schedule()

        for schedule_id, schedule in all_schedules.iteritems():
            if schedule['status'] == 'enabled':
                reenable_schedule_schedule = {
                    'name': 'temporarilyDisableSchedule%s' % str(schedule_id),
                    'time': time_string,
                    'command': {
                        'method': 'PUT',
                        'address': '/api/' + self.bridge.username + '/schedules/' + str(schedule_id),
                        'body': {'status': 'enabled'}
                    }
                }

                result = self.bridge.request('PUT', '/api/' + self.bridge.username + '/schedules/' + str(schedule_id),
                                             dumps({'status': 'disabled'}))
                self.bridge.request('POST', '/api/' + self.bridge.username + '/schedules',
                                    dumps(reenable_schedule_schedule))

                print result

    # Accepts colors in the format of a color name, XY values, RGB values, or hex RGB code.
    # Returns [X,Y] for use in the Philips Hue API.
    def xy_from_color_string(self, string):
        # Our regex patterns
        hex_pattern = re.compile(r"^#?(([A-Fa-f\d]{3}){1,2})$")
        xy_pattern = re.compile(r"^[[({]?\s*(\d+(\.\d+)?)[,\s]+(\d+(\.\d+)?)\s*[])}]?\s*$")
        rgb_integer_pattern = re.compile(r"^[[({]?\s*(\d+)[,\s]+(\d+(\.\d+)?)[,\s]+(\d+)\s*[])}]?\s*$")
        rgb_percent_pattern = re.compile(r"^[[({]?\s*(\d+)%[,\s]+(\d+)%[,\s]+(\d+)%\s*[])}]?\s*$")

        rgb = None
        xy = None

        try:
            rgb = name_to_rgb(string)
        except ValueError:
            pass

        if rgb is None:
            # No name matched
            match = hex_pattern.match(string)

            if match is not None:
                try:
                    rgb = hex_to_rgb("#" + match.group(1))
                except ValueError:
                    pass
            else:
                # No name, no hex
                match = rgb_percent_pattern.match(string)
                r = None
                g = None
                b = None

                if match is not None:
                    r = int(match.group(1)) * 255 / 100
                    g = int(match.group(2)) * 255 / 100
                    b = int(match.group(3)) * 255 / 100

                else:
                    # No name, no hex, no RGB percent
                    match = rgb_integer_pattern.match(string)

                    if match is not None:
                        r = int(match.group(1))
                        g = int(match.group(2))
                        b = int(match.group(4))

                if r is not None and g is not None and b is not None:
                    rgb = [r, g, b]
                else:
                    # No name, no hex, no RGB percent, no RGB integers
                    match = xy_pattern.match(string)

                    if match is not None:
                        xy = [float(match.group(1)), float(match.group(3))]

        if xy is None and rgb is not None:
            # We have RGB.  Convert to XY for Philips-ness.
            xy = self.rgb_to_xy(rgb)

        return xy

    @staticmethod
    def rgb_to_xy(rgb):
        # Some magic number witchcraft to go from rgb 255 to Philips XY
        # from http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
        red = rgb[0] / 255.0
        green = rgb[1] / 255.0
        blue = rgb[2] / 255.0

        red = ((red + 0.055) / (1.0 + 0.055) ** 2.4) if (red > 0.04045) else (red / 12.92)
        green = ((green + 0.055) / (1.0 + 0.055) ** 2.4) if (green > 0.04045) else (green / 12.92)
        blue = ((blue + 0.055) / (1.0 + 0.055) ** 2.4) if (blue > 0.04045) else (blue / 12.92)

        x = red * 0.664511 + green * 0.154324 + blue * 0.162028
        y = red * 0.283881 + green * 0.668433 + blue * 0.047685
        z = red * 0.000088 + green * 0.072310 + blue * 0.986039

        return [x / (x+y+z), y / (x+y+z)]

    def color_change(self, xy, lights):
        for light in lights:
            self.bridge.set_light(int(light), {'on': True, 'xy': xy})

    def brightness_change(self, brightness, lights):
        if '%' in brightness:
            brightness = brightness.strip('%')
            brightness = float(brightness) / 100.0
            brightness = int(brightness * 255.0)
        else:
            brightness = float(brightness)

            if brightness == 1:
                brightness = 255
            elif 0.0 < brightness < 1.0:
                brightness = int(brightness * 255)

        for light in lights:
            self.bridge.set_light(int(light), {'on': True, 'bri': brightness})

    def message_allows_light_control(self, data):
        # Is this person in one of our full control channels?
        if 'channel' in data:
            if (self.allowed_light_control_channel_ids is None
                    or data['channel'] in self.allowed_light_control_channel_ids):
                return True
        if 'user' in data and data['user'] in self.allowed_light_control_user_ids:
            return True

        return False

    def process_nps_score(self, score):
        if score == '10':
            self.whirl()
        elif score == '9':
            self.wigwag()
        elif score == '0':
            self.pulsate()

    def lights_on_or_off(self, off_or_on, lights):
        for light in lights:
            self.bridge.set_light(int(light), {'on': off_or_on})

    def dance_party(self, lights):
        starting_status = {}
        flash_count = 66
        delay_between_flashes = 0.15
        total_duration = flash_count * delay_between_flashes

        self.disable_schedules_for_time(total_duration)

        for light in lights:
            state = self.bridge.get_light(int(light))['state']

            if state is not None:
                starting_status[light] = self.restorable_state_for_light(state)

        for i in lights:
            self.bridge.set_light(int(i), {'on': True})

        for loop_index in range(0,flash_count):
            for i in lights:
                xy = [random.uniform(0.0, 1.0), random.uniform(0.0, 1.0)]
                self.bridge.set_light(int(i), {'bri': 250, 'xy': xy, 'transitionTime': 0, 'alert': 'select'})

                time.sleep(delay_between_flashes)

        for light in lights:
            self.bridge.set_light(int(light), starting_status[light])

    @staticmethod
    def restorable_state_for_light(light_object):
        state = {'bri': light_object['bri'], 'on': light_object['on']}

        if light_object['colormode'] == 'hs':
            state['hue'] = light_object['hue']
            state['sat'] = light_object['sat']
        elif light_object['colormode'] == 'ct':
            state['ct'] = light_object['ct']
        else:
            state['xy'] = light_object['xy']

        return state

    def wigwag(self):
        starting_status = {}
        all_wigwag_lights = self.wigwag_groups[0] + self.wigwag_groups[1]
        transition_time = 5
        in_one_second = 'PT00:00:01'
        repeat_count = 5
        seconds_between_phases = 1
        every_two_seconds = 'R%02d/PT00:00:02' % repeat_count
        total_duration = repeat_count * seconds_between_phases * 2
        after_its_over = 'PT00:00:%02d' % total_duration

        self.disable_schedules_for_time(total_duration)

        for light_id in all_wigwag_lights:
            state = self.bridge.get_light(int(light_id))['state']

            if state is not None:
                starting_status[light_id] = self.restorable_state_for_light(state)

            if not state['on']:
                self.bridge.create_schedule('turn%dOnBeforeWigwag' % light_id, in_one_second, light_id, {'on': True})

        # Ensure all lights will be on
        for light_id in all_wigwag_lights:
            if not self.bridge.lights_by_id[light_id].on:
                self.bridge.create_schedule('turn%dOnBeforeWigwag' % light_id, in_one_second, light_id, {'on': True})

        # First phase
        for light_id in self.wigwag_groups[0]:
            self.bridge.create_schedule('wigwag-1-%d' % light_id, every_two_seconds, light_id, {
                'xy': self.wigwag_color, 'bri': 154, 'transitiontime': transition_time
            })
        for light_id in self.wigwag_groups[1]:
            self.bridge.create_schedule('wigwag-1-%d' % light_id, every_two_seconds, light_id, {
                'xy': self.wigwag_color, 'bri': 0, 'transitiontime': transition_time
            })

        # Delay before setting second phase
        time.sleep(seconds_between_phases)

        # Second phase
        for light_id in self.wigwag_groups[0]:
            self.bridge.create_schedule('wigwag-2-%d' % light_id, every_two_seconds, light_id, {
                'xy': self.wigwag_color, 'bri': 0, 'transitiontime': transition_time
            })
        for light_id in self.wigwag_groups[1]:
            self.bridge.create_schedule('wigwag-2-%d' % light_id, every_two_seconds, light_id, {
                'xy': self.wigwag_color, 'bri': 154, 'transitiontime': transition_time
            })

        # Restore original state
        for light_id in all_wigwag_lights:
            result = self.bridge.create_schedule('wigwag-3-%d' % light_id, after_its_over, light_id,
                                                 starting_status[light_id])

            if self.debug:
                print "Setting light %d to restore state to:\n" % light_id
                print starting_status[light_id]
                print result

        if self.debug:
            print self.bridge.get_schedule()

    def delete_all_sensors_with_name_begining(self, name_prefix):
        all_sensors = self.bridge.get_sensor()

        for sensor_id, sensor in all_sensors.iteritems():
            if name_prefix in sensor['name']:
                result = self.bridge.request('DELETE', '/api/' + self.bridge.username + '/sensors/' + str(sensor_id))
                print result

    def delete_all_schedules_with_name_begining(self, name_prefix):
        all_schedules = self.bridge.request('GET', '/api/' + self.bridge.username + '/schedules')

        for schedule_id, schedule in all_schedules.iteritems():
            if name_prefix in schedule['name']:
                self.bridge.request('DELETE', '/api/' + self.bridge.username + '/schedules/' + str(schedule_id))

    def delete_all_rules_with_name_begining(self, name_prefix):
        all_rules = self.bridge.request('GET', '/api/' + self.bridge.username + '/rules')

        for rule_id, rule in all_rules.iteritems():
            if name_prefix in rule['name']:
                self.bridge.request('DELETE', '/api/' + self.bridge.username + '/rules/' + str(rule_id))

    def pulsate(self):
        lights = self.all_lights
        starting_status = {}

        # More than seven lights would require multiple rules in the Bridge since we are limited to 8 actions per rule.
        # This would be relatively straight forward to solve but is not worth the effort at the moment.
        if len(lights) > 6:
            print '%d lights are specified to pulsate.' % len(lights)\
                 + 'Only pulsating up to 6 is currently supported.' \
                 + 'List will be truncated to 6.'
            lights = lights[:6]

        pulse_bri = 88
        original_fade_duration_deciseconds = 50
        original_fade_schedule_time = 'PT00:00:%02d' % (original_fade_duration_deciseconds / 10)
        half_pulse_duration_deciseconds = 20
        half_pulse_schedule_time = 'PT00:00:%02d' % (half_pulse_duration_deciseconds / 10)
        pulse_count = 5

        total_duration_seconds = pulse_count * half_pulse_duration_deciseconds * 2 / 10
        total_duration_minutes = total_duration_seconds / 60
        total_duration_seconds %= 60
        total_duration_schedule_time = 'PT00:%02d:%02d' % (total_duration_minutes, total_duration_seconds)

        self.delete_all_sensors_with_name_begining('Pulsation')
        self.delete_all_schedules_with_name_begining('Pulsation')
        self.delete_all_rules_with_name_begining('Pulsation')
        self.disable_schedules_for_time(total_duration_seconds)

        for light in lights:
            state = self.bridge.get_light(int(light))['state']

            if state is not None:
                restorable_state = self.restorable_state_for_light(state)
                restorable_state['transitiontime'] = 20
                starting_status[light] = restorable_state

        lights_up_state = {
            'bri': pulse_bri,
            'xy': self.slow_pulse_color,
            'transitiontime': half_pulse_duration_deciseconds
        }

        lights_down_state = {
            'bri': 0,
            'xy': self.slow_pulse_color,
            'transitiontime': half_pulse_duration_deciseconds
        }

        pulsation_status_sensor = {
            'name': 'PulsationStatusSensor',
            'uniqueid': 'PulsationStatusSensor',
            'type': 'CLIPGenericStatus',
            'swversion': '1.0',
            'state': {
                'status': 0
            },
            'manufacturername': 'jasonneel',
            'modelid': 'PulsationStatusSensor'
        }

        # Create the sensors used for status
        result = self.bridge.request('POST', '/api/' + self.bridge.username + '/sensors', dumps(pulsation_status_sensor))
        status_sensor_id = result[0]['success']['id']
        pulsation_state_address = '/sensors/' + str(status_sensor_id) + '/state'

        # Schedules
        going_up_schedule = {
            'name': 'Pulsation going up',
            'time': half_pulse_schedule_time,
            'autodelete': False,
            'status': 'disabled',
            'command': {
                'address': '/api/' + self.bridge.username + pulsation_state_address,
                'method': 'PUT',
                'body': {
                    'status': PULSATION_SENSOR_STATE_GOING_DOWN
                }
            }
        }

        going_down_schedule = {
            'name': 'Pulsation going down',
            'time': half_pulse_schedule_time,
            'autodelete': False,
            'status': 'disabled',
            'command': {
                'address': '/api/' + self.bridge.username + pulsation_state_address,
                'method': 'PUT',
                'body': {
                    'status': PULSATION_SENSOR_STATE_GOING_UP
                }
            }
        }

        going_up_result = self.bridge.request('POST', '/api/' + self.bridge.username + '/schedules',
                                              dumps(going_up_schedule))
        going_up_schedule_id = going_up_result[0]['success']['id']
        going_down_result = self.bridge.request('POST', '/api/' + self.bridge.username + '/schedules',
                                                dumps(going_down_schedule))
        going_down_schedule_id = going_down_result[0]['success']['id']

        # Create the two rules for going up and down
        start_going_up_rule = {
            'name': 'Pulsation at bottom',
            'conditions': [
                {
                    'address': pulsation_state_address + '/status',
                    'operator': 'eq',
                    'value': str(PULSATION_SENSOR_STATE_GOING_UP)
                },
                {
                    'address': pulsation_state_address + '/lastupdated',
                    'operator': 'dx'
                }
            ],
            'actions': [
                {
                    'address': '/schedules/' + str(going_up_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'enabled'}
                },
                {
                    'address': '/schedules/' + str(going_down_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                }
            ]
        }

        start_going_down_rule = {
            'name': 'Pulsation at top',
            'conditions': [
                {
                    'address': pulsation_state_address + '/status',
                    'operator': 'eq',
                    'value': str(PULSATION_SENSOR_STATE_GOING_DOWN)
                },
                {
                    'address': pulsation_state_address + '/lastupdated',
                    'operator': 'dx'
                }
            ],
            'actions': [
                {
                    'address': '/schedules/' + str(going_up_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                },
                {
                    'address': '/schedules/' + str(going_down_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'enabled'}
                }
            ]
        }

        original_light_state_rule = {
            'name': 'Pulsation restore state',
            'conditions': [
                {
                    'address': pulsation_state_address + '/status',
                    'operator': 'eq',
                    'value': str(PULSATION_SENSOR_STATE_CLEANUP)
                },
                {
                    'address': pulsation_state_address + '/lastupdated',
                    'operator': 'dx'
                }
            ],
            'actions': []
        }

        # Add actions to both rules to bring each light up and down as the sensor state changes
        for light_id in lights:
            light_address = '/lights/' + str(light_id) + '/state'
            start_going_up_rule['actions'].append({
                'address': light_address,
                'method': 'PUT',
                'body': lights_up_state
            })
            start_going_down_rule['actions'].append({
                'address': light_address,
                'method': 'PUT',
                'body': lights_down_state
            })
            original_light_state_rule['actions'].append({
                'address': light_address,
                'method': 'PUT',
                'body': starting_status[light_id]
            })

        going_up_result = self.bridge.request('POST', '/api/' + self.bridge.username + '/rules',
                                              dumps(start_going_up_rule))
        going_up_rule_id = going_up_result[0]['success']['id']
        going_down_result = self.bridge.request('POST', '/api/' + self.bridge.username + '/rules',
                                                dumps(start_going_down_rule))
        going_down_rule_id = going_down_result[0]['success']['id']
        result = self.bridge.request('POST', '/api/' + self.bridge.username + '/rules',
                                     dumps(original_light_state_rule))

        cleanup_rule = {
            'name': 'Pulsation clean up',
            'conditions': [
                {
                    'address': pulsation_state_address + '/status',
                    'operator': 'eq',
                    'value': str(PULSATION_SENSOR_STATE_CLEANUP)
                },
                {
                    'address': pulsation_state_address + '/lastupdated',
                    'operator': 'dx'
                }
            ],
            'actions': [
                {
                    'address': '/rules/' + str(going_up_rule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                },
                {
                    'address': '/rules/' + str(going_down_rule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                },
                {
                    'address': '/schedules/' + str(going_up_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                },
                {
                    'address': '/schedules/' + str(going_down_schedule_id),
                    'method': 'PUT',
                    'body': {'status': 'disabled'}
                }
            ]
        }

        result = self.bridge.request('POST', '/api/' + self.bridge.username + '/rules', dumps(cleanup_rule))

        # The schedule that stops the constant
        cleanup_schedule = {
            'name': 'Pulsation clean up',
            'time': total_duration_schedule_time,
            'command': {
                'address': '/api/' + self.bridge.username + pulsation_state_address,
                'method': 'PUT',
                'body': {
                    'status': PULSATION_SENSOR_STATE_CLEANUP
                }
            }
        }

        result = self.bridge.request('POST', '/api/' + self.bridge.username + '/schedules', dumps(cleanup_schedule))

        # First fade them all down to nothing
        lights_totally_off = {
            'bri': 0,
            'transitiontime': original_fade_duration_deciseconds
        }
        for light_id in lights:
            light = self.bridge.lights_by_id[light_id]
            result = self.bridge.request('PUT', '/api/' + self.bridge.username + '/lights/' + str(light_id) + '/state',
                                         dumps(lights_totally_off))
            print result

        # Start the pulsation once that is done
        begin_schedule = {
            'name': 'Pulsation begin',
            'time': original_fade_schedule_time,
            'command': {
                'address': '/api/' + self.bridge.username + pulsation_state_address,
                'method': 'PUT',
                'body': {
                    'status': PULSATION_SENSOR_STATE_GOING_UP
                }
            }
        }

        result = self.bridge.request('POST', '/api/' + self.bridge.username + '/schedules', dumps(begin_schedule))

    def whirl(self):
        starting_status = {}

        # Flatten our array of arrays of light IDs to save the starting states
        flattened_whirl_light_ids = list(itertools.chain.from_iterable(self.whirl_lights))
        for light_id in flattened_whirl_light_ids:
            state = self.bridge.get_light(int(light_id))['state']

            if state is not None:
                starting_status[light_id] = self.restorable_state_for_light(state)

        step_time = 0.075
        time_between_whirls = 0.5
        transition_time = 1
        whirl_count = 10

        total_seconds = ((step_time * len(self.whirl_lights)) + time_between_whirls) * whirl_count
        finished_timestamp = 'PT00:00:%02d' % total_seconds

        self.disable_schedules_for_time(total_seconds)

        # Return to original state after we're done
        for light_id in flattened_whirl_light_ids:
            self.bridge.create_schedule('restore%dAfterWhirl' % light_id, finished_timestamp, light_id,
                                        starting_status[light_id])

        # Build our 'off' states to go with the on state
        off_states = {}

        for light_id, status in starting_status.iteritems():
            state = deepcopy(status)
            del state['on']
            if not status['on']:
                state['bri'] = 0

            off_states[light_id] = state

        # New hotness:
        on_state = {'xy': self.whirl_color, 'bri': 255, 'transitiontime': transition_time}
        on_state_plus_on = on_state.copy()
        on_state_plus_on['on'] = True

        for whirl_index in range(0,whirl_count):
            for group_index in range(0,len(self.whirl_lights)+2):
                coming_down_index = group_index - 2

                if group_index < len(self.whirl_lights):
                    state = on_state if whirl_index != 0 else on_state_plus_on
                    for light_id in self.whirl_lights[group_index]:
                        self.bridge.set_light(light_id, state)

                if coming_down_index >= 0:
                    for light_id in self.whirl_lights[coming_down_index]:
                        self.bridge.set_light(light_id, off_states[light_id])

                time.sleep(step_time)

            time.sleep(time_between_whirls)
Beispiel #2
0
class GridWorker(Thread):

    def __init__(self, stationIP, testMode = True):
        Thread.__init__(self)
        self.stationIP = stationIP
        self.queue = Queue()
        self.testMode = testMode
        if testMode != True:
            self.bridge = Bridge(ip=stationIP, username="******")
        else:
            print "bridge %s started" % (self.stationIP)
        self.transitionTime = 1
        self.running = True
        self.fastMode = False
        self.fastModeReady = False

        self.frameReady = False

    def end(self):
        self.running = False
        self.queue.join()

    ''' take list of bulb ids and colour values, add to queue '''
    def addData(self, dataList):
        if self.fastMode == True and self.fastModeReady == False:
            return
        for item in dataList:
            self.queue.put(item)

    def frameDone (self):
        self.frameReady = True

    def run(self):
        while self.running:
            #read from queue
            if self.fastMode == True:
                self.doFastMode()
            else :
                self.slowMode()

    def doFastMode(self):
        if self.frameReady == False:
            return
        #here the value represents a colour index from the shitty pallette
        #read the queue until empty or 16 have been read
        bulbList = []
        valueList = []
        ct = 0
        doUpdate = False
        while self.queue.empty() == False and ct < 16:
            bulbId, value = self.queue.get(False)
            value = value[0]
            bulbList.append(bulbId)
            valueList.append(value)
            ct += 1
            doUpdate = True
            self.queue.task_done()
	if doUpdate == True and self.testMode == False:
            print 
            #run over the upates in bulb list and compile them into the weird format it needs
            cmd = [01, 01]
            cmd.append(len(valueList))
            for i in range(0, len(bulbList)):
                cmd.append(valueList[i])
                cmd.append(bulbList[i])
            print cmd    
            cmdString = str(bytearray(cmd)).encode('hex')
            command = {"duration" : 10000, "symbolselection" : cmdString}
            self.bridge.request('PUT', '/api/' + self.bridge.username + '/groups/0/transmitsymbol', json.dumps(command))
            sleep(0.05)

        if self.queue.empty():
            self.frameReady = False


    def slowMode(self):
        if self.queue.empty() == False:
            bulbId, value = self.queue.get(False)
            #send to base station
            cmdWaitTime = 0.3 #how long to wait between commands, this depends on what is sent in the next line
            command = {}
            if(value[0] != None):
                command["hue"] = value[0]
                cmdWaitTime += 0.1
            if(value[1] != None):
                command["sat"] = value[1]
                cmdWaitTime += 0.1
            if(value[2] != None):
                command["bri"] = value[2]
                cmdWaitTime += 0.1
            command["transitiontime"] = self.transitionTime
            #command = {'hue' : value[0], 'bri' : value[2], 'transitiontime' : self.transitionTime} 
            #cmdWaitTime += 0.32

            if self.testMode != True:
                self.bridge.set_light(bulbId, command)
            else :
                print "station %s - bulb %i : %s" % (self.stationIP, bulbId, str(command))

            sleep(cmdWaitTime)                
            self.queue.task_done()
    
    def sendPointSymbols(self, lightList):
        col = range(0,7)
        col[0] = "320000002A10101000FF"
        col[1] = "320000002A65FF7F00FF"
        col[2] = "320000002AFF060000FF"
        col[3] = "320000002AE52A0200FF"
        col[4] = "320000002A0AFF1100FF"
        col[5] = "320000002A0DFFC200FF"
        col[6] = "320000002A0300C700FF"
        command = {}
        for i in range(0, 7):
            command[str(i+1)] = col[i]
        for i, light in enumerate(lightList):
            if self.testMode == False:
                self.bridge.request('PUT', '/api/' + self.bridge.username + '/lights/' + str(i+1) + '/pointsymbol', json.dumps(command))
            sleep(0.54)
        print "..upload complete"
        self.fastModeReady = True