better settings on the way

This commit is contained in:
Matthew Frost 2024-04-15 20:28:30 +02:00
parent aef965ae6b
commit caf6846826
7 changed files with 412 additions and 104 deletions

View file

@ -1,30 +1,150 @@
<!DOCTYPE HTML><html> <!DOCTYPE HTML>
<html>
<head> <head>
<title>ESP Web Server</title> <title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<style> <style>
html {font-family: Arial; display: inline-block; text-align: center;} html {
h2 {font-size: 3.0rem;} font-family: Arial;
p {font-size: 3.0rem;} display: inline-block;
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;} text-align: center;
.switch {position: relative; display: inline-block; width: 120px; height: 68px} background-color: #f2f2f2; /* Add background color */
.switch input {display: none} }
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px} h2 {
input:checked+.slider {background-color: #b30000} font-size: 3.0rem;
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)} }
p {
font-size: 2.0rem;
}
body {
max-width: 600px;
margin: 0px auto;
padding-bottom: 25px;
}
.switch {
position: relative;
display: inline-block;
width: 120px;
height: 68px;
}
.switch input {
display: none;
}
.slider {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
border-radius: 6px;
}
.slider:before {
position: absolute;
content: "";
height: 52px;
width: 52px;
left: 8px;
bottom: 8px;
background-color: #fff;
-webkit-transition: .4s;
transition: .4s;
border-radius: 3px;
}
input:checked + .slider {
background-color: #b30000;
}
input:checked + .slider:before {
-webkit-transform: translateX(52px);
-ms-transform: translateX(52px);
transform: translateX(52px);
}
.navbar {
background-color: #333;
overflow: hidden;
width: 100%; /* Make navbar full width */
}
.navbar a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
.navbar a:hover {
background-color: #ddd;
color: black;
}
.navbar a.active {
background-color: #4CAF50;
color: white;
}
.navbar .version {
float: right;
padding: 14px 16px;
color: #f2f2f2;
}
.news {
margin-top: 20px;
border: 1px solid #ccc; /* Add border around news item */
padding: 10px; /* Add padding to news item */
}
</style> </style>
</head> </head>
<body> <body>
<h2>ESP Web Server</h2> <div class="navbar">
%BUTTONPLACEHOLDER% <a class="active" href="/">Home</a>
<script>function toggleCheckbox(element) { <a href="docs">API Docs</a>
var xhr = new XMLHttpRequest(); <a href="update">Update</a>
if(element.checked){ xhr.open("GET", "/gpio?output="+element.id+"&state=1", true); } <span class="version" id="version">Version: <span id="versionNumber"></span></span>
else { xhr.open("GET", "/gpio?output="+element.id+"&state=0", true); } </div>
xhr.send(); <h2>Card Web Server & API</h2>
}
</script> <div class="news">
<h3>Latest News</h3>
<p>Added API docs and Serial Interface docs. To access the API docs, click on "docs" in the navbar. For the Serial docs, just type "help" on the serial CLI.</p>
</div>
<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "/version", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
var versionNumber = document.getElementById("versionNumber");
versionNumber.textContent = response["version"];
}
};
xhr.send();
</script>
<script>
function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if (element.checked) {
xhr.open("GET", "/gpio?output=" + element.id + "&state=1", true);
} else {
xhr.open("GET", "/gpio?output=" + element.id + "&state=0", true);
}
xhr.send();
}
</script>
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
#ifndef aclwebserver_h #ifndef ACLWEBSERVER_H
#define aclwebserver_h #define ACLWEBSERVER_H
#include "Arduino.h" #include "Arduino.h"
#include "stdlib_noniso.h" #include "stdlib_noniso.h"

View file

@ -1,5 +1,5 @@
#ifndef mainwebserver_h #ifndef MAINWEBSERVER_H
#define mainwebserver_h #define MAINWEBSERVER_H
#include "Arduino.h" #include "Arduino.h"
#include "stdlib_noniso.h" #include "stdlib_noniso.h"
@ -37,7 +37,7 @@ class MainWebServerClass{
} }
} }
request->send(SPIFFS, "/index.html", String(), false, processor); request->send(SPIFFS, "/index.html", String(), false);
}); });
_server->on("/version", HTTP_GET, [&](AsyncWebServerRequest *request){ _server->on("/version", HTTP_GET, [&](AsyncWebServerRequest *request){
@ -46,7 +46,7 @@ class MainWebServerClass{
return request->requestAuthentication(); return request->requestAuthentication();
} }
} }
request->send(200, "application/json", "{\"version\":\"3.2.4\"}"); request->send(200, "application/json", "{\"version\":\"3.2.5\"}");
}); });
_server->onNotFound([&](AsyncWebServerRequest *request){ _server->onNotFound([&](AsyncWebServerRequest *request){
@ -161,7 +161,7 @@ class MainWebServerClass{
return request->requestAuthentication(); return request->requestAuthentication();
} }
} }
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"identifier\":\""+String(settings.getTinance2ReaderIdentifier())+"\", \"key\":\""+String(settings.getTinance2ReaderKey())+"\"}"); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"identifier\":\""+settings.tinance2ReaderIdentifier+"\", \"key\":\""+settings.tinance2ReaderKey+"\"}");
request->send(response); request->send(response);
}); });
@ -185,13 +185,53 @@ class MainWebServerClass{
} }
String key = String(request->arg("key")); String key = String(request->arg("key"));
settings.setTinance2ReaderIdentifier(identifier); settings.tinance2ReaderIdentifier = identifier;
settings.setTinance2ReaderKey(key); settings.tinance2ReaderKey = key;
settings.saveSetting("t2", "ri", settings.tinance2ReaderIdentifier, "string");
settings.saveSetting("t2", "rk", settings.tinance2ReaderKey, "string");
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"msg\":\"Tinance credentials updated\"}"); AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"msg\":\"Tinance credentials updated\"}");
request->send(response); request->send(response);
}); });
_server->on("/settings/validatecardurl", HTTP_POST, [&] (AsyncWebServerRequest *request) {
if(_authRequired){
if(!request->authenticate(_username.c_str(), _password.c_str())){
return request->requestAuthentication();
}
}
if(request->hasParam("url", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{\"msg\":\"No url\"}");
request->send(response);
}
String url = String(request->arg("url"));
settings.tinance2_url_validatecard = url;
settings.saveSetting("t2", "vcurl", settings.tinance2_url_validatecard, "string");
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"msg\":\"Tinance URL updated\"}");
request->send(response);
});
_server->on("/settings/validatecardurl", HTTP_GET, [&] (AsyncWebServerRequest *request) {
if(_authRequired){
if(!request->authenticate(_username.c_str(), _password.c_str())){
return request->requestAuthentication();
}
}
AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"value\":\""+settings.tinance2_url_validatecard+"\"}");
request->send(response);
});
} }
// deprecated, keeping for backward compatibility // deprecated, keeping for backward compatibility
@ -203,32 +243,6 @@ class MainWebServerClass{
String _username = ""; String _username = "";
String _password = ""; String _password = "";
bool _authRequired = false; bool _authRequired = false;
static String outputState(int output){
if(digitalRead(output)){
return "checked";
}
else {
return "";
}
}
// Replaces placeholder with button section in your web page
static String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
#ifdef RELAY1
buttons += "<h4>Output - Relay 1 </h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"relay1\" " + MainWebServerClass::outputState(RELAY1_PIN) + "><span class=\"slider\"></span></label>";
#endif
return buttons;
}
return String();
}
}; };
MainWebServerClass MainWebServer; MainWebServerClass MainWebServer;

View file

@ -14,14 +14,23 @@ class Settings {
private: private:
bool disableDoor; bool disableDoor;
String doorMode; String doorMode;
String tinance2ReaderIdentifier;
String tinance2ReaderKey;
public: public:
Settings(); Settings();
String tinance2_url_validatecard;
String tinance2_url_readerinfo;
String tinance2_url_acls;
String tinance2_url_log;
String tinance2ReaderIdentifier;
String tinance2ReaderKey;
bool webhookLockEnabled;
String webhookLockHook;
bool webhookUnlockEnabled;
String webhookUnlockHook;
void loadFromEEPROM(); void loadFromEEPROM();
void saveToEEPROM(); void saveToEEPROM();
void saveSetting(const String& namespaceStr, const String& key, const String& value, const String& typeStr);
void saveStringToEEPROM(String string, String value); void saveStringToEEPROM(String string, String value);
void saveBoolToEEPROM(String string, String value); void saveBoolToEEPROM(String string, String value);
@ -30,12 +39,6 @@ public:
void setDoorMode(String value); void setDoorMode(String value);
String getDoorMode(); String getDoorMode();
void setTinance2ReaderIdentifier(String value);
String getTinance2ReaderIdentifier();
void setTinance2ReaderKey(String value);
String getTinance2ReaderKey();
}; };
extern Settings settings; extern Settings settings;

View file

@ -141,7 +141,7 @@ void handleData1Interrupt() {
void setup() { void setup() {
#if defined SERIAL_DEBUG || defined SERIAL_ACL #if defined SERIAL_DEBUG || defined SERIAL_CLI
Serial.begin(9600); Serial.begin(9600);
#endif #endif
@ -263,7 +263,7 @@ void setup() {
#ifdef LOCAL_ACL #ifdef LOCAL_ACL
#ifdef SERIAL_ACL #ifdef SERIAL_CLI
struct WordList { struct WordList {
char** words; char** words;
@ -326,27 +326,163 @@ void setup() {
int wordCount = wordData.length; int wordCount = wordData.length;
String prefix = wordList[0]; String prefix = wordList[0];
if (prefix.equals("help")) {
Serial.println("Available prefixes:");
Serial.println("tinance2.identifier.get - No arguments");
Serial.println("tinance2.identifier.set - 1 argument: new identifier");
Serial.println("tinance2.key.get - No arguments");
Serial.println("tinance2.key.set - 1 argument: new key");
Serial.println("acl.local.validate - 1 argument: card ID");
Serial.println("acl.local.add - 2 arguments: card ID, description");
Serial.println("acl.local.update - 3 arguments: card ID, new card ID, new description");
Serial.println("acl.local.remove - 1 argument: card ID");
Serial.println("acl.local.list - No arguments");
}
if (prefix.equals("tinance2.identifier.get")) {
// Retrieves the Tinance2 reader identifier from the settings
Serial.println("Tinance2 Identifier: " + settings.tinance2ReaderIdentifier);
}
if (prefix.equals("tinance2.identifier.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader identifier in the settings
settings.tinance2ReaderIdentifier = wordList[1];
settings.saveSetting("t2", "ri", wordList[1], "string");
}
}
if (prefix.equals("tinance2.key.get")) {
// Retrieves the Tinance2 reader key from the settings
Serial.println("Tinance2 Key: " + settings.tinance2ReaderKey);
}
if (prefix.equals("tinance2.key.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader key in the settings
settings.tinance2ReaderKey = wordList[1];
settings.saveSetting("t2", "rk", wordList[1], "string");
}
}
if (prefix.equals("tinance2.validatecardurl.get")) {
// Retrieves the Tinance2 reader key from the settings
Serial.println("Tinance2 Key: " + settings.tinance2_url_validatecard);
}
if (prefix.equals("tinance2.validatecardurl.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader key in the settings
settings.saveSetting("t2", "vcurl", wordList[1], "string");
Serial.println("Tinance2 Key: " + settings.tinance2_url_validatecard);
}
}
if (prefix.equals("tinance2.readerinfourl.get")) {
// Retrieves the Tinance2 reader key from the settings
Serial.println("Tinance2 Key: " + settings.tinance2_url_readerinfo);
}
if (prefix.equals("tinance2.readerinfourl.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader key in the settings
settings.saveSetting("t2", "riurl", wordList[1], "string");
Serial.println("Tinance2 Key: " + settings.tinance2_url_readerinfo);
}
}
if (prefix.equals("tinance2.aclsurl.get")) {
// Retrieves the Tinance2 reader key from the settings
Serial.println("Tinance2 Key: " + settings.tinance2_url_acls);
}
if (prefix.equals("tinance2.aclsurl.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader key in the settings
settings.saveSetting("t2", "aclurl", wordList[1], "string");
Serial.println("Tinance2 Key: " + settings.tinance2_url_acls);
}
}
if (prefix.equals("tinance2.readerlogurl.get")) {
// Retrieves the Tinance2 reader key from the settings
Serial.println("Tinance2 Key: " + settings.tinance2_url_log);
}
if (prefix.equals("tinance2.readerlogurl.set")) {
if (wordCount == 2) {
// Sets the Tinance2 reader key in the settings
settings.saveSetting("t2", "rluri", wordList[1], "string");
Serial.println("Tinance2 Key: " + settings.tinance2_url_log);
}
}
if(prefix.equals("webhook.lock.url.get")) {
Serial.println("Webhook Lock URL: " + settings.webhookLockHook);
}
if(prefix.equals("webhook.lock.url.set")) {
if (wordCount == 2) {
settings.saveSetting("wh", "lockurl", wordList[1], "string");
Serial.println("Webhook Lock URL: " + settings.webhookLockHook);
}
}
if(prefix.equals("webhook.lock.enable.get")) {
Serial.println("Webhook Lock Enabled: " + settings.webhookLockEnabled);
}
if(prefix.equals("webhook.lock.enable.set")) {
if (wordCount == 2) {
settings.saveSetting("wh", "lockena", wordList[1], "bool");
Serial.println("Webhook Lock Enabled: " + settings.webhookLockEnabled);
}
}
if(prefix.equals("webhook.unlock.url.get")) {
Serial.println("Webhook Unlock URL: " + settings.webhookUnlockHook);
}
if(prefix.equals("webhook.unlock.url.set")) {
if (wordCount == 2) {
settings.saveSetting("wh", "unlockurl", wordList[1], "string");
Serial.println("Webhook Unlock URL: " + settings.webhookUnlockHook);
}
}
if(prefix.equals("webhook.unlock.enable.get")) {
Serial.println("Webhook Unlock Enabled: " + settings.webhookUnlockEnabled);
}
if(prefix.equals("webhook.unlock.enable.set")) {
if (wordCount == 2) {
settings.saveSetting("wh", "unlockena", wordList[1], "bool");
Serial.println("Webhook Unlock Enabled: " + settings.webhookUnlockEnabled);
}
}
// Command Validate Access // Command Validate Access
if (prefix.equals("validateAccess")) { if (prefix.equals("acl.local.validate")) {
if (wordCount == 2) { if (wordCount == 2) {
bool result = acl.validateAccess(String(wordList[1])); // Validates access for a given card ID using the local ACL
bool result = acl.validateAccess(String(wordList[1]));
if (result) { if (result) {
Serial.println("Access granted!"); Serial.println("Access granted!");
}
else {
Serial.println("Access denied!");
}
} }
else { else {
Serial.println("validateAccess requires exactly 1 arguments (cardId)"); Serial.println("Access denied!");
return;
} }
}
} }
// Command Add User // Command Add User
if (prefix.equals("addUser")) { if (prefix.equals("acl.local.add")) {
if (wordCount == 3) { if (wordCount == 3) {
acl.addUser(wordList[1], wordList[2]); acl.addUser(wordList[1], wordList[2]);
acl.saveToEEPROM(); acl.saveToEEPROM();
@ -358,7 +494,7 @@ void setup() {
} }
} }
// Command Update User // Command Update User
if (prefix.equals("updateUser")) { if (prefix.equals("acl.local.update")) {
if (wordCount == 4) { if (wordCount == 4) {
acl.updateUser(wordList[1], wordList[2], wordList[3]); acl.updateUser(wordList[1], wordList[2], wordList[3]);
acl.saveToEEPROM(); acl.saveToEEPROM();
@ -372,7 +508,7 @@ void setup() {
// Command Remove User // Command Remove User
if (prefix.equals("removeUser")) { if (prefix.equals("acl.local.remove")) {
if (wordCount == 2) { if (wordCount == 2) {
acl.removeUser(wordList[1]); acl.removeUser(wordList[1]);
acl.saveToEEPROM(); acl.saveToEEPROM();
@ -386,7 +522,7 @@ void setup() {
// Command List Uses // Command List Uses
if (prefix.equals("listUsers")) { if (prefix.equals("acl.local.list")) {
if (wordCount == 1) { if (wordCount == 1) {
// Retrieve the ACL data using the getter function // Retrieve the ACL data using the getter function
const User* aclData = acl.getACL(); const User* aclData = acl.getACL();
@ -457,7 +593,7 @@ void setup() {
* It then calls the appropriate functions based on the defined macros to handle the card data. * It then calls the appropriate functions based on the defined macros to handle the card data.
* If no valid card data is received within the specified time, it resets the card data and bit count. * If no valid card data is received within the specified time, it resets the card data and bit count.
* *
* @note This function assumes the availability of certain macros like TINANCE2_BACKEND, LOCAL_ACL, SERIAL_ACL and SERIAL_DEBUG. * @note This function assumes the availability of certain macros like TINANCE2_BACKEND, LOCAL_ACL, SERIAL_CLI and SERIAL_DEBUG.
* *
* @note This function assumes the availability of certain variables like newDataAvailable, lastDataTime, displayDelay, cardData, bitCount, and maxReaderWaitTime. * @note This function assumes the availability of certain variables like newDataAvailable, lastDataTime, displayDelay, cardData, bitCount, and maxReaderWaitTime.
* *
@ -520,7 +656,7 @@ void loop() {
#endif #endif
#ifdef LOCAL_ACL #ifdef LOCAL_ACL
#ifdef SERIAL_ACL #ifdef SERIAL_CLI
checkSerialCommand(); checkSerialCommand();
#endif #endif
#endif #endif

View file

@ -14,9 +14,62 @@ void Settings::loadFromEEPROM() {
settings_preferences.begin("t2", false); settings_preferences.begin("t2", false);
tinance2ReaderIdentifier = settings_preferences.getString("ri", "default"); tinance2ReaderIdentifier = settings_preferences.getString("ri", "default");
tinance2ReaderKey = settings_preferences.getString("rk", "default"); tinance2ReaderKey = settings_preferences.getString("rk", "default");
tinance2_url_validatecard = settings_preferences.getString("vcurl","https://tinance2.board.techinc.nl/accesscontrol/api/check-card-id");
tinance2_url_readerinfo = settings_preferences.getString("riurl","https://tinance2.board.techinc.nl/accesscontrol/api/readerinfo");
tinance2_url_acls = settings_preferences.getString("aclurl","https://tinance2.board.techinc.nl/accesscontrol/api/acls");
tinance2_url_log = settings_preferences.getString("rluri","https://tinance2.board.techinc.nl/accesscontrol/api/readerlog");
settings_preferences.end();
settings_preferences.begin("wh", false);
webhookLockEnabled = settings_preferences.getBool("lockena", false);
webhookLockHook = settings_preferences.getString("lockurl", "");
webhookUnlockEnabled = settings_preferences.getBool("unlockena", false);
webhookUnlockHook = settings_preferences.getString("unlockurl", "");
settings_preferences.end(); settings_preferences.end();
} }
void Settings::saveSetting(const String& namespaceStr, const String& key, const String& value, const String& typeStr) {
if (typeStr == "bool") {
bool currentValue = settings_preferences.getBool(key.c_str());
bool newValue = (value == "true");
if (currentValue != newValue) {
settings_preferences.begin(namespaceStr.c_str(), false);
settings_preferences.putBool(key.c_str(), newValue);
settings_preferences.end();
}
} else if (typeStr == "int") {
int currentValue = settings_preferences.getInt(key.c_str());
int newValue = atoi(value.c_str());
if (currentValue != newValue) {
settings_preferences.begin(namespaceStr.c_str(), false);
settings_preferences.putInt(key.c_str(), newValue);
settings_preferences.end();
}
} else if (typeStr == "float") {
float currentValue = settings_preferences.getFloat(key.c_str());
float newValue = atof(value.c_str());
if (currentValue != newValue) {
settings_preferences.begin(namespaceStr.c_str(), false);
settings_preferences.putFloat(key.c_str(), newValue);
settings_preferences.end();
}
} else if (typeStr == "string") {
String currentValue = settings_preferences.getString(key.c_str());
if (currentValue != value) {
settings_preferences.begin(namespaceStr.c_str(), false);
settings_preferences.putString(key.c_str(), value);
settings_preferences.end();
}
} else {
// Handle other types here if needed
}
}
void Settings::saveToEEPROM() { void Settings::saveToEEPROM() {
settings_preferences.begin("settings", false); settings_preferences.begin("settings", false);
settings_preferences.putBool("disableDoor", disableDoor); settings_preferences.putBool("disableDoor", disableDoor);
@ -78,22 +131,4 @@ String Settings::getDoorMode() {
return doorMode; return doorMode;
} }
void Settings::setTinance2ReaderIdentifier(String value) {
tinance2ReaderIdentifier = value;
saveToEEPROM();
}
String Settings::getTinance2ReaderIdentifier() {
return tinance2ReaderIdentifier;
}
void Settings::setTinance2ReaderKey(String value){
tinance2ReaderKey = value;
saveToEEPROM();
}
String Settings::getTinance2ReaderKey(){
return tinance2ReaderKey;
}
Settings settings; Settings settings;

View file

@ -17,8 +17,8 @@ HTTPClient tinance2_http;
// Set the headers // Set the headers
tinance2_http.addHeader("Content-Type", "application/json"); tinance2_http.addHeader("Content-Type", "application/json");
tinance2_http.addHeader("X-Identifier", settings.getTinance2ReaderIdentifier()); tinance2_http.addHeader("X-Identifier", settings.tinance2ReaderIdentifier);
tinance2_http.addHeader("X-Access-Key", settings.getTinance2ReaderKey()); tinance2_http.addHeader("X-Access-Key", settings.tinance2ReaderKey);
// Set the HTTP timeout to 5 seconds // Set the HTTP timeout to 5 seconds
tinance2_http.setTimeout(5000); tinance2_http.setTimeout(5000);