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
+
+
+
+
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