let lampContainer = document.getElementById("leuchte"); let fillContainer = document.getElementById("fill"); let configInput = document.getElementById("config"); let configLoadError = document.getElementById("config_error"); let configOutput = document.getElementById("config_out"); let ctrlPane = document.getElementById("ctrlPane"); let serialErrorState = document.getElementById("serialState"); const warn = document.getElementById("configWarn"); // Inputs let input_setup_top = document.getElementById("setup_top"); // Elements const topElement = document.getElementById("module_top"); const config_ring1 = document.getElementById("ring1Confg"); const config_ring2 = document.getElementById("ring2Confg"); const config_ring3 = document.getElementById("ring3Confg"); const config_ring4 = document.getElementById("ring4Confg"); const config_ring5 = document.getElementById("ring5Confg"); const config_ring_list = [config_ring1, config_ring2, config_ring3, config_ring4, config_ring5]; // Serial port let port; const usbVendorId = 0x0403; // URLs const urls = { rings: { red: "res/images/licht_rot.jpg", green: "res/images/licht_gruen.jpg", blue: "res/images/licht_blau.jpg", yellow: "res/images/licht_gelb.jpg", white: "res/images/licht_weiss.jpg", empty: "", }, extras: { top: { normal: "res/images/top.jpg", buzzer: "res/images/licht_summer.jpg" } }, selector: { rings: { red: "res/images/module_red.jpg", green: "res/images/module_green.jpg", blue: "res/images/module_blue.jpg", yellow: "res/images/module_yellow.jpg", white: "res/images/module_white.jpg", empty: "res/images/no.png", }, extras: { top: { normal: "res/images/no.png", buzzer: "res/images/module_sounder.jpg" } } }, ctrls: { off: "res/images/buttons/Off.png", on: "res/images/buttons/On.png", alternate1: "res/images/buttons/Alternate1.png", alternate2: "res/images/buttons/Alternate2.png" } } // Attach event listeners input_setup_top.addEventListener("change", (event) => { myConfig.extras.top = event.target.value; applyConfig(); }); let myConfig = { rings: { 0: "green", 1: "yellow", 2: "red", 3: "empty", 4: "empty", }, extras: { top: "buzzer" }, states: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, } } function validateConfig() { // Check if there is a "hole" (empty) in the rings // We want to detect ELEMENT EMPTY ELEMENT // If the stack is "openended", thats fine // Iterate over keys let wasEmpty = false; for (let i = 0; i < Object.keys(myConfig.rings).length; i++) { console.log("Checking ring ", i); isNowEmpty = myConfig.rings[i] === "empty"; if (wasEmpty && !isNowEmpty) { warn.innerText = "Invalid config: Empty ring in the middle of the stack!"; return false; } console.log("Was empty: ", wasEmpty, " is empty: ", isNowEmpty); wasEmpty = myConfig.rings[i] === "empty"; } let amountOfPopulatedRings = 0; for (let i = 0; i < Object.keys(myConfig.rings).length; i++) { if (myConfig.rings[i] !== "empty") { amountOfPopulatedRings++; } } if(amountOfPopulatedRings === 0) { warn.innerText = "Invalid config: No rings selected!"; return false; } if(amountOfPopulatedRings == 5 && myConfig.extras.top === "buzzer") { warn.innerText = "Invalid config: Buzzer selected with all rings!"; return false; } warn.innerText = ""; return true; } function applyConfig() { validateConfig(); // Remove state from output let configCopy = JSON.parse(JSON.stringify(myConfig)); delete configCopy.states; configOutput.innerText = JSON.stringify(configCopy); topElement.src = urls.extras.top[myConfig.extras.top]; // Update radio buttons for (let i = config_ring_list.length-1; i >= 0; i--) { let currElm = config_ring_list[i]; for (let j = currElm.children.length-1; j >= 0; j--) { const child = currElm.children[j]; if (child.type === "radio") { if (child.value === myConfig.rings[i]) { child.checked = true; } } } } // Populate lamp tower // Iterate over rings keys for (let i = Object.keys(myConfig.rings).length - 1; i >= 0; i--) { const ring = myConfig.rings[i]; let ringElm = document.getElementById("ring" + (i + 1)); if (!ringElm) { ringElm = document.createElement("img"); ringElm.id = "ring" + (i + 1); fillContainer.appendChild(ringElm); } else { ringElm.src = urls.rings[ring]; } if(myConfig.states[i] == 0) { ringElm.classList.remove("is-lit"); ringElm.classList.remove("is-alternating-1"); ringElm.classList.remove("is-alternating-2"); } else if(myConfig.states[i] == 2) { ringElm.classList.add("is-alternating-1"); ringElm.classList.remove("is-alternating-2"); ringElm.classList.remove("is-lit"); }else{ ringElm.classList.add("is-lit"); ringElm.classList.remove("is-alternating-1"); ringElm.classList.remove("is-alternating-2"); } } setTimeout(() => { // Add divs for the controll elements in the same height as the rings // But only show for the visible rings for (let i = 0; i < Object.keys(myConfig.rings).length; i++) { // Get top and bottom y position of the ring from html element const ring = myConfig.rings[i]; let ringElm = document.getElementById("ring" + (i + 1)); if (ring === "empty") { continue; } console.log("Ring ", i, " is ", ring); // output coords console.log("Ring ", i, " coords: ", ringElm.getBoundingClientRect()); console.log(ringElm) let ctrlElm = document.getElementById("ctrl" + (i + 1)); if (!ctrlElm) { ctrlElm = document.createElement("div"); ctrlElm.id = "ctrl" + (i + 1); ctrlElm.classList.add("ctrl"); ctrlPane.appendChild(ctrlElm); } // Set position by top and bottom of the ring (ignore left and right) ctrlElm.style.position = "absolute"; ctrlElm.style.top = ringElm.getBoundingClientRect().top + "px"; ctrlElm.style.bottom = ringElm.getBoundingClientRect().bottom + "px"; ctrlElm.style.width = "250px"; // Add buttons for the modes (off, on, alternate1, alternate2) let actionList = ["off", "on", "alternate1", "alternate2"]; for (let j = 0; j < actionList.length; j++) { let action = actionList[j]; let actionElm = document.getElementById("action" + (i + 1) + action); if (!actionElm) { actionElm = document.createElement("button"); actionElm.id = "action" + (i + 1) + action; // actionElm.innerText = action; actionElm.style.backgroundImage = `url(${urls.ctrls[action]})`; actionElm.style.backgroundSize = "contain"; actionElm.style.backgroundRepeat = "no-repeat"; actionElm.classList.add("ctrl-action"); actionElm.addEventListener("click", () => { writeAction(i, action); }); ctrlElm.appendChild(actionElm); } } // ctrlElm.style.left = "0px"; // ctrlElm.style.right = "0px"; } }, 200); } function writeAction(ring, state){ actionToState = { "off": 0, "on": 1, "alternate1": 2, "alternate2": 3 } // Check if the ring is empty and check if buzzer is enabled if(myConfig.rings[ring] === "empty" && myConfig.extras.top === "buzzer") { // Write to buzzer return; } myConfig.states[ring] = actionToState[state]; applyConfig(); serial_send(); } function loadConfig() { try { let config = JSON.parse(configInput.value); // Merge configs, as states is not part of the input config.states = myConfig.states; myConfig = config; applyConfig(); } catch (error) { console.error("Error loading config: ", error); configLoadError.innerText = "Error loading config: " + error; } finally { configLoadError.innerText = "Config loaded!"; } input_setup_top.value = myConfig.extras.top; } function serial_check_env() { if (!navigator.serial) { console.error("Serial not supported!"); serialErrorState.innerText = "Serial not supported in this browser! Try using something chromium based!"; return false; } return true; } async function serial_connect() { if (!serial_check_env()) { return; } navigator.serial.requestPort({ filters: [{ usbVendorId }] }).then((serialPort) => { serialPort.open({ baudRate: 9600 }); port = serialPort; serialErrorState.innerText = "Connected to serial port!"; }).catch((error) => { console.error("Error connecting to serial port: ", error); serialErrorState.innerText = "Error connecting to serial port: " + error; }); } async function serial_send() { if(port && port.writable) { let data = `WR${myConfig.states[0]}${myConfig.states[1]}${myConfig.states[2]}${myConfig.states[3]}${myConfig.states[4]}\r`; const encoder = new TextEncoder(); const writer = port.writable.getWriter(); await writer.write(encoder.encode(data)); writer.releaseLock(); } } // // Populate config section // // From urls->selector->rings // // Show as radio buttons for (let i = config_ring_list.length-1; i >= 0; i--) { let currElm = config_ring_list[i]; for (const key in urls.selector.rings) { if (urls.selector.rings.hasOwnProperty(key)) { const url = urls.selector.rings[key]; let radio = document.createElement("input"); radio.type = "radio"; radio.name = "ring" + (i + 1); radio.value = key; radio.id = "ring" + (i + 1) + key; radio.addEventListener("change", (event) => { myConfig.rings[i] = event.target.value; applyConfig(); }); currElm.appendChild(radio); let img = document.createElement("img"); img.src = url; img.addEventListener("click", () => { radio.checked = true; myConfig.rings[i] = key; applyConfig(); } ); currElm.appendChild(img); } } } applyConfig(); setTimeout(() => { applyConfig(); } , 200);