Support for global and individual color and power switches on Philips Hue interface

This commit is contained in:
Fabio Manganiello 2018-04-09 01:23:58 +02:00
parent 2bf4ff136b
commit a3fbce1082
3 changed files with 228 additions and 17 deletions

View file

@ -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;
}

View file

@ -38,23 +38,139 @@ $(document).ready(function() {
};
var createPowerToggleElement = function(data) {
var id = data['type'] + '_' + data['id'];
var $powerToggle = $('<div></div>').addClass('toggle toggle--push light-ctrl-switch-container');
var $input = $('<input></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 = $('<label></label>').attr('for', 'toggle--push').addClass('toggle--btn');
var $label = $('<label></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 = $('<div></div>')
.addClass('light-color-selector');
// Hue slider
var $hueContainer = $('<div></div>')
.addClass('slider-container').addClass('row');
var $hueText = $('<div></div>').addClass('two columns').text('Hue');
var $hueSlider = $('<input></input>').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 = $('<div></div>')
.addClass('slider-container').addClass('row');
var $satText = $('<div></div>').addClass('two columns').text('Saturation');
var $satSlider = $('<input></input>').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 = $('<div></div>')
.addClass('slider-container').addClass('row');
var $briText = $('<div></div>').addClass('two columns').text('Brightness');
var $briSlider = $('<input></input>').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 = $('<div></div>')
.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 = $('<div></div>').addClass('row');
var $row2 = $('<div></div>').addClass('row');
var $lightName = $('<div></div>')
.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 = $('<div></div>')
.addClass('light-item')
.data('id', light)
.text(lights[light].name);
var $powerToggle = createPowerToggleElement({
type: 'light',
id: light,
});
var $light = createLightCtrlElement(type='light', id=light);
$light.appendTo($roomLights);
roomByLight[light] = room;
}
roomByLight[light] = room;
$powerToggle.appendTo($light);
$light.appendTo($roomLights);
}
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 <timestamp>" 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();
};

View file

@ -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),