Compare commits

..

14 Commits

32 changed files with 671 additions and 375 deletions
+22
View File
@@ -25,3 +25,25 @@
### 19.02.2022 ### 19.02.2022
+ Stable and fast csv database implemented. + Stable and fast csv database implemented.
+ Basic REST API: read, create and delete users / read and drop csv db + Basic REST API: read, create and delete users / read and drop csv db
### 19.03.2022
+ Settings pos
+ WifiSSID - terminator 0x00 - Pos.: 0-31 (00 - 1F)
+ WifiPassword - terminator 0x00 - Pos.: 32-63 (20 - 3F)
+ Device IP - 40-43 uint32
+ Subnet 44 - 47
+ Gateway 48 - 4B
+ Mode 0 - station; 1 - client 4C uint8
+ AuthError Timeout (secs) 4D
+ OpenLock hold (secs) 4E
### 20.03.2022
+ settings API implemented and tested
+ TODO: factory reset
### 10.04.2022
+ Toni:
+ Reed Contact -> Door closed?
+ Tracking in->out
+ Stats
+ Sensors
+7 -2
View File
@@ -5,9 +5,14 @@ Adress 0x21
### 1.1 Wiring ### 1.1 Wiring
* Red: 3.3V * Red: 3.3V
* Black GND * Black GND
* Green SDA -> D3 * Green SDA -> D2
* Grey SCL -> D4 * Grey SCL -> D1
## 2.0 LCD ## 2.0 LCD
The LCD display is driven on the same i2c bus as the keypad. The LCD display is driven on the same i2c bus as the keypad.
nterface Definition
BYTE BIT
7 (MSB) 6 5 4 3 2 1 0 (LSB)
I2C slave address L H L L A2 A1 A0 R/W
I/O data bus P7 P6 P5 P4 P3 P2 P1 P0
BIN
View File
Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
[
{
"NAME":"Toni",
"PASSWORD":"geheim",
"PIN":1231239
}
]
-5
View File
@@ -1,5 +0,0 @@
{
"THEMEID": 1,
"SSID":"DoorLock",
"PASS":"geheim123"
}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.2" baseProfile="tiny" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 500 500" overflow="visible" xml:space="preserve">
<polyline fill="#CCCCCC" stroke="#000000" stroke-miterlimit="10" points="181.5,395.5 86.5,395.5 86.5,300.5 "/>
<g>
<rect x="355.37" y="40.63" transform="matrix(0.7071 -0.7071 0.7071 0.7071 31.9147 294.7168)" fill="#CCCCCC" stroke="#000000" stroke-width="1" stroke-miterlimit="9.9999" width="32.69" height="136.4"/>
<rect x="94.59" y="168.11" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -95.5641 241.9096)" fill="#CCCCCC" stroke="#000000" stroke-width="1" stroke-miterlimit="9.9999" width="299.29" height="136.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 881 B

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.2" baseProfile="tiny" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 500 500" overflow="visible" xml:space="preserve">
<path fill="#CCCCCC" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M349.5,421.5h-199c-8.28,0-15-6.72-15-15
v-255h229v255C364.5,414.78,357.78,421.5,349.5,421.5z"/>
<path fill="#CCCCCC" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M386.81,99.5H314.5V80.38
c0-9.88-8.01-17.88-17.88-17.88h-92.24c-9.88,0-17.88,8.01-17.88,17.88V99.5h-73.31c-5.35,0-9.69,4.34-9.69,9.69v20.62
c0,5.35,4.34,9.69,9.69,9.69h273.62c5.35,0,9.69-4.34,9.69-9.69v-20.62C396.5,103.84,392.16,99.5,386.81,99.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 874 B

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>doorlock_pwa</title><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#673ab8"><style>*{box-sizing:border-box}html{font-family:Helvetica,sans-serif;font-size:16px}body,html{height:100%}body{background-color:#fff;margin:0;padding:0;width:100%}</style><link href="/bundle.3bf6c.css" rel="stylesheet" media="only x" onload="this.media='all'"><noscript><link rel="stylesheet" href="/bundle.3bf6c.css"></noscript></head><body><script defer="defer" src="/bundle.4bdf3.js"></script><script nomodule="" src="/polyfills.914a6.js"></script></body></html>
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Error 404: Page not found.</h1>
</body>
</html>
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Welcome on ESP8266</h1>
</body>
</html>
-5
View File
@@ -1,5 +0,0 @@
{
"GREET":"Hallo $name",
"FAIL":"PIN Incorrect",
"HOME":"Willkommen!|Pin oder Karte"
}
+34
View File
@@ -0,0 +1,34 @@
#include "AdminAuth.h"
using namespace webconsole;
bool AdminAuth::isAuth(const char *token)
{
return tokenbuffer->exists(token) != -1;
}
bool AdminAuth::logout(const char *token)
{
int16_t tid = tokenbuffer->exists(token);
tokenbuffer->setnull(tid);
return tid != -1;
}
char *AdminAuth::login(const String& username, const String& password)
{
char *res = nullptr;
File adminfile = LittleFS.open("admin", "r");
if (username.equals(adminfile.readStringUntil('\0')) && password.equals(adminfile.readStringUntil('\0')))
res = tokenbuffer->newToken();
adminfile.close();
return res;
}
bool AdminAuth::setAuth(const String& username, const String& password)
{
if (username.length() == 0 || username.length() > MAX_USERNAMEPASSWORD_LENGTH || password.length()==0 || password.length() > MAX_USERNAMEPASSWORD_LENGTH)
return false;
File adminfile = LittleFS.open("admin", "w+");
adminfile.print(username);
adminfile.print('\0');
adminfile.print(password);
adminfile.print('\0');
delay(1);
adminfile.close();
return true;
}
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include "LittleFS.h"
#define TOKENBUFFERCAPACITY 3
#define TOKENLENGHT 10
#define MAX_USERNAMEPASSWORD_LENGTH 25
namespace webconsole
{
struct TokenBuffer
{
TokenBuffer()
{
for (uint16_t j = 0; j < TOKENBUFFERCAPACITY; j++)
setnull(j);
}
char *newToken()
{
if (next >= TOKENBUFFERCAPACITY)
next = 0;
for (uint16_t i = 0; i < TOKENLENGHT; i++)
token[next][i] = randomChar();
token[next][TOKENLENGHT] = 0x00;
return &token[next++][0];
}
int16_t exists(const char *cmp)
{
for (uint16_t j = 0; j < TOKENBUFFERCAPACITY; j++)
{
bool match = true;
for (uint16_t i = 0; match && i < TOKENLENGHT; i++)
if (token[j][i] != cmp[i])
match = false;
if (match)
return j;
}
return -1;
}
void setnull(uint16_t index)
{
if (index >= 0 && index < TOKENBUFFERCAPACITY)
for (uint16_t i = 0; i < TOKENLENGHT; i++)
token[index][i] = 0x00; // initialize with nullbytes
}
private:
uint16_t next = 0;
char token[TOKENBUFFERCAPACITY][TOKENLENGHT + 1];
char randomChar()
{
auto charrype = random(0, 3);
switch (charrype)
{
case 0:
return random(0x30, 0x3A); // Numbers
break;
case 1:
return random(0x41, 0x5B); // Capital letters
break;
case 2:
return random(0x61, 0x7B); // Lowercase letters
break;
}
return 0x21;
}
};
class AdminAuth
{
public:
//char *login(const char *username, const char *password);
char *login(const String &username,const String &password);
bool isAuth(const char *token);
bool logout(const char *token);
bool setAuth(const String &username, const String &password);
private:
TokenBuffer *tokenbuffer = new TokenBuffer();
};
}
+106
View File
@@ -0,0 +1,106 @@
#include "Config.h"
Config::Config(const char* filepath): _filepath(filepath)
{
if (!LittleFS.begin())
{
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
}
Config::~Config()
{
free(buffer);
}
bool Config::setFilePath(const char* filepath){
this->_filepath = filepath;
return loadBin();
}
bool Config::loadBin()
{
File config_file = LittleFS.open(_filepath, "r");
//if (buffer == nullptr) // Allocate only once
// buffer = (uint8_t *)malloc(CONFIG_SIZE);
if (config_file.available())
{
config_file.read(buffer, CONFIG_SIZE);
buffer[OFFSET_SSID + 0x1F] = 0x00; // ensure ssid and password are terminated with a null character
buffer[OFFSET_PASS + 0x1F] = 0x00;
}
config_file.close();
return true;
}
bool Config::saveBin()
{
File config_file = LittleFS.open(_filepath, "w");
config_file.write(buffer, CONFIG_SIZE);
delay(100);
config_file.close();
return true;
}
bool Config::setSSID(const char *ssid)
{
size_t offset = 0;
bool copy = true;
for (int i = 0; i < 31; i++)
{
if (ssid[i] == 0x00)
copy = false;
buffer[offset + i] = copy ? ssid[i] : 0x00;
}
buffer[offset + 31] = '\n';
return true;
}
bool Config::setPASS(const char *pass)
{
size_t offset = 0x20;
bool copy = true;
for (int i = 0; i < 31; i++)
{
if (pass[i] == 0x00)
copy = false;
buffer[offset + i] = copy ? pass[i] : 0x00;
}
buffer[offset + 31] = '\n';
return true;
}
void Config::print()
{
Serial.print("BufferHEX ");
for (int i = 0; i < CONFIG_SIZE; i++)
{
Serial.print(i, HEX);
Serial.print(">");
Serial.print(buffer[i], HEX);
Serial.print("|");
}
Serial.print("SSID: ");
Serial.println(SSID);
Serial.print("PASS: ");
Serial.println(PASS);
Serial.print("IP");
Serial.print(*((uint8_t *)&ip), DEC);
Serial.print(".");
Serial.print(* (((uint8_t *)&ip + 1)), DEC);
Serial.print(".");
Serial.print(* ((uint8_t *)&ip + 2), DEC);
Serial.print(".");
Serial.println( * ((uint8_t *)&ip + 3), DEC);
Serial.print("GW");
Serial.print( * ((uint8_t *)&gw), DEC);
Serial.print(".");
Serial.print( * ((uint8_t *)&gw + 1), DEC);
Serial.print(".");
Serial.print( * ((uint8_t *)&gw + 2), DEC);
Serial.print(".");
Serial.println( * ((uint8_t *)&gw + 3), DEC);
Serial.print("SUBNET");
Serial.print( * ((uint8_t *)&subnet), DEC);
Serial.print(".");
Serial.print( * ((uint8_t *)&subnet + 1), DEC);
Serial.print(".");
Serial.print( * ((uint8_t *)&subnet + 2), DEC);
Serial.print(".");
Serial.println( * ((uint8_t *)&subnet + 3), DEC);
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "LittleFS.h"
#include "ArduinoJson.h"
#define OFFSET_SSID 0x00
#define OFFSET_PASS 0x20
#define OFFSET_IP 0x40
#define OFFSET_SUBNET 0x44
#define OFFSET_GW 0x48
#define OFFSET_MODE 0x4C
#define OFFSET_FAIL_TIMEOUT 0x4D
#define OFFSET_HOLD_TIME 0x4E
#define CONFIG_SIZE 0x4F
class Config
{
private:
uint8_t *buffer = (uint8_t *)malloc(CONFIG_SIZE);
public:
Config(const char* filepath);
~Config();
const char *SSID = (char *)(buffer + OFFSET_SSID);
const char *PASS = (char *)(buffer + OFFSET_PASS);
uint32_t &ip = *((uint32_t*)(buffer+OFFSET_IP));
uint32_t &subnet = *((uint32_t*)(buffer+OFFSET_SUBNET));
uint32_t &gw = *((uint32_t*)(buffer+OFFSET_GW));
uint8_t &mode = *(buffer + OFFSET_MODE);
uint8_t &fail_timeout = *(buffer+OFFSET_FAIL_TIMEOUT);
uint8_t &hold_time = *(buffer+OFFSET_HOLD_TIME);
const char* _filepath;
bool setFilePath(const char* filepath);
bool loadConfig();
bool loadBin();
bool saveBin();
void print();
bool setSSID(const char *ssid);
bool setPASS(const char *pass);
};
+2 -4
View File
@@ -1,8 +1,6 @@
#include "Keyboard.h" #include "Keyboard.h"
//#define DEBUG
#define PIN_WIRE_SDA D3
#define PIN_WIRE_SCL D4
Keyboard::Keyboard(uint8_t _debounce) Keyboard::Keyboard(uint8_t _debounce)
{ {
this->keybind.insert({ this->keybind.insert({
@@ -23,7 +21,7 @@ Keyboard::Keyboard(uint8_t _debounce)
} }
void Keyboard::begin(TwoWire *databus) void Keyboard::begin(TwoWire *databus)
{ {
pcf8574 = new PCF8574(databus, 0x21, PIN_WIRE_SDA, PIN_WIRE_SCL); pcf8574 = new PCF8574(databus, 0x21);
pcf8574->pinMode(0, OUTPUT); pcf8574->pinMode(0, OUTPUT);
for (int i = 1; i < 8; i++) for (int i = 1; i < 8; i++)
{ {
-54
View File
@@ -1,54 +0,0 @@
#include "Persistence.h"
Persistence::Persistence()
{
if (!LittleFS.begin())
{
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
}
String Persistence::TestRead()
{
File file = LittleFS.open("/users.json", "r");
String result = "";
if (!file)
{
Serial.println("Failed to open file for reading");
return result;
}
Serial.println("File Content:");
while (file.available())
{
result += (char)file.read();
}
file.close();
return result;
}
Persistence::Configuration Persistence::loadConfig()
{
StaticJsonDocument<1024> doc;
File config_file = LittleFS.open("/config.json","r");
deserializeJson(doc, config_file);
Configuration res;
res.SSID=doc["SSID"].as<const char*>();
res.PASS=doc["PASS"].as<const char*>();
return res;
}
DynamicJsonDocument Persistence::readUsers(){
File user_file = LittleFS.open("/users.json","r");
size_t buffersize = user_file.size()+512;
Serial.printf("Reserved %i for reading UserDB File", buffersize);
DynamicJsonDocument doc(user_file.size()+512);
deserializeJson(doc, user_file);
user_file.close();
return doc;
}
bool Persistence::saveUsers(DynamicJsonDocument &doc){
File user_file = LittleFS.open("/users.json","w");
serializeJson(doc, user_file);
user_file.close();
return true;
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "LittleFS.h"
#include "ArduinoJson.h"
class Persistence
{
public:
Persistence();
String TestRead();
typedef struct Configuration{
const char* SSID;
const char* PASS;
int THEME;
} config;
Configuration loadConfig();
DynamicJsonDocument readUsers();
bool saveUsers(DynamicJsonDocument &doc);
};
+26
View File
@@ -0,0 +1,26 @@
#include "Relais.h"
Relais::Relais(uint8_t pin)
{
pinMode(pin, OUTPUT);
digitalWrite(_pin, 1);
_pin = pin;
}
void Relais::cylce()
{
if (!_state)
return;
if (millis() > _call_time)
{
digitalWrite(_pin, 1);
_state = false;
Serial.println("Relay released.");
}
}
void Relais::activate(uint8_t seconds)
{
Serial.println("Relay activated for "+String(seconds)+" Seconds.");
digitalWrite(_pin, 0);
_state = true;
_call_time = millis() + seconds * 1000;
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <Arduino.h>
#include <Wire.h>
class Relais
{
public:
Relais(uint8_t pin);
void cylce();
void activate(uint8_t seconds);
private:
unsigned long _call_time = 0;
bool _state = false;
uint8_t _pin;
};
+4 -3
View File
@@ -1,8 +1,8 @@
#include "Rfid.h" #include "Rfid.h"
#define SS_PIN D8 #define SS_PIN D8
#define RST_PIN D1 #define RST_PIN D0
#define RFID_TIMEOUT 3000 #define RFID_TIMEOUT 3000
Rfid::Rfid(/* args */) : _mfrc522(SS_PIN) Rfid::Rfid(/* args */) : _mfrc522(SS_PIN, RST_PIN)
{ {
} }
@@ -33,11 +33,12 @@ void Rfid::scan()
{ {
_status = 1; _status = 1;
#ifdef DEBUG #ifdef DEBUG
Serial.print(this->_rfid); _mfrc522.PICC_DumpToSerial(&(_mfrc522.uid));
#endif #endif
this->_lastRfid = this->_rfid; this->_lastRfid = this->_rfid;
this->_lastRfidScan = millis(); this->_lastRfidScan = millis();
} }
} }
} }
+6 -3
View File
@@ -1,5 +1,5 @@
#pragma once #pragma once
#include "Persistence.h" #include "Config.h"
#include "LittleFS.h" #include "LittleFS.h"
#include <sstream> #include <sstream>
#include <iterator> #include <iterator>
@@ -46,6 +46,8 @@ namespace userdb
} }
}; };
class UserDb class UserDb
{ {
private: private:
@@ -148,7 +150,8 @@ namespace userdb
{ {
close_file(); close_file();
} }
void close_file(){ void close_file()
{
db_file.close(); db_file.close();
} }
bool has_next() bool has_next()
@@ -164,7 +167,7 @@ namespace userdb
if (matchline == line) if (matchline == line)
{ {
current = read_csv_line(current, db_file, line, match, filter_attr); current = read_csv_line(current, db_file, line, match, filter_attr);
current.match=true; current.match = true;
} }
} }
else else
-118
View File
@@ -1,118 +0,0 @@
#include "Users.h"
Users::Users(Persistence &p): persistence(p){
}
Users::~Users()
{
}
bool Users::ImportFromPersistence(){
Serial.println("Importing Users from Persistence");
DynamicJsonDocument user_persistence = persistence.readUsers();
JsonArray array = user_persistence.as<JsonArray>();
for(JsonObject userdata : array){
User *imported = new User();
imported->uid= userdata["ID"].as<unsigned long>();
if(_userdb.count(*imported))
continue;
imported->first_name = userdata["FIRST_NAME"].as<String>();
imported->last_name = userdata["LAST_NAME"].as<String>();
imported->rfid_uid = userdata["RFID_UID"].as<String>();
imported->user_pin = userdata["USER_PIN"].as<String>();
imported->enabled = userdata["ENABLED"].as<bool>();
this->_userdb.insert(*imported);
}
Serial.println("User import is done!");
return true;
}
bool Users::ExportToPersistence(){
Serial.println("Exporting Users to Persistence");
size_t user_amount = _userdb.size()+2;
Serial.print( " User Amount: " );
Serial.print(user_amount);
size_t capacity = JSON_ARRAY_SIZE(user_amount) + user_amount*JSON_OBJECT_SIZE(6);
Serial.print(" JSON Capacity");
Serial.print(capacity);
Serial.print(" Free Heap: ");
Serial.print(ESP.getFreeHeap());
//DynamicJsonDocument<CAPACITY> doc;
DynamicJsonDocument doc(capacity);
for(Users::User u : _userdb){
JsonObject exported_user = doc.createNestedObject();
exported_user["ID"] = u.uid;
exported_user["FIRST_NAME"] = u.first_name;
exported_user["LAST_NAME"] = u.last_name;
exported_user["RFID_UID"] = u.rfid_uid;
exported_user["USER_PIN"] = u.user_pin;
exported_user["ENABLED"] = u.enabled;
}
persistence.saveUsers(doc);
Serial.println("User export is done!");
return true;
}
bool Users::checkPin(String pin_code, std::vector<Users::User> *logon_users)
{
std::copy_if(this->_userdb.begin(), this->_userdb.end(), inserter(*logon_users, logon_users->end()), [=](user_account user)
{ return pin_code == user.user_pin&&user.enabled==true; });
return (logon_users->size() > 0);
}
bool Users::checkRfid(String rfid_code, std::vector<Users::User> *logon_users)
{
std::copy_if(this->_userdb.begin(), this->_userdb.end(), inserter(*logon_users, logon_users->end()), [=](user_account user)
{ return rfid_code == user.rfid_uid&&user.enabled==true; });
return (logon_users->size() > 0);
}
unsigned long Users::addUser(String first_name, String last_name, String rifd_uid, String user_pin)
{
Serial.println("Adding User:" + first_name);
unsigned long new_id = this->_userdb.rbegin()->uid + 1;
this->_userdb.insert(user_account{.uid = new_id, .first_name = first_name, .last_name = last_name, .rfid_uid = rifd_uid, .user_pin = user_pin});
return new_id;
}
bool Users::delUser(unsigned long id)
{
return this->_userdb.erase(user_account{.uid = id})>0;
}
bool Users::updateUser(unsigned long id, ATTRIBUTES attr, String value)
{
auto modify = this->_userdb.find(user_account{.uid = id});
switch (attr)
{
case FIRST_NAME:
modify->first_name = value;
break;
case LAST_NAME:
modify->last_name = value;
break;
case RFID_UID:
modify->rfid_uid = value;
break;
case USER_PIN:
modify->user_pin = value;
break;
default:
break;
}
return false;
}
size_t Users::countUsers(){
return _userdb.size();
}
String Users::toString(User *user)
{
return "UID: " + String(user->uid) + " Name: " + user->first_name + " " + user->last_name;
}
void Users::PrintAllToSerial(){
Serial.println("#All Users:");
for(Users::User u : _userdb){
Serial.println(toString(&u));
}
}
-46
View File
@@ -1,46 +0,0 @@
#pragma once
#include <Arduino.h>
#include <set>
#include <vector>
#include "Persistence.h"
class Users
{
public:
enum ATTRIBUTES{
FIRST_NAME,
LAST_NAME,
RFID_UID,
USER_PIN
};
typedef struct User
{
//User(unsigned long new_uid): uid(new_uid){}
unsigned long uid;
mutable String first_name;
mutable String last_name;
mutable String rfid_uid;
mutable String user_pin;
mutable bool enabled = true;
bool operator < (const User &o) const { return uid <o.uid; }
bool operator == (const User &o) const { return uid ==o.uid; }
} user_account;
private:
std::set<User> _userdb;
Persistence &persistence;
/* data */
public:
Users(Persistence &persistence);
~Users();
bool checkRfid(String rfid_code, std::vector<Users::User> *logon_users);
bool checkPin(String pin_code, std::vector<Users::User> *logon_users);
unsigned long addUser(String first_name, String last_name, String rifd_uid, String user_pin);
bool delUser(unsigned long id);
bool updateUser(unsigned long id, ATTRIBUTES attr, String value);
String toString(User *user);
bool ImportFromPersistence();
bool ExportToPersistence();
void PrintAllToSerial();
size_t countUsers();
};
+259 -48
View File
@@ -2,29 +2,52 @@
using namespace webconsole; using namespace webconsole;
WebConsole::WebConsole() {} WebConsole::WebConsole()
{
}
WebConsole::~WebConsole() WebConsole::~WebConsole()
{ {
_server->close(); _server->close();
delete (_server); delete (_server);
} }
bool WebConsole::init(userdb::UserDb *userdb) bool WebConsole::init(Config *config, userdb::UserDb *userdb)
{ {
_server = new ESP8266WebServer(80);
this->userdb = userdb; this->userdb = userdb;
this->_config = config;
this->userdb = userdb;
// Wifi Setup
this->_dnsServer = new DNSServer;
WiFi.mode(WIFI_AP);
Serial.print("\t1. Network config... ");
Serial.println(WiFi.softAPConfig(_config->ip, _config->gw, _config->subnet) ? "Ready" : "Failed!");
Serial.print("\t2. DNS config... ");
Serial.println(_dnsServer->start(53, "*", _config->ip) ? "Ready" : "Failed!");
Serial.print("\t3 AP setup SSID:\"" + String(_config->SSID) + "\"... ");
if (strlen(_config->PASS) > 0)
Serial.println(WiFi.softAP(_config->SSID, _config->PASS) ? "Ready" : "Failed!");
else
Serial.println(WiFi.softAP(_config->SSID) ? "Ready" : "Failed!");
WiFi.hostname("Doorlock");
Serial.println("Please connect via http://" + WiFi.softAPIP().toString() + "/");
// Webserver Setup
this->_server = new ESP8266WebServer(80);
const char *headerkeys[] = {"Authentification"};
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);
_server->collectHeaders(headerkeys, headerkeyssize);
_server->begin(); _server->begin();
_server->on("/api/auth", HTTPMethod::HTTP_POST, std::bind(&WebConsole::_auth, this));
_server->on("/api/userdb", HTTPMethod::HTTP_DELETE, std::bind(&WebConsole::_dropUserDb, 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/userdb", HTTPMethod::HTTP_GET, std::bind(&WebConsole::_getUserDb, this));
_server->on("/api/rfid", std::bind(&WebConsole::_catchRFID, this)); _server->on("/api/rfid", HTTPMethod::HTTP_GET, std::bind(&WebConsole::_catchRFID, this));
_server->on("/api/debug/printfile", std::bind(&WebConsole::_print_db_raw, 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_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_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_PUT, std::bind(&WebConsole::_createUser, this));
_server->on(UriBraces("/api/user/{}"), HTTPMethod::HTTP_POST, std::bind(&WebConsole::_updateUser, 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(UriBraces("/api/config"), HTTPMethod::HTTP_POST, std::bind(&WebConsole::_settings, this));
//_server->on("/bypin",std::bind(&WebConsole::_findPin,this)); _server->serveStatic("/", LittleFS, "/s/");
_server->serveStatic("/", LittleFS, "/static/index.html"); _server->onNotFound(std::bind(&WebConsole::_handleUnknown, this));
//_server->onNotFound(std::bind(&WebConsole::_handleStatic, this));
return true; return true;
} }
void WebConsole::attachRfid(Rfid *rfid) void WebConsole::attachRfid(Rfid *rfid)
@@ -33,51 +56,221 @@ void WebConsole::attachRfid(Rfid *rfid)
} }
void WebConsole::serve() void WebConsole::serve()
{ {
if (catch_rfid && rfid != nullptr && rfid->available()) _dnsServer->processNextRequest();
if (catch_rfid)
{ {
rfid_buffer = rfid->getID(); if (millis() - catch_rfid_millis > 2000)
catch_rfid_updated = true; {
catch_rfid = false; catch_rfid = 0;
}
else if (catch_rfid > 1 && rfid != nullptr && rfid->available())
{
rfid_buffer = rfid->getID();
catch_rfid = 1;
}
} }
_server->handleClient(); _server->handleClient();
} }
bool WebConsole::isInterceptingRfid() uint8_t WebConsole::isInterceptingRfid()
{ {
return catch_rfid; if (catch_rfid == 3)
}
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"; catch_rfid = 2;
src.close(); return 3;
src = LittleFS.open(path, "r");
}
if (!src)
{
path = "/static/error404.html";
src.close();
src = LittleFS.open(path, "r");
} }
Serial.println(" resolved to " + path); return catch_rfid;
if (src) }
bool WebConsole::_isAuth()
{
if (_server->method() == HTTPMethod::HTTP_OPTIONS)
{ {
String content_type = "text/html"; _server->send(204);
_server->streamFile(src, content_type); return false;
src.close(); }
if (!_server->hasHeader("Authentification"))
{
_server->send(401, "text/plain", "Error 401: Unauthorized (missing auth token)");
return false;
}
const char *token = _server->header("Authentification").c_str();
bool res = auth.isAuth(token);
if (!res)
_server->send(401, "text/plain", "Error 401: Unauthorized (missing auth token)");
return res;
}
void WebConsole::_auth()
{
_sendCORS();
String action = _server->arg("action");
if (action.equals("check"))
{
const char *token = _server->arg("token").c_str();
bool res = auth.isAuth(token);
_server->send(200, "text/plain", res ? "valid" : "invalid");
}
else if (action.equals("login"))
{
char *token = auth.login(_server->arg("username"), _server->arg("password"));
if (token == nullptr)
_server->send(401, "text/plain", "login_failed");
else
_server->send(200, "text/plain", token);
}
else if (action.equals("logout"))
{
const char *token = _server->arg("token").c_str();
bool res = auth.logout(token);
_server->send(200, "text/plain", res ? "success" : "failed");
}
else if (action.equals("update"))
{
// if (!_isAuth())
// return;
bool res = auth.setAuth(_server->arg("username"), _server->arg("password"));
_server->send(200, "text/plain", res ? "success" : "failed");
}
else
_server->send(404, "text/plain", "unknown action");
}
void WebConsole::_settings()
{
_sendCORS();
if (!_isAuth())
return;
String action = _server->arg("action");
if (action.equals("update"))
{
uint8_t error = 0b00000000;
uint8_t sucess = 0b00000000;
if (_server->hasArg("SSID"))
{
_config->setSSID(_server->arg("SSID").c_str());
int length = strlen(_config->SSID);
if (length == 0 || length > 31)
error |= 0b00000001;
else
sucess |= 0b00000001;
}
if (_server->hasArg("PASS"))
{
_config->setSSID(_server->arg("PASS").c_str());
int length = strlen(_config->SSID);
if (length == 0 || length > 31)
error |= 0b00000010;
else
sucess |= 0b00000010;
}
if (_server->hasArg("ip"))
{
IPAddress temp;
if (temp.fromString(_server->arg("ip")))
{
_config->ip = temp.v4();
sucess |= 0b00000100;
}
else
error |= 0b00000100;
}
if (_server->hasArg("subnet"))
{
IPAddress temp;
if (temp.fromString(_server->arg("subnet")))
{
_config->subnet = temp.v4();
sucess |= 0b00001000;
}
else
error |= 0b00001000;
}
if (_server->hasArg("gw"))
{
IPAddress temp;
if (temp.fromString(_server->arg("gw")))
{
_config->gw = temp.v4();
sucess |= 0b00010000;
}
else
error |= 0b00010000;
}
if (_server->hasArg("mode"))
{
_config->mode = _server->arg("mode").toInt();
if (_config->mode >= 0 && _config->mode <= 1)
sucess |= 0b00100000;
else
error |= 0b00100000;
}
if (_server->hasArg("fail_timeout"))
{
_config->fail_timeout = _server->arg("fail_timeout").toInt();
if (_config->fail_timeout >= 0 && _config->fail_timeout <= 250)
sucess |= 0b01000000;
else
error |= 0b01000000;
}
if (_server->hasArg("hold_time"))
{
_config->hold_time = _server->arg("hold_time").toInt();
if (_config->hold_time > 0 && _config->hold_time <= 250)
sucess |= 0b10000000;
else
error |= 0b10000000;
}
if (error == 0 && sucess > 0)
{ // Save
_config->saveBin();
_server->send(200, "text/json", "{\"status\":\"ok\", \"sucess\":\"" + String(sucess) + "\"}");
}
else if (error > 0)
{ // Rollback
_config->loadBin();
_server->send(200, "text/json", "{\"status\":\"failed\", \"sucess\":\"" + String(sucess) + "\", \"error\":\"" + String(error) + "\"}");
}
else
_server->send(200, "text/json", "{\"status\":\"noting_todo\"}");
}
else if (action.equals("get"))
{
_server->send(200, "text/json", "{\"SSID\":\"" + String(_config->SSID) + "\", \"PASS\":\"" + String(_config->PASS) + "\", \"ip\":\"" + IPAddress(_config->ip).toString() + "\", \"subnet\":\"" + IPAddress(_config->subnet).toString() + "\", \"gw\":\"" + IPAddress(_config->gw).toString() + "\", \"mode\":\"" + String(_config->mode) + "\", \"fail_timeout\":\"" + String(_config->fail_timeout) + "\", \"hold_time\":\"" + String(_config->hold_time) + "\"}");
}
else if (action.equals("apply"))
{
_server->send(200, "text/json", "{\"status\":\"restarting\"}");
ESP.restart();
}
else
_server->send(404, "text/plain", "unknown action");
}
void WebConsole::_sendCORS()
{
_server->sendHeader("Access-Control-Allow-Origin", "*");
_server->sendHeader("Access-Control-Max-Age", "10000");
_server->sendHeader("Access-Control-Allow-Methods", "PUT,POST,GET,OPTIONS,DELETE");
_server->sendHeader("Access-Control-Allow-Headers", "*");
}
void WebConsole::_handleUnknown()
{
_sendCORS();
if (_server->method() == HTTP_OPTIONS)
{
_server->send(204);
} }
else else
{ {
_server->send(500, "text/plain", "Internal error 500"); File src = LittleFS.open("s/index.html", "r");
_server->streamFile(src, "text/html");
src.close();
} }
} }
void WebConsole::_getUserDb() void WebConsole::_getUserDb()
{ {
_sendCORS();
if (!_isAuth())
return;
File src = LittleFS.open("userdb.csv", "r"); File src = LittleFS.open("userdb.csv", "r");
if (src) if (src)
{ {
@@ -87,6 +280,9 @@ void WebConsole::_getUserDb()
} }
void WebConsole::_deleteUser() void WebConsole::_deleteUser()
{ {
_sendCORS();
if (!_isAuth())
return;
if (userdb == nullptr) if (userdb == nullptr)
{ {
_server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}"); _server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
@@ -107,6 +303,9 @@ void WebConsole::_deleteUser()
void WebConsole::_getUser() void WebConsole::_getUser()
{ {
_sendCORS();
if (!_isAuth())
return;
if (userdb == nullptr) if (userdb == nullptr)
{ {
_server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}"); _server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
@@ -125,6 +324,9 @@ void WebConsole::_getUser()
} }
void WebConsole::_updateUser() void WebConsole::_updateUser()
{ {
_sendCORS();
if (!_isAuth())
return;
userdb::User updated; userdb::User updated;
String body = _server->arg("plain"); String body = _server->arg("plain");
const int capacity = 256; const int capacity = 256;
@@ -171,11 +373,14 @@ void WebConsole::_updateUser()
updated.user_pin = doc["user_pin"].as<String>(); updated.user_pin = doc["user_pin"].as<String>();
if (doc.containsKey("enabled")) if (doc.containsKey("enabled"))
updated.enabled = doc["enabled"].as<bool>(); updated.enabled = doc["enabled"].as<bool>();
bool res = userdb->update_user(&updated); userdb->update_user(&updated);
_server->send(200, "text/json", updated.toJSONString().c_str()); _server->send(200, "text/json", updated.toJSONString().c_str());
} }
void WebConsole::_createUser() void WebConsole::_createUser()
{ {
_sendCORS();
if (!_isAuth())
return;
userdb::User created; userdb::User created;
String body = _server->arg("plain"); String body = _server->arg("plain");
const int capacity = 1024; const int capacity = 1024;
@@ -210,6 +415,9 @@ void WebConsole::_createUser()
} }
void WebConsole::_dropUserDb() void WebConsole::_dropUserDb()
{ {
_sendCORS();
if (!_isAuth())
return;
if (userdb->drop()) if (userdb->drop())
_server->send(500, "text/json", "{\"ok\":\"UserDb dropped.\"}"); _server->send(500, "text/json", "{\"ok\":\"UserDb dropped.\"}");
else else
@@ -217,24 +425,27 @@ void WebConsole::_dropUserDb()
} }
void WebConsole::_catchRFID() void WebConsole::_catchRFID()
{ {
_sendCORS();
if (!_isAuth())
return;
if (rfid == nullptr) if (rfid == nullptr)
{ {
_server->send(500, "text/json", "{\"error\":\"RFID not attached.\"}"); _server->send(500, "text/json", "{\"error\":\"RFID not attached.\",\"state\":\"" + String(catch_rfid) + "\"}");
return; return;
} }
if (catch_rfid_updated) if (catch_rfid == 1)
{ {
String response = "{\"rfid_uid\":\"" + rfid_buffer + "\"}"; String response = "{\"rfid_uid\":\"" + rfid_buffer + "\",\"state\":\"" + String(catch_rfid) + "\"}";
_server->send(500, "text/json", response); _server->send(200, "text/json", response);
catch_rfid_updated = false; catch_rfid = 0;
}
else if (catch_rfid)
{
_server->send(500, "text/json", "{\"ok\":\"already activated\"}");
} }
else else
{ {
catch_rfid = true; catch_rfid = 3; // 3 - Start listening
_server->send(500, "text/json", "{\"ok\":\"now activated\"}"); catch_rfid_millis = millis();
_server->send(200, "text/json", "{\"ok\":\"listening\",\"state\":\"" + String(catch_rfid) + "\"}");
} }
} }
void WebConsole::_updateAdmin()
{
}
+23 -7
View File
@@ -1,4 +1,6 @@
#pragma once #pragma once
#include <ESP8266mDNS.h>
#include <DNSServer.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266WebServer.h> #include <ESP8266WebServer.h>
#include <uri/UriBraces.h> #include <uri/UriBraces.h>
@@ -7,9 +9,12 @@
#include "ArduinoJson.h" #include "ArduinoJson.h"
#include "UserDb.h" #include "UserDb.h"
#include "Rfid.h" #include "Rfid.h"
#include "Config.h"
#include "AdminAuth.h"
#include "Interface.h"
namespace webconsole namespace webconsole
{ {
static const char path_prefix[] PROGMEM = "/static"; static const char path_prefix[] PROGMEM = "/s";
static ESP8266WebServer _server(80); static ESP8266WebServer _server(80);
class WebConsole class WebConsole
{ {
@@ -17,13 +22,17 @@ namespace webconsole
WebConsole(); WebConsole();
~WebConsole(); ~WebConsole();
bool init(userdb::UserDb *userdb); bool init(Config *config, userdb::UserDb *userdb);
void attachRfid(Rfid *rfid); void attachRfid(Rfid *rfid);
void serve(); void serve();
bool isInterceptingRfid(); uint8_t isInterceptingRfid();
private: private:
void _handleStatic(); void _sendCORS();
void _settings();
void _auth();
bool _isAuth();
void _handleUnknown();
void _getUserDb(); void _getUserDb();
void _deleteUser(); void _deleteUser();
void _getUser(); void _getUser();
@@ -31,21 +40,28 @@ namespace webconsole
void _createUser(); void _createUser();
void _dropUserDb(); void _dropUserDb();
void _catchRFID(); void _catchRFID();
void _updateAdmin();
void _print_db_raw() void _print_db_raw()
{ {
File f = LittleFS.open("userdb.csv", "r"); File f = LittleFS.open("userdb.csv", "r");
while (f.available()){ while (f.available())
{
f.sendAvailable(Serial); f.sendAvailable(Serial);
} }
_server->send(200,"text/plain","printed to serial"); _server->send(200, "text/plain", "printed to serial");
f.close(); f.close();
} }
Config *_config;
DNSServer *_dnsServer;
ESP8266WebServer *_server; ESP8266WebServer *_server;
userdb::UserDb *userdb = nullptr; userdb::UserDb *userdb = nullptr;
bool catch_rfid = false; unsigned long catch_rfid_millis = 0;
uint8_t catch_rfid = 0;
bool catch_rfid_updated = false; bool catch_rfid_updated = false;
String rfid_buffer; String rfid_buffer;
Rfid *rfid = nullptr; Rfid *rfid = nullptr;
Interface *iface = nullptr;
AdminAuth auth;
}; };
} }
+21 -32
View File
@@ -7,45 +7,30 @@
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include "WebConsole.h" #include "WebConsole.h"
#include "UserDb.h" #include "UserDb.h"
#include "Persistence.h" #include "Config.h"
// File persistence #include "Relais.h"
Persistence persistence;
userdb::UserDb userdatabase("userdb.csv"); // File config
Config config("/settings");
userdb::UserDb userdatabase("/userdb.csv");
webconsole::WebConsole web; webconsole::WebConsole web;
// Rfid // Rfid
Rfid rfid; Rfid rfid;
// i2C Bus // i2C Bus
#define PIN_WIRE_SDA D3 #define PIN_WIRE_SDA D1
#define PIN_WIRE_SCL D4 #define PIN_WIRE_SCL D2
Relais relay(D4);
Keyboard keyboard(200); Keyboard keyboard(200);
Interface iface; Interface iface;
// Wifi control
IPAddress local_IP(192, 168, 4, 22);
IPAddress gateway(192, 168, 4, 9);
IPAddress subnet(255, 255, 255, 0);
// User DB
// Users users(persistence);
void setup() void setup()
{ {
Persistence::Configuration config = persistence.loadConfig(); config.loadBin();
Serial.begin(115200); Serial.begin(115200);
Serial.println("Starting System"); Serial.println("Starting System...");
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(WiFi.softAP(config.SSID, config.PASS) ? "Ready" : "Failed!");
else
Serial.println(WiFi.softAP(config.SSID) ? "Ready" : "Failed!");
delay(150);
#ifdef DEBUG #ifdef DEBUG
userdatabase.print_to_serial(); userdatabase.print_to_serial();
#endif #endif
web.init(&userdatabase); web.init(&config, &userdatabase);
keyboard.begin(&Wire); keyboard.begin(&Wire);
rfid.begin(); rfid.begin();
web.attachRfid(&rfid); web.attachRfid(&rfid);
@@ -54,15 +39,18 @@ void setup()
void loop() void loop()
{ {
relay.cylce();
rfid.scan(); rfid.scan();
web.serve(); web.serve();
keyboard.scanAsync(); keyboard.scanAsync();
userdb::User login_user; userdb::User login_user;
if(web.isInterceptingRfid()&&iface.getState()==0){ if (web.isInterceptingRfid() == 3)
iface.showMessage("WebUI connected", "Awaiting RFID", 3000); iface.showMessage("WebUI connected", "Awaiting RFID", 2000);
} else if (web.isInterceptingRfid() == 1)
else if (iface.pinAvailable()&&iface.getState()!=Interface::GREET) iface.showMessage("WebUI connected", "RFID Found", 1000);
else if (iface.pinAvailable() && iface.getState() != Interface::GREET)
{ {
unsigned long delta = millis(); unsigned long delta = millis();
login_user = userdatabase.user_by_pin(iface.getPin()); login_user = userdatabase.user_by_pin(iface.getPin());
Serial.println("Query duration: " + String(millis() - delta)); Serial.println("Query duration: " + String(millis() - delta));
@@ -72,7 +60,7 @@ void loop()
return; return;
} }
} }
else if (rfid.available()&&iface.getState()!=Interface::GREET) else if (rfid.available() && iface.getState() != Interface::GREET)
{ {
unsigned long delta = millis(); unsigned long delta = millis();
login_user = userdatabase.user_by_rfid(rfid.getID()); login_user = userdatabase.user_by_rfid(rfid.getID());
@@ -88,6 +76,7 @@ void loop()
{ {
iface.greetUser(login_user.first_name + " " + login_user.last_name); iface.greetUser(login_user.first_name + " " + login_user.last_name);
Serial.println("Logon from User " + login_user.toString()); Serial.println("Logon from User " + login_user.toString());
relay.activate(config.hold_time);
} }
iface.render(); iface.render();
} }