Files
signalmen/nxtRender.js
2025-04-25 23:20:12 +02:00

348 lines
9.4 KiB
JavaScript

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);