From 74da018c9939ffbabe57fe33c794155d8715acd9 Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Wed, 23 Feb 2022 16:57:06 +0100 Subject: [PATCH] Implemented user-update command for REST-API --- src/Interface.cpp | 8 ++- src/Interface.h | 1 + src/UserDb.cpp | 118 ++++++++++++++++++++++++++++++++++++++++----- src/UserDb.h | 82 +++++++++++++++---------------- src/WebConsole.cpp | 90 +++++++++++++++++++++++++++++----- src/WebConsole.h | 15 +++++- src/main.cpp | 4 +- 7 files changed, 248 insertions(+), 70 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index 1d2db98..f6d3ad4 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -49,6 +49,7 @@ void Interface::render() { if (this->_keyboard->available()) { + this->_inputTimeout = millis(); if (this->_keyboard->getLastChr() == 'X') { this->_keyboard->clear(); @@ -79,7 +80,12 @@ void Interface::render() //this->setState(states::VALIDATE_PIN); } } - + if(this->_display_state==states::ENTER_PIN_ADD_NUM||this->_display_state==states::ENTER_PIN_START){ + if(millis()-_inputTimeout>10000){ + this->_keyboard->clear(); + this->setState(states::ABORT); + } + } if (this->_displayUpdate) { switch (this->_display_state) diff --git a/src/Interface.h b/src/Interface.h index ad8e01f..e100f4a 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -12,6 +12,7 @@ private: unsigned long _lastDisplayUpdate = 0; unsigned long _displayTimer1 = 0; unsigned long _displayTimer2 = 0; + unsigned long _inputTimeout = 0; bool _displayUpdate = true; String _username = ""; unsigned int _scroll_index = 0; diff --git a/src/UserDb.cpp b/src/UserDb.cpp index 181c077..609aa27 100644 --- a/src/UserDb.cpp +++ b/src/UserDb.cpp @@ -1,14 +1,74 @@ #include "UserDb.h" -#define DEBUG +//#define DEBUG using namespace userdb; UserDb::UserDb(const char *filename) : filename(filename) { } - +void UserDb::coursor_to_end_of_line(File &f) +{ + while (f.available() && f.read() != '\n') + { + } +} bool UserDb::remove_user(User &user) { - return remove_user_by_line(filename, user.line); + return update_db_line(filename, user.line); +} +bool UserDb::update_user(User *user) +{ + return update_db_line(filename, user->line, user);; +} +bool UserDb::update_db_line(const char *file, linenumber line, User *replace) +{ + const char *temp_file = ".tmp.csv"; + File old_db = LittleFS.open(file, "r"); + File new_db = LittleFS.open(temp_file, "w+"); + linenumber curr_line = 0; + bool line_found = false; + while (old_db.available()) + { + if (curr_line == line) + { + +#ifdef DEBUG + Serial.println("Found line: " + String(line)); +#endif + if (replace != nullptr) + { + String insert = replace->toCSVString(); +#ifdef DEBUG + Serial.println("Inserting: " + insert); +#endif + if (new_db.position() > 0) + new_db.write('\n'); + new_db.write(insert.c_str()); + } + coursor_to_end_of_line(old_db); + line_found = true; + } + + else + { + if (new_db.position() > 0) + new_db.write('\n'); + old_db.sendUntil(new_db, '\n'); + } + curr_line++; + } + old_db.close(); +#ifdef DEBUG + Serial.println("NewDB:"); + new_db.seek(0); + while (new_db.available()) + new_db.sendAvailable(Serial); +#endif + new_db.close(); + if (LittleFS.remove(file) && LittleFS.rename(temp_file, file)) + Serial.println("UserDb updated"); + else + Serial.println("Could not delete old userdb"); + return line_found; } bool UserDb::add_user(User &user) { @@ -36,8 +96,14 @@ User UserDb::user_by_pin(String cmp) do { User temp = *it; - if (temp.match) - return temp; + if ((*it).match && (*it).enabled) + { +#ifdef DEBUG + Serial.println(" -> Found match"); +#endif + it.close_file(); + return *it; + } ++it; } while (it.has_next()); return User{.enabled = false}; @@ -50,9 +116,14 @@ User UserDb::user_by_rfid(String cmp) Iterator it = begin_with_filter(&cmp, USERATTRIBUTES::RFID_UID); do { - User temp = *it; - if (temp.match) - return temp; + if ((*it).match && (*it).enabled) + { +#ifdef DEBUG + Serial.println(" -> Found match"); +#endif + it.close_file(); + return *it; + } ++it; } while (it.has_next()); return User{.enabled = false}; @@ -71,9 +142,34 @@ User UserDb::user_by_uid(String cmp) Iterator it = begin_with_filter(&cmp, USERATTRIBUTES::USER_ID); do { - User temp = *it; - if (temp.match) - return temp; + if ((*it).match) + { +#ifdef DEBUG + Serial.println(" -> Found match"); +#endif + it.close_file(); + return *it; + } + ++it; + } while (it.has_next()); + return User{.enabled = false}; +} +User UserDb::user_by_line(linenumber cmp) +{ +#ifdef DEBUG + Serial.println("Searching for user with line number " + String(cmp)); +#endif + Iterator it = begin_with_filter(cmp, USERATTRIBUTES::LINE_ID); + do + { + if ((*it).match) + { +#ifdef DEBUG + Serial.println(" -> Found match"); +#endif + it.close_file(); + return (*it); + } ++it; } while (it.has_next()); return User{.enabled = false}; diff --git a/src/UserDb.h b/src/UserDb.h index 3cc6167..c692ba0 100644 --- a/src/UserDb.h +++ b/src/UserDb.h @@ -7,6 +7,7 @@ namespace userdb { + typedef uint16_t linenumber; enum USERATTRIBUTES { LINE_ID, @@ -22,7 +23,7 @@ namespace userdb { // User(unsigned long new_uid): uid(new_uid){} unsigned long uid = -1; - uint32_t line; + linenumber line = UINT16_MAX; mutable String first_name; mutable String last_name; mutable String rfid_uid; @@ -39,8 +40,9 @@ namespace userdb { 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")+"}"; + 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") + ",\"line\":\"" + line + "\"}"; } }; @@ -58,15 +60,16 @@ namespace userdb User user_by_rfid(String); User user_by_uid(unsigned long); User user_by_uid(String cmp); - bool drop(){ + User user_by_line(linenumber cmp); + bool drop() + { LittleFS.remove(filename); - File n = LittleFS.open(filename,"a"); + 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) + static inline User read_csv_line(User &res, File &instream, linenumber &line, String *match, USERATTRIBUTES attr) { - User res; String uid = instream.readStringUntil(';'); if (attr == USER_ID && !uid.equals(*match)) return res; @@ -84,39 +87,11 @@ namespace userdb res.match = true; return res; } - bool remove_user(User &user); - static bool remove_user_by_line(const char *file, uint32_t line) - { - const char *temp_file = ".tmp.csv"; - File old_db = LittleFS.open(file, "r"); - File new_db = LittleFS.open(temp_file, "w+"); - uint32_t curr_line = 0; - while (old_db.available()) - { - if (curr_line == line) - while (old_db.available()&&old_db.read() != '\n') - { - } - else - { - if (new_db.position() > 0) - new_db.write('\n'); - old_db.sendUntil(new_db, '\n'); - } - curr_line++; - } - old_db.close(); -#ifdef DEBUG - new_db.seek(0); - while (new_db.available()) - new_db.sendAvailable(Serial); -#endif - new_db.close(); - LittleFS.remove(file); - LittleFS.rename(temp_file, file); - return false; - } + void inline static coursor_to_end_of_line(File &f); + bool remove_user(User &user); + bool update_user(User *user); + static bool update_db_line(const char *file, linenumber line, User *replace = nullptr); bool add_user(User &user); struct Iterator { @@ -129,6 +104,17 @@ namespace userdb Iterator(File data) : Iterator(data, nullptr, NONE) { } + Iterator(File data, linenumber m, USERATTRIBUTES attr) : db_file(data), filter_attr(attr), matchline(m) + { + if (!data) + { + Serial.println("Failed to open userdb while creating an iterator"); + } + else + { + next(); + } + } Iterator(File data, String *m, USERATTRIBUTES attr) : db_file(data), filter_attr(attr), match(m) { #ifdef DEBUG @@ -160,6 +146,9 @@ namespace userdb } ~Iterator() { + close_file(); + } + void close_file(){ db_file.close(); } bool has_next() @@ -170,7 +159,16 @@ namespace userdb { if (db_file.available()) { - current = read_csv_line(db_file, line, match, filter_attr); + if (filter_attr == USERATTRIBUTES::LINE_ID) + { + if (matchline == line) + { + current = read_csv_line(current, db_file, line, match, filter_attr); + current.match=true; + } + } + else + current = read_csv_line(current, db_file, line, match, filter_attr); // Position stream pointer to beginning of the next line while (db_file.available()) { @@ -195,9 +193,11 @@ namespace userdb USERATTRIBUTES filter_attr; String *match; bool available = true; - uint32_t line = 0; + linenumber line = 0; + linenumber matchline = 0; }; Iterator begin() { return Iterator(LittleFS.open(filename, "r")); } Iterator begin_with_filter(String *match, USERATTRIBUTES attr) { return Iterator(LittleFS.open(filename, "r"), match, attr); } + Iterator begin_with_filter(linenumber 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 index 375929a..a6f7084 100644 --- a/src/WebConsole.cpp +++ b/src/WebConsole.cpp @@ -13,19 +13,23 @@ 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("/api/userdb", HTTPMethod::HTTP_DELETE, std::bind(&WebConsole::_dropUserDb, this)); + _server->on("/api/userdb", HTTPMethod::HTTP_GET, std::bind(&WebConsole::_getUserDb, this)); + _server->on("/api/rfid", std::bind(&WebConsole::_catchRFID, this)); + _server->on("/api/debug/printfile", std::bind(&WebConsole::_print_db_raw, this)); + _server->on(UriBraces("/api/user/{}"), HTTPMethod::HTTP_DELETE, std::bind(&WebConsole::_deleteUser, this)); + _server->on(UriBraces("/api/user/{}"), HTTPMethod::HTTP_GET, std::bind(&WebConsole::_getUser, this)); + _server->on(UriBraces("/api/user/{}"), HTTPMethod::HTTP_PUT, std::bind(&WebConsole::_createUser, this)); + _server->on(UriBraces("/api/user/{}"), HTTPMethod::HTTP_POST, std::bind(&WebConsole::_updateUser, this)); + _server->on(UriBraces("/api/config/{}"), std::bind(&WebConsole::_deleteUser, this)); //_server->on("/bypin",std::bind(&WebConsole::_findPin,this)); - _server->onNotFound(std::bind(&WebConsole::_handleStatic, this)); + _server->serveStatic("/", LittleFS, "/static/index.html"); + //_server->onNotFound(std::bind(&WebConsole::_handleStatic, this)); return true; } -void WebConsole::attachRfid(Rfid *rfid){ - this->rfid=rfid; +void WebConsole::attachRfid(Rfid *rfid) +{ + this->rfid = rfid; } void WebConsole::serve() { @@ -37,7 +41,8 @@ void WebConsole::serve() } _server->handleClient(); } -bool WebConsole::isInterceptingRfid(){ +bool WebConsole::isInterceptingRfid() +{ return catch_rfid; } void WebConsole::_handleStatic() @@ -99,6 +104,7 @@ void WebConsole::_deleteUser() _server->send(500, "text/json", "{\"error\":\"User not found.\"}"); } } + void WebConsole::_getUser() { if (userdb == nullptr) @@ -117,6 +123,57 @@ void WebConsole::_getUser() _server->send(500, "text/json", "{\"error\":\"User not found.\"}"); } } +void WebConsole::_updateUser() +{ + userdb::User updated; + String body = _server->arg("plain"); + const int capacity = 256; + 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 (doc.containsKey("line")) + { + updated = userdb->user_by_line(doc["line"].as()); + } + if (!updated.match && doc.containsKey("uid")) + { + updated = userdb->user_by_uid(doc["uid"].as()); + } + if (!updated.match && !_server->pathArg(0).isEmpty()) + { + updated = userdb->user_by_uid(_server->pathArg(0).toInt()); + } + + if (updated.match == false) + { + _server->send(500, "text/json", "{\"error\":\"user could not be found.\"}"); + return; + } + // created.line = doc["line"].as(); + if (doc.containsKey("uid")) + updated.uid = doc["uid"].as(); + if (doc.containsKey("first_name")) + updated.first_name = doc["first_name"].as(); + if (doc.containsKey("last_name")) + updated.last_name = doc["last_name"].as(); + if (doc.containsKey("rfid_uid")) + updated.rfid_uid = doc["rfid_uid"].as(); + if (doc.containsKey("user_pin")) + updated.user_pin = doc["user_pin"].as(); + if (doc.containsKey("enabled")) + updated.enabled = doc["enabled"].as(); + bool res = userdb->update_user(&updated); + _server->send(200, "text/json", updated.toJSONString().c_str()); +} void WebConsole::_createUser() { userdb::User created; @@ -160,16 +217,23 @@ void WebConsole::_dropUserDb() } void WebConsole::_catchRFID() { + if (rfid == nullptr) + { + _server->send(500, "text/json", "{\"error\":\"RFID not attached.\"}"); + return; + } if (catch_rfid_updated) { String response = "{\"rfid_uid\":\"" + rfid_buffer + "\"}"; _server->send(500, "text/json", response); catch_rfid_updated = false; } - else if(catch_rfid){ + else if (catch_rfid) + { _server->send(500, "text/json", "{\"ok\":\"already activated\"}"); } - else{ + else + { catch_rfid = true; _server->send(500, "text/json", "{\"ok\":\"now activated\"}"); } diff --git a/src/WebConsole.h b/src/WebConsole.h index b3328d4..8895bea 100644 --- a/src/WebConsole.h +++ b/src/WebConsole.h @@ -16,20 +16,31 @@ namespace 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 _updateUser(); void _createUser(); void _dropUserDb(); void _catchRFID(); - ESP8266WebServer* _server; + void _print_db_raw() + { + File f = LittleFS.open("userdb.csv", "r"); + while (f.available()){ + f.sendAvailable(Serial); + } + _server->send(200,"text/plain","printed to serial"); + f.close(); + } + ESP8266WebServer *_server; userdb::UserDb *userdb = nullptr; bool catch_rfid = false; bool catch_rfid_updated = false; diff --git a/src/main.cpp b/src/main.cpp index 02b2e5c..1be655f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ void loop() if(web.isInterceptingRfid()&&iface.getState()==0){ iface.showMessage("WebUI connected", "Awaiting RFID", 3000); } - else if (iface.pinAvailable()) + else if (iface.pinAvailable()&&iface.getState()!=Interface::GREET) { unsigned long delta = millis(); login_user = userdatabase.user_by_pin(iface.getPin()); @@ -72,7 +72,7 @@ void loop() return; } } - else if (rfid.available()) + else if (rfid.available()&&iface.getState()!=Interface::GREET) { unsigned long delta = millis(); login_user = userdatabase.user_by_rfid(rfid.getID());