Merge pull request #47 from HendrikRauh/DMX-3-configuration-WebUI

Dmx 3 configuration web UI
This commit is contained in:
Hendrik Rauh 2024-12-16 21:32:11 +01:00 committed by GitHub
commit 4a14d86e15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 184 additions and 83 deletions

View file

@ -11,6 +11,7 @@
<script type="module" src="/networks.js" defer></script> <script type="module" src="/networks.js" defer></script>
<script type="module" src="/submit.js" defer></script> <script type="module" src="/submit.js" defer></script>
<script type="module" src="/reset.js" defer></script> <script type="module" src="/reset.js" defer></script>
<script type="module" src="/range-input.js" defer></script>
</head> </head>
<body> <body>
<main> <main>
@ -39,6 +40,7 @@
name="ip-method" name="ip-method"
id="input-ip-method" id="input-ip-method"
title="IP-" title="IP-"
required
> >
<option value="0">Statisch</option> <option value="0">Statisch</option>
<option value="1">DHCP</option> <option value="1">DHCP</option>
@ -52,6 +54,7 @@
name="ip" name="ip"
id="input-ip" id="input-ip"
placeholder="IP-Adresse" placeholder="IP-Adresse"
required
/> />
</label> </label>
<label> <label>
@ -61,6 +64,7 @@
name="subnet" name="subnet"
id="input-subnet" id="input-subnet"
placeholder="Subnetzmaske" placeholder="Subnetzmaske"
required
/> />
</label> </label>
<label> <label>
@ -70,6 +74,7 @@
name="gateway" name="gateway"
id="input-gateway" id="input-gateway"
placeholder="Gateway" placeholder="Gateway"
required
/> />
</label> </label>
</div> </div>
@ -79,6 +84,7 @@
name="connection" name="connection"
id="input-connection" id="input-connection"
title="Verbindung" title="Verbindung"
required
> >
<option value="0">WiFi-Station</option> <option value="0">WiFi-Station</option>
<option value="1">WiFi-AccessPoint</option> <option value="1">WiFi-AccessPoint</option>
@ -93,6 +99,7 @@
name="ssid" name="ssid"
id="input-ssid" id="input-ssid"
placeholder="SSID" placeholder="SSID"
required
/> />
</label> </label>
</div> </div>
@ -103,6 +110,7 @@
name="ssid" name="ssid"
id="select-network" id="select-network"
title="Netzwerk" title="Netzwerk"
required
></select> ></select>
<button <button
type="button" type="button"
@ -133,8 +141,8 @@
type="checkbox" type="checkbox"
name="direction-1" name="direction-1"
id="input-direction-1" id="input-direction-1"
data-value-not-checked="output" data-value-not-checked="0"
data-value-checked="input" data-value-checked="1"
/> />
<span class="slider"></span> <span class="slider"></span>
<span>Input</span> <span>Input</span>
@ -159,8 +167,8 @@
type="checkbox" type="checkbox"
name="direction-2" name="direction-2"
id="input-direction-2" id="input-direction-2"
data-value-not-checked="output" data-value-not-checked="0"
data-value-checked="input" data-value-checked="1"
/> />
<span class="slider"></span> <span class="slider"></span>
<span>Input</span> <span>Input</span>
@ -177,6 +185,23 @@
/> />
</label> </label>
</fieldset> </fieldset>
<fieldset>
<legend>Sonstiges</legend>
<label>
LED-Helligkeit
<div>
<input
type="range"
name="led-brightness"
id="led-brightness"
min="0"
max="255"
class="range"
/>
<span class="range-value"></span>
</div>
</label>
</fieldset>
<div class="buttons"> <div class="buttons">
<button type="reset">Zurücksetzen</button> <button type="reset">Zurücksetzen</button>
<button type="submit">Speichern</button> <button type="submit">Speichern</button>

View file

@ -6,6 +6,8 @@ import {
const form = document.querySelector("form"); const form = document.querySelector("form");
export let data = {};
export async function loadData(timeout = null) { export async function loadData(timeout = null) {
const req = await fetch("/config", { const req = await fetch("/config", {
method: "GET", method: "GET",
@ -38,7 +40,7 @@ export function writeDataToInput(data) {
showLoadingScreen("Konfiguration wird geladen..."); showLoadingScreen("Konfiguration wird geladen...");
try { try {
const data = await loadData(); data = await loadData();
hideLoadingScreen(); hideLoadingScreen();
writeDataToInput(data); writeDataToInput(data);
} catch (error) { } catch (error) {

View file

@ -1,3 +1,5 @@
import { data } from "./load-data.js";
const networkDropdown = document.querySelector("#select-network"); const networkDropdown = document.querySelector("#select-network");
const refreshButton = document.querySelector("#refresh-networks"); const refreshButton = document.querySelector("#refresh-networks");
const refreshIcon = refreshButton.querySelector("img"); const refreshIcon = refreshButton.querySelector("img");
@ -5,11 +7,24 @@ const refreshIcon = refreshButton.querySelector("img");
let isLoading = false; let isLoading = false;
refreshButton.addEventListener("click", async () => { refreshButton.addEventListener("click", async () => {
// check if interface is connected via WiFi
if (data.connection == 0 || data.connection == 1) {
alert(
"Beim WLAN-Scan wird die Verbindung hardwarebedingt kurzzeitig" +
"unterbrochen.\n" +
"Möglicherweise muss das Interface neu verbunden werden."
);
}
updateNetworks(); updateNetworks();
}); });
// check if connected via WiFi-Station
if (data.connection === 0) {
// show currently connected wifi
insertNetworks([data.ssid]);
}
function insertNetworks(networks) { function insertNetworks(networks) {
networks.unshift(""); // add empty option
networkDropdown.textContent = ""; // clear dropdown networkDropdown.textContent = ""; // clear dropdown
for (const ssid of networks) { for (const ssid of networks) {
@ -53,8 +68,6 @@ async function loadNetworks() {
async function updateNetworks() { async function updateNetworks() {
const networks = await loadNetworks(); const networks = await loadNetworks();
if (networks) { if (networks) {
insertNetworks(networks); insertNetworks(["", ...networks], true);
} }
} }
updateNetworks();

14
data/range-input.js Normal file
View file

@ -0,0 +1,14 @@
document.querySelector("form").addEventListener("input", (event) => {
if (event.target.classList.contains("range")) {
updateValue(event.target);
}
});
function updateValue(slider) {
const percentage = Math.round((slider.value / slider.max) * 100);
slider.nextElementSibling.innerText = `${percentage}%`;
}
document.querySelectorAll("input[type='range'].range").forEach((element) => {
updateValue(element);
});

View file

@ -3,7 +3,7 @@
--color-on-primary: white; --color-on-primary: white;
--color-background: #222; --color-background: #222;
--color-danger: #fa2b58; --color-danger: #fa2b58;
--icon-button-size: 2.5rem; --appended-item-size: 2.5rem;
} }
body { body {
@ -50,8 +50,13 @@ label span {
} }
input, input,
select { select,
label div {
width: clamp(200px, 100%, 400px); width: clamp(200px, 100%, 400px);
}
input,
select {
background-color: var(--color-background); background-color: var(--color-background);
color: white; color: white;
border: 1px solid white; border: 1px solid white;
@ -66,8 +71,13 @@ select:focus {
border-color: var(--color-primary); border-color: var(--color-primary);
} }
select:has(+ .icon-button) { select:has(+ .icon-button),
width: calc(clamp(200px, 100%, 400px) - var(--icon-button-size) - 8px); label div input[type="range"] {
width: calc(clamp(200px, 100%, 400px) - var(--appended-item-size) - 8px);
}
input[type="range"] {
accent-color: var(--color-primary);
} }
button { button {
@ -213,8 +223,8 @@ button.reload {
.icon-button { .icon-button {
padding: 8px; padding: 8px;
font-size: 0; font-size: 0;
width: var(--icon-button-size); width: var(--appended-item-size);
height: var(--icon-button-size); height: var(--appended-item-size);
} }
.spinning { .spinning {
@ -223,3 +233,16 @@ button.reload {
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: linear; animation-timing-function: linear;
} }
div:has(.range-value) {
display: flex;
flex-direction: row;
gap: 8px;
}
.range-value {
width: var(--appended-item-size);
height: var(--appended-item-size);
text-align: center;
line-height: var(--appended-item-size);
}

View file

@ -18,7 +18,7 @@ function parseValue(input) {
return null; return null;
} }
if (input.type === "number") { if (input.type === "number" || input.type === "range") {
const number = Number(input.value); const number = Number(input.value);
return Number.isNaN(number) ? null : number; return Number.isNaN(number) ? null : number;
} }

View file

@ -16,6 +16,7 @@
#include "ESPDMX.h" #include "ESPDMX.h"
#include <LittleFS.h> #include <LittleFS.h>
#include "routes/config.h" #include "routes/config.h"
#include "routes/networks.h"
DMXESPSerial dmx1; DMXESPSerial dmx1;
DMXESPSerial dmx2; DMXESPSerial dmx2;
@ -94,6 +95,9 @@ void setup()
esp_read_mac(mac, ESP_MAC_ETH); esp_read_mac(mac, ESP_MAC_ETH);
// LED // LED
config.begin("dmx", true);
brightness_led = config.getUInt("led-brightness", DEFAULT_LED_BRIGHTNESS);
config.end();
analogWrite(PIN_LED, brightness_led); analogWrite(PIN_LED, brightness_led);
// Button // Button
@ -124,11 +128,11 @@ void setup()
config.begin("dmx", true); config.begin("dmx", true);
universe1 = config.getUInt("universe-1", 1); universe1 = config.getUInt("universe-1", DEFAULT_UNIVERSE1);
universe2 = config.getUInt("universe-2", 2); universe2 = config.getUInt("universe-2", DEFAULT_UNIVERSE2);
direction1 = static_cast<Direction>(config.getUInt("direction-1", 0)); direction1 = static_cast<Direction>(config.getUInt("direction-1", DEFAULT_DIRECTION1));
direction2 = static_cast<Direction>(config.getUInt("direction-2", 1)); direction2 = static_cast<Direction>(config.getUInt("direction-2", DEFAULT_DIRECTION2));
Serial.print("Port A: Universe "); Serial.print("Port A: Universe ");
Serial.print(universe1); Serial.print(universe1);
@ -140,25 +144,22 @@ void setup()
Serial.print(" "); Serial.print(" ");
Serial.println((direction2 == Input) ? "DMX -> Art-Net" : "Art-Net -> DMX"); Serial.println((direction2 == Input) ? "DMX -> Art-Net" : "Art-Net -> DMX");
Connection connection = static_cast<Connection>(config.getUInt("connection", WiFiAP)); Connection connection = static_cast<Connection>(config.getUInt("connection", DEFAULT_CONNECTION));
IpMethod ipMethod = static_cast<IpMethod>(config.getUInt("ip-method"), DHCP); IpMethod ipMethod = static_cast<IpMethod>(config.getUInt("ip-method"), DEFAULT_IP_METHOD);
WiFi.macAddress(mac);
char hostname[30]; char hostname[30];
snprintf(hostname, sizeof(hostname), "ChaosDMX-%02X%02X", mac[4], mac[5]); snprintf(hostname, sizeof(hostname), "ChaosDMX-%02X%02X", mac[4], mac[5]);
DEFAULT_SSID = hostname;
Serial.print("Hostname: "); Serial.print("Hostname: ");
Serial.println(hostname); Serial.println(hostname);
String ssid = config.getString("ssid", hostname); String ssid = config.getString("ssid", DEFAULT_SSID);
String pwd = config.getString("password", "mbgmbgmbg"); String pwd = config.getString("password", DEFAULT_PASSWORD);
// Default IP as defined in standard https://art-net.org.uk/downloads/art-net.pdf, page 13 // Default IP as defined in standard https://art-net.org.uk/downloads/art-net.pdf, page 13
IPAddress defaultIp(2, mac[3], mac[4], mac[5]); IPAddress ip = config.getUInt("ip", DEFAULT_IP);
IPAddress ip = config.getUInt("ip", defaultIp); IPAddress subnet = config.getUInt("subnet", DEFAULT_SUBNET);
IPAddress defaultSubnet(255, 0, 0, 0); IPAddress gateway = config.getUInt("gateway", NULL);
IPAddress subnet = config.getUInt("subnet", defaultSubnet);
IPAddress defaultGateway(2, 0, 0, 1);
IPAddress gateway = config.getUInt("gateway", defaultGateway);
config.end(); config.end();
@ -320,6 +321,9 @@ void setup()
server.begin(); server.begin();
Serial.println("Server started!"); Serial.println("Server started!");
// scan networks and cache them
WiFi.scanNetworks(true);
ledBlink(0); ledBlink(0);
} }

View file

@ -4,6 +4,7 @@
#include "WiFi.h" #include "WiFi.h"
Preferences config; Preferences config;
String DEFAULT_SSID = "";
#pragma region Utility #pragma region Utility
@ -75,27 +76,23 @@ void onGetConfig(AsyncWebServerRequest *request)
{ {
config.begin("dmx", true); config.begin("dmx", true);
IPAddress defaultIp(192, 168, 1, 201); IPAddress ip = config.getUInt("ip", DEFAULT_IP);
IPAddress ip = config.getUInt("ip", defaultIp); IPAddress subnet = config.getUInt("subnet", DEFAULT_SUBNET);
IPAddress gateway = config.getUInt("gateway", NULL);
IPAddress defaultSubnet(255, 255, 255, 0);
IPAddress subnet = config.getUInt("subnet", defaultSubnet);
IPAddress defaultGateway(192, 168, 1, 1);
IPAddress gateway = config.getUInt("gateway", defaultGateway);
JsonDocument doc; JsonDocument doc;
doc["connection"] = config.getUInt("connection", WiFiSta); doc["connection"] = config.getUInt("connection", DEFAULT_CONNECTION);
doc["ssid"] = config.getString("ssid", "artnet"); doc["ssid"] = config.getString("ssid", DEFAULT_SSID);
doc["password"] = config.getString("password", "mbgmbgmbg"); doc["password"] = config.getString("password", DEFAULT_PASSWORD);
doc["ip-method"] = config.getUInt("ip-method"), Static; doc["ip-method"] = config.getUInt("ip-method", DEFAULT_IP_METHOD);
doc["ip"] = ip.toString(); doc["ip"] = ip.toString();
doc["subnet"] = subnet.toString(); doc["subnet"] = subnet.toString();
doc["gateway"] = gateway.toString(); doc["gateway"] = gateway != NULL ? gateway.toString() : "";
doc["universe-1"] = config.getUInt("universe-1", 1); doc["universe-1"] = config.getUInt("universe-1", DEFAULT_UNIVERSE1);
doc["direction-1"] = config.getUInt("direction-1", Output); doc["direction-1"] = config.getUInt("direction-1", DEFAULT_DIRECTION1);
doc["universe-2"] = config.getUInt("universe-2", 1); doc["universe-2"] = config.getUInt("universe-2", DEFAULT_UNIVERSE2);
doc["direction-2"] = config.getUInt("direction-2", Input); doc["direction-2"] = config.getUInt("direction-2", DEFAULT_DIRECTION2);
doc["led-brightness"] = config.getUInt("led-brightness", DEFAULT_LED_BRIGHTNESS);
config.end(); config.end();
@ -152,6 +149,8 @@ void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size
config.putUInt("universe-1", doc["universe-1"]); config.putUInt("universe-1", doc["universe-1"]);
config.putUInt("universe-2", doc["universe-2"]); config.putUInt("universe-2", doc["universe-2"]);
config.putUInt("led-brightness", doc["led-brightness"]);
config.end(); config.end();
request->send(200); request->send(200);
@ -162,31 +161,3 @@ void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size
request->send(400, "text/plain", e.what()); request->send(400, "text/plain", e.what());
} }
} }
void onGetNetworks(AsyncWebServerRequest *request)
{
JsonDocument doc;
JsonArray array = doc.to<JsonArray>();
int numberOfNetworks = WiFi.scanComplete();
if (numberOfNetworks == WIFI_SCAN_FAILED)
{
WiFi.scanNetworks(true);
}
else if (numberOfNetworks)
{
for (int i = 0; i < numberOfNetworks; ++i)
{
array.add(WiFi.SSID(i));
}
WiFi.scanDelete();
if (WiFi.scanComplete() == WIFI_SCAN_FAILED)
{
WiFi.scanNetworks(true);
}
}
String jsonString;
serializeJson(doc, jsonString);
request->send(200, "application/json", jsonString);
}

View file

@ -1,11 +1,9 @@
#pragma once
#include <AsyncWebServer_ESP32_W5500.h> #include <AsyncWebServer_ESP32_W5500.h>
#include <ESPDMX.h> #include <ESPDMX.h>
#include <Preferences.h> #include <Preferences.h>
// #ifndef CONFIG_h #ifndef CONFIG_h
// #define CONFIG_h #define CONFIG_h
extern Preferences config; extern Preferences config;
@ -31,10 +29,23 @@ enum Direction
}; };
const uint8_t DIRECTION_SIZE = 2; const uint8_t DIRECTION_SIZE = 2;
const Connection DEFAULT_CONNECTION = WiFiAP;
const IpMethod DEFAULT_IP_METHOD = DHCP;
extern String DEFAULT_SSID; // initialized in setup because it depends on the mac address
const String DEFAULT_PASSWORD = "mbgmbgmbg";
const IPAddress DEFAULT_IP(192, 168, 4, 1);
const IPAddress DEFAULT_SUBNET(255, 255, 255, 0);
const IPAddress DEFAULT_GATEWAY(2, 0, 0, 1);
const Direction DEFAULT_DIRECTION1 = Output;
const Direction DEFAULT_DIRECTION2 = Input;
const uint8_t DEFAULT_UNIVERSE1 = 1;
const uint8_t DEFAULT_UNIVERSE2 = 2;
const uint8_t DEFAULT_LED_BRIGHTNESS = 25;
void onGetConfig(AsyncWebServerRequest *request); void onGetConfig(AsyncWebServerRequest *request);
void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total); void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
void onGetNetworks(AsyncWebServerRequest *request); #endif
// #endif

29
src/routes/networks.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "networks.h";
void onGetNetworks(AsyncWebServerRequest *request)
{
JsonDocument doc;
JsonArray array = doc.to<JsonArray>();
int numberOfNetworks = WiFi.scanComplete();
if (numberOfNetworks == WIFI_SCAN_FAILED)
{
WiFi.scanNetworks(true);
}
else if (numberOfNetworks)
{
for (int i = 0; i < numberOfNetworks; ++i)
{
array.add(WiFi.SSID(i));
}
WiFi.scanDelete();
if (WiFi.scanComplete() == WIFI_SCAN_FAILED)
{
WiFi.scanNetworks(true);
}
}
String jsonString;
serializeJson(doc, jsonString);
request->send(200, "application/json", jsonString);
}

9
src/routes/networks.h Normal file
View file

@ -0,0 +1,9 @@
#include <AsyncWebServer_ESP32_W5500.h>
#include <ArduinoJson.h>
#ifndef NETWORKS_H
#define NETWORKS_H
void onGetNetworks(AsyncWebServerRequest *request);
#endif