Compare commits

...

14 Commits

32 changed files with 671 additions and 375 deletions
+22
View File
@@ -25,3 +25,25 @@
### 19.02.2022
+ Stable and fast csv database implemented.
+ 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
* Red: 3.3V
* Black GND
* Green SDA -> D3
* Grey SCL -> D4
* Green SDA -> D2
* Grey SCL -> D1
## 2.0 LCD
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"
//#define DEBUG
#define PIN_WIRE_SDA D3
#define PIN_WIRE_SCL D4
Keyboard::Keyboard(uint8_t _debounce)
{
this->keybind.insert({
@@ -23,7 +21,7 @@ Keyboard::Keyboard(uint8_t _debounce)
}
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);
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"
#define SS_PIN D8
#define RST_PIN D1
#define RST_PIN D0
#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;
#ifdef DEBUG
Serial.print(this->_rfid);
_mfrc522.PICC_DumpToSerial(&(_mfrc522.uid));
#endif
this->_lastRfid = this->_rfid;
this->_lastRfidScan = millis();
}
}
}
+6 -3
View File
@@ -1,5 +1,5 @@
#pragma once
#include "Persistence.h"
#include "Config.h"
#include "LittleFS.h"
#include <sstream>
#include <iterator>
@@ -46,6 +46,8 @@ namespace userdb
}
};
class UserDb
{
private:
@@ -148,7 +150,8 @@ namespace userdb
{
close_file();
}
void close_file(){
void close_file()
{
db_file.close();
}
bool has_next()
@@ -164,7 +167,7 @@ namespace userdb
if (matchline == line)
{
current = read_csv_line(current, db_file, line, match, filter_attr);
current.match=true;
current.match = true;
}
}
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();
};
+258 -47
View File
@@ -2,29 +2,52 @@
using namespace webconsole;
WebConsole::WebConsole() {}
WebConsole::WebConsole()
{
}
WebConsole::~WebConsole()
{
_server->close();
delete (_server);
}
bool WebConsole::init(userdb::UserDb *userdb)
bool WebConsole::init(Config *config, userdb::UserDb *userdb)
{
_server = new ESP8266WebServer(80);
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->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_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(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->serveStatic("/", LittleFS, "/static/index.html");
//_server->onNotFound(std::bind(&WebConsole::_handleStatic, this));
_server->on(UriBraces("/api/config"), HTTPMethod::HTTP_POST, std::bind(&WebConsole::_settings, this));
_server->serveStatic("/", LittleFS, "/s/");
_server->onNotFound(std::bind(&WebConsole::_handleUnknown, this));
return true;
}
void WebConsole::attachRfid(Rfid *rfid)
@@ -33,51 +56,221 @@ void WebConsole::attachRfid(Rfid *rfid)
}
void WebConsole::serve()
{
if (catch_rfid && rfid != nullptr && rfid->available())
_dnsServer->processNextRequest();
if (catch_rfid)
{
if (millis() - catch_rfid_millis > 2000)
{
catch_rfid = 0;
}
else if (catch_rfid > 1 && rfid != nullptr && rfid->available())
{
rfid_buffer = rfid->getID();
catch_rfid_updated = true;
catch_rfid = false;
catch_rfid = 1;
}
}
_server->handleClient();
}
bool WebConsole::isInterceptingRfid()
uint8_t 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())
if (catch_rfid == 3)
{
path += "index.html";
src.close();
src = LittleFS.open(path, "r");
}
if (!src)
{
path = "/static/error404.html";
src.close();
src = LittleFS.open(path, "r");
catch_rfid = 2;
return 3;
}
Serial.println(" resolved to " + path);
if (src)
return catch_rfid;
}
bool WebConsole::_isAuth()
{
if (_server->method() == HTTPMethod::HTTP_OPTIONS)
{
String content_type = "text/html";
_server->streamFile(src, content_type);
src.close();
_server->send(204);
return false;
}
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
{
_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()
{
_sendCORS();
if (!_isAuth())
return;
File src = LittleFS.open("userdb.csv", "r");
if (src)
{
@@ -87,6 +280,9 @@ void WebConsole::_getUserDb()
}
void WebConsole::_deleteUser()
{
_sendCORS();
if (!_isAuth())
return;
if (userdb == nullptr)
{
_server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
@@ -107,6 +303,9 @@ void WebConsole::_deleteUser()
void WebConsole::_getUser()
{
_sendCORS();
if (!_isAuth())
return;
if (userdb == nullptr)
{
_server->send(500, "text/json", "{\"error\":\"UserDb not initialized\"}");
@@ -125,6 +324,9 @@ void WebConsole::_getUser()
}
void WebConsole::_updateUser()
{
_sendCORS();
if (!_isAuth())
return;
userdb::User updated;
String body = _server->arg("plain");
const int capacity = 256;
@@ -171,11 +373,14 @@ void WebConsole::_updateUser()
updated.user_pin = doc["user_pin"].as<String>();
if (doc.containsKey("enabled"))
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());
}
void WebConsole::_createUser()
{
_sendCORS();
if (!_isAuth())
return;
userdb::User created;
String body = _server->arg("plain");
const int capacity = 1024;
@@ -210,6 +415,9 @@ void WebConsole::_createUser()
}
void WebConsole::_dropUserDb()
{
_sendCORS();
if (!_isAuth())
return;
if (userdb->drop())
_server->send(500, "text/json", "{\"ok\":\"UserDb dropped.\"}");
else
@@ -217,24 +425,27 @@ void WebConsole::_dropUserDb()
}
void WebConsole::_catchRFID()
{
_sendCORS();
if (!_isAuth())
return;
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;
}
if (catch_rfid_updated)
if (catch_rfid == 1)
{
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\"}");
String response = "{\"rfid_uid\":\"" + rfid_buffer + "\",\"state\":\"" + String(catch_rfid) + "\"}";
_server->send(200, "text/json", response);
catch_rfid = 0;
}
else
{
catch_rfid = true;
_server->send(500, "text/json", "{\"ok\":\"now activated\"}");
catch_rfid = 3; // 3 - Start listening
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
#include <ESP8266mDNS.h>
#include <DNSServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <uri/UriBraces.h>
@@ -7,9 +9,12 @@
#include "ArduinoJson.h"
#include "UserDb.h"
#include "Rfid.h"
#include "Config.h"
#include "AdminAuth.h"
#include "Interface.h"
namespace webconsole
{
static const char path_prefix[] PROGMEM = "/static";
static const char path_prefix[] PROGMEM = "/s";
static ESP8266WebServer _server(80);
class WebConsole
{
@@ -17,13 +22,17 @@ namespace webconsole
WebConsole();
~WebConsole();
bool init(userdb::UserDb *userdb);
bool init(Config *config, userdb::UserDb *userdb);
void attachRfid(Rfid *rfid);
void serve();
bool isInterceptingRfid();
uint8_t isInterceptingRfid();
private:
void _handleStatic();
void _sendCORS();
void _settings();
void _auth();
bool _isAuth();
void _handleUnknown();
void _getUserDb();
void _deleteUser();
void _getUser();
@@ -31,21 +40,28 @@ namespace webconsole
void _createUser();
void _dropUserDb();
void _catchRFID();
void _updateAdmin();
void _print_db_raw()
{
File f = LittleFS.open("userdb.csv", "r");
while (f.available()){
while (f.available())
{
f.sendAvailable(Serial);
}
_server->send(200,"text/plain","printed to serial");
_server->send(200, "text/plain", "printed to serial");
f.close();
}
Config *_config;
DNSServer *_dnsServer;
ESP8266WebServer *_server;
userdb::UserDb *userdb = nullptr;
bool catch_rfid = false;
unsigned long catch_rfid_millis = 0;
uint8_t catch_rfid = 0;
bool catch_rfid_updated = false;
String rfid_buffer;
Rfid *rfid = nullptr;
Interface *iface = nullptr;
AdminAuth auth;
};
}
+21 -32
View File
@@ -7,45 +7,30 @@
#include <ESP8266WiFi.h>
#include "WebConsole.h"
#include "UserDb.h"
#include "Persistence.h"
// File persistence
Persistence persistence;
userdb::UserDb userdatabase("userdb.csv");
#include "Config.h"
#include "Relais.h"
// File config
Config config("/settings");
userdb::UserDb userdatabase("/userdb.csv");
webconsole::WebConsole web;
// Rfid
Rfid rfid;
// i2C Bus
#define PIN_WIRE_SDA D3
#define PIN_WIRE_SCL D4
#define PIN_WIRE_SDA D1
#define PIN_WIRE_SCL D2
Relais relay(D4);
Keyboard keyboard(200);
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()
{
Persistence::Configuration config = persistence.loadConfig();
config.loadBin();
Serial.begin(115200);
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);
Serial.println("Starting System...");
#ifdef DEBUG
userdatabase.print_to_serial();
#endif
web.init(&userdatabase);
web.init(&config, &userdatabase);
keyboard.begin(&Wire);
rfid.begin();
web.attachRfid(&rfid);
@@ -54,15 +39,18 @@ void setup()
void loop()
{
relay.cylce();
rfid.scan();
web.serve();
keyboard.scanAsync();
userdb::User login_user;
if(web.isInterceptingRfid()&&iface.getState()==0){
iface.showMessage("WebUI connected", "Awaiting RFID", 3000);
}
else if (iface.pinAvailable()&&iface.getState()!=Interface::GREET)
if (web.isInterceptingRfid() == 3)
iface.showMessage("WebUI connected", "Awaiting RFID", 2000);
else if (web.isInterceptingRfid() == 1)
iface.showMessage("WebUI connected", "RFID Found", 1000);
else if (iface.pinAvailable() && iface.getState() != Interface::GREET)
{
unsigned long delta = millis();
login_user = userdatabase.user_by_pin(iface.getPin());
Serial.println("Query duration: " + String(millis() - delta));
@@ -72,7 +60,7 @@ void loop()
return;
}
}
else if (rfid.available()&&iface.getState()!=Interface::GREET)
else if (rfid.available() && iface.getState() != Interface::GREET)
{
unsigned long delta = millis();
login_user = userdatabase.user_by_rfid(rfid.getID());
@@ -88,6 +76,7 @@ void loop()
{
iface.greetUser(login_user.first_name + " " + login_user.last_name);
Serial.println("Logon from User " + login_user.toString());
relay.activate(config.hold_time);
}
iface.render();
}