esp32_ethernet_milight_hub/web/src/js/script.js

1365 lines
36 KiB
JavaScript

$(function() {
$(document).on('change', ':file', function() {
var input = $(this),
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [label]);
});
$(document).ready( function() {
$(':file').on('fileselect', function(event, label) {
var input = $(this).parents('.input-group').find(':text'),
log = label;
if( input.length ) {
input.val(log);
}
});
});
});
var UNIT_PARAMS = {
minMireds: 153,
maxMireds: 370,
maxBrightness: 255
};
var UI_TABS = [ {
tag: "tab-ethernet",
friendly: "Ethernet",
}, {
tag: "tab-wifi",
friendly: "Wifi",
}, {
tag: "tab-setup",
friendly: "PINs",
}, {
tag: "tab-led",
friendly: "LED",
}, {
tag: "tab-radio",
friendly: "MiLight Radio",
}, {
tag: "tab-mqtt",
friendly: "MQTT"
}, {
tag: "tab-transitions",
friendly: "Transitions"
}
];
var UI_FIELDS = [ {
tag: "ethernet_enable",
friendly: "Ethernet aktivieren",
help: "Ethernet einschalten, WLAN wird dadurch getrennt, aber Einstellungen bleiben erhalten.",
type: "option_buttons",
options: {
true: 'Ein',
false: 'Aus'
},
tab: "tab-ethernet"
}, {
tag: "ethernet_static_ip",
friendly: "Statische IP-Adresse",
help: "Statische IP-Adresse (Leer lassen für DHCP)",
type: "string",
tab: "tab-ethernet"
}, {
tag: "ethernet_static_ip_netmask",
friendly: "Statische IP-Netzmaske",
help: "Netzmaske für die statische IP-Adresse",
type: "string",
tab: "tab-ethernet"
}, {
tag: "ethernet_static_ip_gateway",
friendly: "Statisches IP-Gateway",
help: "IP-Adresse des Gateways",
type: "string",
tab: "tab-ethernet"
}, {
tag: "admin_username",
friendly: "Admin Benutzername",
help: "Benutzername zum Einloggen",
type: "string",
tab: "tab-wifi"
}, {
tag: "admin_password",
friendly: "Passwort",
help: "Passwort für Admin-Benutzer",
type: "string",
tab: "tab-wifi"
}, {
tag: "hostname",
friendly: "Hostname",
help: "Hostname des MiLight Gateways, wird für DHCP verwendet",
type: "string",
tab: "tab-wifi"
}, {
tag: "wifi_static_ip",
friendly: "Statische IP-Adresse",
help: "Statische IP-Adresse (Leer lassen für DHCP)",
type: "string",
tab: "tab-wifi"
}, {
tag: "wifi_static_ip_netmask",
friendly: "Statische IP-Netzmaske",
help: "Netzmaske für die statische IP-Adresse",
type: "string",
tab: "tab-wifi"
}, {
tag: "wifi_static_ip_gateway",
friendly: "Statisches IP-Gateway",
help: "IP-Adresse des Gateways",
type: "string",
tab: "tab-wifi"
}, {
tag: "wifi_mode",
friendly: "WiFi Modus",
help: "Bei Instabilitäten G-Modus verwenden",
type: "option_buttons",
options: {
'b': 'B',
'g': 'G',
'n': 'N'
},
tab: "tab-wifi"
}, {
tag: "ce_pin",
friendly: "CE / PKT pin",
help: "Pin auf ESP32-PoE für 'CE' (NRF24L01 interface) oder 'PKT' ('PL1167/LT8900' interface)",
type: "string",
tab: "tab-setup"
}, {
tag: "csn_pin",
friendly: "CSN pin",
help: "Pin auf ESP32-PoE für 'CSN'",
type: "string",
tab: "tab-setup"
}, {
tag: "reset_pin",
friendly: "RESET pin",
help: "Pin auf ESP32-PoE für 'RESET'",
type: "string",
tab: "tab-setup"
}, {
tag: "packet_repeats",
friendly: "Paketwiederholungen",
help: "Anzahl der Pakete die wiederholt an die Lampen gesendet werden.",
type: "string",
tab: "tab-radio"
}, {
tag: "packet_repeats_per_loop",
friendly: "Paketwiederholungen pro Zyklus",
help: "Paketwiederholungen in einem Sendezyklus. Höhere Werte garantieren bessere Übernahme bei den Lampen, "+
"aber die Anzahl max gleichzeitiger Veränderungen sinkt.",
type: "string",
tab: "tab-radio"
}, {
tag: "http_repeat_factor",
friendly: "HTTP Wiederholungsfaktor",
help: "Faktor für Paketwiederholungen die durch die HTTP API ausgelöst werden sollen. " +
"UDP bekommt eh mehere Pakete, die gesendet werden.",
type: "string",
tab: "tab-wifi"
}, {
tag: "auto_restart_period",
friendly: "Automatischer Neustart des ESP32-PoE MiLight Gateways",
help: "Automatischer alle X Minunten, 0 zum Deaktivieren der Funktion",
type: "string",
tab: "tab-setup"
}, {
tag: "discovery_port",
friendly: "Discovery Port",
help: "UDP port um auf Discover Anfragen von MiLight Geräten/Apps zu hören. Standardmäßig verwendet die Original App Port 48899. 0 zum Deaktivieren.",
type: "string",
tab: "tab-wifi"
}, {
tag: "mqtt_server",
friendly: "MQTT Server",
help: "Domain oder IP-Adresse des MQTT-Servers. Es kann auch ein Port in Form von :1883 angegeben werden",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_topic_pattern",
friendly: "MQTT Topic Struktur",
help: "MQTT-Struktur auf welcher nach Befehlen gehört werden soll. Beispiel: lights/:device_id/:device_type/:group_id.",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_update_topic_pattern",
friendly: "MQTT Update Topic Struktur",
help: "Alle Updates die von diesem Gerät oder von anderen Geräten (Fernbedinungen etc.) gesendet werden, werden in diesem Topic veröffentlicht.",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_state_topic_pattern",
friendly: "MQTT Status Topic Struktur",
help: "MQTT-Struktur auf die der vollständige Zustand einer Gruppe gesendet wird.",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_username",
friendly: "MQTT Benutzername",
help: "Benutzername zum Einloggen in den MQTT-Server",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_password",
friendly: "MQTT Passwort",
help: "Passwort zum Einloggen in den MQTT-Server",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_client_status_topic",
friendly: "MQTT Client Status Topic",
help: "Der Verbindungsstatus vom ESP32-PoE MiLight Gateway wird hier veröffentlicht. (LWT und birth).",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_retain",
friendly: "MQTT Zustandsmeldungen mit Retain-Flag senden",
help: "Wenn der Haken gesetzt wird, werden alle Zustandsmeldungen mit dem Retain-Flag gesendet.",
type: "option_buttons",
options: {
true: "Aktiviert",
false: "Deaktiviert"
},
tab: "tab-mqtt"
}, {
tag: "simple_mqtt_client_status",
friendly: "Client Status Messages Mode",
help: "Im einfachen Modus wird nur der Status 'verbunden' oder 'getrennt' übermittelt, im detaillierten Modus werden Infos wie IP-Adresse, Version etc. übermittelt",
type: "option_buttons",
options: {
true: "Einfach",
false: "Detailliert"
},
tab: "tab-mqtt"
}, {
tag: "home_assistant_discovery_prefix",
friendly: "HomeAssistant MQTT Discovery Präfix",
help: "Eingebaute MQTT Discovery von Home Assistant ermöglichen, dieses Gateway automatisch zu erkennen und konfigurieren.",
type: "string",
tab: "tab-mqtt"
}, {
tag: "radio_interface_type",
friendly: "Radio interface Typ",
help: "2.4 GHz Version. Nur Verwenden wenn kein NRF24L01 benutzt wird!",
type: "option_buttons",
options: {
'nRF24': 'nRF24',
'LT8900': 'PL1167/LT8900'
},
tab: "tab-radio"
}, {
tag: "rf24_power_level",
friendly: "nRF24 Power Level",
help: "Power Level für nRF24L01",
type: "option_buttons",
options: {
'MIN': 'Min',
'LOW': 'Niedrig',
'HIGH': 'Hoch',
'MAX': 'Max'
},
tab: "tab-radio"
}, {
tag: "rf24_listen_channel",
friendly: "nRF24 Listen Channel",
help: "Kanal auf dem nRF24 der abgehört werden soll",
type: "option_buttons",
options: {
'LOW': 'Min',
'MID': 'Mid',
'HIGH': 'High'
},
tab: "tab-radio"
}, {
tag: "rf24_channels",
friendly: "nRF24 Send Channels",
help: "Auf diesen Kanälen wir der nRF24 senden, umso weniger umso schnellere Reaktion der Lampen.",
type: "option_buttons",
settings: {
multiple: true,
},
options: {
'LOW': 'Min',
'MID': 'Mid',
'HIGH': 'High'
},
tab: "tab-radio"
}, {
tag: "listen_repeats",
friendly: "Listen repeats",
help: "Erhöhen um mehr Zeit zum Abhören des Funks auf MiLight Paketen zu verwenden " +
"0 = Kein Abhören auf Pakete, Standard ist 3.",
type: "string",
tab: "tab-wifi"
}, {
tag: "state_flush_interval",
friendly: "State flush interval",
help: "Kleinster Zeitintervall (Millisekunden) bevor die zustände auf dem Flash gespeichert werden. " +
"Auf 0 setzen um keine Verzögerung zu verwenden und den Zustand sofort auf dem Flash zu speichern",
type: "string",
tab: "tab-setup"
}, {
tag: "mqtt_state_rate_limit",
friendly: "MQTT state rate limit",
help: "Kleinster Zeitintervall (Millisekunden), bevor die Zustände der Lampen per MQTT gesendet werden",
type: "string",
tab: "tab-mqtt"
}, {
tag: "mqtt_debounce_delay",
friendly: "MQTT debounce delay",
help: "Kleinster Zeitintervall (Millisekunden), bevor Änderungen auf MQTT gesendet werden",
type: "string",
tab: "tab-mqtt"
}, {
tag: "packet_repeat_throttle_threshold",
friendly: "Packet repeat throttle threshold",
help: "Controls how packet repeats are throttled. Packets sent " +
"with less time between them than this value (in milliseconds) will cause " +
"packet repeats to be throttled down. More than this value will unthrottle " +
"up. Defaults to 200ms",
type: "string",
tab: "tab-radio"
}, {
tag: "packet_repeat_throttle_sensitivity",
friendly: "Packet repeat throttle sensitivity",
help: "Controls how packet repeats are throttled. " +
"Higher values cause packets to be throttled up and down faster " +
"(defaults to 0, maximum value 1000, 0 disables)",
type: "string",
tab: "tab-radio"
}, {
tag: "packet_repeat_minimum",
friendly: "Packet repeat minimum",
help: "Controls how far throttling can decrease the number " +
"of repeated packets (defaults to 3)",
type: "string",
tab: "tab-radio"
}, {
tag: "group_state_fields",
friendly: "Gruppenzustands Felder",
help: "Auswählen welche Eigenschaften auf MQTT oder per REST-API veröffentlicht werden sollen.",
type: "group_state_fields",
tab: "tab-mqtt"
}, {
tag: "enable_automatic_mode_switching",
friendly: "Automatisches Umschalten des Moduses (RGB-Farbmodus oder Farbtemperatur)",
help: "Bei RGBWW Lampen wird beim Senden des entsprechendes Befehles wie Farbe oder Farbtemp, kurz in den passenden Modus gewechselt, der Befehl gesendet " +
"und wieder auf den vorherigen Modus zurück geschaltet.",
type: "option_buttons",
options: {
true: 'Aktiviert',
false: 'Deaktiviert'
},
tab: "tab-radio"
}, {
tag: "default_transition_period",
friendly: "Abstand zwischen Transition-Paleten (Millisekunden)",
help: "Für feinere Farbübergänge einen kleineren Wert verwenden.",
type: "string",
tab: "tab-transitions"
}
];
// TODO: sync this with GroupStateField.h
var GROUP_STATE_KEYS = [
"state",
"status",
"brightness",
"level",
"hue",
"saturation",
"color",
"mode",
"kelvin",
"color_temp",
"bulb_mode",
"computed_color",
"effect",
"device_id",
"group_id",
"device_type",
"oh_color",
"hex_color"
];
var LED_MODES = [
"Off",
"Slow toggle",
"Fast toggle",
"Slow blip",
"Fast blip",
"Flicker",
"On"
];
var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];
var DEFAULT_UDP_PROTOCL_VERSION = 5;
var selectize;
var aliasesSelectize;
var sniffing = false;
var loadingSettings = false;
// When true, will not attempt to load group parameters
var updatingGroupId = false;
// When true, will not attempt to update group parameters
var updatingAlias = false;
// don't attempt websocket if we are debugging locally
if (location.hostname != "") {
var webSocket = new WebSocket("ws://" + location.hostname + ":81");
webSocket.onmessage = function(e) {
if (sniffing) {
var message = e.data;
$('#sniffed-traffic').prepend('<pre>' + message + '</pre>');
}
}
}
var toHex = function(v) {
return "0x" + (v).toString(16).toUpperCase();
}
var updateGroupId = function(params) {
updatingGroupId = true;
selectize.setValue(params.deviceId);
setGroupId(params.groupId);
setMode(params.deviceType);
updatingGroupId = false;
refreshGroupState();
}
var setGroupId = function(value) {
$('#groupId input[data-value="' + value + '"]').click();
}
var setMode = function(value) {
$('#mode li[data-value="' + value + '"]').click();
}
var getCurrentDeviceId = function() {
// return $('#deviceId option:selected').val();
return parseInt(selectize.getValue());
};
var getCurrentGroupId = function() {
return $('#groupId .active input').data('value');
}
var findAndSelectAlias = function() {
if (!updatingGroupId) {
var params = {
deviceType: getCurrentMode(),
deviceId: getCurrentDeviceId(),
groupId: getCurrentGroupId()
};
var foundAlias = Object.entries(aliasesSelectize.options).filter(function(x) {
return _.isEqual(x[1].savedGroupParams, params);
});
updatingAlias = true;
if (foundAlias.length > 0) {
aliasesSelectize.setValue(foundAlias[0]);
} else {
aliasesSelectize.clear();
}
updatingAlias = false;
}
}
var activeUrl = function() {
var deviceId = getCurrentDeviceId()
, groupId = getCurrentGroupId()
, mode = getCurrentMode();
if (deviceId == "") {
throw "Must enter device ID";
}
if (! $('#group-option').data('for').split(',').includes(mode)) {
groupId = 0;
}
if (typeof groupId === "undefined") {
throw "Must enter group ID";
}
return "/gateways/" + deviceId + "/" + mode + "/" + groupId;
}
var refreshGroupState = function() {
if (! updatingGroupId) {
$.getJSON(
activeUrl(),
function(e) {
handleStateUpdate(e);
}
);
}
}
var getCurrentMode = function() {
return $('#mode li.active').data('value');
};
var updateGroup = _.throttle(
function(params) {
try {
$.ajax({
url: activeUrl() + "?blockOnQueue=true",
method: 'PUT',
data: JSON.stringify(params),
contentType: 'application/json',
success: function(e) {
handleStateUpdate(e);
}
});
} catch (e) {
alert(e);
}
},
1000
);
var sendCommand = _.throttle(
function(params) {
$.ajax(
'/system',
{
method: 'POST',
data: JSON.stringify(params),
contentType: 'application/json'
}
);
},
1000
)
var gatewayServerRow = function(deviceId, port, version) {
var elmt = '<tr>';
elmt += '<td>';
elmt += '<input name="deviceIds[]" class="form-control" value="' + deviceId + '"/>';
elmt += '</td>';
elmt += '<td>'
elmt += '<input name="ports[]" class="form-control" value="' + port + '"/>';;
elmt += '</td>';
elmt += '<td>';
elmt += '<div class="btn-group" data-toggle="buttons">';
for (var i = 0; i < UDP_PROTOCOL_VERSIONS.length; i++) {
var val = UDP_PROTOCOL_VERSIONS[i]
, selected = (version == val || (val == DEFAULT_UDP_PROTOCL_VERSION && !UDP_PROTOCOL_VERSIONS.includes(version)));
elmt += '<label class="btn btn-secondary' + (selected ? ' active' : '') + '">';
elmt += '<input type="radio" name="versions[]" autocomplete="off" data-value="' + val + '" '
+ (selected ? 'checked' : '') +'> ' + val;
elmt += '</label>';
}
elmt += '</div></td>';
elmt += '<td>';
elmt += '<button class="btn btn-danger remove-gateway-server">';
elmt += '<i class="glyphicon glyphicon-remove"></i>';
elmt += '</button>';
elmt += '</td>';
elmt += '</tr>';
return elmt;
}
var loadSettings = function() {
$('select.select-init').selectpicker();
if (location.hostname == "") {
// if deugging locally, don't try get settings
return;
}
$.getJSON('/settings', function(val) {
loadingSettings = true;
Object.keys(val).forEach(function(k) {
var field = $('#settings input[name="' + k + '"]');
var selectVal = function(selectVal) {
field.filter('[value="' + selectVal + '"]').click();
};
if (field.length > 0) {
if (field.attr('type') === 'radio' || field.attr('type') === 'checkbox') {
if (Array.isArray(val[k])) {
val[k].forEach(function(x) {
selectVal(x);
});
} else {
selectVal(val[k]);
}
} else {
field.val(val[k]);
}
}
});
if (val.hostname) {
var title = "MiLight Hub: " + val.hostname;
document.title = title;
$('.navbar-brand').html(title);
}
if (val.group_id_aliases) {
aliasesSelectize.clearOptions();
Object.entries(val.group_id_aliases).forEach(function(entry) {
var label = entry[0]
, groupParams = entry[1]
, savedParams = {
deviceType: groupParams[0],
deviceId: groupParams[1],
groupId: groupParams[2]
}
;
aliasesSelectize.addOption({
text: label,
value: label,
savedGroupParams: savedParams
});
aliasesSelectize.refreshOptions(false);
});
}
if (val.device_ids) {
selectize.clearOptions();
val.device_ids.forEach(function(v) {
selectize.addOption({text: toHex(v), value: v});
});
selectize.refreshOptions(false);
}
if (val.group_state_fields) {
var elmt = $('select[name="group_state_fields"]');
elmt.selectpicker('val', val.group_state_fields);
}
if (val.led_mode_wifi_config) {
var elmt = $('select[name="led_mode_wifi_config"]');
elmt.selectpicker('val', val.led_mode_wifi_config);
}
if (val.led_mode_wifi_failed) {
var elmt = $('select[name="led_mode_wifi_failed"]');
elmt.selectpicker('val', val.led_mode_wifi_failed);
}
if (val.led_mode_operating) {
var elmt = $('select[name="led_mode_operating"]');
elmt.selectpicker('val', val.led_mode_operating);
}
if (val.led_mode_packet) {
var elmt = $('select[name="led_mode_packet"]');
elmt.selectpicker('val', val.led_mode_packet);
}
var gatewayForm = $('#gateway-server-configs').html('');
if (val.gateway_configs) {
val.gateway_configs.forEach(function(v) {
gatewayForm.append(gatewayServerRow(toHex(v[0]), v[1], v[2]));
});
}
loadingSettings = false;
});
};
var saveGatewayConfigs = function() {
var form = $('#tab-udp-gateways')
, errors = false;
$('input', form).removeClass('error');
var deviceIds = $('input[name="deviceIds[]"]', form).map(function(i, v) {
var val = $(v).val();
if (isNaN(val)) {
errors = true;
$(v).addClass('error');
return null;
} else {
return val;
}
});
var ports = $('input[name="ports[]"]', form).map(function(i, v) {
var val = $(v).val();
if (isNaN(val)) {
errors = true;
$(v).addClass('error');
return null;
} else {
return val;
}
});
var versions = $('.active input[name="versions[]"]', form).map(function(i, v) {
return $(v).data('value');
});
if (!errors) {
var data = [];
for (var i = 0; i < deviceIds.length; i++) {
data[i] = [deviceIds[i], ports[i], versions[i]];
}
$.ajax(
'/settings',
{
method: 'put',
contentType: 'application/json',
data: JSON.stringify({gateway_configs: data})
}
)
}
};
var patchSettings = function(patch) {
if (!loadingSettings) {
$.ajax(
"/settings",
{
method: 'put',
contentType: 'application/json',
data: JSON.stringify(patch)
}
);
}
};
var saveDeviceIds = function() {
if (!loadingSettings) {
var deviceIds = _.map(
$('#deviceId')[0].selectize.options,
function(option) {
return option.value;
}
);
patchSettings({device_ids: deviceIds});
}
};
var saveDeviceAliases = function() {
if (!loadingSettings) {
var deviceAliases = Object.entries(aliasesSelectize.options).reduce(
function(aggregate, x) {
var params = x[1].savedGroupParams;
aggregate[x[0]] = [
params.deviceType,
params.deviceId,
params.groupId
]
return aggregate;
},
{}
);
patchSettings({group_id_aliases: deviceAliases});
}
};
var deleteDeviceId = function() {
selectize.removeOption($(this).data('value'));
selectize.refreshOptions();
saveDeviceIds();
};
var deleteDeviceAlias = function() {
aliasesSelectize.removeOption($(this).data('value'));
aliasesSelectize.refreshOptions();
saveDeviceAliases();
};
var deviceIdError = function(v) {
if (!v) {
$('#device-id-label').removeClass('error');
} else {
$('#device-id-label').addClass('error');
$('#device-id-label .error-info').html(v);
}
};
var updateModeOptions = function() {
var currentMode = getCurrentMode()
, modeLabel = $('#mode li[data-value="' + currentMode + '"] a').html();
$('label', $('#mode').closest('.dropdown')).html(modeLabel);
$('.mode-option').map(function() {
if ($(this).data('for').split(',').includes(currentMode)) {
$(this).show();
} else {
$(this)
// De-select unselectable group
.removeClass('active')
.hide();
}
});
};
var parseVersion = function(v) {
var matches = v.match(/(\d+)\.(\d+)\.(\d+)(-(.*))?/);
return {
major: matches[1],
minor: matches[2],
patch: matches[3],
revision: matches[5],
parts: [matches[1], matches[2], matches[3], matches[5]]
};
};
var isNewerVersion = function(a, b) {
var va = parseVersion(a)
, vb = parseVersion(b);
return va.parts > vb.parts;
};
var handleCheckForUpdates = function() {
var currentVersion = null
, latestRelease = null;
var handleReceiveData = function() {
if (currentVersion != null) {
$('#current-version').html(currentVersion.version + " (" + currentVersion.variant + ")");
}
if (latestRelease != null) {
$('#latest-version .info-key').each(function() {
var value = latestRelease[$(this).data('key')];
var prop = $(this).data('prop');
if (prop) {
$(this).prop(prop, value);
} else {
$(this).html(value);
}
});
}
if (currentVersion != null && latestRelease != null) {
$('#latest-version .status').html('');
$('#latest-version-info').show();
var summary;
if (isNewerVersion(latestRelease.tag_name, currentVersion.version)) {
summary = "New version available!";
} else {
summary = "You're on the most recent version.";
}
$('#version-summary').html(summary);
var releaseAsset = latestRelease.assets.filter(function(x) {
return x.name.indexOf(currentVersion.variant) != -1;
});
if (releaseAsset.length > 0) {
$('#firmware-link').prop('href', releaseAsset[0].browser_download_url);
}
}
}
var handleError = function(e, d) {
console.log(e);
console.log(d);
}
$('#current-version,#latest-version .status').html('<i class="spinning glyphicon glyphicon-refresh"></i>');
$('#version-summary').html('');
$('#latest-version-info').hide();
$.ajax(
'/about',
{
success: function(data) {
currentVersion = data;
handleReceiveData();
},
failure: handleError
}
);
/*$.ajax(
'https://api.github.com/repos/sidoh/esp8266_milight_hub/releases/latest',
{
success: function(data) {
latestRelease = data;
handleReceiveData();
},
failure: handleError
}
);*/
};
var handleStateUpdate = function(state) {
if (state.state) {
// Set without firing an event
$('input[name="status"]')
.prop('checked', state.state == 'ON')
.bootstrapToggle('destroy')
.bootstrapToggle();
}
if (state.color) {
// Browsers don't support HSV, but saturation from HSL doesn't match
// saturation from bulb state.
var hsl = rgbToHsl(state.color.r, state.color.g, state.color.b);
var hsv = RGBtoHSV(state.color.r, state.color.g, state.color.b);
$('input[name="saturation"]').slider('setValue', hsv.s*100);
updatePreviewColor(hsl.h*360,hsl.s*100,hsl.l*100);
}
if (state.color_temp) {
var scaledTemp
= 100*(state.color_temp - UNIT_PARAMS.minMireds) / (UNIT_PARAMS.maxMireds - UNIT_PARAMS.minMireds);
$('input[name="temperature"]').slider('setValue', scaledTemp);
}
if (state.brightness) {
var scaledBrightness = state.brightness * (100 / UNIT_PARAMS.maxBrightness);
$('input[name="level"]').slider('setValue', scaledBrightness);
}
};
var updatePreviewColor = function(hue, saturation, lightness) {
if (! saturation) {
saturation = 100;
}
if (! lightness) {
lightness = 50;
}
$('.hue-value-display').css({
backgroundColor: "hsl(" + hue + "," + saturation + "%," + lightness + "%)"
});
};
var stopSniffing = function() {
var elmt = $('#sniff');
sniffing = false;
$('i', elmt)
.removeClass('glyphicon-stop')
.addClass('glyphicon-play');
$('span', elmt).html('Start Sniffing');
};
var startSniffing = function() {
var elmt = $('#sniff');
sniffing = true;
$('i', elmt)
.removeClass('glyphicon-play')
.addClass('glyphicon-stop');
$('span', elmt).html('Stop Sniffing');
$("#traffic-sniff").show();
};
var generateDropdownField = function(fieldName, options, settings) {
var s = '<div class="btn-group" id="' + fieldName + '" data-toggle="buttons">';
var inputType = settings.multiple ? 'checkbox' : 'radio';
Object.keys(options).forEach(function(optionValue) {
var optionLabel = options[optionValue];
s += '<label class="btn btn-secondary">' +
'<input type="' + inputType + '" id="' + fieldName + '" name="' + fieldName + '" autocomplete="off" value="' + optionValue + '" /> ' + optionLabel +
'</label>';
});
s += '</div>';
return s;
};
$(function() {
$('.radio-option').click(function() {
$(this).prev().prop('checked', true);
});
var hueDragging = false;
var colorUpdated = function(e) {
var x = e.pageX - $(this).offset().left
, pct = x/(1.0*$(this).width())
, hue = Math.round(360*pct)
;
updatePreviewColor(hue);
updateGroup({hue: hue});
};
$('.hue-picker-inner')
.mousedown(function(e) {
hueDragging = true;
colorUpdated.call(this, e);
})
.mouseup(function(e) {
hueDragging = false;
})
.mouseout(function(e) {
hueDragging = false;
})
.mousemove(function(e) {
if (hueDragging) {
colorUpdated.call(this, e);
}
});
$('.slider').slider();
$('.raw-update').change(function() {
var data = {}
, val = $(this).attr('type') == 'checkbox' ? ($(this).is(':checked') ? 'on' : 'off') : $(this).val()
;
data[$(this).attr('name')] = val;
updateGroup(data);
});
$('.command-btn').click(function() {
updateGroup({command: $(this).data('command')});
});
$('.system-btn').click(function() {
sendCommand({command: $(this).data('command')});
});
$('#sniff').click(function(e) {
e.preventDefault();
if (sniffing) {
stopSniffing();
} else {
startSniffing();
}
});
$('#traffic-sniff-close').click(function() {
stopSniffing();
$('#traffic-sniff').hide();
});
$('body').on('click', '#add-server-btn', function(e) {
e.preventDefault();
$('#gateway-server-configs').append(gatewayServerRow('', ''));
});
$('#mode li').click(function(e) {
e.preventDefault();
$('li', $(this).parent()).removeClass('active');
$(this).addClass('active');
updateModeOptions.bind(this)();
});
$('body').on('click', '.remove-gateway-server', function() {
$(this).closest('tr').remove();
});
for (var i = 0; i < 9; i++) {
$('.mode-dropdown').append('<li><a href="#" data-mode-value="' + i + '">' + i + '</a></li>');
}
$('body').on('click', '.mode-dropdown li a', function(e) {
updateGroup({mode: $(this).data('mode-value')});
e.preventDefault();
return false;
});
var onGroupParamsChange = function(e) {
findAndSelectAlias();
try {
refreshGroupState();
} catch (e) {
// Skip
}
};
$('input[name="options"],#deviceId').change(onGroupParamsChange);
$('#mode li').click(onGroupParamsChange);
aliasesSelectize = $('#deviceAliases').selectize({
create: true,
allowEmptyOption: true,
openOnFocus: true,
createOnBlur: true,
render: {
option: function(data, escape) {
// Mousedown selects an option -- prevent event from bubbling up to select option
// when delete button is clicked.
var deleteBtn = $('<span class="selectize-delete"><a href="#"><i class="glyphicon glyphicon-trash"></i></a></span>')
.mousedown(function(e) {
e.preventDefault();
return false;
})
.click(function(e) {
deleteDeviceAlias.call($(this).closest('.c-selectize-item'));
e.preventDefault();
return false;
});
var elmt = $('<div class="c-selectize-item"></div>');
elmt.append('<span>' + data.text + '</span>');
elmt.append(deleteBtn);
return elmt;
}
},
onOptionAdd: function(v, item) {
if (!item.savedGroupParams) {
item.savedGroupParams = {
deviceId: getCurrentDeviceId(),
groupId: getCurrentGroupId(),
deviceType: getCurrentMode()
};
}
saveDeviceAliases();
}
})[0].selectize;
selectize = $('#deviceId').selectize({
create: true,
sortField: 'value',
allowEmptyOption: true,
createOnBlur: true,
render: {
option: function(data, escape) {
// Mousedown selects an option -- prevent event from bubbling up to select option
// when delete button is clicked.
var deleteBtn = $('<span class="selectize-delete"><a href="#"><i class="glyphicon glyphicon-trash"></i></a></span>')
.mousedown(function(e) {
e.preventDefault();
return false;
})
.click(function(e) {
deleteDeviceId.call($(this).closest('.c-selectize-item'));
e.preventDefault();
return false;
});
var elmt = $('<div class="c-selectize-item"></div>');
elmt.append('<span>' + data.text + '</span>');
elmt.append(deleteBtn);
return elmt;
}
},
onOptionAdd: function(v, item) {
var unparsedValue = item.value;
item.value = parseInt(unparsedValue);
selectize.updateOption(unparsedValue, item);
selectize.addItem(item.value);
saveDeviceIds();
},
createFilter: function(v) {
if (! v.match(/^(0x[a-fA-F0-9]{1,4}|[0-9]{1,5})$/)) {
deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
return false;
}
var value = parseInt(v);
if (! (0 <= v && v <= 0xFFFF)) {
deviceIdError("Must be an integer between 0x0000 and 0xFFFF");
return false;
}
deviceIdError(false);
return true;
}
});
selectize = selectize[0].selectize;
var settings = '<ul class="nav nav-tabs" id="setupTabs">';
var tabClass = 'active';
UI_TABS.forEach(function(t) {
settings += '<li class="' + tabClass + '"><a href="#' + t.tag + '" data-toggle="tab">' + t.friendly + '</a></li>';
tabClass = '';
});
settings += '<li><a href="#tab-udp-gateways" data-toggle="tab">UDP</a></li>';
settings += "</ul>";
settings += '<div class="tab-content">';
tabClass = 'active in';
UI_TABS.forEach(function(t) {
settings += '<div class="tab-pane fade ' + tabClass + '" id="' + t.tag + '">';
tabClass = '';
UI_FIELDS.forEach(function(k) {
if (k.tab == t.tag) {
var elmt = '<div class="form-entry">';
elmt += '<div>';
elmt += '<label for="' + k.tag + '">' + k.friendly + '</label>';
if (k.help) {
elmt += '<div class="field-help" data-help-text="' + k.help + '"></div>';
}
elmt += '</div>';
if (k.type == 'group_state_fields') {
elmt += '<select class="selectpicker select-init" name="group_state_fields" multiple>';
GROUP_STATE_KEYS.forEach(function(stateKey) {
elmt += '<option>' + stateKey + '</option>';
});
elmt += '</select>';
} else if (k.type == 'led_mode') {
elmt += '<select class="selectpicker select-init" name="' + k.tag + '">';
LED_MODES.forEach(function(stateKey) {
elmt += '<option>' + stateKey + '</option>';
});
elmt += '</select>';
} else if (k.type == 'option_buttons') {
elmt += generateDropdownField(k.tag, k.options, k.settings || {});
} else {
elmt += '<input type="text" class="form-control" name="' + k.tag + '"/>';
}
elmt += '</div>';
settings += elmt;
}
});
settings += "</div>";
});
// UDP gateways tab
settings += '<div class="tab-pane fade ' + tabClass + '" id="tab-udp-gateways">';
settings += $('#gateway-servers-modal .modal-body').remove().html();
settings += '</div>';
settings += "</div>";
$('#settings').prepend(settings);
function saveSettings(settingsEntries) {
var entries = settingsEntries.slice(0)
function saveBatch() {
if (entries.length > 0) {
var batch = Object.fromEntries(entries.splice(0, 30))
$.ajax(
"/settings",
{
method: "PUT",
contentType: "application/json",
data: JSON.stringify(batch)
}
)
.done(saveBatch)
}
}
saveBatch()
}
$('#settings').submit(function(e) {
e.preventDefault();
// Save UDP settings separately from the rest of the stuff since input is handled differently
if ($('#tab-udp-gateways').hasClass('active')) {
saveGatewayConfigs();
} else {
var obj = $('#settings').serializeArray();
obj = obj
.reduce(function(a, x) {
var val = a[x.name];
if (! val) {
a[x.name] = x.value;
} else if (! Array.isArray(val)) {
a[x.name] = [val, x.value];
} else {
val.push(x.value);
}
return a;
},
{
// Make sure the value is always an array, even if a single item is selected
rf24_channels: []
});
// Make sure we're submitting a value for group_state_fields (will be empty
// if no values were selected).
obj = $.extend({group_state_fields: []}, obj);
saveSettings(Object.entries(obj))
}
$('#settings-modal').modal('hide');
return false;
});
$('#gateway-server-form').submit(function(e) {
saveGatewayConfigs();
e.preventDefault();
$('#gateway-servers-modal').modal('hide');
return false;
});
$('.field-help').each(function() {
var elmt = $('<i></i>')
.addClass('glyphicon glyphicon-question-sign')
.tooltip({
placement: 'top',
title: $(this).data('help-text'),
container: 'body'
});
$(this).append(elmt);
});
$('#updates-btn').click(handleCheckForUpdates);
loadSettings();
updateModeOptions();
});
$(function() {
$(document).on('change', ':file', function() {
var input = $(this),
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [label]);
});
$(document).on('change', '#deviceAliases', function() {
var selectedValue = aliasesSelectize.getValue()
, selectizeItem = aliasesSelectize.options[selectedValue]
;
if (selectizeItem && !updatingAlias) {
updateGroupId(selectizeItem.savedGroupParams);
}
});
$(document).ready( function() {
$(':file').on('fileselect', function(event, label) {
var input = $(this).parents('.input-group').find(':text'),
log = label;
if( input.length ) {
input.val(log);
}
});
});
});