diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..8011dbf --- /dev/null +++ b/data/index.html @@ -0,0 +1,144 @@ + + + + + + Konfiguration + + + + + + +
+

Konfiguration

+
+
+ Verbindung + +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ Input/Output 1 + + +
+
+ Input/Output 2 + + +
+ +
+
+ + diff --git a/data/input-visibility.js b/data/input-visibility.js new file mode 100644 index 0000000..d22615f --- /dev/null +++ b/data/input-visibility.js @@ -0,0 +1,23 @@ +const form = document.querySelector("form"); +const dynamicInputs = form.querySelectorAll("[data-field][data-values]"); + +document.addEventListener("change", updateVisibility); + +function updateVisibility() { + dynamicInputs.forEach((element) => { + const input = form.querySelector(`#${element.dataset.field}`); + if (element.dataset.values.split("|").includes(input.value)) { + element.classList.remove("hidden"); + element + .querySelectorAll("input, select, button, textarea") + .forEach((childInput) => (childInput.disabled = false)); + } else { + element.classList.add("hidden"); + element + .querySelectorAll("input, select, button, textarea") + .forEach((childInput) => (childInput.disabled = true)); + } + }); +} + +updateVisibility(); diff --git a/data/load-data.js b/data/load-data.js new file mode 100644 index 0000000..2ae6f1e --- /dev/null +++ b/data/load-data.js @@ -0,0 +1,35 @@ +const form = document.querySelector("form"); + +async function loadData() { + try { + const req = await fetch("/config", { + method: "GET", + }); + if (!req.ok) { + throw new Error(`Response status: ${req.status}`); + } + + const json = await req.json(); + console.log(json); + return json; + } catch (error) { + console.log(error); + return null; + } +} + +function writeDataToInput(data) { + console.log("write data", typeof data); + for (const [key, value] of Object.entries(data)) { + const element = document.querySelector(`[name=${key}]`); + console.log(element); + element.value = value; + } + // send "change" event + form.dispatchEvent(new Event("change", { bubbles: true })); +} + +const data = await loadData(); +if (data !== null) { + writeDataToInput(data); +} diff --git a/data/style.css b/data/style.css new file mode 100644 index 0000000..b0676df --- /dev/null +++ b/data/style.css @@ -0,0 +1,128 @@ +:root { + --color-primary: #087e8b; + --color-on-primary: white; +} + +body { + margin: 0; + padding: 0; + background: linear-gradient(to left, #065760, black, black, #065760); + color: white; + font-family: Arial, Helvetica, sans-serif; + overflow: hidden; +} + +main { + background-color: #222; + max-width: 700px; + padding: 8px max(5%, 8px); + margin: 0 auto; + height: 100vh; + overflow: scroll; +} + +h1 { + text-align: center; +} + +form > * { + margin-bottom: 16px; +} + +fieldset { + border-radius: 8px; +} + +label { + display: block; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; +} + +input, +select { + width: clamp(200px, 100%, 400px); + background-color: #222; + color: white; + border: 1px solid white; + border-radius: 8px; + padding: 8px; + box-sizing: border-box; +} + +input:focus, +select:focus { + outline: none; + border-color: var(--color-primary); +} + +button { + display: block; + border: none; + inset: none; + border-radius: 8px; + background-color: var(--color-primary); + color: var(--color-on-primary); + padding: 8px 16px; + margin: 0 auto; +} + +:is(div:has(:is(input, select)), input, select, label) + + :is(div:has(:is(input, select)), input, select, label) { + margin-top: 8px; +} + +.hidden { + display: none; +} + +label.switch { + display: inline-flex; + flex-direction: row; + align-items: center; + gap: 8px; + -webkit-user-select: none; + user-select: none; +} + +label.switch input { + display: none; +} + +label.switch .slider { + display: inline-block; + position: relative; + height: 1em; + width: 2em; + background-color: #444; + border-radius: 1em; + border: 4px solid #444; +} + +label.switch .slider::before { + content: ""; + position: absolute; + height: 100%; + aspect-ratio: 1 / 1; + border-radius: 50%; + top: 50%; + background-color: white; + transition: all 0.1s linear; +} + +label.switch:active .slider::before { + transform: scale(1.3); + transform-origin: 50% 50%; +} + +label.switch input:not(:checked) + .slider::before { + left: 0%; + translate: 0 -50%; +} + +label.switch input:checked + .slider::before { + left: 100%; + translate: -100% -50%; +} diff --git a/data/submit.js b/data/submit.js new file mode 100644 index 0000000..6a24318 --- /dev/null +++ b/data/submit.js @@ -0,0 +1,55 @@ +const form = document.querySelector("form"); + +function parseValue(input) { + if (input.type === "checkbox") { + return input.checked + ? input.dataset.valueChecked + : input.dataset.valueNotChecked; + } + + if (input.value === "") { + return null; + } + + if (input.type === "number") { + const number = Number(input.value); + return isNaN(number) ? null : number; + } + + return input.value; +} + +form.addEventListener("submit", (event) => { + event.preventDefault(); + const inputFields = document.querySelectorAll( + "form :is(input, select, textarea):not(:disabled)" + ); + + const data = Array.from(inputFields).reduce((data, input) => { + data[input.name] = parseValue(input); + return data; + }, {}); + console.log(data); + + putData(data); +}); + +async function putData(data) { + try { + const res = await fetch("/config", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + if (!res.ok) { + throw new Error(`Response status: ${res.status}`); + } + + const json = await res.json(); + console.log(json); + } catch (error) { + console.error(error.message); + } +} diff --git a/platformio.ini b/platformio.ini index 07919a5..eec3bd7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,4 +14,5 @@ board = lolin_s2_mini framework = arduino lib_deps = hideakitai/ArtNet @ ^0.8.0 - someweisguy/esp_dmx @ ^4.1.0 \ No newline at end of file + bblanchon/ArduinoJson @ ^7.2.0 + ESP Async WebServer diff --git a/src/main.cpp b/src/main.cpp index f703549..39c9646 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,34 +1,39 @@ -// Art-Net DMX Interface Demo -// 2024-10-17 Patrick Schwarz - #include // #include +#include #include "ESPDMX.h" +#include +#include +#include -// WiFi stuff -const char *ssid = "artnet"; -const char *pwd = "mbgmbgmbg"; -const IPAddress ip(192, 168, 1, 201); -const IPAddress gateway(192, 168, 1, 1); -const IPAddress subnet(255, 255, 255, 0); +Preferences config; +DMXESPSerial dmx; + +AsyncWebServer server(80); -// Art-Net stuff ArtnetWiFi artnet; -// const String target_ip = "192.168.1.200"; -uint8_t universe = 1; // 0 - 15 const uint16_t size = 512; uint8_t data[size]; -uint8_t value = 0; - -// DMX stuff -DMXESPSerial dmx; void setup() { + Serial.begin(9600); - // Serial console - // Serial.begin(115200); + config.begin("dmx", false); + + uint8_t universe = config.getUChar("universe", 1); + + String ssid = config.getString("ssid", "artnet"); + String pwd = config.getString("pwd", "mbgmbgmbg"); + IPAddress defaultIp(192, 168, 1, 201); + IPAddress ip = config.getUInt("ip", defaultIp); + + IPAddress cidr = config.getUChar("cidr", 24); + + // TODO: \/ Herleiten \/ @psxde + const IPAddress gateway(192, 168, 1, 1); + const IPAddress subnet(255, 255, 255, 0); // WiFi stuff // WiFi.begin(ssid, pwd); @@ -51,54 +56,43 @@ void setup() // if Artnet packet comes to this universe, this function is called artnet.subscribeArtDmxUniverse(universe, [&](const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) { - /*Serial.print("lambda : artnet data from "); - Serial.print(remote.ip); - Serial.print(":"); - Serial.print(remote.port); - Serial.print(", universe = "); - Serial.print(universe); - Serial.print(", size = "); - Serial.print(size); - Serial.print(") :");*/ + for (size_t i = 0; i < size; ++i) + { + dmx.write((i + 1), data[i]); + } - for (size_t i = 0; i < size; ++i) - { - dmx.write((i + 1), data[i]); - // Serial.print(data[i]); - // Serial.print(","); - } - // Serial.println(); - - dmx.update(); }); + dmx.update(); }); // if Artnet packet comes, this function is called to every universe - artnet.subscribeArtDmx([&](const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) - { - /*Serial.print("received ArtNet data from "); - Serial.print(remote.ip); - Serial.print(":"); - Serial.print(remote.port); - Serial.print(", net = "); - Serial.print(metadata.net); - Serial.print(", subnet = "); - Serial.print(metadata.subnet); - Serial.print(", universe = "); - Serial.print(metadata.universe); - Serial.print(", sequence = "); - Serial.print(metadata.sequence); - Serial.print(", size = "); - Serial.print(size); - Serial.println(")");*/ }); + artnet.subscribeArtDmx([&](const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) {}); + + if (!SPIFFS.begin(true)) + { + Serial.println("An Error has occurred while mounting SPIFFS"); + return; + } + + server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); + + server.on("/config", HTTP_GET, [&, defaultIp, ssid, pwd, universe](AsyncWebServerRequest *request) + { + DynamicJsonDocument doc(1024); + + doc["ssid"] = ssid; + doc["pwd"] = pwd; + doc["ip"] = defaultIp; + doc["universe"] = universe; + + String jsonString; + serializeJson(doc, jsonString); + + request->send(200, "application/json", jsonString); }); + delay(1000); + server.begin(); + Serial.println("Server started!"); } void loop() { artnet.parse(); // check if artnet packet has come and execute callback - - /*value = (millis() / 4) % 256; - memset(data, value, size); - - artnet.setArtDmxData(data, size); - artnet.streamArtDmxTo(target_ip, universe); // automatically send set data in 40fps - // artnet.streamArtDmxTo(target_ip, net, subnet, univ); // or you can set net, subnet, and universe */ } \ No newline at end of file