var socket; function load() { socket = init_websocket(); // initialize RelayOutlet SVG color Object.entries(init_state).forEach(([device_id, sub_devices]) => { let device = document.querySelector('#' + device_id); Object.entries(sub_devices).forEach(([sub_device_id, state]) => { let sub_device = device.querySelector('.' + sub_device_id); let svg = sub_device.querySelector('object').getSVGDocument().firstElementChild; if (state) { svg.classList.remove('off'); svg.classList.add('on'); } }); }); /*document.querySelectorAll('input[type=color]').forEach((led_input) => { led_input.onchange = function(event) { } });*/ } /* Websocket setup */ function init_websocket() { let socket = new WebSocket('wss://' + window.location.hostname + ws_uri); socket._send = socket.send; socket.send = function(event_title, data) { data = JSON.stringify({event: event_title, data: data}); if (socket.readyState == 0) { console.log("Socket is still opening!"); return; } socket._send(data); } socket.onmessage = onmessage; socket.onclose = onclose; socket.onerror = onerror; socket.events = {}; socket.events['toggle_outlet'] = toggle_outlet_recv; socket.events['edit_field'] = edit_field_recv; socket.events['new_device'] = new_device_recv; socket.events['lock_device'] = lock_device_recv; socket.events['delete_device'] = delete_device_recv; socket.events['neopixel'] = neopixel_recv; socket.events['lixie_clock'] = lixie_clock_recv; return socket; } function onmessage (e) { let data = JSON.parse(e.data); let event = data.event; data = data.data; if (!data.ok) { throw new Error("Socket error: event = " + event + ", error = " + data.error); } if (socket.events[event] === undefined) { console.log("Unknown socket event: " + event); return; } socket.events[event](data); } async function onclose(e) { if (e.wasClean) { return; } // no need to reconnect console.log(e); console.log('Websocket lost connection to server. Re-trying...'); socket = init_websocket(); await sleep(5000); } function onerror(e) { console.log("Websocket error!") console.log(e); } /* Websocket receive */ function toggle_outlet_recv(data) { let device = document.querySelector('#' + data.device_id); let sub_device = device.querySelector('.' + data.sub_device_id); let svg = sub_device.querySelector('.outlet_image').getSVGDocument().firstChild; if (data.state === true) { svg.classList.remove('off'); svg.classList.add('on'); } else { svg.classList.remove('on'); svg.classList.add('off'); } } function edit_field_recv(data) { let device = document.querySelector('#' + data.device_id); if (data.sub_device_id) { device = device.querySelector('.' + data.sub_device_id); } let field = device.querySelector('.' + data.field); let span = document.createElement('span'); span.innerText = data['value']; span.className = 'field_value'; field.firstElementChild.replaceWith(span); let edit = document.createElement('span'); edit.innerHTML = ''; edit.className = 'edit font-awesome'; edit.setAttribute('onclick', 'edit_field(this.parentElement)'); field.children[1].replaceWith(edit); } function new_device_recv(data) { let device_type, sub_device_template; if (data.device_type == 'relay_device') { device_template = document.querySelector('#RelayDevice_template'); sub_device_template = document.querySelector('#RelayOutlet_template'); } else if (data.device_type == 'light_strip') { device_template = document.querySelector('#LightStrip_template'); sub_device_template = document.querySelector('#NeoPixel_template'); } else if (data.device_type == 'lixie_clock') { device_template = document.querySelector('#LixieClock_template'); sub_device_template = document.querySelector('#LixieDisplay_template'); } let device = document.importNode(device_template.content, true); device.querySelector('.id').querySelector('.field_value').textContent = data.device_id; for (let sub_device_id of data.sub_device_ids) { let sub_device = document.importNode(sub_device_template.content, true); sub_device.firstElementChild.classList.add(sub_device_id); if (sub_device.querySelector('.id')) { sub_device.querySelector('.id').textContent = sub_device_id; } device.querySelector('.sub_devices').appendChild(sub_device); } document.querySelector('#devices').appendChild(device); let children = document.querySelector('#devices').children; children[children.length - 1].id = data.device_id; } function lock_device_recv(data) { let device = document.querySelector('#' + data.device_id); let fields = device.querySelectorAll('.editable'); if (data.locked) { fields.forEach(function(field) { field.querySelector('.edit').remove(); }); device.querySelector('.id').querySelector('.lock').remove(); device.querySelector('.id').querySelector('.delete').remove(); let unlock = document.createElement('span'); unlock.innerHTML = ''; unlock.className = 'unlock font-awesome'; unlock.setAttribute('onclick', 'unlock_device(this.parentElement.parentElement)'); device.querySelector('.id').appendChild(unlock); } else { fields.forEach(function(field) { let edit = document.createElement('span'); edit.innerHTML = ''; edit.className = 'edit font-awesome'; edit.setAttribute('onclick', 'edit_field(this.parentElement)'); field.appendChild(edit); }); device.querySelector('.id').querySelector('.unlock').remove(); let delete_elem = document.createElement('span'); delete_elem.innerHTML = ''; delete_elem.className = 'delete font-awesome'; delete_elem.setAttribute('onclick', 'delete_device(this.parentElement.parentElement)'); device.querySelector('.id').appendChild(delete_elem); let lock = document.createElement('span'); lock.innerHTML = ''; lock.className = 'lock font-awesome'; lock.setAttribute('onclick', 'lock_device(this.parentElement.parentElement)'); device.querySelector('.id').appendChild(lock); } } function delete_device_recv(data) { let device = document.querySelector('#' + data.device_id); device.remove() } function neopixel_recv(data) { let device = document.querySelector('#' + data.device_id); if (data.change_mode === 'state') { select = device.querySelector('.state_select'); if (data.type === 'solid') { select.value = data.type; state_select(select, false); if (data.amount === 'all') { let sub_devices = device.querySelector('.sub_devices'); for (let sub_device of sub_devices.children) { sub_device.firstElementChild.style.backgroundColor = data.color; sub_device.firstElementChild.firstElementChild.value = data.color; } device.querySelector('#state_solid_all_color_' + device.id).value = data.color; } else if (data.amount === 'single') { let sub_device = device.querySelector('.' + data.sub_device_id); sub_device.firstElementChild.style.backgroundColor = data.color; sub_device.firstElementChild.firstElementChild.value = data.color; } } else if (data.type === 'rainbow') { select.value = data.type; state_select(select, false); device.querySelector('#state_rainbow_red_freq_' + device.id).value = data.rainbow_params[0]; device.querySelector('#state_rainbow_green_freq_' + device.id).value = data.rainbow_params[1]; device.querySelector('#state_rainbow_blue_freq_' + device.id).value = data.rainbow_params[2]; device.querySelector('#state_rainbow_red_phase_' + device.id).value = data.rainbow_params[3]; device.querySelector('#state_rainbow_green_phase_' + device.id).value = data.rainbow_params[4]; device.querySelector('#state_rainbow_blue_phase_' + device.id).value = data.rainbow_params[5]; device.querySelector('#state_rainbow_center_' + device.id).value = data.rainbow_params[6]; device.querySelector('#state_rainbow_width_' + device.id).value = data.rainbow_params[7]; } else if (data.type === 'america') { select.value = data.type; state_select(select, false); device.querySelector('#state_america_stripe_' + device.id).value = data.america_params[0]; device.querySelector('#state_america_magnitude_' + device.id).value = data.america_params[1]; } } else if (data.change_mode === 'animation') { if (data.property_type === 'mode') { select = device.querySelector('.animation_select'); if (data.type === 'static') { select.value = data.type; animation_select(select, false); } else if (data.type === 'rotate_left') { select.value = data.type; animation_select(select, false); device.querySelector('#animation_rotate_count_' + device.id).value = data.rotate_count; } else if (data.type === 'rotate_right') { select.value = data.type; animation_select(select, false); device.querySelector('#animation_rotate_count_' + device.id).value = data.rotate_count; } } else if (data.property_type === 'delay') { device.querySelector('#animation_delay_' + device.id).value = data.delay; } } else if (data.change_mode === 'strip') { if (data.amount === 'full') { let sub_devices = device.querySelector('.sub_devices'); for (let i = 0; i < sub_devices.children.length; i++) { sub_devices.children[i].firstElementChild.style.backgroundColor = data.colors[i]; sub_devices.children[i].firstElementChild.firstElementChild.value = data.colors[i]; } } } } function lixie_clock_recv(data) { let device = document.querySelector('#' + data.device_id); if (data.change_mode === 'time') { let radio = device.querySelector('#display_mode_time_' + device.id); radio.checked = true; lixie_display_mode(radio, false); } else if (data.change_mode === 'number') { let radio = device.querySelector('#display_mode_number_' + device.id); radio.checked = true; lixie_display_mode(radio, false); device.querySelector('#lixie_number_' + device.id).value = data.display_number; } else if (data.change_mode === 'color') { device.querySelector('#display_color_' + device.id).value = data.color; } else if (data.change_mode === 'time_offset') { device.querySelector('#lixie_time_offset_' + device.id).value = data.time_offset; } } /* Websocket send */ function toggle_outlet(svg) { let sub_dev = get_object_from_svg(svg).parentElement; let data = { device_id: sub_dev.parentElement.parentElement.id, sub_device_id: sub_dev.querySelector('.id').innerText, }; socket.send('toggle_outlet', data); } function save_field(field) { let value = field.firstElementChild.value; let device_id = field.parentElement.id; let sub_device_id; if (field.parentElement.className.includes('sub_device')) { sub_device_id = field.parentElement.children[0].textContent; device_id = field.parentElement.parentElement.parentElement.id; } else { sub_device_id = ''; } let data = { device_id: device_id, sub_device_id: sub_device_id, field: field.classList[0], value: value }; socket.send('edit_field', data); } function new_device(dialog) { let device_id = dialog.querySelector('#new_device_id').value; let device_type = dialog.querySelector('input[name=new_device_type]:checked').value; let num_sub_devices = dialog.querySelector('#new_device_num_sub_devices').value; let data = { 'device_id': device_id, 'device_type': device_type, 'num_sub_devices': num_sub_devices, } socket.send('new_device', data); } function lock_device(device) { if (device.querySelector('.save')) { return; } let data = { device_id: device.id, locked: true, }; socket.send('lock_device', data); } function unlock_device(device) { let data = { device_id: device.id, locked: false, }; socket.send('lock_device', data); } function delete_device(device) { if (!window.confirm("Are you sure you want to delete this device?")) { return; } let data = { device_id: device.id, }; socket.send('delete_device', data); } function neopixel_state(device) { let sub_device; if (device.classList.contains('sub_device')) { sub_device = device; device = sub_device.closest('.device'); } let state_mode = device.querySelector('.state_select').value; let data = { device_id: device.id, change_mode: 'state', type: state_mode, } if (state_mode === 'solid') { let amount; let radios = device.querySelector('.state_solid').querySelectorAll('input[type=radio]'); for (let radio of radios) { if (radio.checked) { amount = radio.value; break; } } data['amount'] = amount; if (amount === 'all') { data['color'] = device.querySelector('#state_solid_all_color_' + device.id).value; } else if (amount === 'single') { data['color'] = sub_device.firstElementChild.firstElementChild.value; data['sub_device_id'] = sub_device.classList[2]; } } else if (state_mode === 'rainbow') { data['rainbow_params'] = [ device.querySelector('#state_rainbow_red_freq_' + device.id).value, device.querySelector('#state_rainbow_green_freq_' + device.id).value, device.querySelector('#state_rainbow_blue_freq_' + device.id).value, device.querySelector('#state_rainbow_red_phase_' + device.id).value, device.querySelector('#state_rainbow_green_phase_' + device.id).value, device.querySelector('#state_rainbow_blue_phase_' + device.id).value, device.querySelector('#state_rainbow_center_' + device.id).value, device.querySelector('#state_rainbow_width_' + device.id).value ] } else if (state_mode === 'america') { data['america_params'] = [ device.querySelector('#state_america_stripe_' + device.id).value, device.querySelector('#state_america_magnitude_' + device.id).value ] } socket.send('neopixel', data); } function neopixel_animation(device) { let property_type; if (device.nodeName === 'INPUT') { property_type = 'delay'; device = device.closest('.device'); } else { property_type = 'mode'; } let data = { device_id: device.id, change_mode: 'animation', property_type: property_type } if (property_type === 'mode') { let animation_mode = device.querySelector('.animation_select').value; data['type'] = animation_mode; if (animation_mode === 'static') { } else if (animation_mode === 'rotate_left' || animation_mode === 'rotate_right') { data['rotate_count'] = device.querySelector('#animation_rotate_count_' + device.id).value; } } else if (property_type === 'delay') { data['delay'] = device.querySelector('#animation_delay_' + device.id).value; } socket.send('neopixel', data); } function lixie_clock(input) { let device = input.closest('.device'); data = { device_id: device.id } if (input.className === 'time') { data['change_mode'] = 'time'; } else if (input.className === 'number') { data['change_mode'] = 'number'; data['display_number'] = device.querySelector('#lixie_number_' + device.id).value; } else if (input.className === 'color') { data['change_mode'] = 'color'; data['color'] = input.value } else if (input.className === 'time_offset') { data['change_mode'] = 'time_offset'; data['time_offset'] = device.querySelector('#lixie_time_offset_' + device.id).value; } socket.send('lixie_clock', data); } /* DOM */ function edit_field(field) { let value = field.firstElementChild.innerText; let input = document.createElement('input'); input.value = value; field.firstElementChild.replaceWith(input); let save = document.createElement('span'); save.innerHTML = ''; save.className = 'save font-awesome'; save.setAttribute('onclick', 'save_field(this.parentElement)'); field.children[1].replaceWith(save); } function state_solid_amount(radio) { let device = radio.closest('.device'); let sub_devices = device.querySelector('.sub_devices'); for (let sub_device of sub_devices.children) { let input = sub_device.firstElementChild.firstElementChild; if (radio.value === 'all') { input.disabled = true; } else if (radio.value === 'single') { input.disabled = false; } } if (radio.value === 'all') { device.querySelector('#state_solid_all_color_' + device.id).disabled = false; } else if (radio.value === 'single') { device.querySelector('#state_solid_all_color_' + device.id).disabled = true; } } function state_select(select, send_socket_event = true) { let device = select.closest('.device'); if (select.value === 'solid') { select.parentElement.querySelector('.state_solid').style.display = 'block'; select.parentElement.querySelector('.state_rainbow').style.display = 'none'; select.parentElement.querySelector('.state_america').style.display = 'none'; } else if (select.value === 'rainbow') { select.parentElement.querySelector('.state_solid').style.display = 'none'; select.parentElement.querySelector('.state_rainbow').style.display = 'block'; select.parentElement.querySelector('.state_america').style.display = 'none'; } else if (select.value === 'america') { select.parentElement.querySelector('.state_solid').style.display = 'none'; select.parentElement.querySelector('.state_rainbow').style.display = 'none'; select.parentElement.querySelector('.state_america').style.display = 'block'; } let sub_devices = device.querySelector('.sub_devices'); for (let sub_device of sub_devices.children) { let input = sub_device.firstElementChild.firstElementChild; input.disabled = true; } if (send_socket_event) { neopixel_state(device); } } function animation_select(select, send_socket_event = true) { let device = select.closest('.device'); if (select.value === 'static') { select.parentElement.querySelector('.animation_static').style.display = 'block'; select.parentElement.querySelector('.animation_rotate').style.display = 'none'; } else if (select.value === 'rotate_left' || select.value === 'rotate_right') { select.parentElement.querySelector('.animation_static').style.display = 'none'; select.parentElement.querySelector('.animation_rotate').style.display = 'block'; } if (send_socket_event) { neopixel_animation(device); } } function lixie_display_mode(radio, send_socket_event = true) { let device = radio.closest('.device'); if (radio.value === 'time') { device.querySelector('.lixie_number').style.display = 'none'; device.querySelector('.lixie_time_offset').style.display = 'block'; } else if (radio.value === 'number') { device.querySelector('.lixie_number').style.display = 'block'; device.querySelector('.lixie_time_offset').style.display = 'none'; } if (send_socket_event) { lixie_clock(radio); } } /* Misc */ function get_object_from_svg(svg) { let all_objects = document.getElementsByTagName("object"); for (let i=0; i < all_objects.length; i++) { if (svg === all_objects[i].getSVGDocument().firstElementChild) { return all_objects[i]; } } return null; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }