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
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
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
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
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
__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)
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)
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)
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 :
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]
__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)
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)