diff --git a/Notes.md b/Notes.md
index 172c4df..b827aac 100644
--- a/Notes.md
+++ b/Notes.md
@@ -9,8 +9,19 @@
- Allow integrity check for duplicate pin numbers, rfid etc.
* Use text templates for Display
* Rebuild WEB UI -> Mobile first approach. More limited features but therefore easy to understand
+### 19.02.2022
+* CSV upload
+* Admin authentification
+* Api for device settings
+* capitve portal
+* (Optional) REST user update
+* Create WEB UI without React
+
------
## Log
### 13.02.2022
+ Finally I found a solution for using a json document with variable file size. Import and export works well - till you have more than 300 users.
+### 19.02.2022
++ Stable and fast csv database implemented.
++ Basic REST API: read, create and delete users / read and drop csv db
diff --git a/data/static/error404.html b/data/static/error404.html
new file mode 100644
index 0000000..aa3bce1
--- /dev/null
+++ b/data/static/error404.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Document
+
+
+ Error 404: Page not found.
+
+
\ No newline at end of file
diff --git a/data/static/index.html b/data/static/index.html
new file mode 100644
index 0000000..9847b8e
--- /dev/null
+++ b/data/static/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Document
+
+
+ Welcome on ESP8266
+
+
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index aa162ac..28dfe43 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -13,6 +13,9 @@ platform = espressif8266
board = nodemcuv2
framework = arduino
board_build.filesystem = littlefs
+monitor_speed = 115200 ; set to the baud rate you pass to Serial.begin(…)
+monitor_filters = esp8266_exception_decoder, default
+build_type = debug
lib_deps =
xreef/PCF8574 library@^2.2.1
marcoschwartz/LiquidCrystal_I2C@^1.1.4
diff --git a/src/Interface.cpp b/src/Interface.cpp
index e3816cd..1d2db98 100644
--- a/src/Interface.cpp
+++ b/src/Interface.cpp
@@ -41,6 +41,10 @@ void Interface::showMessage(String msg1, String msg2, unsigned long display_time
this->_msg_display_time = display_time;
this->setState(states::MSG);
}
+
+int Interface::getState(){
+ return _display_state;
+}
void Interface::render()
{
if (this->_keyboard->available())
diff --git a/src/Interface.h b/src/Interface.h
index 299042f..ad8e01f 100644
--- a/src/Interface.h
+++ b/src/Interface.h
@@ -37,6 +37,7 @@ public:
void begin(Keyboard* Keyboard);
void render();
void setState(states state);
+ int getState();
void greetUser(String username);
String getPin();
bool pinAvailable();
diff --git a/src/UserDb.cpp b/src/UserDb.cpp
index 4dc4a42..181c077 100644
--- a/src/UserDb.cpp
+++ b/src/UserDb.cpp
@@ -14,10 +14,19 @@ bool UserDb::add_user(User &user)
{
File db = LittleFS.open(filename, "a");
- db.write("\n" + String(user.uid) + ";" + user.first_name + ";" + user.last_name + ";" + user.rfid_uid + ";" + user.user_pin + ";" + user.enabled ? "1" : "0");
- db.close();
- return true;
+ if (db)
+ {
+ if (db.size() > 0)
+ db.write("\n");
+ String csvline = user.toCSVString();
+ db.write(csvline.c_str());
+ db.close();
+ return true;
+ }
+ else
+ return false;
}
+
User UserDb::user_by_pin(String cmp)
{
#ifdef DEBUG
@@ -27,7 +36,7 @@ User UserDb::user_by_pin(String cmp)
do
{
User temp = *it;
- if (temp.enabled)
+ if (temp.match)
return temp;
++it;
} while (it.has_next());
@@ -42,7 +51,7 @@ User UserDb::user_by_rfid(String cmp)
do
{
User temp = *it;
- if (temp.enabled)
+ if (temp.match)
return temp;
++it;
} while (it.has_next());
@@ -50,15 +59,20 @@ User UserDb::user_by_rfid(String cmp)
}
User UserDb::user_by_uid(unsigned long cmp)
+{
+ String cmps = String(cmp);
+ return user_by_uid(cmps);
+}
+User UserDb::user_by_uid(String cmp)
{
#ifdef DEBUG
- Serial.println("Searching for user with user id " + String(cmp));
+ Serial.println("Searching for user with id " + cmp);
#endif
- Iterator it = begin();
+ Iterator it = begin_with_filter(&cmp, USERATTRIBUTES::USER_ID);
do
{
User temp = *it;
- if (temp.uid == cmp)
+ if (temp.match)
return temp;
++it;
} while (it.has_next());
diff --git a/src/UserDb.h b/src/UserDb.h
index 3f8dacd..3cc6167 100644
--- a/src/UserDb.h
+++ b/src/UserDb.h
@@ -28,12 +28,20 @@ namespace userdb
mutable String rfid_uid;
mutable String user_pin;
mutable bool enabled = false;
+ mutable bool match = false;
bool operator<(const User &o) const { return uid < o.uid; }
bool operator==(const User &o) const { return uid == o.uid; }
String toString()
{
return "ID: " + String(uid) + " Name: " + last_name + " " + first_name + " Enabled: " + (enabled ? "Yes" : "No") + " LineNo.: " + String(line) + "[RFID/PIN]" + rfid_uid + "//" + user_pin;
}
+ String toCSVString()
+ {
+ return String(uid) + ";" + first_name + ";" + last_name + ";" + rfid_uid + ";" + user_pin + ";" + (enabled ? "1" : "0");
+ }
+ String toJSONString(){
+ return "{\"uid\":\""+String(uid) + "\",\"first_name\":\"" + first_name + "\",\"last_name\":\"" + last_name + "\",\"rfid_uid\":\"" + rfid_uid + "\",\"user_pin\":\"" + user_pin + "\",\"enabled\":" + (enabled ? "true" : "false")+"}";
+ }
};
class UserDb
@@ -49,45 +57,12 @@ namespace userdb
User user_by_pin(String);
User user_by_rfid(String);
User user_by_uid(unsigned long);
- inline User read_csv_line(String &instring, uint32_t &line, String *match, USERATTRIBUTES attr)
- {
- int locations[5];
- int location_index = 0;
- int str_length = instring.length() - 1;
- for (int i = 0; i < str_length; i++)
- {
- if (instring.charAt(i) == ';')
- {
- locations[location_index] = i;
- location_index++;
- }
- }
- User res;
- if (attr == RFID_UID)
- {
- res.rfid_uid = instring.substring(locations[2] + 1, locations[3]);
- if (!res.rfid_uid.equals(*match))
- return res;
- res.user_pin = instring.substring(locations[3] + 1, locations[4]);
- }
- else if (attr == USER_PIN)
- {
- res.user_pin = instring.substring(locations[3] + 1, locations[4]);
- if (!res.user_pin.equals(*match))
- return res;
- res.rfid_uid = instring.substring(locations[2] + 1, locations[3]);
- }
- else
- {
- res.rfid_uid = instring.substring(locations[2] + 1, locations[3]);
- res.user_pin = instring.substring(locations[3] + 1, locations[4]);
- }
- res.uid = instring.substring(0, locations[0]).toInt();
- res.first_name = instring.substring(locations[0] + 1, locations[1]);
- res.last_name = instring.substring(locations[1] + 1, locations[2]);
- res.enabled = instring.charAt(locations[4] + 1) == '1' ? true : false;
- res.line = line;
- return res;
+ User user_by_uid(String cmp);
+ bool drop(){
+ LittleFS.remove(filename);
+ File n = LittleFS.open(filename,"a");
+ n.close();
+ return true;
}
static inline User read_csv_line(File &instream, uint32_t &line, String *match, USERATTRIBUTES attr)
{
@@ -106,6 +81,7 @@ namespace userdb
return res;
res.enabled = instream.read() == '1' ? true : false;
res.line = line;
+ res.match = true;
return res;
}
bool remove_user(User &user);
@@ -119,7 +95,7 @@ namespace userdb
while (old_db.available())
{
if (curr_line == line)
- while (old_db.read() != '\n')
+ while (old_db.available()&&old_db.read() != '\n')
{
}
else
@@ -194,10 +170,8 @@ namespace userdb
{
if (db_file.available())
{
- // String temp = db_file.readStringUntil('\n');
- // current = read_csv_line(temp, line, match, filter_attr);
- // line++;
current = read_csv_line(db_file, line, match, filter_attr);
+ // Position stream pointer to beginning of the next line
while (db_file.available())
{
if (db_file.read() == '\n')
@@ -223,7 +197,7 @@ namespace userdb
bool available = true;
uint32_t line = 0;
};
- Iterator begin() { return Iterator(LittleFS.open("userdb.csv", "r")); }
- Iterator begin_with_filter(String *match, USERATTRIBUTES attr) { return Iterator(LittleFS.open("userdb.csv", "r"), match, attr); }
+ Iterator begin() { return Iterator(LittleFS.open(filename, "r")); }
+ Iterator begin_with_filter(String *match, USERATTRIBUTES attr) { return Iterator(LittleFS.open(filename, "r"), match, attr); }
};
}
\ No newline at end of file
diff --git a/src/WebConsole.cpp b/src/WebConsole.cpp
new file mode 100644
index 0000000..375929a
--- /dev/null
+++ b/src/WebConsole.cpp
@@ -0,0 +1,176 @@
+#include "WebConsole.h"
+
+using namespace webconsole;
+
+WebConsole::WebConsole() {}
+WebConsole::~WebConsole()
+{
+ _server->close();
+ delete (_server);
+}
+bool WebConsole::init(userdb::UserDb *userdb)
+{
+ _server = new ESP8266WebServer(80);
+ this->userdb = userdb;
+ _server->begin();
+ _server->on("/userdb", HTTPMethod::HTTP_DELETE, std::bind(&WebConsole::_dropUserDb, this));
+ _server->on("/userdb", HTTPMethod::HTTP_GET, std::bind(&WebConsole::_getUserDb, this));
+ _server->on("/rfid", std::bind(&WebConsole::_catchRFID, this));
+ _server->on(UriBraces("/user/{}"), HTTPMethod::HTTP_DELETE, std::bind(&WebConsole::_deleteUser, this));
+ _server->on(UriBraces("/user/{}"), HTTPMethod::HTTP_GET, std::bind(&WebConsole::_getUser, this));
+ _server->on(UriBraces("/user/{}"), HTTPMethod::HTTP_PUT, std::bind(&WebConsole::_createUser, this));
+ _server->on(UriBraces("/get/{}"), std::bind(&WebConsole::_deleteUser, this));
+ //_server->on("/bypin",std::bind(&WebConsole::_findPin,this));
+ _server->onNotFound(std::bind(&WebConsole::_handleStatic, this));
+ return true;
+}
+void WebConsole::attachRfid(Rfid *rfid){
+ this->rfid=rfid;
+}
+void WebConsole::serve()
+{
+ if (catch_rfid && rfid != nullptr && rfid->available())
+ {
+ rfid_buffer = rfid->getID();
+ catch_rfid_updated = true;
+ catch_rfid = false;
+ }
+ _server->handleClient();
+}
+bool WebConsole::isInterceptingRfid(){
+ return catch_rfid;
+}
+void WebConsole::_handleStatic()
+{
+ String path = path_prefix + _server->uri();
+ Serial.print("Request " + path);
+ File src = LittleFS.open(path, "r");
+ if (src.isDirectory())
+ {
+ path += "index.html";
+ src.close();
+ src = LittleFS.open(path, "r");
+ }
+ if (!src)
+ {
+ path = "/static/error404.html";
+ src.close();
+ src = LittleFS.open(path, "r");
+ }
+
+ Serial.println(" resolved to " + path);
+ if (src)
+ {
+ String content_type = "text/html";
+ _server->streamFile(src, content_type);
+ src.close();
+ }
+ else
+ {
+ _server->send(500, "text/plain", "Internal error 500");
+ }
+}
+
+void WebConsole::_getUserDb()
+{
+ File src = LittleFS.open("userdb.csv", "r");
+ if (src)
+ {
+ _server->streamFile(src, "text/csv");
+ }
+ src.close();
+}
+void WebConsole::_deleteUser()
+{
+ if (userdb == nullptr)
+ {
+ _server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
+ return;
+ }
+ String uid = _server->pathArg(0);
+ userdb::User del = userdb->user_by_uid(uid);
+ if (del.match == true)
+ {
+ userdb->remove_user(del);
+ _server->send(200, "text/plain", del.toJSONString());
+ }
+ else
+ {
+ _server->send(500, "text/json", "{\"error\":\"User not found.\"}");
+ }
+}
+void WebConsole::_getUser()
+{
+ if (userdb == nullptr)
+ {
+ _server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
+ return;
+ }
+ String uid = _server->pathArg(0);
+ userdb::User res = userdb->user_by_uid(uid);
+ if (res.match == true)
+ {
+ _server->send(200, "text/json", res.toJSONString());
+ }
+ else
+ {
+ _server->send(500, "text/json", "{\"error\":\"User not found.\"}");
+ }
+}
+void WebConsole::_createUser()
+{
+ userdb::User created;
+ String body = _server->arg("plain");
+ const int capacity = 1024;
+ StaticJsonDocument doc;
+ DeserializationError err = deserializeJson(doc, body);
+ if (err.code() != err.Ok)
+ {
+ Serial.println(err.c_str());
+ Serial.println(body);
+ String response = "{\"error\":\"";
+ response += err.c_str();
+ response += "\"}";
+ _server->send(500, "text/json", response);
+ return;
+ }
+ if (!_server->pathArg(0).isEmpty())
+ created.uid = _server->pathArg(0).toInt();
+ else if (!doc["uid"].isNull())
+ created.uid = doc["uid"].as();
+ else
+ {
+ _server->send(500, "text/json", "{\"error\":\"UserId not provided.\"}");
+ return;
+ }
+ created.first_name = doc["first_name"].as();
+ created.last_name = doc["last_name"].as();
+ created.rfid_uid = doc["rfid_uid"].as();
+ created.user_pin = doc["user_pin"].as();
+ created.enabled = doc["enabled"].as();
+ userdb->add_user(created);
+ _server->send(200, "text/json", created.toJSONString());
+}
+void WebConsole::_dropUserDb()
+{
+ if (userdb->drop())
+ _server->send(500, "text/json", "{\"ok\":\"UserDb dropped.\"}");
+ else
+ _server->send(500, "text/json", "{\"error\":\"UserDb could not be dropped.\"}");
+}
+void WebConsole::_catchRFID()
+{
+ if (catch_rfid_updated)
+ {
+ String response = "{\"rfid_uid\":\"" + rfid_buffer + "\"}";
+ _server->send(500, "text/json", response);
+ catch_rfid_updated = false;
+ }
+ else if(catch_rfid){
+ _server->send(500, "text/json", "{\"ok\":\"already activated\"}");
+ }
+ else{
+ catch_rfid = true;
+ _server->send(500, "text/json", "{\"ok\":\"now activated\"}");
+ }
+}
diff --git a/src/WebConsole.h b/src/WebConsole.h
new file mode 100644
index 0000000..b3328d4
--- /dev/null
+++ b/src/WebConsole.h
@@ -0,0 +1,40 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include "LittleFS.h"
+#include "ArduinoJson.h"
+#include "UserDb.h"
+#include "Rfid.h"
+namespace webconsole
+{
+ static const char path_prefix[] PROGMEM = "/static";
+ static ESP8266WebServer _server(80);
+ class WebConsole
+ {
+ public:
+ WebConsole();
+ ~WebConsole();
+
+ bool init(userdb::UserDb *userdb);
+ void attachRfid(Rfid *rfid);
+ void serve();
+ bool isInterceptingRfid();
+ private:
+ void _handleStatic();
+ void _getUserDb();
+ void _deleteUser();
+ void _getUser();
+ void _createUser();
+ void _dropUserDb();
+ void _catchRFID();
+ ESP8266WebServer* _server;
+ userdb::UserDb *userdb = nullptr;
+ bool catch_rfid = false;
+ bool catch_rfid_updated = false;
+ String rfid_buffer;
+ Rfid *rfid = nullptr;
+ };
+
+}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 3b0b3c9..02b2e5c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,13 +5,13 @@
#include "Rfid.h"
#include "Interface.h"
#include
-#include
-//#include "Users.h"
+#include "WebConsole.h"
#include "UserDb.h"
#include "Persistence.h"
// File persistence
Persistence persistence;
userdb::UserDb userdatabase("userdb.csv");
+webconsole::WebConsole web;
// Rfid
Rfid rfid;
// i2C Bus
@@ -32,52 +32,56 @@ IPAddress subnet(255, 255, 255, 0);
void setup()
{
Persistence::Configuration config = persistence.loadConfig();
- // users.ImportFromPersistence();
- Serial.begin(9600);
+ Serial.begin(115200);
Serial.println("Starting System");
- Serial.println("\t1. Network config: " + WiFi.softAPConfig(local_IP, gateway, subnet) ? "Ready" : "Failed!");
+ Serial.print("\t1. Network config ->");
+ Serial.println(WiFi.softAPConfig(local_IP, gateway, subnet) ? "Ready" : "Failed!");
+ Serial.print("\t2 AP setup " + String(config.SSID)+ " -> ");
if (strlen(config.PASS) > 0)
- Serial.println("\t1. AP setup with password: " + WiFi.softAP(config.SSID, config.PASS) ? "Ready" : "Failed!");
+ Serial.println(WiFi.softAP(config.SSID, config.PASS) ? "Ready" : "Failed!");
else
- Serial.println("\t1. AP setup without password: " +WiFi.softAP(config.SSID) ? "Ready" : "Failed!");
- delay(250);
+ Serial.println(WiFi.softAP(config.SSID) ? "Ready" : "Failed!");
+ delay(150);
#ifdef DEBUG
userdatabase.print_to_serial();
#endif
+ web.init(&userdatabase);
keyboard.begin(&Wire);
rfid.begin();
+ web.attachRfid(&rfid);
iface.begin(&keyboard);
- userdatabase.remove_user_by_line("userdb.csv",2);
}
void loop()
{
- keyboard.scanAsync();
rfid.scan();
+ web.serve();
+ keyboard.scanAsync();
userdb::User login_user;
- if (iface.pinAvailable())
+ if(web.isInterceptingRfid()&&iface.getState()==0){
+ iface.showMessage("WebUI connected", "Awaiting RFID", 3000);
+ }
+ else if (iface.pinAvailable())
{
unsigned long delta = millis();
login_user = userdatabase.user_by_pin(iface.getPin());
- Serial.println("Query duration: " +String(millis()-delta));
+ Serial.println("Query duration: " + String(millis() - delta));
if (login_user.enabled == false)
{
iface.showMessage("Login failed!", "-> Pin incorrect", 3000);
return;
}
-
}
else if (rfid.available())
{
unsigned long delta = millis();
login_user = userdatabase.user_by_rfid(rfid.getID());
- Serial.println("Query duration: " +String(millis()-delta));
+ Serial.println("Query duration: " + String(millis() - delta));
if (login_user.enabled == false)
{
iface.showMessage("Login failed!", "-> Unkown Card", 3000);
return;
}
-
}
if (login_user.enabled == true)