var FORM_SETTINGS = [ "admin_username", "admin_password", "ce_pin", "csn_pin", "reset_pin","packet_repeats", "http_repeat_factor", "auto_restart_period", "discovery_port", "mqtt_server", "mqtt_topic_pattern", "mqtt_update_topic_pattern", "mqtt_username", "mqtt_password", "radio_interface_type", "listen_repeats" ]; var FORM_SETTINGS_HELP = { ce_pin : "'CE' for NRF24L01 interface, and 'PKT' for 'PL1167/LT8900' interface", packet_repeats : "The number of times to repeat RF packets sent to bulbs", http_repeat_factor : "Multiplicative factor on packet_repeats for " + "requests initiated by the HTTP API. UDP API typically receives " + "duplicate packets, so more repeats should be used for HTTP.", auto_restart_period : "Automatically restart the device every number of " + "minutes specified. Use 0 for disabled.", radio_interface_type : "2.4 GHz radio model. Only change this if you know " + "You're not using an NRF24L01!", mqtt_server : "Domain or IP address of MQTT broker. Optionally specify a port " + "with (example) mymqqtbroker.com:1884.", mqtt_topic_pattern : "Pattern for MQTT topics to listen on. Example: " + "lights/:device_id/:device_type/:group_id. See README for further details.", mqtt_update_topic_pattern : "Pattern to publish MQTT updates. Packets that " + "are received from other devices, and packets that are sent from this device will " + "result in updates being sent.", discovery_port : "UDP port to listen for discovery packets on. Defaults to " + "the same port used by MiLight devices, 48899. Use 0 to disable.", listen_repeats : "Increasing this increases the amount of time spent listening for " + "packets. Set to 0 to disable listening. Default is 3." } var UDP_PROTOCOL_VERSIONS = [ 5, 6 ]; var DEFAULT_UDP_PROTOCL_VERSION = 5; var selectize; var toHex = function(v) { return "0x" + (v).toString(16).toUpperCase(); } var activeUrl = function() { var deviceId = $('#deviceId option:selected').val() , groupId = $('#groupId .active input').data('value') , mode = getCurrentMode(); if (deviceId == "") { alert("Please enter a device ID."); throw "Must enter device ID"; } if (! $('#group-option').data('for').split(',').includes(mode)) { groupId = 0; } return "/gateways/" + deviceId + "/" + mode + "/" + groupId; } var getCurrentMode = function() { return $('input[name="mode"]:checked').data('value'); }; var updateGroup = _.throttle( function(params) { $.ajax( activeUrl(), { method: 'PUT', data: JSON.stringify(params), contentType: 'application/json' } ); }, 1000 ); var sendCommand = _.throttle( function(params) { $.ajax( '/system', { method: 'POST', data: JSON.stringify(params), contentType: 'application/json' } ); }, 1000 ) var sniffRequest; var sniffing = false; var getTraffic = function() { sniffRequest = $.get('/gateway_traffic', function(data) { $('#sniffed-traffic').prepend('
' + data + '
'); getTraffic(); }); }; var gatewayServerRow = function(deviceId, port, version) { var elmt = ''; elmt += ''; elmt += ''; elmt += ''; elmt += '' elmt += '';; elmt += ''; elmt += ''; elmt += '
'; 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 += ''; } elmt += '
'; elmt += ''; elmt += ''; elmt += ''; elmt += ''; return elmt; } var loadSettings = function() { $.getJSON('/settings', function(val) { Object.keys(val).forEach(function(k) { var field = $('#settings input[name="' + k + '"]'); if (field.length > 0) { if (field.attr('type') === 'radio') { field.filter('[value="' + val[k] + '"]').click(); } else { field.val(val[k]); } } }); if (val.device_ids) { selectize.clearOptions(); val.device_ids.forEach(function(v) { selectize.addOption({text: toHex(v), value: v}); }); selectize.refreshOptions(); } 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])); }); } }); }; var saveGatewayConfigs = function() { var form = $('#gateway-server-form') , 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 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(); $('.mode-option').map(function() { if ($(this).data('for').split(',').includes(currentMode)) { $(this).show(); } else { $(this).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) { console.log(releaseAsset[0].url); $('#firmware-link').prop('href', releaseAsset[0].browser_download_url); } } console.log(latestRelease); } var handleError = function(e, d) { console.log(e); console.log(d); } $('#current-version,#latest-version .status').html(''); $('#version-summary').html(''); $('#latest-version-info').hide(); $('#updates-modal').modal(); $.ajax( '/about', { success: function(data) { currentVersion = JSON.parse(data); handleReceiveData(); }, failure: handleError } ); $.ajax( '/latest_release', { success: function(data) { latestRelease = data; handleReceiveData(); }, failure: handleError } ); }; $(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) ; $('.hue-value-display').css({ backgroundColor: "hsl(" + hue + ",100%,50%)" }); 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') : $(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() { if (sniffing) { sniffRequest.abort(); sniffing = false; $(this).html('Start Sniffing'); } else { sniffing = true; getTraffic(); $(this).html('Stop Sniffing'); } }); $('#add-server-btn').click(function() { $('#gateway-server-configs').append(gatewayServerRow('', '')); }); $('#mode').change(updateModeOptions); $('body').on('click', '.remove-gateway-server', function() { $(this).closest('tr').remove(); }); selectize = $('#deviceId').selectize({ create: true, sortField: 'text', onOptionAdd: function(v, item) { item.value = parseInt(item.value); }, 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 = ""; FORM_SETTINGS.forEach(function(k) { var elmt = '
'; elmt += '
'; elmt += ''; if (FORM_SETTINGS_HELP[k]) { elmt += '
'; } elmt += '
'; if(k === "radio_interface_type") { elmt += '
' + ''+ '' + '
'; } else { elmt += ''; elmt += '
'; } settings += elmt; }); $('#settings').prepend(settings); $('#settings').submit(function(e) { var obj = {}; FORM_SETTINGS.forEach(function(k) { var elmt = $('#settings input[name="' + k + '"]'); if (elmt.attr('type') === 'radio') { obj[k] = elmt.filter(':checked').val(); } else { obj[k] = elmt.val(); } }); // pretty hacky. whatever. obj.device_ids = _.map( $('.selectize-control .option'), function(x) { return $(x).data('value') } ); $.ajax( "/settings", { method: 'put', contentType: 'application/json', data: JSON.stringify(obj) } ); e.preventDefault(); return false; }); $('#gateway-server-form').submit(function(e) { saveGatewayConfigs(); e.preventDefault(); return false; }); $('.field-help').each(function() { var elmt = $('') .addClass('glyphicon glyphicon-question-sign') .tooltip({ placement: 'top', title: $(this).data('help-text'), container: 'body' }); $(this).append(elmt); }); $('#updates-btn').click(handleCheckForUpdates); loadSettings(); updateModeOptions(); });