Merge pull request #20 from HendrikRauh/main

Merge main into DMX-3-configuration-Web-UI
This commit is contained in:
Raffael Wolf 2024-11-09 14:49:36 +01:00 committed by GitHub
commit 5c5e8e9e5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 573 additions and 158 deletions

6
.gitignore vendored
View file

@ -1,7 +1,5 @@
# Development # Development
.pio .pio
.vscode/.browse.c_cpp.db* .vscode
.vscode/c_cpp_properties.json !.vscode\extensions.json
.vscode/launch.json
.vscode/ipch

View file

@ -1,6 +1,10 @@
{ {
// See http://go.microsoft.com/fwlink/?LinkId=827846 // See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format // for the documentation about the extensions.json format
"recommendations": ["platformio.platformio-ide"], "recommendations": [
"unwantedRecommendations": ["ms-vscode.cpptools-extension-pack"] "platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
} }

33
README.md Normal file
View file

@ -0,0 +1,33 @@
# DMX-Interface
Art-Net-Interface
## Case
[OnShape](https://cad.onshape.com/documents/7363818fd18bf0cbf094790e/w/52455282b39e47fbde5d0e53/e/9bec98aa83a813dc9a4d6ab2)
STL's coming soon, case still being developed.
## Wiring
![Handwritten diagram](/assets/circuit/handwritten/circuit%20diagram.jpeg)
## Pin usage
| GPIO | Usage |
| ---- | -------------- |
| GND | GND to others |
| 3,5V | VIN on RS485 |
| 5V | VIN on W5500 |
| 0 | Onboard Button |
| 5 | Ext. Button |
| 7 | Ext. LED |
| 15 | Onboard LED |
| 17 | U1TXD |
| 18 | U1RXD |
| 21 | U0TXD |
| 33 | U0RXD |
| 34 | SPI CS |
| 35 | SPI MOS |
| 36 | SPI SCK |
| 37 | SPI MISO |

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

View file

@ -6,13 +6,30 @@
<title>Konfiguration</title> <title>Konfiguration</title>
<link rel="stylesheet" href="/style.css" /> <link rel="stylesheet" href="/style.css" />
<script type="module" src="/input-visibility.js" defer></script> <script type="module" src="/input-visibility.js" defer></script>
<script type="module" src="/loading-screen.js" defer></script>
<script type="module" src="/load-data.js" defer></script> <script type="module" src="/load-data.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>
</head> </head>
<body> <body>
<main> <main>
<h1>Konfiguration</h1> <section class="loading-screen">
<form> <div class="spinner-container">
<!-- h2 is filled dynamically -->
<h2></h2>
<div class="spinner"></div>
<button
class="reload"
type="button"
onclick="window.location.reload()"
>
Seite neu laden
</button>
</div>
</section>
<form class="hidden">
<h1>Konfiguration</h1>
<fieldset> <fieldset>
<legend>Verbindung</legend> <legend>Verbindung</legend>
<label for="ip-method" <label for="ip-method"
@ -22,18 +39,36 @@
id="input-ip-method" id="input-ip-method"
title="IP-" title="IP-"
> >
<option value="static">Statisch</option> <option value="0">Statisch</option>
<option value="dhcp">DHCP</option> <option value="1">DHCP</option>
</select> </select>
</label> </label>
<div data-field="input-ip-method" data-values="static"> <div data-field="input-ip-method" data-values="0">
<label> <label>
IP-Adresse/CIDR: IP-Adresse:
<input <input
type="text" type="text"
name="ip" name="ip"
id="input-ip" id="input-ip"
placeholder="IP-Adresse/CIDR" placeholder="IP-Adresse"
/>
</label>
<label>
Subnetzmaske:
<input
type="text"
name="subnet"
id="input-subnet"
placeholder="Subnetzmaske"
/>
</label>
<label>
Gateway:
<input
type="text"
name="gateway"
id="input-gateway"
placeholder="Gateway"
/> />
</label> </label>
</div> </div>
@ -44,12 +79,12 @@
id="input-connection" id="input-connection"
title="Verbindung" title="Verbindung"
> >
<option value="wifi-sta">WiFi-Station</option> <option value="0">WiFi-AccessPoint</option>
<option value="wifi-ap">WiFi-AccessPoint</option> <option value="1">WiFi-Station</option>
<option value="ethernet">Ethernet</option> <option value="2">Ethernet</option>
</select> </select>
</label> </label>
<div data-field="input-connection" data-values="wifi-sta"> <div data-field="input-connection" data-values="0">
<label> <label>
SSID: SSID:
<input <input
@ -60,20 +95,17 @@
/> />
</label> </label>
</div> </div>
<div data-field="input-connection" data-values="wifi-ap"> <div data-field="input-connection" data-values="1">
<label> <label>
Netzwerk: Netzwerk:
<select <select
name="network" name="ssid"
id="input-network" id="input-network"
title="Netzwerk" title="Netzwerk"
></select> ></select>
</label> </label>
</div> </div>
<div <div data-field="input-connection" data-values="0|1">
data-field="input-connection"
data-values="wifi-sta|wifi-ap"
>
<label> <label>
Password: Password:
<input <input
@ -91,8 +123,8 @@
<span>Output</span> <span>Output</span>
<input <input
type="checkbox" type="checkbox"
name="input-or-output-1" name="direction-1"
id="input-input-or-output-1" id="input-direction-1"
data-value-not-checked="output" data-value-not-checked="output"
data-value-checked="input" data-value-checked="input"
/> />
@ -117,8 +149,8 @@
<span>Output</span> <span>Output</span>
<input <input
type="checkbox" type="checkbox"
name="input-or-output-2" name="direction-2"
id="input-input-or-output-2" id="input-direction-2"
data-value-not-checked="output" data-value-not-checked="output"
data-value-checked="input" data-value-checked="input"
/> />
@ -137,7 +169,10 @@
/> />
</label> </label>
</fieldset> </fieldset>
<button type="submit">Speichern</button> <div class="buttons">
<button type="reset">Zurücksetzen</button>
<button type="submit">Speichern</button>
</div>
</form> </form>
</main> </main>
</body> </body>

View file

@ -1,35 +1,47 @@
import {
showLoadingScreen,
showError,
hideLoadingScreen,
} from "./loading-screen.js";
const form = document.querySelector("form"); const form = document.querySelector("form");
async function loadData() { export async function loadData(timeout = null) {
try { const req = await fetch("/config", {
const req = await fetch("/config", { method: "GET",
method: "GET", signal: timeout !== null ? AbortSignal.timeout(timeout) : undefined,
}); });
if (!req.ok) { if (!req.ok) {
throw new Error(`Response status: ${req.status}`); throw new Error(`Response status: ${req.status}`);
}
const json = await req.json();
console.log(json);
return json;
} catch (error) {
console.log(error);
return null;
} }
const json = await req.json();
console.log(json);
return json;
} }
function writeDataToInput(data) { export function writeDataToInput(data) {
console.log("write data", typeof data); console.log("write data");
for (const [key, value] of Object.entries(data)) { for (const [key, value] of Object.entries(data)) {
const element = document.querySelector(`[name=${key}]`); const element = document.querySelector(`[name=${key}]`);
console.log(element); console.log(key, element);
element.value = value;
if (element.type === "checkbox") {
element.checked = value;
} else {
element.value = value;
}
} }
// send "change" event // send "change" event
form.dispatchEvent(new Event("change", { bubbles: true })); form.dispatchEvent(new Event("change", { bubbles: true }));
} }
const data = await loadData(); showLoadingScreen("Konfiguration wird geladen...");
if (data !== null) { try {
const data = await loadData();
hideLoadingScreen();
writeDataToInput(data); writeDataToInput(data);
} catch (error) {
console.log(error.message);
showError("Die Konfiguration konnte nicht geladen werden.");
} }

40
data/loading-screen.js Normal file
View file

@ -0,0 +1,40 @@
const form = document.querySelector("form");
const loadingScreen = document.querySelector(".loading-screen");
const loadingMsg = loadingScreen.querySelector("h2");
const spinner = loadingScreen.querySelector(".spinner");
const reloadBtn = loadingScreen.querySelector(".reload");
export function showLoadingScreen(msg) {
hide(form, reloadBtn);
show(loadingScreen, spinner);
loadingMsg.classList.remove("error");
loadingMsg.textContent = msg;
}
export function showError(msg) {
showLoadingScreen(msg);
loadingMsg.innerHTML +=
"<br/>Stelle sicher, dass du mit dem DMX-Interface verbunden bist und die IP-Adresse stimmt.";
show(reloadBtn);
hide(spinner);
loadingMsg.classList.add("error");
}
export function hideLoadingScreen() {
hide(loadingScreen, reloadBtn);
show(form);
loadingMsg.classList.remove("error");
loadingMsg.textContent = "";
}
function show(...elements) {
for (const element of elements) {
element.classList.remove("hidden");
}
}
function hide(...elements) {
for (const element of elements) {
element.classList.add("hidden");
}
}

16
data/reset.js Normal file
View file

@ -0,0 +1,16 @@
import { updateConfig } from "/submit.js";
const form = document.querySelector("form");
form.addEventListener("reset", async (event) => {
event.preventDefault();
const ok = confirm(
"Sicher, dass du alle Einstellungen zurücksetzen möchtest?"
);
if (ok) {
updateConfig({
method: "DELETE",
});
}
});

View file

@ -1,6 +1,8 @@
:root { :root {
--color-primary: #087e8b; --color-primary: #087e8b;
--color-on-primary: white; --color-on-primary: white;
--color-background: #222;
--color-danger: #fa2b58;
} }
body { body {
@ -13,7 +15,7 @@ body {
} }
main { main {
background-color: #222; background-color: var(--color-background);
max-width: 700px; max-width: 700px;
padding: 8px max(5%, 8px); padding: 8px max(5%, 8px);
margin: 0 auto; margin: 0 auto;
@ -44,7 +46,7 @@ label {
input, input,
select { select {
width: clamp(200px, 100%, 400px); width: clamp(200px, 100%, 400px);
background-color: #222; background-color: var(--color-background);
color: white; color: white;
border: 1px solid white; border: 1px solid white;
border-radius: 8px; border-radius: 8px;
@ -59,14 +61,16 @@ select:focus {
} }
button { button {
display: block;
border: none; border: none;
inset: none; inset: none;
border-radius: 8px; border-radius: 8px;
background-color: var(--color-primary); background-color: var(--color-primary);
color: var(--color-on-primary); color: var(--color-on-primary);
padding: 8px 16px; padding: 8px 16px;
margin: 0 auto; }
button[type="reset"] {
background-color: var(--color-danger);
} }
:is(div:has(:is(input, select)), input, select, label) :is(div:has(:is(input, select)), input, select, label)
@ -75,7 +79,7 @@ button {
} }
.hidden { .hidden {
display: none; display: none !important;
} }
label.switch { label.switch {
@ -126,3 +130,68 @@ label.switch input:checked + .slider::before {
left: 100%; left: 100%;
translate: -100% -50%; translate: -100% -50%;
} }
.buttons {
display: flex;
flex-direction: row;
justify-content: center;
gap: 8px;
}
.loading-screen {
display: grid;
justify-content: center;
}
h2.error {
color: var(--color-danger);
}
button.reload {
display: block;
margin: 0 auto;
}
.spinner-container {
width: min(max-content, 100%);
}
.spinner {
position: relative;
margin: 10px auto;
background: conic-gradient(transparent 150deg, var(--color-primary));
--outer-diameter: 50px;
width: var(--outer-diameter);
height: var(--outer-diameter);
border-radius: 50%;
animation-name: spin;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.spinner::after {
position: absolute;
content: "";
display: block;
--spinner-border: 5px;
top: var(--spinner-border);
left: var(--spinner-border);
--inner-diameter: calc(var(--outer-diameter) - 2 * var(--spinner-border));
width: var(--inner-diameter);
height: var(--inner-diameter);
background-color: var(--color-background);
border-radius: 50%;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -1,3 +1,10 @@
import { loadData, writeDataToInput } from "./load-data.js";
import {
showLoadingScreen,
hideLoadingScreen,
showError,
} from "./loading-screen.js";
const form = document.querySelector("form"); const form = document.querySelector("form");
function parseValue(input) { function parseValue(input) {
@ -13,7 +20,7 @@ function parseValue(input) {
if (input.type === "number") { if (input.type === "number") {
const number = Number(input.value); const number = Number(input.value);
return isNaN(number) ? null : number; return Number.isNaN(number) ? null : number;
} }
return input.value; return input.value;
@ -31,25 +38,35 @@ form.addEventListener("submit", (event) => {
}, {}); }, {});
console.log(data); console.log(data);
putData(data); updateConfig({
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}); });
async function putData(data) { export async function updateConfig(fetchOptions) {
showLoadingScreen("Konfiguration anwenden und ESP neustarten...");
try { try {
const res = await fetch("/config", { const res = await fetch("/config", fetchOptions);
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!res.ok) { if (!res.ok) {
throw new Error(`Response status: ${res.status}`); throw new Error(`Response status: ${res.status}`);
} }
const json = await res.json(); // wait for the esp to restart
console.log(json); const delay = new Promise((resolve) =>
setTimeout(() => resolve(), 500)
);
await delay;
const data = await loadData(30 * 1000);
writeDataToInput(data);
hideLoadingScreen();
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
showError(error.message);
} }
} }

View file

@ -15,4 +15,4 @@ framework = arduino
lib_deps = lib_deps =
hideakitai/ArtNet @ ^0.8.0 hideakitai/ArtNet @ ^0.8.0
bblanchon/ArduinoJson @ ^7.2.0 bblanchon/ArduinoJson @ ^7.2.0
ESP Async WebServer me-no-dev/ESP Async WebServer

View file

@ -1,94 +1,71 @@
// - - - - -
// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port.
// ESPDMX.cpp: Library implementation file
//
// Copyright (C) 2015 Rick <ricardogg95@gmail.com>
// This work is licensed under a GNU style license.
//
// Last change: Marcel Seerig <https://github.com/mseerig>
//
// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx
// - - - - -
/* ----- LIBRARIES ----- */ /* ----- LIBRARIES ----- */
#include <Arduino.h> #include <Arduino.h>
#include "ESPDMX.h" #include "ESPDMX.h"
#define DMXSPEED 250000
#define DMXFORMAT SERIAL_8N2
#define BREAKSPEED 83333
#define BREAKFORMAT SERIAL_8N1
#define SERIALPORT Serial0
#define DMXCHANNELS 512
bool dmxStarted = false;
int sendPin = 18;
int receivePin = -1;
// DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements // DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements
uint8_t dmxDataStore[DMXCHANNELS + 1] = {}; // std::vector<uint8_t[DMXCHANNELS + 1]> dmxDataStores(MAX_IDS);
// uint8_t dmxDataStores[MAX_IDS][DMXCHANNELS + 1];
// Set up the DMX-Protocol // Set up the DMX-Protocol
void DMXESPSerial::init() void DMXESPSerial::init(int pinSend = 19, int pinRecv = -1)
{ {
SERIALPORT.begin(DMXSPEED, DMXFORMAT, receivePin, sendPin); sendPin = pinSend;
recvPin = pinRecv;
SERIALPORT.begin(DMXSPEED, DMXFORMAT, recvPin, sendPin);
pinMode(sendPin, OUTPUT); pinMode(sendPin, OUTPUT);
dmxStarted = true; dmxStarted = true;
} }
// Function to read DMX data // Function to read DMX data
uint8_t DMXESPSerial::read(int Channel) uint8_t DMXESPSerial::read(int channel)
{ {
if (dmxStarted == false) if (dmxStarted == false)
init(); init();
if (Channel < 1) if (channel < 1)
Channel = 1; channel = 1;
if (Channel > DMXCHANNELS) if (channel > DMXCHANNELS)
Channel = DMXCHANNELS; channel = DMXCHANNELS;
return (dmxDataStore[Channel]); return (dmxDataStore[channel]);
} }
// Function to send DMX data // Function to send DMX data
void DMXESPSerial::write(int Channel, uint8_t value) void DMXESPSerial::write(int channel, uint8_t value)
{ {
if (dmxStarted == false) if (dmxStarted == false)
init(); init();
if (Channel < 1) if (channel < 1)
Channel = 1; channel = 1;
if (Channel > DMXCHANNELS) if (channel > DMXCHANNELS)
Channel = DMXCHANNELS; channel = DMXCHANNELS;
if (value < 0) if (value < 0)
value = 0; value = 0;
if (value > 255) if (value > 255)
value = 255; value = 255;
dmxDataStore[Channel] = value; dmxDataStore[channel] = value;
} }
void DMXESPSerial::end() void DMXESPSerial::end()
{ {
SERIALPORT.end(); SERIALPORT.end();
dmxStarted = false;
} }
// Function to update the DMX bus // Function to update the DMX bus
void DMXESPSerial::update() void DMXESPSerial::update()
{ {
if (dmxStarted == false)
init();
// Send break // Send break
digitalWrite(sendPin, HIGH); digitalWrite(sendPin, HIGH);
SERIALPORT.begin(BREAKSPEED, BREAKFORMAT, receivePin, sendPin); SERIALPORT.begin(BREAKSPEED, BREAKFORMAT, recvPin, sendPin);
SERIALPORT.write(0); SERIALPORT.write(0);
SERIALPORT.flush(); SERIALPORT.flush();
delay(1); delay(1);
SERIALPORT.end(); SERIALPORT.end();
// send data // send data
SERIALPORT.begin(DMXSPEED, DMXFORMAT, receivePin, sendPin); SERIALPORT.begin(DMXSPEED, DMXFORMAT, recvPin, sendPin);
digitalWrite(sendPin, LOW); digitalWrite(sendPin, LOW);
SERIALPORT.write(dmxDataStore, DMXCHANNELS); SERIALPORT.write(dmxDataStore, DMXCHANNELS);
SERIALPORT.flush(); SERIALPORT.flush();

View file

@ -1,29 +1,28 @@
// - - - - -
// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port.
// ESPDMX.cpp: Library implementation file
//
// Copyright (C) 2015 Rick <ricardogg95@gmail.com>
// This work is licensed under a GNU style license.
//
// Last change: Marcel Seerig <https://github.com/mseerig>
//
// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx
// - - - - -
#include <inttypes.h> #include <inttypes.h>
#ifndef ESPDMX_h #ifndef ESPDMX_h
#define ESPDMX_h #define ESPDMX_h
// ---- Methods ---- #define DMXSPEED 250000
#define DMXFORMAT SERIAL_8N2
#define BREAKSPEED 83333
#define BREAKFORMAT SERIAL_8N1
#define SERIALPORT Serial0
#define DMXCHANNELS 512
class DMXESPSerial { class DMXESPSerial
public: {
void init(); public:
uint8_t read(int Channel); int sendPin;
void write(int channel, uint8_t value);void update(); int recvPin;
void end(); bool dmxStarted;
uint8_t dmxDataStore[DMXCHANNELS + 1];
void init(int pinSend, int pinRecv);
uint8_t read(int Channel);
void write(int channel, uint8_t value);
void update();
void end();
}; };
#endif #endif

View file

@ -1,18 +1,18 @@
#include <ArtnetWiFi.h> #include <ArtnetWiFi.h>
// #include <ArtnetEther.h> // #include <ArtnetEther.h>
#include <ArduinoJson.h>
#include "ESPDMX.h" #include "ESPDMX.h"
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SPIFFS.h> #include <SPIFFS.h>
#include <Preferences.h> #include "routes/config.h"
Preferences config; DMXESPSerial dmx1;
DMXESPSerial dmx; DMXESPSerial dmx2;
AsyncWebServer server(80); AsyncWebServer server(80);
ArtnetWiFi artnet; ArtnetWiFi artnet;
DMXESPSerial dmx;
const uint16_t size = 512; const uint16_t size = 512;
uint8_t data[size]; uint8_t data[size];
@ -20,20 +20,27 @@ void setup()
{ {
Serial.begin(9600); Serial.begin(9600);
config.begin("dmx", false); config.begin("dmx", true);
uint8_t universe = config.getUChar("universe", 1); uint8_t universe1 = config.getUChar("universe-1", 1);
uint8_t universe2 = config.getUChar("universe-2", 1);
Direction direction1 = static_cast<Direction>(config.getUInt("direction-1", 0));
Direction direction2 = static_cast<Direction>(config.getUInt("direction-2", 1));
Connection connection = static_cast<Connection>(config.getUInt("connection", WiFiSta));
IpMethod ipMethod = static_cast<IpMethod>(config.getUInt("ip-method"), Static);
String ssid = config.getString("ssid", "artnet"); String ssid = config.getString("ssid", "artnet");
String pwd = config.getString("pwd", "mbgmbgmbg"); String pwd = config.getString("password", "mbgmbgmbg");
IPAddress defaultIp(192, 168, 1, 201); IPAddress defaultIp(192, 168, 1, 201);
IPAddress ip = config.getUInt("ip", defaultIp); 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 cidr = config.getUChar("cidr", 24); config.end();
// TODO: \/ Herleiten \/ @psxde
const IPAddress gateway(192, 168, 1, 1);
const IPAddress subnet(255, 255, 255, 0);
// WiFi stuff // WiFi stuff
// WiFi.begin(ssid, pwd); // WiFi.begin(ssid, pwd);
@ -51,17 +58,17 @@ void setup()
artnet.begin(); artnet.begin();
// Initialize DMX ports // Initialize DMX ports
dmx.init(); dmx1.init(19, -1);
// if Artnet packet comes to this universe, this function is called // 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) artnet.subscribeArtDmxUniverse(universe1, [&](const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote)
{ {
for (size_t i = 0; i < size; ++i) for (size_t i = 0; i < size; ++i)
{ {
dmx.write((i + 1), data[i]); dmx1.write((i + 1), data[i]);
} }
dmx.update(); }); dmx1.update(); });
// if Artnet packet comes, this function is called to every universe // 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) {}); artnet.subscribeArtDmx([&](const uint8_t *data, uint16_t size, const ArtDmxMetadata &metadata, const ArtNetRemoteInfo &remote) {});
@ -74,19 +81,26 @@ void setup()
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html"); server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
server.on("/config", HTTP_GET, [&, defaultIp, ssid, pwd, universe](AsyncWebServerRequest *request) server.on("/config", HTTP_GET, [](AsyncWebServerRequest *request)
{ onGetConfig(request); });
server.on("/config", HTTP_DELETE, [](AsyncWebServerRequest *request)
{ {
DynamicJsonDocument doc(1024); config.begin("dmx", false);
config.clear();
config.end();
// respond with default config
onGetConfig(request);
doc["ssid"] = ssid; ESP.restart(); });
doc["pwd"] = pwd;
doc["ip"] = defaultIp;
doc["universe"] = universe;
String jsonString; server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
serializeJson(doc, jsonString); {
if (request->url() == "/config" && request->method() == HTTP_PUT) {
onPutConfig(request, data, len, index, total);
ESP.restart();
} });
request->send(200, "application/json", jsonString); });
delay(1000); delay(1000);
server.begin(); server.begin();
Serial.println("Server started!"); Serial.println("Server started!");
@ -95,4 +109,4 @@ void setup()
void loop() void loop()
{ {
artnet.parse(); // check if artnet packet has come and execute callback artnet.parse(); // check if artnet packet has come and execute callback
} }

163
src/routes/config.cpp Normal file
View file

@ -0,0 +1,163 @@
#include "config.h"
#include <stdexcept>
#include <ArduinoJson.h>
Preferences config;
#pragma region Utility
uint32_t parseIp(String str)
{
const int size = 4;
String ipStrings[size];
uint8_t ipIndex = 0;
for (int i = 0; i < str.length(); i++)
{
if (str[i] == '.')
{
ipIndex++;
continue;
}
ipStrings[ipIndex] += str[i];
}
String ip = "";
for (int i = 0; i < size; i++)
{
String paddedString = ipStrings[i];
while (paddedString.length() < 3)
{
paddedString = "0" + paddedString;
}
ip.concat(paddedString);
}
Serial.println("ip string: " + ip);
return atoi(ip.c_str());
}
IpMethod parseIpMethod(uint8_t ipMethod)
{
if (ipMethod > 0 || ipMethod < IP_METHOD_SIZE)
{
return static_cast<IpMethod>(ipMethod);
}
throw ::std::invalid_argument("Invalid IP method value" + ipMethod);
}
Connection parseConnection(uint8_t connection)
{
if (connection > 0 || connection < CONNECTION_SIZE)
{
return static_cast<Connection>(connection);
}
throw ::std::invalid_argument("Invalid connection value: " + connection);
}
Direction parseDirection(uint8_t direction)
{
if (direction > 0 || direction < DIRECTION_SIZE)
{
return static_cast<Direction>(direction);
}
throw ::std::invalid_argument("Invalid direction value: " + direction);
}
#pragma endregion
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);
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["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);
config.end();
String jsonString;
serializeJson(doc, jsonString);
request->send(200, "application/json", jsonString);
}
void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
Serial.printf("[REQUEST]\t%s\r\n", (const char *)data);
JsonDocument doc;
deserializeJson(doc, data);
try
{
config.begin("dmx", false);
IpMethod ipMethod = parseIpMethod(doc["ip-method"].as<uint8_t>());
config.putUInt("ip-method", ipMethod);
if (ipMethod == Static)
{
IPAddress ipAddress;
ipAddress.fromString(doc["ip"].as<String>());
config.putUInt("ip", ipAddress);
IPAddress subnet;
subnet.fromString(doc["subnet"].as<String>());
config.putUInt("subnet", subnet);
IPAddress gateway;
gateway.fromString(doc["gateway"].as<String>());
config.putUInt("gateway", gateway);
}
Connection connection = parseConnection(doc["connection"].as<uint8_t>());
config.putUInt("connection", connection);
if (connection == WiFiSta || connection == WiFiAP)
{
config.putString("ssid", doc["ssid"].as<String>());
config.putString("password", doc["password"].as<String>());
}
Direction direction1 = parseDirection(doc["direction-1"].as<uint8_t>());
config.putUInt("direction-1", direction1);
Direction direction2 = parseDirection(doc["direction-2"].as<uint8_t>());
config.putUInt("direction-2", direction2);
config.putUInt("universe-1", doc["universe-1"]);
config.putUInt("universe-2", doc["universe-2"]);
config.end();
request->send(200);
}
catch (::std::invalid_argument &e)
{
config.end();
request->send(400, "text/plain", e.what());
}
}

38
src/routes/config.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <ESPAsyncWebServer.h>
#include <ESPDMX.h>
#include <Preferences.h>
// #ifndef CONFIG_h
// #define CONFIG_h
extern Preferences config;
enum IpMethod
{
Static,
DHCP
};
const uint8_t IP_METHOD_SIZE = 2;
enum Connection
{
WiFiSta,
WiFiAP,
Ethernet
};
const uint8_t CONNECTION_SIZE = 3;
enum Direction
{
Output,
Input
};
const uint8_t DIRECTION_SIZE = 2;
void onGetConfig(AsyncWebServerRequest *request);
void onPutConfig(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
// #endif