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

View file

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

View file

@ -1,3 +1,5 @@
import { data } from "./load-data.js";
const networkDropdown = document.querySelector("#select-network");
const refreshButton = document.querySelector("#refresh-networks");
const refreshIcon = refreshButton.querySelector("img");
@ -5,11 +7,24 @@ const refreshIcon = refreshButton.querySelector("img");
let isLoading = false;
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();
});
// check if connected via WiFi-Station
if (data.connection === 0) {
// show currently connected wifi
insertNetworks([data.ssid]);
}
function insertNetworks(networks) {
networks.unshift(""); // add empty option
networkDropdown.textContent = ""; // clear dropdown
for (const ssid of networks) {
@ -53,8 +68,6 @@ async function loadNetworks() {
async function updateNetworks() {
const networks = await loadNetworks();
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-background: #222;
--color-danger: #fa2b58;
--icon-button-size: 2.5rem;
--appended-item-size: 2.5rem;
}
body {
@ -50,8 +50,13 @@ label span {
}
input,
select {
select,
label div {
width: clamp(200px, 100%, 400px);
}
input,
select {
background-color: var(--color-background);
color: white;
border: 1px solid white;
@ -66,8 +71,13 @@ select:focus {
border-color: var(--color-primary);
}
select:has(+ .icon-button) {
width: calc(clamp(200px, 100%, 400px) - var(--icon-button-size) - 8px);
select:has(+ .icon-button),
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 {
@ -213,8 +223,8 @@ button.reload {
.icon-button {
padding: 8px;
font-size: 0;
width: var(--icon-button-size);
height: var(--icon-button-size);
width: var(--appended-item-size);
height: var(--appended-item-size);
}
.spinning {
@ -223,3 +233,16 @@ button.reload {
animation-iteration-count: infinite;
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;
}
if (input.type === "number") {
if (input.type === "number" || input.type === "range") {
const number = Number(input.value);
return Number.isNaN(number) ? null : number;
}

View file

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

View file

@ -4,6 +4,7 @@
#include "WiFi.h"
Preferences config;
String DEFAULT_SSID = "";
#pragma region Utility
@ -75,27 +76,23 @@ void onGetConfig(AsyncWebServerRequest *request)
{
config.begin("dmx", true);
IPAddress defaultIp(192, 168, 1, 201);
IPAddress ip = config.getUInt("ip", defaultIp);
IPAddress defaultSubnet(255, 255, 255, 0);
IPAddress subnet = config.getUInt("subnet", defaultSubnet);
IPAddress defaultGateway(192, 168, 1, 1);
IPAddress gateway = config.getUInt("gateway", defaultGateway);
IPAddress ip = config.getUInt("ip", DEFAULT_IP);
IPAddress subnet = config.getUInt("subnet", DEFAULT_SUBNET);
IPAddress gateway = config.getUInt("gateway", NULL);
JsonDocument doc;
doc["connection"] = config.getUInt("connection", WiFiSta);
doc["ssid"] = config.getString("ssid", "artnet");
doc["password"] = config.getString("password", "mbgmbgmbg");
doc["ip-method"] = config.getUInt("ip-method"), Static;
doc["connection"] = config.getUInt("connection", DEFAULT_CONNECTION);
doc["ssid"] = config.getString("ssid", DEFAULT_SSID);
doc["password"] = config.getString("password", DEFAULT_PASSWORD);
doc["ip-method"] = config.getUInt("ip-method", DEFAULT_IP_METHOD);
doc["ip"] = ip.toString();
doc["subnet"] = subnet.toString();
doc["gateway"] = gateway.toString();
doc["universe-1"] = config.getUInt("universe-1", 1);
doc["direction-1"] = config.getUInt("direction-1", Output);
doc["universe-2"] = config.getUInt("universe-2", 1);
doc["direction-2"] = config.getUInt("direction-2", Input);
doc["gateway"] = gateway != NULL ? gateway.toString() : "";
doc["universe-1"] = config.getUInt("universe-1", DEFAULT_UNIVERSE1);
doc["direction-1"] = config.getUInt("direction-1", DEFAULT_DIRECTION1);
doc["universe-2"] = config.getUInt("universe-2", DEFAULT_UNIVERSE2);
doc["direction-2"] = config.getUInt("direction-2", DEFAULT_DIRECTION2);
doc["led-brightness"] = config.getUInt("led-brightness", DEFAULT_LED_BRIGHTNESS);
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-2", doc["universe-2"]);
config.putUInt("led-brightness", doc["led-brightness"]);
config.end();
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());
}
}
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 <ESPDMX.h>
#include <Preferences.h>
// #ifndef CONFIG_h
// #define CONFIG_h
#ifndef CONFIG_h
#define CONFIG_h
extern Preferences config;
@ -31,10 +29,23 @@ enum Direction
};
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 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