From a3fbce1082db21c33aaead4609b1a82f78210f00 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 9 Apr 2018 01:23:58 +0200 Subject: [PATCH] Support for global and individual color and power switches on Philips Hue interface --- .../backend/http/static/css/light.hue.css | 24 ++ platypush/backend/http/static/js/light.hue.js | 219 ++++++++++++++++-- platypush/plugins/light/hue/__init__.py | 2 +- 3 files changed, 228 insertions(+), 17 deletions(-) diff --git a/platypush/backend/http/static/css/light.hue.css b/platypush/backend/http/static/css/light.hue.css index 0e4ce40f9b..597652bcfc 100644 --- a/platypush/backend/http/static/css/light.hue.css +++ b/platypush/backend/http/static/css/light.hue.css @@ -74,8 +74,32 @@ border-bottom: 1px solid #ddd; } +.light-item-name { + display: inline-block; +} + +.all-lights-item { + background-color: #ececec; +} + + .all-lights-item * > .light-item-name { + font-weight: bold; + } + .light-ctrl-switch-container { float: right; margin-top: -5px; } +.light-color-selector { + display: none; + background-color: white; + padding: 10px; + margin: 10px -10px -10px -10px; + border-radius: 10px; +} + +.light-slider { + margin-top: 10px; +} + diff --git a/platypush/backend/http/static/js/light.hue.js b/platypush/backend/http/static/js/light.hue.js index c10efe087d..4158977d4a 100644 --- a/platypush/backend/http/static/js/light.hue.js +++ b/platypush/backend/http/static/js/light.hue.js @@ -38,23 +38,139 @@ $(document).ready(function() { }; var createPowerToggleElement = function(data) { + var id = data['type'] + '_' + data['id']; var $powerToggle = $('
').addClass('toggle toggle--push light-ctrl-switch-container'); var $input = $('').attr('type', 'checkbox') - .attr('id', 'toggle--push').addClass('toggle--checkbox light-ctrl-switch'); + .attr('id', id).addClass('toggle--checkbox light-ctrl-switch'); data = data || {}; for (var attr of Object.keys(data)) { $input.data(attr, data[attr]); } - var $label = $('').attr('for', 'toggle--push').addClass('toggle--btn'); + var $label = $('').attr('for', id).addClass('toggle--btn'); $input.appendTo($powerToggle); $label.appendTo($powerToggle); + if ('on' in data && data['on']) { + $input.prop('checked', true); + } + return $powerToggle; }; + var createColorSelector = function(data) { + var type = data.type; + var element; + + if (type === 'light') { + element = lights[data.id]; + } else if (type === 'room') { + element = groups[data.id]; + } else { + throw "Unknown type: " + type; + } + + var $colorSelector = $('
') + .addClass('light-color-selector'); + + // Hue slider + var $hueContainer = $('
') + .addClass('slider-container').addClass('row'); + + var $hueText = $('
').addClass('two columns').text('Hue'); + var $hueSlider = $('').addClass('slider light-slider') + .addClass('ten columns').addClass('hue').data('type', type) + .attr('type', 'range').attr('min', 0).attr('max', 65535).data('property', 'hue') + .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.hue : 0); + + $hueText.appendTo($hueContainer); + $hueSlider.appendTo($hueContainer); + $hueContainer.appendTo($colorSelector); + + // Saturation slider + var $satContainer = $('
') + .addClass('slider-container').addClass('row'); + + var $satText = $('
').addClass('two columns').text('Saturation'); + var $satSlider = $('').addClass('slider light-slider') + .addClass('ten columns').addClass('sat').data('type', type) + .attr('type', 'range').attr('min', 0).attr('max', 255).data('property', 'sat') + .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.sat : 0); + + $satText.appendTo($satContainer); + $satSlider.appendTo($satContainer); + $satContainer.appendTo($colorSelector); + + // Brightness slider + var $briContainer = $('
') + .addClass('slider-container').addClass('row'); + + var $briText = $('
').addClass('two columns').text('Brightness'); + var $briSlider = $('').addClass('slider light-slider') + .addClass('ten columns').addClass('bri').data('type', type) + .attr('type', 'range').attr('min', 0).attr('max', 255).data('property', 'bri') + .data('id', data.id).data('name', element.name).val(type === 'light' ? element.state.bri : 0); + + $briText.appendTo($briContainer); + $briSlider.appendTo($briContainer); + $briContainer.appendTo($colorSelector); + + return $colorSelector; + }; + + var createLightCtrlElement = function(type, id) { + var element; + + if (type === 'light') { + element = lights[id]; + } else if (type === 'room') { + element = groups[id]; + } else { + throw "Unknown type: " + type; + } + + var on = type === 'light' ? element.state.on : element.state.any_on; + var $light = $('
') + .addClass('light-item') + .data('type', type) + .data('id', id) + .data('name', element.name) + .data('on', on); + + if (type === 'room') { + $light.addClass('all-lights-item'); + } + + var $row1 = $('
').addClass('row'); + var $row2 = $('
').addClass('row'); + + var $lightName = $('
') + .addClass('light-item-name') + .text(type === 'light' ? element.name : 'All Lights'); + + var $powerToggle = createPowerToggleElement({ + type: type, + id: id, + on: on, + }); + + var $colorSelector = createColorSelector({ + type: type, + id: id, + }); + + $lightName.appendTo($row1); + $powerToggle.appendTo($row1); + $colorSelector.appendTo($row2); + + $row1.appendTo($light); + $row2.appendTo($light); + + return $light; + }; + var updateRooms = function(rooms) { var roomByLight = {}; var roomsByScene = {}; @@ -83,23 +199,24 @@ $(document).ready(function() { $roomScenes.appendTo($scenesList); for (var light of rooms[room].lights) { - var $light = $('
') - .addClass('light-item') - .data('id', light) - .text(lights[light].name); - - var $powerToggle = createPowerToggleElement({ - type: 'light', - id: light, - }); - - roomByLight[light] = room; - $powerToggle.appendTo($light); + var $light = createLightCtrlElement(type='light', id=light); $light.appendTo($roomLights); + roomByLight[light] = room; } + + roomByLight[light] = room; + + var $allLights = createLightCtrlElement(type='room', id=room); + $allLights.prependTo($roomLights); } for (var scene of Object.keys(scenes)) { + if (scenes[scene].name.match(/(on|off) \d+$/)) { + // Old 1.0 scenes are saved as "scene_name on " but aren't visible + // not settable through the app - ignore them + continue; + } + roomsByScene[scene] = new Set(); for (var light of scenes[scene].lights) { var room = roomByLight[light]; @@ -125,7 +242,7 @@ $(document).ready(function() { } }; - var refreshStatus = function() { + var initUi = function() { $.when( execute({ type: 'request', action: 'light.hue.get_lights' }), execute({ type: 'request', action: 'light.hue.get_groups' }), @@ -145,6 +262,24 @@ $(document).ready(function() { }); }; + var refreshStatus = function() { + var onResponse = function(data) { + var response = data.response.output; + for (var light of Object.keys(response)) { + var $element = $('.light-item').filter(function(i, item) { + return $(item).data('type') === 'light' && $(item).data('id') === light + }); + + $element.find('input.light-ctrl-switch').prop('checked', response[light].state.on); + $element.find('input.hue').val(response[light].state.hue); + $element.find('input.sat').val(response[light].state.sat); + $element.find('input.bri').val(response[light].state.bri); + } + }; + + execute({ type: 'request', action: 'light.hue.get_lights' }, onResponse); + }; + var initBindings = function() { $roomsList.on('click touch', '.room-item', function() { $('.room-item').removeClass('selected'); @@ -175,12 +310,64 @@ $(document).ready(function() { args: { name: $(this).data('name') } + }, refreshStatus); + }); + + $lightsList.on('click touch', '.light-item-name', function() { + var $lightItem = $(this).parents('.light-item'); + var $colorSelector = $lightItem.find('.light-color-selector'); + + $('.light-color-selector').hide(); + $colorSelector.toggle(); + + $('.light-item').removeClass('selected'); + $lightItem.addClass('selected'); + }); + + $lightsList.on('click touch', '.light-ctrl-switch', function(e) { + e.stopPropagation(); + + var $lightItem = $($(this).parents('.light-item')); + var type = $lightItem.data('type'); + var name = $lightItem.data('name'); + var isOn = $lightItem.data('on'); + var action = 'light.hue.' + (isOn ? 'off' : 'on'); + var key = (type == 'light' ? 'lights' : 'groups'); + var args = { + type: 'request', + action: action, + args: {} + }; + + args['args'][key] = name; + execute(args, function() { + $lightItem.data('on', !isOn); + refreshStatus(); }); }); + + $lightsList.on('mouseup touchend', '.light-slider', function() { + var property = $(this).data('property'); + var type = $(this).data('type'); + var name = $(this).data('name'); + var args = { + type: 'request', + action: 'light.hue.' + property, + args: { value: $(this).val() } + }; + + if (type === 'light') { + args.args.lights = [name]; + } else { + args.args.groups = [name]; + } + + execute(args, refreshStatus); + }); }; var init = function() { - refreshStatus(); + initUi(); initBindings(); }; diff --git a/platypush/plugins/light/hue/__init__.py b/platypush/plugins/light/hue/__init__.py index adbe901023..4a1541c916 100644 --- a/platypush/plugins/light/hue/__init__.py +++ b/platypush/plugins/light/hue/__init__.py @@ -113,7 +113,7 @@ class LightHuePlugin(LightPlugin): return self._exec('on', True, lights=lights, groups=groups) def off(self, lights=[], groups=[]): - return self._exec('on', False, lights=lights, groups=groups) + return self._exec('off', False, lights=lights, groups=groups) def bri(self, value, lights=[], groups=[]): return self._exec('bri', int(value) % (self.MAX_BRI+1),