Beispiel #1
0
def read_bridge_info ():
    global bridge_bureau
    global lights_diction
    global group_diction
    global group_dic_name_lights
    global sensor_diction
    global scene_diction
    global scene_dic_name_lights
    global bridge_bureau_ip
    global laptop_username
    global iphone_bart
  
    # bridge_bureau is an object of type Bridge
    bridge_bureau = Bridge(ip = bridge_bureau_ip, username = iphone_bart)

    # make a Diction of all lights by names
    lights_diction = bridge_bureau.get_light_objects(mode="name")
    
    # make a Diction of all groups by key= name and value = [lights]
    group_diction = bridge_bureau.get_group()
    group_dic_name_lights = {}
   
    # make a diction of all sensors
    sensor_diction = bridge_bureau.get_sensor()
    for x in sensor_diction :
        if sensor_diction[x]["name"]=="Hal kamer Gust" :
            print (sensor_diction[x]["config"])
    

    # make a diction of all scenes

    scene_diction = bridge_bureau.get_scene()
    scene_dic_name_lights = {}
    return
Beispiel #2
0
 def get_groups_and_scenes_by_ph_settings(self):
     try:
         phSetting = self.room_philip_group_and_scene.all().first()
         b = Bridge(phSetting.bridge_url, username=phSetting.username)
         b.connect()
         # import pdb;pdb.set_trace()
         phGroups = b.get_group()
         phScenes = b.get_scene()
     except (AttributeError, socket_error, PhueRequestTimeout):
         phSetting = None
         phGroups = []
         phScenes = []
     return phSetting, phGroups, phScenes
Beispiel #3
0
class LightHuePlugin(LightPlugin):
    """
    Philips Hue lights plugin.

    Requires:

        * **phue** (``pip install phue``)
    """

    MAX_BRI = 255
    MAX_SAT = 255
    MAX_HUE = 65535
    ANIMATION_CTRL_QUEUE_NAME = 'platypush/light/hue/AnimationCtrl'
    _BRIDGE_RECONNECT_SECONDS = 5
    _MAX_RECONNECT_TRIES = 5

    class Animation(Enum):
        COLOR_TRANSITION = 'color_transition'
        BLINK = 'blink'

        def __eq__(self, other):
            if isinstance(other, str):
                return self.value == other
            elif isinstance(other, self.__class__):
                return self == other

    def __init__(self, bridge, lights=None, groups=None):
        """
        :param bridge: Bridge address or hostname
        :type bridge: str

        :param lights: Default lights to be controlled (default: all)
        :type lights: list[str]

        :param groups Default groups to be controlled (default: all)
        :type groups: list[str]
        """

        super().__init__()

        self.bridge_address = bridge
        self.bridge = None
        self.logger.info('Initializing Hue lights plugin - bridge: "{}"'.format(self.bridge_address))

        self.connect()
        self.lights = []; self.groups = []

        if lights:
            self.lights = lights
        elif groups:
            self.groups = groups
            self._expand_groups()
        else:
            self.lights = [l.name for l in self.bridge.lights]

        self.redis = None
        self.animation_thread = None
        self.animations = {}
        self._init_animations()
        self.logger.info('Configured lights: "{}"'. format(self.lights))

    def _expand_groups(self):
        groups = [g for g in self.bridge.groups if g.name in self.groups]
        for g in groups:
            for l in g.lights:
                self.lights += [l.name]

    def _init_animations(self):
        self.animations = {
            'groups': {},
            'lights': {},
        }

        for g in self.bridge.groups:
            self.animations['groups'][g.group_id] = None
        for l in self.bridge.lights:
            self.animations['lights'][l.light_id] = None

    @action
    def connect(self):
        """
        Connect to the configured Hue bridge. If the device hasn't been paired
        yet, uncomment the ``.connect()`` and ``.get_api()`` lines and retry
        after clicking the pairing button on your bridge.
        """

        # Lazy init
        if not self.bridge:
            from phue import Bridge, PhueRegistrationException
            success = False
            n_tries = 0

            while not success:
                try:
                    n_tries += 1
                    self.bridge = Bridge(self.bridge_address)
                    success = True
                except PhueRegistrationException as e:
                    self.logger.warning('Bridge registration error: {}'.
                                        format(str(e)))

                    if n_tries >= self._MAX_RECONNECT_TRIES:
                        self.logger.error(('Bridge registration failed after ' +
                                           '{} attempts').format(n_tries))
                        break

                    time.sleep(self._BRIDGE_RECONNECT_SECONDS)

            self.logger.info('Bridge connected')
            self.get_scenes()
        else:
            self.logger.info('Bridge already connected')

    @action
    def get_scenes(self):
        """
        Get the available scenes on the devices.

        :returns: The scenes configured on the bridge.

        Example output::

            {
                "scene-id-1": {
                    "name": "Scene 1",
                    "lights": [
                        "1",
                        "3"
                    ],

                    "owner": "owner-id",
                    "recycle": true,
                    "locked": false,
                    "appdata": {},
                    "picture": "",
                    "lastupdated": "2018-06-01T00:00:00",
                    "version": 1
                }
            }

        """

        return self.bridge.get_scene()

    @action
    def get_lights(self):
        """
        Get the configured lights.

        :returns: List of available lights as id->dict.

        Example::

            {
                "1": {
                    "state": {
                        "on": true,
                        "bri": 254,
                        "hue": 1532,
                        "sat": 215,
                        "effect": "none",
                        "xy": [
                            0.6163,
                            0.3403
                        ],

                        "ct": 153,
                        "alert": "none",
                        "colormode": "hs",
                        "reachable": true
                    },

                    "type": "Extended color light",
                    "name": "Lightbulb 1",
                    "modelid": "LCT001",
                    "manufacturername": "Philips",
                    "uniqueid": "00:11:22:33:44:55:66:77-88",
                    "swversion": "5.105.0.21169"
                }
            }

        """

        return self.bridge.get_light()

    @action
    def get_groups(self):
        """
        Get the list of configured light groups.

        :returns: List of configured light groups as id->dict.

        Example::

            {
                "1": {
                    "name": "Living Room",
                    "lights": [
                        "16",
                        "13",
                        "12",
                        "11",
                        "10",
                        "9",
                        "1",
                        "3"
                    ],

                    "type": "Room",
                    "state": {
                        "all_on": true,
                        "any_on": true
                    },

                    "class": "Living room",
                    "action": {
                        "on": true,
                        "bri": 241,
                        "hue": 37947,
                        "sat": 221,
                        "effect": "none",
                        "xy": [
                            0.2844,
                            0.2609
                        ],

                        "ct": 153,
                        "alert": "none",
                        "colormode": "hs"
                    }
                }
            }

        """

        return self.bridge.get_group()

    @action
    def get_animations(self):
        """
        Get the list of running light animations.

        :returns: dict.

        Structure::

            {
                "groups": {
                    "id_1": {
                        "type": "color_transition",
                        "hue_range": [0,65535],
                        "sat_range": [0,255],
                        "bri_range": [0,255],
                        "hue_step": 10,
                        "sat_step": 10,
                        "bri_step": 2,
                        "transition_seconds": 2

                    }

                },

                "lights": {
                    "id_1": {}

                }

            }

        """

        return self.animations

    def _exec(self, attr, *args, **kwargs):
        try:
            self.connect()
            self.stop_animation()
        except Exception as e:
            # Reset bridge connection
            self.bridge = None
            raise e

        lights = []; groups = []
        if 'lights' in kwargs:
            lights = kwargs.pop('lights').split(',').strip() \
                if isinstance(lights, str) else kwargs.pop('lights')
        if 'groups' in kwargs:
            groups = kwargs.pop('groups').split(',').strip() \
                if isinstance(groups, str) else kwargs.pop('groups')

        if not lights and not groups:
            lights = self.lights
            groups = self.groups

        if not self.bridge:
            self.connect()

        try:
            if attr == 'scene':
                self.bridge.run_scene(groups[0], kwargs.pop('name'))
            else:
                if groups:
                    self.bridge.set_group(groups, attr, *args, **kwargs)
                if lights:
                    self.bridge.set_light(lights, attr, *args, **kwargs)
        except Exception as e:
            # Reset bridge connection
            self.bridge = None
            raise e

    @action
    def set_light(self, light, **kwargs):
        """
        Set a light (or lights) property.

        :param light: Light or lights to set. Can be a string representing the light name,
            a light object, a list of string, or a list of light objects.
        :param kwargs: key-value list of parameters to set.

        Example call::

            {
                "type": "request",
                "target": "hostname",
                "action": "light.hue.set_light",
                "args": {
                    "light": "Bulb 1",
                    "sat": 255
                }
            }

        """

        self.connect()
        self.bridge.set_light(light, **kwargs)

    @action
    def set_group(self, group, **kwargs):
        """
        Set a group (or groups) property.

        :param group: Group or groups to set. Can be a string representing the group name, a group object, a list of strings, or a list of group objects.
        :param kwargs: key-value list of parameters to set.

        Example call::

            {
                "type": "request",
                "target": "hostname",
                "action": "light.hue.set_group",
                "args": {
                    "light": "Living Room",
                    "sat": 255
                }
            }

        """

        self.connect()
        self.bridge.set_group(group, **kwargs)

    @action
    def on(self, lights=None, groups=None, **kwargs):
        """
        Turn lights/groups on.

        :param lights: Lights to turn on (names or light objects). Default: plugin default lights
        :param groups: Groups to turn on (names or group objects). Default: plugin default groups
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('on', True, lights=lights, groups=groups, **kwargs)

    @action
    def off(self, lights=None, groups=None, **kwargs):
        """
        Turn lights/groups off.

        :param lights: Lights to turn off (names or light objects). Default: plugin default lights
        :param groups: Groups to turn off (names or group objects). Default: plugin default groups
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('on', False, lights=lights, groups=groups, **kwargs)

    @action
    def toggle(self, lights=None, groups=None, **kwargs):
        """
        Toggle lights/groups on/off.

        :param lights: Lights to turn off (names or light objects). Default: plugin default lights
        :param groups: Groups to turn off (names or group objects). Default: plugin default groups
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        lights_on  = []
        lights_off = []
        groups_on  = []
        groups_off = []

        if groups:
            all_groups = self.bridge.get_group().values()

            groups_on = [
                group['name'] for group in all_groups
                if group['name'] in groups and group['state']['any_on'] is True
            ]

            groups_off = [
                group['name'] for group in all_groups
                if group['name'] in groups and group['state']['any_on'] is False
            ]

        if not groups and not lights:
            lights = self.lights

        if lights:
            all_lights = self.bridge.get_light().values()

            lights_on = [
                light['name'] for light in all_lights
                if light['name'] in lights and light['state']['on'] is True
            ]

            lights_off = [
                light['name'] for light in all_lights
                if light['name'] in lights and light['state']['on'] is False
            ]

        if lights_on or groups_on:
            self._exec('on', False, lights=lights_on, groups=groups_on, **kwargs)

        if lights_off or groups_off:
            self._exec('on', True, lights=lights_off, groups=groups_off, **kwargs)

    @action
    def bri(self, value, lights=None, groups=None, **kwargs):
        """
        Set lights/groups brightness.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param value: Brightness value (range: 0-255)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('bri', int(value) % (self.MAX_BRI+1),
                          lights=lights, groups=groups, **kwargs)

    @action
    def sat(self, value, lights=None, groups=None, **kwargs):
        """
        Set lights/groups saturation.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param value: Saturation value (range: 0-255)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('sat', int(value) % (self.MAX_SAT+1),
                          lights=lights, groups=groups, **kwargs)

    @action
    def hue(self, value, lights=None, groups=None, **kwargs):
        """
        Set lights/groups color hue.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param value: Hue value (range: 0-65535)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('hue', int(value) % (self.MAX_HUE+1),
                          lights=lights, groups=groups, **kwargs)

    @action
    def xy(self, value, lights=None, groups=None, **kwargs):
        """
        Set lights/groups XY colors.

        :param value: xY value
        :type value: list[float] containing the two values
        """
        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('xy', value, lights=lights, groups=groups, **kwargs)

    @action
    def ct(self, value, lights=None, groups=None, **kwargs):
        """
        Set lights/groups color temperature.

        :param value: Temperature value (range: 0-255)
        :type value: int
        """
        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('ct', value, lights=lights, groups=groups, **kwargs)

    @action
    def delta_bri(self, delta, lights=None, groups=None, **kwargs):
        """
        Change lights/groups brightness by a delta [-100, 100] compared to the current state.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param delta: Brightness delta value (range: -100, 100)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        bri = 0

        if lights:
            bri = statistics.mean([
                light['state']['bri']
                for light in self.bridge.get_light().values()
                if light['name'] in lights
            ])
        elif groups:
            bri = statistics.mean([
                group['action']['bri']
                for group in self.bridge.get_group().values()
                if group['name'] in groups
            ])
        else:
            bri = statistics.mean([
                light['state']['bri']
                for light in self.bridge.get_light().values()
                if light['name'] in self.lights
            ])

        delta *= (self.MAX_BRI/100)
        if bri+delta < 0:
            bri = 0
        elif bri+delta > self.MAX_BRI:
            bri = self.MAX_BRI
        else:
            bri += delta

        return self._exec('bri', int(bri), lights=lights, groups=groups, **kwargs)

    @action
    def delta_sat(self, delta, lights=None, groups=None, **kwargs):
        """
        Change lights/groups saturation by a delta [-100, 100] compared to the current state.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param delta: Saturation delta value (range: -100, 100)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        sat = 0

        if lights:
            sat = statistics.mean([
                light['state']['sat']
                for light in self.bridge.get_light().values()
                if light['name'] in lights
            ])
        elif groups:
            sat = statistics.mean([
                group['action']['sat']
                for group in self.bridge.get_group().values()
                if group['name'] in groups
            ])
        else:
            sat = statistics.mean([
                light['state']['sat']
                for light in self.bridge.get_light().values()
                if light['name'] in self.lights
            ])

        delta *= (self.MAX_SAT/100)
        if sat+delta < 0:
            sat = 0
        elif sat+delta > self.MAX_SAT:
            sat = self.MAX_SAT
        else:
            sat += delta

        return self._exec('sat', int(sat), lights=lights, groups=groups, **kwargs)

    @action
    def delta_hue(self, delta, lights=None, groups=None, **kwargs):
        """
        Change lights/groups hue by a delta [-100, 100] compared to the current state.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param delta: Hue delta value (range: -100, 100)
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        hue = 0

        if lights:
            hue = statistics.mean([
                light['state']['hue']
                for light in self.bridge.get_light().values()
                if light['name'] in lights
            ])
        elif groups:
            hue = statistics.mean([
                group['action']['hue']
                for group in self.bridge.get_group().values()
                if group['name'] in groups
            ])
        else:
            hue = statistics.mean([
                light['state']['hue']
                for light in self.bridge.get_light().values()
                if light['name'] in self.lights
            ])

        delta *= (self.MAX_HUE/100)
        if hue+delta < 0:
            hue = 0
        elif hue+delta > self.MAX_HUE:
            hue = self.MAX_HUE
        else:
            hue += delta

        return self._exec('hue', int(hue), lights=lights, groups=groups, **kwargs)

    @action
    def scene(self, name, lights=None, groups=None, **kwargs):
        """
        Set a scene by name.

        :param lights: Lights to control (names or light objects). Default: plugin default lights
        :param groups: Groups to control (names or group objects). Default: plugin default groups
        :param name: Name of the scene
        """

        if groups is None:
            groups = []
        if lights is None:
            lights = []
        return self._exec('scene', name=name, lights=lights, groups=groups, **kwargs)

    @action
    def is_animation_running(self):
        """
        :returns: True if there is an animation running, false otherwise.
        """

        return self.animation_thread is not None

    @action
    def stop_animation(self):
        """
        Stop a running animation if any
        """

        if self.animation_thread and self.animation_thread.is_alive():
            redis = self._get_redis()
            redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
            self._init_animations()

    @action
    def animate(self, animation, duration=None,
                hue_range=None, sat_range=None,
                bri_range=None, lights=None, groups=None,
                hue_step=1000, sat_step=2, bri_step=1, transition_seconds=1.0):
        """
        Run a lights animation.

        :param animation: Animation name. Supported types: **color_transition** and **blink**
        :type animation: str

        :param duration: Animation duration in seconds (default: None, i.e. continue until stop)
        :type duration: float

        :param hue_range: If you selected a color color_transition.html, this will specify the hue range of your color color_transition.html.
            Default: [0, 65535]
        :type hue_range: list[int]

        :param sat_range: If you selected a color color_transition.html, this will specify the saturation range of your color
            color_transition.html. Default: [0, 255]
        :type sat_range: list[int]

        :param bri_range: If you selected a color color_transition.html, this will specify the brightness range of your color
            color_transition.html. Default: [254, 255] :type bri_range: list[int]

        :param lights: Lights to control (names, IDs or light objects). Default: plugin default lights
        :param groups: Groups to control (names, IDs or group objects). Default: plugin default groups

        :param hue_step: If you selected a color color_transition.html, this will specify by how much the color hue will change
            between iterations. Default: 1000 :type hue_step: int

        :param sat_step: If you selected a color color_transition.html, this will specify by how much the saturation will change
            between iterations. Default: 2 :type sat_step: int

        :param bri_step: If you selected a color color_transition.html, this will specify by how much the brightness will change
            between iterations. Default: 1 :type bri_step: int

        :param transition_seconds: Time between two transitions or blinks in seconds. Default: 1.0
        :type transition_seconds: float
        """

        if bri_range is None:
            bri_range = [self.MAX_BRI - 1, self.MAX_BRI]
        if sat_range is None:
            sat_range = [0, self.MAX_SAT]
        if hue_range is None:
            hue_range = [0, self.MAX_HUE]
        if groups:
            groups = [g for g in self.bridge.groups if g.name in groups or g.group_id in groups]
            lights = lights or []
            for g in groups:
                lights.extend([l.name for l in g.lights])
        elif lights:
            lights = [l.name for l in self.bridge.lights if l.name in lights or l.light_id in lights]
        else:
            lights = self.lights

        info = {
            'type': animation,
            'duration': duration,
            'hue_range': hue_range,
            'sat_range': sat_range,
            'bri_range': bri_range,
            'hue_step': hue_step,
            'sat_step': sat_step,
            'bri_step': bri_step,
            'transition_seconds': transition_seconds,
        }

        if groups:
            for g in groups:
                self.animations['groups'][g.group_id] = info

        for l in self.bridge.lights:
            if l.name in lights:
                self.animations['lights'][l.light_id] = info

        def _initialize_light_attrs(lights):
            if animation == self.Animation.COLOR_TRANSITION:
                return { l: {
                    'hue': random.randint(hue_range[0], hue_range[1]),
                    'sat': random.randint(sat_range[0], sat_range[1]),
                    'bri': random.randint(bri_range[0], bri_range[1]),
                } for l in lights }
            elif animation == self.Animation.BLINK:
                return { l: {
                    'on': True,
                    'bri': self.MAX_BRI,
                    'transitiontime': 0,
                } for l in lights }

        def _next_light_attrs(lights):
            if animation == self.Animation.COLOR_TRANSITION:
                for (light, attrs) in lights.items():
                    for (attr, value) in attrs.items():
                        attr_range = [0,0]
                        attr_step = 0

                        if attr == 'hue':
                            attr_range = hue_range
                            attr_step = hue_step
                        elif attr == 'bri':
                            attr_range = bri_range
                            attr_step = bri_step
                        elif attr == 'sat':
                            attr_range = sat_range
                            attr_step = sat_step

                        lights[light][attr] = ((value - attr_range[0] + attr_step) %
                                                (attr_range[1]-attr_range[0]+1)) + \
                                                attr_range[0]
            elif animation == self.Animation.BLINK:
                lights = { light: {
                    'on': False if attrs['on'] else True,
                    'bri': self.MAX_BRI,
                    'transitiontime': 0,
                } for (light, attrs) in lights.items() }

            return lights

        def _should_stop():
            try:
                redis = self._get_redis(transition_seconds)
                redis.blpop(self.ANIMATION_CTRL_QUEUE_NAME)
                return True
            except QueueTimeoutError:
                return False

        def _animate_thread(lights):
            set_thread_name('HueAnimate')
            self.logger.info('Starting {} animation'.format(
                animation, (lights or groups)))

            lights = _initialize_light_attrs(lights)
            animation_start_time = time.time()
            stop_animation = False

            while True:
                if stop_animation or \
                        (duration and time.time() - animation_start_time > duration):
                    break

                try:
                    if animation == self.Animation.COLOR_TRANSITION:
                        for (light, attrs) in lights.items():
                            self.logger.debug('Setting {} to {}'.format(light, attrs))
                            self.bridge.set_light(light, attrs)
                            stop_animation = _should_stop()
                            if stop_animation: break
                    elif animation == self.Animation.BLINK:
                        conf = lights[list(lights.keys())[0]]
                        self.logger.debug('Setting lights to {}'.format(conf))

                        if groups:
                            self.bridge.set_group([g.name for g in groups], conf)
                        else:
                            self.bridge.set_light(lights.keys(), conf)

                        stop_animation = _should_stop()
                        if stop_animation: break
                except Exception as e:
                    self.logger.warning(e)
                    time.sleep(2)

                lights = _next_light_attrs(lights)

            self.logger.info('Stopping animation')
            self.animation_thread = None
            self.redis = None

        self.stop_animation()
        self.animation_thread = Thread(target=_animate_thread,
                                       name='HueAnimate',
                                       args=(lights,))
        self.animation_thread.start()

    def _get_redis(self, socket_timeout=1.0):
        if not self.redis:
            redis_args = get_backend('redis').redis_args
            redis_args['socket_timeout'] = socket_timeout
            self.redis = Redis(**redis_args)
        return self.redis

    def status(self):
        # TODO
        pass
Beispiel #4
0
class PhillipsHueSkill(MycroftSkill):
    def __init__(self):
        super(PhillipsHueSkill, self).__init__(name="PhillipsHueSkill")
        self.brightness_step = int(self.settings.get('brightness_step'))
        self.color_temperature_step = \
            int(self.settings.get('color_temperature_step'))
        self.verbose = self.settings.get('verbose', False)
        self.username = self.settings.get('username')
        if self.username == '':
            self.username = None
        self.ip = None  # set in _connect_to_bridge
        self.bridge = None
        self.default_group = None
        self.groups_to_ids_map = dict()
        self.scenes_to_ids_map = dict()

    @property
    def connected(self):
        return self.bridge is not None

    @property
    def user_supplied_ip(self):
        return self.settings.get('ip') != ''

    @property
    def user_supplied_username(self):
        return self.settings.get('username') != ''

    def _register_with_bridge(self):
        """
        Helper for connecting to the bridge. If we don't
        have a valid username for the bridge (ip) we are trying
        to use, this will cause one to be generated.
        """
        self.speak_dialog('connect.to.bridge')
        i = 0
        while i < 30:
            sleep(1)
            try:
                self.bridge = Bridge(self.ip)
            except PhueRegistrationException:
                continue
            else:
                break
        if not self.connected:
            self.speak_dialog('failed.to.register')
        else:
            self.speak_dialog('successfully.registered')

    def _update_bridge_data(self):
        """
        This should be called any time a successful
        connection is established. It sets some
        member variables, and ensures that scenes and
        groups are registered as vocab.
        """
        self.username = self.bridge.username

        with self.file_system.open('username', 'w') as conf_file:
            conf_file.write(self.username)

        if not self.default_group:
            self._set_default_group(self.settings.get('default_group'))

        self._register_groups_and_scenes()

    def _attempt_connection(self):
        """
        This will attempt to connect to the bridge,
        but will not handle any errors on it's own.

        Raises
        ------
        UnauthorizedUserException
            If self.username is not None, and is not registered with the bridge
        """
        if self.user_supplied_ip:
            self.ip = self.settings.get('ip')
        else:
            self.ip = _discover_bridge()
        if self.username:
            url = 'http://{ip}/api/{user}'.format(ip=self.ip,
                                                  user=self.username)
            data = get(url).json()
            data = data[0] if isinstance(data, list) else data
            error = data.get('error')
            if error:
                description = error.get('description')
                if description == "unauthorized user":
                    raise UnauthorizedUserException(self.username)
                else:
                    raise Exception('Unknown Error: {0}'.format(description))

        self.bridge = Bridge(self.ip, self.username)

    def _connect_to_bridge(self, acknowledge_successful_connection=False):
        """
        Calls _attempt_connection, handling various exceptions
        by either alerting the user to issues with the config/setup,
        or registering the application with the bridge.

        Parameters
        ----------
        acknowledge_successful_connection : bool
            Speak when a successful connection is established.

        Returns
        -------
        bool
            True if a connection is established.

        """
        try:
            self._attempt_connection()
        except DeviceNotFoundException:
            self.speak_dialog('bridge.not.found')
            return False
        except ConnectionError:
            self.speak_dialog('failed.to.connect')
            if self.user_supplied_ip:
                self.speak_dialog('ip.in.config')
            return False
        except socket.error as e:
            if 'No route to host' in e.args:
                self.speak_dialog('no.route')
            else:
                self.speak_dialog('failed.to.connect')
            return False
        except UnauthorizedUserException:
            if self.user_supplied_username:
                self.speak_dialog('invalid.user')
                return False
            else:
                self._register_with_bridge()
        except PhueRegistrationException:
            self._register_with_bridge()

        if not self.connected:
            return False

        if acknowledge_successful_connection:
            self.speak_dialog('successfully.connected')

        self._update_bridge_data()

        return True

    def _set_default_group(self, identifier):
        """
        Sets the group to which commands will be applied, when
        a group is not specified in the command.

        Parameters
        ----------
        identifier : str or int
            The name of the group, or it's integer id

        Notes
        -----
        If the group does not exist, 0 (all lights) will be
        used.

        """
        try:
            self.default_group = Group(self.bridge, identifier)
        except LookupError:
            self.speak_dialog('could.not.find.group', {'name': identifier})
            self.speak_dialog('using.group.0')
            self.default_group = Group(self.bridge, 0)

    def _register_groups_and_scenes(self):
        """
        Register group and scene names as vocab,
        and update our caches.
        """
        groups = self.bridge.get_group()
        for id, group in groups.items():
            name = group['name'].lower()
            self.groups_to_ids_map[name] = id
            self.register_vocabulary(name, "Group")

        scenes = self.bridge.get_scene()
        for id, scene in scenes.items():
            name = scene['name'].lower()
            self.scenes_to_ids_map[name] = id
            self.register_vocabulary(name, "Scene")

    def initialize(self):
        """
        Attempt to connect to the bridge,
        and build/register intents.
        """
        self.load_data_files(dirname(__file__))

        if self.file_system.exists('username'):
            if not self.user_supplied_username:
                with self.file_system.open('username', 'r') as conf_file:
                    self.username = conf_file.read().strip(' \n')
            try:
                self._attempt_connection()
                self._update_bridge_data()
            except (PhueRegistrationException, DeviceNotFoundException,
                    UnauthorizedUserException, ConnectionError, socket.error):
                # Swallow it for now; _connect_to_bridge will deal with it
                pass

        toggle_intent = IntentBuilder("ToggleIntent") \
            .one_of("OffKeyword", "OnKeyword") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(toggle_intent, self.handle_toggle_intent)

        activate_scene_intent = IntentBuilder("ActivateSceneIntent") \
            .require("Scene") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(activate_scene_intent,
                             self.handle_activate_scene_intent)

        adjust_brightness_intent = IntentBuilder("AdjustBrightnessIntent") \
            .one_of("IncreaseKeyword", "DecreaseKeyword", "DimKeyword") \
            .one_of("Group", "LightsKeyword") \
            .optionally("BrightnessKeyword") \
            .build()
        self.register_intent(adjust_brightness_intent,
                             self.handle_adjust_brightness_intent)

        set_brightness_intent = IntentBuilder("SetBrightnessIntent") \
            .require("Value") \
            .one_of("Group", "LightsKeyword") \
            .optionally("BrightnessKeyword") \
            .build()
        self.register_intent(set_brightness_intent,
                             self.handle_set_brightness_intent)

        adjust_color_temperature_intent = \
            IntentBuilder("AdjustColorTemperatureIntent") \
            .one_of("IncreaseKeyword", "DecreaseKeyword") \
            .one_of("Group", "LightsKeyword") \
            .require("ColorTemperatureKeyword") \
            .build()
        self.register_intent(adjust_color_temperature_intent,
                             self.handle_adjust_color_temperature_intent)

        connect_lights_intent = \
            IntentBuilder("ConnectLightsIntent") \
            .require("ConnectKeyword") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(connect_lights_intent,
                             self.handle_connect_lights_intent)

    @intent_handler
    def handle_toggle_intent(self, message, group):
        if "OffKeyword" in message.data:
            dialog = 'turn.off'
            group.on = False
        else:
            dialog = 'turn.on'
            group.on = True
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_activate_scene_intent(self, message, group):
        scene_name = message.data['Scene'].lower()
        scene_id = self.scenes_to_ids_map[scene_name]
        if scene_id:
            if self.verbose:
                self.speak_dialog('activate.scene', {'scene': scene_name})
            self.bridge.activate_scene(group.group_id, scene_id)
        else:
            self.speak_dialog('scene.not.found', {'scene': scene_name})

    @intent_handler
    def handle_adjust_brightness_intent(self, message, group):
        if "IncreaseKeyword" in message.data:
            brightness = group.brightness + self.brightness_step
            group.brightness = \
                brightness if brightness < 255 else 254
            dialog = 'increase.brightness'
        else:
            brightness = group.brightness - self.brightness_step
            group.brightness = brightness if brightness > 0 else 0
            dialog = 'decrease.brightness'
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_set_brightness_intent(self, message, group):
        value = int(message.data['Value'].rstrip('%'))
        brightness = int(value / 100.0 * 254)
        group.on = True
        group.brightness = brightness
        if self.verbose:
            self.speak_dialog('set.brightness', {'brightness': value})

    @intent_handler
    def handle_adjust_color_temperature_intent(self, message, group):
        if "IncreaseKeyword" in message.data:
            color_temperature = \
                group.colortemp_k + self.color_temperature_step
            group.colortemp_k = \
                color_temperature if color_temperature < 6500 else 6500
            dialog = 'increase.color.temperature'
        else:
            color_temperature = \
                group.colortemp_k - self.color_temperature_step
            group.colortemp_k = \
                color_temperature if color_temperature > 2000 else 2000
            dialog = 'decrease.color.temperature'
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_connect_lights_intent(self, message, group):
        if self.user_supplied_ip:
            self.speak_dialog('ip.in.config')
            return
        if self.verbose:
            self.speak_dialog('connecting')
        self._connect_to_bridge(acknowledge_successful_connection=True)

    def stop(self):
        pass
Beispiel #5
0
class PhillipsHueSkill(MycroftSkill):

    def __init__(self):
        super(PhillipsHueSkill, self).__init__(name="PhillipsHueSkill")
        self.brightness_step = int(self.settings.get('brightness_step',
                                                     DEFAULT_BRIGHTNESS_STEP))
        self.color_temperature_step = \
            int(self.settings.get('color_temperature_step',
                                  DEFAULT_COLOR_TEMPERATURE_STEP))

        verbose = self.settings.get('verbose', False)
        if type(verbose) == str:
            verbose = verbose.lower()
            verbose = True if verbose == 'true' else False
        self.verbose = verbose
        converter = Converter()
        self.colors = {
                "red":    (65160, 254),
                "green":  (27975, 254),
                "blue":   (45908, 254),
                "pink":   (52673, 254),
                "violet": (48156, 254),
                "yellow": (10821, 254),
                "orange": ( 6308, 254),
                "white":  (41439,  81),
                }

        self.username = self.settings.get('username')
        if self.username == '':
            self.username = None

        self.ip = None  # set in _connect_to_bridge
        self.bridge = None
        self.default_group = None
        self.groups_to_ids_map = dict()
        self.scenes_to_ids_map = defaultdict(dict)

    @property
    def connected(self):
        return self.bridge is not None

    @property
    def user_supplied_ip(self):
        return self.settings.get('ip') != ''

    @property
    def user_supplied_username(self):
        return self.settings.get('username') != ''

    def _register_with_bridge(self):
        """
        Helper for connecting to the bridge. If we don't
        have a valid username for the bridge (ip) we are trying
        to use, this will cause one to be generated.
        """
        self.speak_dialog('connect.to.bridge')
        i = 0
        while i < 30:
            sleep(1)
            try:
                self.bridge = Bridge(self.ip)
            except PhueRegistrationException:
                continue
            else:
                break
        if not self.connected:
            self.speak_dialog('failed.to.register')
        else:
            self.speak_dialog('successfully.registered')

    def _update_bridge_data(self):
        """
        This should be called any time a successful
        connection is established. It sets some
        member variables, and ensures that scenes and
        groups are registered as vocab.
        """
        self.username = self.bridge.username

        with self.file_system.open('username', 'w') as conf_file:
            conf_file.write(self.username)

        if not self.default_group:
            self._set_default_group(self.settings.get('default_group'))

        self._register_groups_and_scenes()

    def _attempt_connection(self):
        """
        This will attempt to connect to the bridge,
        but will not handle any errors on it's own.

        Raises
        ------
        UnauthorizedUserException
            If self.username is not None, and is not registered with the bridge
        """
        if self.user_supplied_ip:
            self.ip = self.settings.get('ip')
        else:
            device = next(filter(lambda device : "Philips hue" in device.model_name, upnpclient.discover()))
            self.ip = urllib.parse.urlparse(device.location).hostname
        if self.username:
            url = 'http://{ip}/api/{user}'.format(ip=self.ip,
                                                  user=self.username)
            data = get(url).json()
            data = data[0] if isinstance(data, list) else data
            error = data.get('error')
            if error:
                description = error.get('description')
                if description == "unauthorized user":
                    raise UnauthorizedUserException(self.username)
                else:
                    raise Exception('Unknown Error: {0}'.format(description))

        self.bridge = Bridge(self.ip, self.username)

    def _connect_to_bridge(self, acknowledge_successful_connection=False):
        """
        Calls _attempt_connection, handling various exceptions
        by either alerting the user to issues with the config/setup,
        or registering the application with the bridge.

        Parameters
        ----------
        acknowledge_successful_connection : bool
            Speak when a successful connection is established.

        Returns
        -------
        bool
            True if a connection is established.

        """
        try:
            self._attempt_connection()
        except DeviceNotFoundException:
            self.speak_dialog('bridge.not.found')
            return False
        except ConnectionError:
            self.speak_dialog('failed.to.connect')
            if self.user_supplied_ip:
                self.speak_dialog('ip.in.config')
            return False
        except socket.error as e:
            if 'No route to host' in e.args:
                self.speak_dialog('no.route')
            else:
                self.speak_dialog('failed.to.connect')
            return False
        except UnauthorizedUserException:
            if self.user_supplied_username:
                self.speak_dialog('invalid.user')
                return False
            else:
                self._register_with_bridge()
        except PhueRegistrationException:
            self._register_with_bridge()

        if not self.connected:
            return False

        if acknowledge_successful_connection:
            self.speak_dialog('successfully.connected')

        self._update_bridge_data()

        return True

    def _set_default_group(self, identifier):
        """
        Sets the group to which commands will be applied, when
        a group is not specified in the command.

        Parameters
        ----------
        identifier : str or int
            The name of the group, or it's integer id

        Notes
        -----
        If the group does not exist, 0 (all lights) will be
        used.

        """
        try:
            self.default_group = Group(self.bridge, identifier)
        except LookupError:
            self.speak_dialog('could.not.find.group', {'name': identifier})
            self.speak_dialog('using.group.0')
            self.default_group = Group(self.bridge, 0)

    def _register_groups_and_scenes(self):
        """
        Register group and scene names as vocab,
        and update our caches.
        """
        groups = self.bridge.get_group()
        for id, group in groups.items():
            name = group['name'].lower()
            self.groups_to_ids_map[name] = id
            self.register_vocabulary(name, "Group")

        scenes = self.bridge.get_scene()
        for id, scene in scenes.items():
            name = scene['name'].lower()
            group_id = scene.get('group')
            group_id = int(group_id) if group_id else None
            self.scenes_to_ids_map[group_id][name] = id
            self.register_vocabulary(name, "Scene")

    def initialize(self):
        """
        Attempt to connect to the bridge,
        and build/register intents.
        """
        self.load_data_files(dirname(__file__))

        if self.file_system.exists('username'):
            if not self.user_supplied_username:
                with self.file_system.open('username', 'r') as conf_file:
                    self.username = conf_file.read().strip(' \n')
            try:
                self._attempt_connection()
                self._update_bridge_data()
            except (PhueRegistrationException,
                    DeviceNotFoundException,
                    UnauthorizedUserException,
                    ConnectionError,
                    socket.error):
                # Swallow it for now; _connect_to_bridge will deal with it
                pass

        self.register_intent_file("turn.on.intent", self.handle_turn_on_intent)
        self.register_intent_file("turn.off.intent", self.handle_turn_off_intent)
        self.register_intent_file("set.lights.intent", self.handle_set_lights_brightness_intent)
        self.register_intent_file("set.lights.scene.intent", self.handle_set_lights_scene_intent)
        self.register_intent_file("set.lights.color.intent", self.handle_set_lights_color_intent)

        # adjust_brightness_intent = IntentBuilder("AdjustBrightnessIntent") \
        #     .one_of("IncreaseKeyword", "DecreaseKeyword", "DimKeyword") \
        #     .one_of("Group", "LightsKeyword") \
        #     .optionally("BrightnessKeyword") \
        #     .build()
        # self.register_intent(adjust_brightness_intent,
        #                      self.handle_adjust_brightness_intent)

        # adjust_color_temperature_intent = \
        #     IntentBuilder("AdjustColorTemperatureIntent") \
        #     .one_of("IncreaseKeyword", "DecreaseKeyword") \
        #     .one_of("Group", "LightsKeyword") \
        #     .require("ColorTemperatureKeyword") \
        #     .build()
        # self.register_intent(adjust_color_temperature_intent,
        #                      self.handle_adjust_color_temperature_intent)

        # connect_lights_intent = \
        #     IntentBuilder("ConnectLightsIntent") \
        #     .require("ConnectKeyword") \
        #     .one_of("Group", "LightsKeyword") \
        #     .build()
        # self.register_intent(connect_lights_intent,
        #                      self.handle_connect_lights_intent)

    def _find_fuzzy(self, dictionary, value):
        result = process.extractOne(value, dictionary.keys())
        if result is None:
            return None
        (value, confidence) = result
        if confidence < 60:
            return None
        else:
            return dictionary[value]

    def _find_group(self, group_name):
        group_id = self._find_fuzzy(self.groups_to_ids_map, group_name)
        if group_id is not None:
            return Group(self.bridge, group_id)

    @get_group
    def handle_turn_on_intent(self, message, group):
        group.on = True

    @get_group
    def handle_turn_off_intent(self, message, group):
        group.on = False

    @get_group
    def handle_set_lights_brightness_intent(self, message, group):
        value = message.data.get('percent')
        value = int(value.rstrip('%'))
        if value == 0:
            group.on = False
        else:
            brightness = int(value / 100.0 * 254)
            group.on = True
            group.brightness = brightness
        if self.verbose:
            self.speak_dialog('set.brightness', {'brightness': value})


    @get_group
    def handle_set_lights_scene_intent(self, message, group):
        scene_name = message.data.get('scene')
        scene_id = self._find_fuzzy(self.scenes_to_ids_map[group.group_id], scene_name)
        if not scene_id:
            scene_id = self._find_fuzzy(self.scenes_to_ids_map[None], scene_name)
        if scene_id:
            if self.verbose:
                self.speak_dialog('activate.scene',
                                  {'scene': scene_name})
            self.bridge.activate_scene(group.group_id, scene_id)
        else:
            self.speak_dialog('scene.not.found',
                              {'scene': scene_name})

    @get_group
    def handle_set_lights_color_intent(self, message, group):
        color_name = message.data.get('color')
        (hue, sat) = self.colors[color_name]
        for light in group.lights:
            light.on = True
            light.hue = hue
            light.saturation = sat

    # TODO support
    # @intent_handler
    # def handle_adjust_brightness_intent(self, message, group):
    #     if "IncreaseKeyword" in message.data:
    #         brightness = group.brightness + self.brightness_step
    #         group.brightness = \
    #             brightness if brightness < 255 else 254
    #         dialog = 'increase.brightness'
    #     else:
    #         brightness = group.brightness - self.brightness_step
    #         group.brightness = brightness if brightness > 0 else 0
    #         dialog = 'decrease.brightness'
    #     if self.verbose:
    #         self.speak_dialog(dialog)


    # TODO support
    # @intent_handler
    # def handle_adjust_color_temperature_intent(self, message, group):
    #     if "IncreaseKeyword" in message.data:
    #         color_temperature = \
    #             group.colortemp_k + self.color_temperature_step
    #         group.colortemp_k = \
    #             color_temperature if color_temperature < 6500 else 6500
    #         dialog = 'increase.color.temperature'
    #     else:
    #         color_temperature = \
    #             group.colortemp_k - self.color_temperature_step
    #         group.colortemp_k = \
    #             color_temperature if color_temperature > 2000 else 2000
    #         dialog = 'decrease.color.temperature'
    #     if self.verbose:
    #         self.speak_dialog(dialog)

    # TODO support
    # @intent_handler
    # def handle_connect_lights_intent(self, message, group):
    #     if self.user_supplied_ip:
    #         self.speak_dialog('ip.in.config')
    #         return
    #     if self.verbose:
    #         self.speak_dialog('connecting')
    #     self._connect_to_bridge(acknowledge_successful_connection=True)

    def stop(self):
        pass
Beispiel #6
0
__author__ = 'sander'

from phue import Bridge

b = Bridge(ip="192.168.2.9")

#b.connect()

ap = b.get_api()
#grp = b.get_group(1)
scn = b.get_scene(u'e07a35d7e-on-0')
print scn
#b.set_light("Tafel",'on',True)
Beispiel #7
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 #8
0
class Component(ThreadComponent):
    MATRIX = matrices.LIGHT_BULB
    TRANSITION_TIME = 2  # * 100 milliseconds

    def __init__(self, component_config):
        super().__init__(component_config)

        self.bridge = Bridge(component_config['ip_address'],
                             component_config['username'])
        self.id = component_config['id']
        self.group_num = None
        self.scenes = {}
        self.first = component_config['first']

        # TODO: Created groups are not deleted when a Nuimo is removed
        self.nuimo_mac_address = component_config['nuimo_mac_address']

        global hue_instances
        global mac_idx

        if self.first and hue_instances != {} and self.nuimo_mac_address in hue_instances:
            temp = hue_instances[self.nuimo_mac_address]['mac_idx']
            hue_instances[self.nuimo_mac_address] = {}
            hue_instances[self.nuimo_mac_address]['mac_idx'] = temp

        if self.nuimo_mac_address not in hue_instances:
            hue_instances[self.nuimo_mac_address] = {}
            hue_instances[self.nuimo_mac_address]['mac_idx'] = mac_idx
            mac_idx = mac_idx + 1

        if self.id not in hue_instances[self.nuimo_mac_address]:
            hue_instances[self.nuimo_mac_address][self.id] = hue_instances[
                self.nuimo_mac_address]['mac_idx'] * 10 + len(
                    hue_instances[self.nuimo_mac_address])

        self.group_num = hue_instances[self.nuimo_mac_address][self.id]
        self.delta_range = range(-254, 254)
        self.delta = 0

        # Extract light IDs, they are stored with format `<bridgeID>-light-<lightID>`
        self.light_ids = component_config['device_ids']
        self.light_ids = [
            i.split('-light-')[1].strip() for i in self.light_ids
        ]

        self.lights = self.create_lights(self.light_ids)
        self.lights.update_state()

        self.station_id_1 = component_config.get('station1', None)
        self.station_id_2 = component_config.get('station2', None)
        self.station_id_3 = component_config.get('station3', None)

        if not any((self.station_id_1, self.station_id_2, self.station_id_3)):
            try:
                self.scenes = self.bridge.get_scene()
            except ConnectionResetError:
                logger.error("Hue Bridge not reachable, handle exception")
            except socket.error as socketerror:
                logger.error("Socket Error: ", socketerror)

            self.scenes = {
                k: v
                for k, v in self.scenes.items()
                if v['lights'] == self.light_ids
            }

            if len(list(self.scenes.keys())) >= 3:
                for scene in self.scenes:
                    self.station_id_1 = {
                        'name': self.scenes[scene]['name']
                    } if self.scenes[scene][
                        'name'] == 'Nightlight' else self.station_id_1
                    self.station_id_2 = {
                        'name': self.scenes[scene]['name']
                    } if self.scenes[scene][
                        'name'] == 'Relax' else self.station_id_2
                    self.station_id_3 = {
                        'name': self.scenes[scene]['name']
                    } if self.scenes[scene][
                        'name'] == 'Concentrate' else self.station_id_3

    def create_lights(self, light_ids):
        reachable_lights = None
        try:
            reachable_lights = self.filter_reachable(light_ids)
        except ConnectionResetError:
            # TODO: add a library wrapper to handle the issue properly, this is a workaround
            logger.error("Hue Bridge not reachable, handle exception")
        except socket.error as socketerror:
            logger.error("Socket Error: ", socketerror)
        if not reachable_lights:
            lights = EmptyLightSet()
        elif len(reachable_lights) > 10:
            lights = Group(self.bridge, reachable_lights, self.group_num,
                           self.first)
        else:
            lights = LightSet(self.bridge, reachable_lights, self.group_num,
                              self.first)

        return lights

    def filter_reachable(self, light_ids):
        lights = self.bridge.get_light()
        reachable = [
            i for i in light_ids
            if i in lights and lights[i]['state']['reachable']
        ]
        logger.debug("lights: %s reachable: %s", list(lights.keys()),
                     reachable)
        return reachable

    def on_button_press(self):
        self.set_light_attributes(on=not self.lights.on,
                                  bri=self.lights.brightness)

    def on_longtouch_left(self):
        logger.debug("on_longtouch_left()")
        if self.station_id_1 is not None:
            self.set_station(1, self.station_id_1['name'])
            self.nuimo.display_matrix(matrices.STATION1)

    def on_longtouch_bottom(self):
        logger.debug("on_longtouch_bottom()")
        if self.station_id_2 is not None:
            self.set_station(2, self.station_id_2['name'])
            self.nuimo.display_matrix(matrices.STATION2)

    def on_longtouch_right(self):
        logger.debug("on_longtouch_right()")
        if self.station_id_3 is not None:
            self.set_station(3, self.station_id_3['name'])
            self.nuimo.display_matrix(matrices.STATION3)

    def set_light_attributes(self, **attributes):
        response = self.lights.set_attributes(attributes)

        if 'errors' in response:
            logger.error("Failed to set light attributes: %s",
                         response['errors'])
            if response['errors'][0][
                    'description'] == "parameter, bri, is not modifiable. Device is set to off.":
                pass
            else:
                self.nuimo.display_matrix(matrices.ERROR)
            return

        if 'xy' in attributes:
            if 'bri' in attributes:
                self.nuimo.display_matrix(matrices.LETTER_W)
            else:
                self.nuimo.display_matrix(matrices.SHUFFLE)

        elif 'on' in attributes and not ('bri_inc' in attributes):
            if self.lights.on:
                self.nuimo.display_matrix(matrices.LIGHT_ON)
            else:
                self.nuimo.display_matrix(matrices.LIGHT_OFF)

        elif 'on' in attributes or 'bri_inc' in attributes:
            if self.lights.brightness:
                matrix = matrices.progress_bar(self.lights.brightness /
                                               self.delta_range.stop)
                self.nuimo.display_matrix(matrix,
                                          fading=True,
                                          ignore_duplicates=True)
            else:
                self.set_light_attributes(on=False)

    def on_swipe_left(self):
        self.set_light_attributes(on=True,
                                  bri=self.lights.brightness,
                                  xy=COLOR_WHITE_XY)

    def on_swipe_right(self):
        self.set_light_attributes(on=True, xy=(random(), random()))

    def on_rotation(self, value):
        self.delta += value

    def run(self):
        prev_sync_time = time()
        prev_update_time = time()

        while not self.stopped:
            now = time()

            if self.delta and now - prev_update_time >= self.lights.update_interval:
                self.send_updates()
                self.delta = 0

                prev_update_time = now

            if now - max([prev_sync_time, prev_update_time
                          ]) >= self.lights.sync_interval:
                try:
                    self.lights.update_state()
                except ConnectionResetError:
                    # TODO: add a library wrapper to handle the issue properly, this is a workaround
                    logger.error(
                        "connection with Hue Bridge reset by peer, handle exception"
                    )
                except socket.error as socketerror:
                    logger.error("Socket Error: ", socketerror)

                prev_sync_time = now

            sleep(0.05)

    def set_station(self, station_number, station_name):
        light_attr = cps.CUSTOM_SCENES['scenes'][station_name]['lightstates']
        logger.info(self.light_ids)

        for l in self.light_ids:
            self.bridge.set_light(int(l),
                                  light_attr,
                                  transitiontime=self.TRANSITION_TIME)

    def send_updates(self):
        delta = round(
            clamp_value(self.delta_range.stop * self.delta, self.delta_range))

        if self.lights.on:
            self.set_light_attributes(bri_inc=delta)
        else:
            if delta > 0:
                self.set_light_attributes(on=True)
                self.set_light_attributes(bri_inc=delta)
Beispiel #9
0
    lights_diction = bridge_bureau.get_light_objects(mode="name")
    
    # make a Diction of all groups by key= name and value = [lights]
    group_diction = bridge_bureau.get_group()
    group_dic_name_lights = {}
   
    # make a diction of all sensors
    sensor_diction = bridge_bureau.get_sensor()
    for x in sensor_diction :
        if sensor_diction[x]["name"]=="Hal kamer Gust" :
           pass
            

    # make a diction of all scenes

    scene_diction = bridge_bureau.get_scene()
    scene_dic_name_lights = {}
    
    # Read list of switches out of text file switches.txt
    # txt files ends with "end"
    # connected_switches is a list of list with name switch, io port, group name

    with open ("switches.txt") as switches_file:
       read_switches =[ line for line in switches_file if line != "end"]
       connected_switches = [json.loads(x) for x in read_switches]
       print (connected_switches)

    # Create Switch Objects based on info in switches.txt   
    # switches[] is the list with all the switch objects
    switches = []
    for x in connected_switches :
Beispiel #10
0
class PhillipsHueSkill(MycroftSkill):
    def __init__(self):
        super(PhillipsHueSkill, self).__init__(name="PhillipsHueSkill")
        self.brightness_step = int(
            self.settings.get('brightness_step', DEFAULT_BRIGHTNESS_STEP))
        self.color_temperature_step = \
            int(self.settings.get('color_temperature_step',
                                  DEFAULT_COLOR_TEMPERATURE_STEP))

        verbose = self.settings.get('verbose', False)
        if type(verbose) == str:
            verbose = verbose.lower()
            verbose = True if verbose == 'true' else False
        self.verbose = verbose

        self.username = self.settings.get('username')
        if self.username == '':
            self.username = None

        self.ip = None  # set in _connect_to_bridge
        self.bridge = None
        self.default_group = None
        self.groups_to_ids_map = dict()
        self.scenes_to_ids_map = defaultdict(dict)
        self.colors_to_cie_color_map = self._map_colors_to_cie_colors(
            os.path.join(os.path.dirname(os.path.realpath(__file__)),
                         "colors"))

    @property
    def connected(self):
        return self.bridge is not None

    @property
    def user_supplied_ip(self):
        return self.settings.get('ip') != ''

    @property
    def user_supplied_username(self):
        return self.settings.get('username') != ''

    def _register_with_bridge(self):
        """
        Helper for connecting to the bridge. If we don't
        have a valid username for the bridge (ip) we are trying
        to use, this will cause one to be generated.
        """
        self.speak_dialog('connect.to.bridge')
        i = 0
        while i < 30:
            sleep(1)
            try:
                self.bridge = Bridge(self.ip)
            except PhueRegistrationException:
                continue
            else:
                break
        if not self.connected:
            self.speak_dialog('failed.to.register')
        else:
            self.speak_dialog('successfully.registered')

    def _update_bridge_data(self):
        """
        This should be called any time a successful
        connection is established. It sets some
        member variables, and ensures that scenes and
        groups are registered as vocab.
        """
        self.username = self.bridge.username

        with self.file_system.open('username', 'w') as conf_file:
            conf_file.write(self.username)

        if not self.default_group:
            self._set_default_group(self.settings.get('default_group'))

        self._register_groups_and_scenes()
        self._register_colors()

    def _attempt_connection(self):
        """
        This will attempt to connect to the bridge,
        but will not handle any errors on it's own.

        Raises
        ------
        UnauthorizedUserException
            If self.username is not None, and is not registered with the bridge
        """
        if self.user_supplied_ip:
            self.ip = self.settings.get('ip')
        else:
            self.ip = _discover_bridge()
        if self.username:
            url = 'http://{ip}/api/{user}'.format(ip=self.ip,
                                                  user=self.username)
            data = get(url).json()
            data = data[0] if isinstance(data, list) else data
            error = data.get('error')
            if error:
                description = error.get('description')
                if description == "unauthorized user":
                    raise UnauthorizedUserException(self.username)
                else:
                    raise Exception('Unknown Error: {0}'.format(description))

        self.bridge = Bridge(self.ip, self.username)

    def _connect_to_bridge(self, acknowledge_successful_connection=False):
        """
        Calls _attempt_connection, handling various exceptions
        by either alerting the user to issues with the config/setup,
        or registering the application with the bridge.

        Parameters
        ----------
        acknowledge_successful_connection : bool
            Speak when a successful connection is established.

        Returns
        -------
        bool
            True if a connection is established.

        """
        try:
            self._attempt_connection()
        except DeviceNotFoundException:
            self.speak_dialog('bridge.not.found')
            return False
        except ConnectionError:
            self.speak_dialog('failed.to.connect')
            if self.user_supplied_ip:
                self.speak_dialog('ip.in.config')
            return False
        except socket.error as e:
            if 'No route to host' in e.args:
                self.speak_dialog('no.route')
            else:
                self.speak_dialog('failed.to.connect')
            return False
        except UnauthorizedUserException:
            if self.user_supplied_username:
                self.speak_dialog('invalid.user')
                return False
            else:
                self._register_with_bridge()
        except PhueRegistrationException:
            self._register_with_bridge()

        if not self.connected:
            return False

        if acknowledge_successful_connection:
            self.speak_dialog('successfully.connected')

        self._update_bridge_data()

        return True

    def _set_default_group(self, identifier):
        """
        Sets the group to which commands will be applied, when
        a group is not specified in the command.

        Parameters
        ----------
        identifier : str or int
            The name of the group, or it's integer id

        Notes
        -----
        If the group does not exist, 0 (all lights) will be
        used.

        """
        try:
            self.default_group = Group(self.bridge, identifier)
        except LookupError:
            self.speak_dialog('could.not.find.group', {'name': identifier})
            self.speak_dialog('using.group.0')
            self.default_group = Group(self.bridge, 0)

    def _register_groups_and_scenes(self):
        """
        Register group and scene names as vocab,
        and update our caches.
        """
        groups = self.bridge.get_group()
        for id, group in groups.items():
            name = group['name'].lower()
            self.groups_to_ids_map[name] = id
            self.register_vocabulary(name, "Group")

        scenes = self.bridge.get_scene()
        for id, scene in scenes.items():
            name = scene['name'].lower()
            group_id = scene.get('group')
            group_id = int(group_id) if group_id else None
            self.scenes_to_ids_map[group_id][name] = id
            self.register_vocabulary(name, "Scene")

    def initialize(self):
        """
        Attempt to connect to the bridge,
        and build/register intents.
        """
        self.load_data_files(dirname(__file__))

        if self.file_system.exists('username'):
            if not self.user_supplied_username:
                with self.file_system.open('username', 'r') as conf_file:
                    self.username = conf_file.read().strip(' \n')
            try:
                self._attempt_connection()
                self._update_bridge_data()
            except (PhueRegistrationException, DeviceNotFoundException,
                    UnauthorizedUserException, ConnectionError, socket.error):
                # Swallow it for now; _connect_to_bridge will deal with it
                pass

        toggle_intent = IntentBuilder("ToggleIntent") \
            .one_of("OffKeyword", "OnKeyword") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(toggle_intent, self.handle_toggle_intent)

        activate_scene_intent = IntentBuilder("ActivateSceneIntent") \
            .require("Scene") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(activate_scene_intent,
                             self.handle_activate_scene_intent)

        adjust_brightness_intent = IntentBuilder("AdjustBrightnessIntent") \
            .one_of("IncreaseKeyword", "DecreaseKeyword", "DimKeyword") \
            .one_of("Group", "LightsKeyword") \
            .optionally("BrightnessKeyword") \
            .build()
        self.register_intent(adjust_brightness_intent,
                             self.handle_adjust_brightness_intent)

        set_brightness_intent = IntentBuilder("SetBrightnessIntent") \
            .require("Value") \
            .one_of("Group", "LightsKeyword") \
            .optionally("BrightnessKeyword") \
            .build()
        self.register_intent(set_brightness_intent,
                             self.handle_set_brightness_intent)

        adjust_color_temperature_intent = \
            IntentBuilder("AdjustColorTemperatureIntent") \
            .one_of("IncreaseKeyword", "DecreaseKeyword") \
            .one_of("Group", "LightsKeyword") \
            .require("ColorTemperatureKeyword") \
            .build()
        self.register_intent(adjust_color_temperature_intent,
                             self.handle_adjust_color_temperature_intent)

        connect_lights_intent = \
            IntentBuilder("ConnectLightsIntent") \
            .require("ConnectKeyword") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(connect_lights_intent,
                             self.handle_connect_lights_intent)

        change_color_intent = \
            IntentBuilder("ChangeLightColorIntent") \
            .require("Color") \
            .one_of("Group", "LightsKeyword") \
            .build()
        self.register_intent(change_color_intent,
                             self.handle_change_color_intent)

    @intent_handler
    def handle_toggle_intent(self, message, group):
        if "OffKeyword" in message.data:
            dialog = 'turn.off'
            group.on = False
        else:
            dialog = 'turn.on'
            group.on = True
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_activate_scene_intent(self, message, group):
        scene_name = message.data['Scene'].lower()
        scene_id = self.scenes_to_ids_map[group.group_id].get(scene_name)
        if not scene_id:
            scene_id = self.scenes_to_ids_map[None].get(scene_name)
        if scene_id:
            if self.verbose:
                self.speak_dialog('activate.scene', {'scene': scene_name})
            self.bridge.activate_scene(group.group_id, scene_id)
        else:
            self.speak_dialog('scene.not.found', {'scene': scene_name})

    @intent_handler
    def handle_adjust_brightness_intent(self, message, group):
        if "IncreaseKeyword" in message.data:
            brightness = group.brightness + self.brightness_step
            group.brightness = \
                brightness if brightness < 255 else 254
            dialog = 'increase.brightness'
        else:
            brightness = group.brightness - self.brightness_step
            group.brightness = brightness if brightness > 0 else 0
            dialog = 'decrease.brightness'
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_set_brightness_intent(self, message, group):
        value = int(message.data['Value'].rstrip('%'))
        brightness = int(value / 100.0 * 254)
        group.on = True
        group.brightness = brightness
        if self.verbose:
            self.speak_dialog('set.brightness', {'brightness': value})

    @intent_handler
    def handle_adjust_color_temperature_intent(self, message, group):
        if "IncreaseKeyword" in message.data:
            color_temperature = \
                group.colortemp_k + self.color_temperature_step
            group.colortemp_k = \
                color_temperature if color_temperature < 6500 else 6500
            dialog = 'increase.color.temperature'
        else:
            color_temperature = \
                group.colortemp_k - self.color_temperature_step
            group.colortemp_k = \
                color_temperature if color_temperature > 2000 else 2000
            dialog = 'decrease.color.temperature'
        if self.verbose:
            self.speak_dialog(dialog)

    @intent_handler
    def handle_connect_lights_intent(self, message, group):
        if self.user_supplied_ip:
            self.speak_dialog('ip.in.config')
            return
        if self.verbose:
            self.speak_dialog('connecting')
        self._connect_to_bridge(acknowledge_successful_connection=True)

    @intent_handler
    def handle_change_color_intent(self, message, group):
        if message.data["Color"] in self.colors_to_cie_color_map:
            group.xy = self.colors_to_cie_color_map[message.data["Color"]]
            dialog = "change.color"
        else:
            dialog = "color.not.found"

        if self.verbose:
            self.speak_dialog(dialog, {"color": message.data["Color"]})

    def stop(self):
        pass

    def _map_colors_to_cie_colors(self, color_files_directory_path):
        cie_colors_map = dict()
        try:
            colors_json = json.load(
                open(
                    os.path.join(color_files_directory_path,
                                 "color-definitions.json")))

            if self.lang.startswith("en"):
                for color_name, rgb_values in colors_json.items():
                    cie_colors_map[color_name] = self._rgb_to_cie(
                        rgb_values[0], rgb_values[1], rgb_values[2])
            else:
                color_mapping = json.load(
                    open(
                        self._get_color_file_path(color_files_directory_path)))
                for foreign_color_name, english_color_name in color_mapping.items(
                ):
                    if english_color_name in colors_json:
                        rgb_values = colors_json[english_color_name]
                        cie_colors_map[foreign_color_name] = self._rgb_to_cie(
                            rgb_values[0], rgb_values[1], rgb_values[2])

        except Exception as e:
            LOGGER.error(e)
        return cie_colors_map

    def _get_color_file_path(self, color_files_directory_path):
        file_path = os.path.join(color_files_directory_path,
                                 self.lang.split("-")[0] + "-colors.json")
        return file_path if os.path.isfile(file_path) else ""

    def _register_colors(self):
        for color_name in self.colors_to_cie_color_map:
            self.register_vocabulary(color_name, "Color")

    """
    This function is based on the project https://github.com/usolved/cie-rgb-converter which is licensed under MIT license.

    MIT License

    Copyright (c) 2017 www.usolved.net

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    """

    def _rgb_to_cie(self, red, green, blue):
        red = pow((red + 0.055) /
                  (1.0 + 0.055), 2.4) if (red > 0.04045) else (red / 12.92)
        green = pow(
            (green + 0.055) /
            (1.0 + 0.055), 2.4) if (green > 0.04045) else (green / 12.92)
        blue = pow((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

        x = round((X / (X + Y + Z)), 4)
        y = round((Y / (X + Y + Z)), 4)

        return [x, y]
Beispiel #11
0
__author__ = 'sander'

from phue import Bridge


b=Bridge(ip="192.168.2.9")


#b.connect()

ap = b.get_api()
#grp = b.get_group(1)
scn = b.get_scene(u'e07a35d7e-on-0')
print scn
#b.set_light("Tafel",'on',True)
Beispiel #12
0
class LightHuePlugin(LightPlugin):
    """ Philips Hue lights plugin """

    MAX_BRI = 255
    MAX_SAT = 255
    MAX_HUE = 65535

    def __init__(self, bridge, lights=None, groups=None):
        """
        Constructor
        Params:
            bridge -- Bridge address or hostname
            lights -- Lights to be controlled (default: all)
            groups -- Groups to be controlled (default: all)
        """

        super().__init__()

        self.bridge_address = bridge
        self.bridge = None
        logging.info('Initializing Hue lights plugin - bridge: "{}"'.format(
            self.bridge_address))

        self.connect()
        self.lights = []
        self.groups = []

        if lights:
            self.lights = lights
        elif groups:
            self.groups = groups
            self._expand_groups()
        else:
            self.lights = [l.name for l in self.bridge.lights]

        logging.info('Configured lights: "{}"'.format(self.lights))

    def _expand_groups(self):
        groups = [g for g in self.bridge.groups if g.name in self.groups]
        for g in groups:
            self.lights.extend([l.name for l in g.lights])

    def connect(self):
        # Lazy init
        if not self.bridge:
            self.bridge = Bridge(self.bridge_address)
            logging.info('Bridge connected')

            self.get_scenes()

            # uncomment these lines if you're running huectrl for
            # the first time and you need to pair it to the switch

            # self.bridge.connect()
            # self.bridge.get_api()
        else:
            logging.info('Bridge already connected')

    def get_scenes(self):
        return Response(output=self.bridge.get_scene())

    def get_lights(self):
        return Response(output=self.bridge.get_light())

    def get_groups(self):
        return Response(output=self.bridge.get_group())

    def _exec(self, attr, *args, **kwargs):
        try:
            self.connect()
        except Exception as e:
            # Reset bridge connection
            self.bridge = None
            raise e

        lights = []
        groups = []
        if 'lights' in kwargs and kwargs['lights']:
            lights = kwargs['lights'].split(',') \
                if isinstance(lights, str) else kwargs['lights']
        elif 'groups' in kwargs and kwargs['groups']:
            groups = kwargs['groups'].split(',') \
                if isinstance(groups, str) else kwargs['groups']
        else:
            lights = self.lights
            groups = self.groups

        try:
            if attr == 'scene':
                self.bridge.run_scene(groups[0], kwargs['name'])
            elif groups:
                self.bridge.set_group(groups, attr, *args)
            elif lights:
                self.bridge.set_light(lights, attr, *args)
        except Exception as e:
            # Reset bridge connection
            self.bridge = None
            raise e

        return Response(output='ok')

    def on(self, lights=[], groups=[]):
        return self._exec('on', True, lights=lights, groups=groups)

    def off(self, lights=[], groups=[]):
        return self._exec('on', False, lights=lights, groups=groups)

    def bri(self, value, lights=[], groups=[]):
        return self._exec('bri',
                          int(value) % (self.MAX_BRI + 1),
                          lights=lights,
                          groups=groups)

    def sat(self, value, lights=[], groups=[]):
        return self._exec('sat',
                          int(value) % (self.MAX_SAT + 1),
                          lights=lights,
                          groups=groups)

    def hue(self, value, lights=[], groups=[]):
        return self._exec('hue',
                          int(value) % (self.MAX_HUE + 1),
                          lights=lights,
                          groups=groups)

    def scene(self, name, lights=[], groups=[]):
        return self._exec('scene', name=name, lights=lights, groups=groups)