Initial commit

This commit is contained in:
Matthew Frost 2023-06-12 19:17:28 +02:00
commit 94c087ece4
21 changed files with 1309 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
include/secrets.h
.DS_Store
upload_params.ini

10
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

13
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"regex": "cpp"
}
}

View file

@ -0,0 +1,188 @@
_type: export
__export_format: 4
__export_date: 2023-06-12T09:12:10.701Z
__export_source: insomnia.desktop.app:v2023.2.2
resources:
- _id: req_fab13abf4f6146a3b0db6955b4968837
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1686560719521
created: 1680522514230
url: http://reader-hostname/users/
name: Get Card ACL's
description: ""
method: GET
body:
mimeType: application/json
text: ""
parameters: []
headers:
- id: pair_cf9752f319fc4ce2bbc5c1d802d7a82b
name: Content-Type
value: application/json
description: ""
authentication:
type: basic
useISO88591: false
disabled: false
username: admin
password: admin
metaSortKey: -1680522514230
isPrivate: false
settingStoreCookies: true
settingSendCookies: true
settingDisableRenderRequestBody: false
settingEncodeUrl: true
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: wrk_3465ebd3f0ef47dcac39369bc6f6475e
parentId: null
modified: 1680522505347
created: 1680522505347
name: my-spec.yaml
description: ""
scope: design
_type: workspace
- _id: req_fbe2da3ea1b942149289048a145c2e1f
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1686561076133
created: 1686523684626
url: http://reader-hostname/users/create
name: Create Card ACL
description: ""
method: POST
body:
mimeType: multipart/form-data
params:
- name: cardId
value: "1234"
id: pair_601e929fe2cf4f35a8fef542dbdff232
- id: pair_8156862f80b84f988f5677eec2332f70
name: desc
value: Person A
description: ""
parameters: []
headers:
- id: pair_cf9752f319fc4ce2bbc5c1d802d7a82b
name: Content-Type
value: multipart/form-data
description: ""
authentication:
type: basic
useISO88591: false
disabled: false
username: admin
password: admin
metaSortKey: -1678709813012.5
isPrivate: false
settingStoreCookies: true
settingSendCookies: true
settingDisableRenderRequestBody: false
settingEncodeUrl: true
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: req_60a5d5a1e2d444b18d05c1892f23ba0c
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1686560730675
created: 1686527136306
url: http://reader-hostname/users/update
name: Update Card ACL
description: ""
method: POST
body:
mimeType: multipart/form-data
params:
- name: cardId
value: "1234"
id: pair_601e929fe2cf4f35a8fef542dbdff232
- id: pair_fb6e32685ee1484da24a96d5a445dab5
name: newCardId
value: "1234"
description: ""
- id: pair_688c548f6db94924be254a3b4593862c
name: desc
value: Test
description: ""
parameters: []
headers:
- id: pair_cf9752f319fc4ce2bbc5c1d802d7a82b
name: Content-Type
value: multipart/form-data
description: ""
authentication:
type: basic
useISO88591: false
disabled: false
username: admin
password: admin
metaSortKey: -1678256637708.125
isPrivate: false
settingStoreCookies: true
settingSendCookies: true
settingDisableRenderRequestBody: false
settingEncodeUrl: true
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: req_df860cce04cf49c98c9abc4e92ec5a72
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1686560728749
created: 1686526808857
url: http://reader-hostname/users/remove
name: Remove Card ACL
description: ""
method: POST
body:
mimeType: multipart/form-data
params:
- name: cardId
value: "1234"
id: pair_601e929fe2cf4f35a8fef542dbdff232
parameters: []
headers:
- id: pair_cf9752f319fc4ce2bbc5c1d802d7a82b
name: Content-Type
value: multipart/form-data
description: ""
authentication:
type: basic
useISO88591: false
disabled: false
username: admin
password: admin
metaSortKey: -1677803462403.75
isPrivate: false
settingStoreCookies: true
settingSendCookies: true
settingDisableRenderRequestBody: false
settingEncodeUrl: true
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: env_72c9ec50419adc3e8768056161ce4d086c486204
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1680522505349
created: 1680522505349
name: Base Environment
data: {}
dataPropertyOrder: null
color: null
isPrivate: false
metaSortKey: 1680522505349
_type: environment
- _id: jar_72c9ec50419adc3e8768056161ce4d086c486204
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1680522505350
created: 1680522505350
name: Default Jar
cookies: []
_type: cookie_jar
- _id: spc_deaf3f00ef8847429199de99aca75d06
parentId: wrk_3465ebd3f0ef47dcac39369bc6f6475e
modified: 1680522505347
created: 1680522505347
fileName: my-spec.yaml
contents: ""
contentType: yaml
_type: api_spec

23
README Normal file
View file

@ -0,0 +1,23 @@
This project is a card reader system for Techinc that uses a WIEGAND based reader and allows platform.io based user flags to allow change of certain features.
# Currently supported flags:
-DBOARD1 : Used to set Pins for BOARD1 config
-DBOARD2 : Used to set Pins for BOARD2 config
-DRELAY1 : Enable the First Relay
-DRELAY2 : Not implemented yet
-DWIFI : Enable WIFI and use secrets.h to manage.
-DWEB_SERVER : Enable Web Server
-DSERIAL_DEBUG : Enable Serial Console output and debug.
-DWEB_SERIAL_DEBUG : Enable Web Serial console to monitor for issues (does not support auth, needs -DWEB_SERVER)
-DNET_FAIL_SAFE : If WIFI disconnects release door / relay.
-DWEB_OTA_UPDATE : Present Web Interface on /update for managing firmware over the air (needs -DWEB_SERVER)
-DLOCAL_ACL : Enable local ACL list
-DLOCAL_ACL_API : Enable API for managing ACL's (needs -DWEB_SERVER and -DLOCAL_ACL)
-DLATCH_DOOR : Release RELAY1 for a a certain amount of time in this case its 1 second
-DTOGGLE_DOOR : Instead of Relasing relay for 1 second keep it in the opposite state to what it was at the time the card was scanned.
# Important Notes:
* The webserver uses Spiffs to manage the HTML files for the web server you need to run ("Upload Filesystem Image" step via serial or use the OTA upgrade and run "Upload Filesystem Image OTA" step and uplaod it.)

30
data/index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML><html>
<head>
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>ESP Web Server</h2>
%BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if(element.checked){ xhr.open("GET", "/gpio?output="+element.id+"&state=1", true); }
else { xhr.open("GET", "/gpio?output="+element.id+"&state=0", true); }
xhr.send();
}
</script>
</body>
</html>

44
include/ACL.h Normal file
View file

@ -0,0 +1,44 @@
#ifndef ACL_H
#define ACL_H
#include <Arduino.h>
#include <initializer_list>
#include <EEPROM.h>
#include <Preferences.h>
#ifdef WEB_SERVER
#ifdef WEB_SERIAL_DEBUG
#include <WebSerial.h>
#endif
#endif
struct User {
String cardId;
String desc;
};
class ACL {
public:
ACL();
ACL(std::initializer_list<User> userList);
~ACL();
void addUser(const String& cardId, const String& desc);
void updateUser(const String& cardId, const String& newCardId, const String& newdesc);
bool removeUser(const String& cardId);
bool validateAccess(const String& cardId);
int getACLSize() const;
// Additional EEPROM functions
void saveToEEPROM();
void loadFromEEPROM();
const User* getACL() const { return acl; } // Getter for acl
private:
User* acl;
int aclSize;
};
#endif

39
include/README Normal file
View file

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

11
include/buzzer_ctl.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef BUZZER_CTL_H
#define BUZZER_CTL_H
#include "hardware.h"
void short_beep();
void granted_beep();
void denied_beep();
void clear_states();
#endif

37
include/hardware.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef HARDWARE_H
#define HARDWARE_H
#include <Arduino.h>
#ifdef WEB_SERVER
#ifdef WEB_SERIAL_DEBUG
#include <WebSerial.h>
#endif
#endif
#ifdef BOARD1
#define TAMPER_PIN 26
#define LEDCTL_PIN 32
#define DATA0_PIN 34
#define DATA1_PIN 36
#define RELAY1_PIN 32
#define RELAY2_PIN 33
#define RELAY_DELAY 3000
#endif
#ifdef BOARD2
#define TAMPER_PIN 0
#define ALARM_PIN 33
#define WIEGAND_PIN 32
#define LEDCTL_PIN 25
#define DATA0_PIN 26
#define DATA1_PIN 27
#define RELAY1_PIN 25
#define RELAY2_PIN 19
#define RELAY_DELAY 3000
#endif
void controlRelay();
void unlockDoor(bool silent);
void lockDoor();
void toggleDoor();
#endif

26
include/main.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef MAIN_H
#define MAIN_H
#include "hardware.h"
#include <Arduino.h>
#include "secrets.h"
#ifdef WIFI
#include "WiFi.h"
#endif
#ifdef WEB_SERVER
#include "web_server.h"
#ifdef WEB_SERIAL_DEBUG
#include <WebSerial.h>
#endif
#ifdef WEB_OTA_UPDATE
#include <AsyncElegantOTA.h>
#endif
#endif
#ifdef BUZZER
#include "buzzer_ctl.h"
#endif
#endif

25
include/secrets.h.dist Normal file
View file

@ -0,0 +1,25 @@
#ifndef SECRETS_H
#define SECRETS_H
#ifdef WIFI
const char* ssid = "WIFI";
const char* password = "123";
#endif
#ifdef WEB_SERVER
const char* http_username = "admin";
const char* http_password = "admin";
#endif
#ifdef LOCAL_ACL
#include "ACL.h"
ACL acl = {
{"1234", "Card 1"},
{"4567", "Card 2"},
{"7891", "Card 3"}
};
#endif
#endif

19
include/web_server.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include "SPIFFS.h"
#include <ArduinoJson.h>
#include <AsyncJson.h>
// Set web server port number to 80
AsyncWebServer server(80);
#ifdef LOCAL_ACL
#include "ACL.h"
ACL acl = {
};
#endif
#endif

46
lib/README Normal file
View file

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

34
platformio.ini Normal file
View file

@ -0,0 +1,34 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32-evb]
platform = espressif32
board = esp32-evb
framework = arduino
build_flags = -DBOARD1 -DRELAY1 -DWIFI -DWEB_SERVER -DNET_FAIL_SAFE -DWEB_OTA_UPDATE -DLOCAL_ACL -DLOCAL_ACL_API -DLATCH_DOOR
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^3.0.0
ayushsharma82/WebSerial@^1.4.0
ayushsharma82/AsyncElegantOTA@^2.2.7
bblanchon/ArduinoJson@^6.21.2
[env:mcu-esp32s]
platform = espressif32
framework = arduino
board = nodemcu-32s
build_flags = -DBOARD2 -DBUZZER -DRELAY1 -DWIFI -DWEB_SERVER -DNET_FAIL_SAFE -DWEB_OTA_UPDATE -DLOCAL_ACL -DLOCAL_ACL_API -DLATCH_DOOR
lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^3.0.0
ayushsharma82/WebSerial@^1.4.0
ayushsharma82/AsyncElegantOTA@^2.2.7
bblanchon/ArduinoJson@^6.21.2
extra_scripts = platformio_upload.py
extra_configs = upload_params.ini

53
platformio_upload.py Normal file
View file

@ -0,0 +1,53 @@
# Allows PlatformIO to upload directly to AsyncElegantOTA
#
# To use:
# - copy this script into the same folder as your platformio.ini
# - set the following for your project in platformio.ini:
#
# extra_scripts = platformio_upload.py
# upload_protocol = custom
# upload_url = <your upload URL>
#
# An example of an upload URL:
# upload_URL = http://192.168.1.123/update
import requests
import hashlib
Import('env')
try:
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from tqdm import tqdm
except ImportError:
env.Execute("$PYTHONEXE -m pip install requests_toolbelt")
env.Execute("$PYTHONEXE -m pip install tqdm")
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from tqdm import tqdm
def on_upload(source, target, env):
firmware_path = str(source[0])
upload_url = env.GetProjectOption('upload_url')
with open(firmware_path, 'rb') as firmware:
md5 = hashlib.md5(firmware.read()).hexdigest()
firmware.seek(0)
encoder = MultipartEncoder(fields={
'MD5': md5,
'firmware': ('firmware', firmware, 'application/octet-stream')}
)
bar = tqdm(desc='Upload Progress',
total=encoder.len,
dynamic_ncols=True,
unit='B',
unit_scale=True,
unit_divisor=1024
)
monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update(monitor.bytes_read - bar.n))
response = requests.post(upload_url, data=monitor, headers={'Content-Type': monitor.content_type})
bar.close()
print(response,response.text)
env.Replace(UPLOADCMD=on_upload)

133
src/ACL.cpp Normal file
View file

@ -0,0 +1,133 @@
#include "ACL.h"
ACL::ACL() {
acl = nullptr;
aclSize = 0;
}
ACL::ACL(std::initializer_list<User> userList) {
aclSize = userList.size();
acl = new User[aclSize];
int i = 0;
for (const User& user : userList) {
acl[i] = user;
i++;
}
}
ACL::~ACL() {
if (acl != nullptr) {
delete[] acl;
}
}
void ACL::addUser(const String& cardId, const String& desc) {
User newUser;
newUser.cardId = cardId;
newUser.desc = desc;
User* newACL = new User[aclSize + 1];
for (int i = 0; i < aclSize; i++) {
newACL[i] = acl[i];
}
newACL[aclSize] = newUser;
aclSize++;
acl = newACL;
}
void ACL::updateUser(const String& cardId, const String& newCardId, const String& newdesc) {
for (int i = 0; i < aclSize; i++) {
if (acl[i].cardId == cardId) {
acl[i].cardId = newCardId;
acl[i].desc = newdesc;
return; // Exit the loop and method if user is found and updated
}
}
}
bool ACL::removeUser(const String& cardId) {
int userIndex = -1;
for (int i = 0; i < aclSize; i++) {
if (acl[i].cardId == cardId) {
userIndex = i;
break;
}
}
if (userIndex == -1) {
return false; // User not found
}
User* newACL = new User[aclSize - 1];
int j = 0;
for (int i = 0; i < aclSize; i++) {
if (i != userIndex) {
newACL[j] = acl[i];
j++;
}
}
aclSize--;
acl = newACL;
return true; // User removed successfully
}
bool ACL::validateAccess(const String& cardId) {
for (int i = 0; i < aclSize; i++) {
#ifdef SERIAL_DEBUG
Serial.println("Checking ACL match for Card: " + String(cardId) + " Against Card: " + String(acl[i].cardId) + " desc: " + String(acl[i].desc));
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.println("Checking ACL match for Card: " + String(cardId) + " Against Card: " + String(acl[i].cardId) + " desc: " + String(acl[i].desc));
#endif
if (acl[i].cardId.equals(cardId)) {
#ifdef SERIAL_DEBUG
Serial.println("Card matched and authenticated.");
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.println("Card matched and authenticated.");
#endif
return true;
}
}
#ifdef SERIAL_DEBUG
Serial.println("Card did not match.");
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.println("Card did not match.");
#endif
return false;
}
Preferences preferences;
int ACL::getACLSize() const {
return aclSize;
}
void ACL::loadFromEEPROM() {
preferences.begin("acl", true);
aclSize = preferences.getUInt("aclSize", 0);
acl = new User[aclSize];
for (int i = 0; i < aclSize; i++) {
String key = "user_" + String(i);
acl[i].cardId = preferences.getString((key + "_cardId").c_str(), "");
acl[i].desc = preferences.getString((key + "_desc").c_str(), "");
}
preferences.end();
}
void ACL::saveToEEPROM() {
preferences.begin("acl", false);
preferences.putUInt("aclSize", aclSize);
for (int i = 0; i < aclSize; i++) {
String key = "user_" + String(i);
preferences.putString((key + "_cardId").c_str(), acl[i].cardId.c_str());
preferences.putString((key + "_desc").c_str(), acl[i].desc.c_str());
}
preferences.end();
}

40
src/buzzer_ctl.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "buzzer_ctl.h"
void short_beep()
{
#ifdef ALARM_PIN
digitalWrite(ALARM_PIN, LOW);
delay(50);
digitalWrite(ALARM_PIN, HIGH);
#endif
}
void granted_beep()
{
#ifdef ALARM_PIN
digitalWrite(ALARM_PIN, LOW);
delay(50);
digitalWrite(ALARM_PIN, HIGH);
delay(50);
digitalWrite(ALARM_PIN, LOW);
delay(50);
digitalWrite(ALARM_PIN, HIGH);
delay(50);
digitalWrite(ALARM_PIN, LOW);
delay(50);
digitalWrite(ALARM_PIN, HIGH);
#endif
}
void denied_beep(void)
{
#ifdef ALARM_PIN
for (int i=0; i<100; i++)
{
delay(4);
digitalWrite(ALARM_PIN, LOW);
delay(4);
digitalWrite(ALARM_PIN, HIGH);
}
#endif
}

50
src/hardware.cpp Normal file
View file

@ -0,0 +1,50 @@
#include "hardware.h"
#include "buzzer_ctl.h"
void controlRelay(int relayPin, bool on) {
digitalWrite(relayPin, on ? HIGH : LOW); // Turn the relay on or off
}
void unlockDoor(bool silent) {
#ifdef BUZZER
if (!silent) {
granted_beep();
}
#endif
#ifdef RELAY1
controlRelay(RELAY1_PIN,false);
#endif
#ifdef SERIAL_DEBUG
Serial.println("door unlocked");
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.println("door unlocked");
#endif
}
void lockDoor() {
#ifdef RELAY1
controlRelay(RELAY1_PIN,true);
#endif
#ifdef SERIAL_DEBUG
Serial.println("door locked");
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.println("door locked");
#endif
}
void toggleDoor() {
#ifdef RELAY1
int relayState = digitalRead(RELAY1_PIN);
// Toggle the relay based on the current state
if (relayState == LOW) {
lockDoor();
} else {
unlockDoor(false);
}
#endif
}

477
src/main.cpp Normal file
View file

@ -0,0 +1,477 @@
#include "main.h"
volatile uint64_t cardData = 0;
volatile bool newDataAvailable = false;
unsigned long lastDataTime = 0;
const unsigned long displayDelay = 1000; // Delay in milliseconds after which the data is displayed
const unsigned long wifiRebootTimeout = 20000; // Delay before reboot after disconnect
unsigned int bitCount = 0; // Variable to keep track of the bit count
unsigned int maxBitCount = 34; // Variable to make sure reader does not exceed maxiumum bits
#ifdef WIFI
void connectToWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
#ifdef SERIAL_DEBUG
Serial.print("Connecting to WiFi ..");
#endif
unsigned long startTime = millis(); // Record the start time of connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
#ifdef SERIAL_DEBUG
Serial.print(".");
#endif
// Check if it's time to reboot
if (millis() - startTime >= wifiRebootTimeout) { // Reboot after 20 seconds
#ifdef SERIAL_DEBUG
Serial.println("\nFailed to connect. Rebooting...");
#endif
ESP.restart(); // Reboot the Arduino board
}
}
#ifdef SERIAL_DEBUG
Serial.println("WiFi connected");
#endif
}
void GetWifiInfo(){
if(WiFi.status() == WL_CONNECTED) {
#ifdef SERIAL_DEBUG
Serial.print("[*] Network information for ");
Serial.println(ssid);
Serial.println("[+] BSSID : " + WiFi.BSSIDstr());
Serial.print("[+] Gateway IP : ");
Serial.println(WiFi.gatewayIP());
Serial.print("[+] Subnet Mask : ");
Serial.println(WiFi.subnetMask());
Serial.println((String)"[+] RSSI : " + WiFi.RSSI() + " dB");
Serial.print("[+] ESP32 IP : ");
Serial.println(WiFi.localIP());
#endif
}
}
void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
#ifdef NET_FAIL_SAFE
unlockDoor(true);
#endif
#ifdef SERIAL_DEBUG
Serial.println("Disconnected from WiFi access point");
Serial.print("WiFi lost connection. Reason: ");
Serial.println(info.wifi_sta_disconnected.reason);
Serial.println("Trying to Reconnect");
#endif
connectToWiFi();
}
void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){
#ifdef SERIAL_DEBUG
Serial.println("Connected to AP successfully!");
#endif
#ifdef NET_FAIL_SAFE
lockDoor();
#endif
}
void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info){
#ifdef SERIAL_DEBUG
#ifdef WIFI
GetWifiInfo();
#endif
#endif
}
#endif
#ifdef WEB_SERVER
String outputState(int output){
if(digitalRead(output)){
return "checked";
}
else {
return "";
}
}
// Replaces placeholder with button section in your web page
String processor(const String& var){
//Serial.println(var);
if(var == "BUTTONPLACEHOLDER"){
String buttons = "";
#ifdef RELAY1
buttons += "<h4>Output - Relay 1 </h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\""+String(RELAY1_PIN)+"\" " + outputState(RELAY1_PIN) + "><span class=\"slider\"></span></label>";
#endif
#ifdef BUZZER
buttons += "<h4>Output - Buzzer </h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\""+String(ALARM_PIN)+"\" " + outputState(ALARM_PIN) + "><span class=\"slider\"></span></label>";
#endif
return buttons;
}
return String();
}
#ifdef LOCAL_ACL
#ifdef LOCAL_ACL_API
// Handler for the '/users' endpoint to list all users
void handleListUsers(AsyncWebServerRequest* request) {
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
// Create a JSON array to store the users
StaticJsonDocument<512> jsonDoc;
JsonArray usersArray = jsonDoc.to<JsonArray>();
// Retrieve the ACL data using the getter function
const User* aclData = acl.getACL();
// Iterate over each user in the ACL and add it to the JSON array
for (int i = 0; i < acl.getACLSize(); i++) {
JsonObject user = usersArray.createNestedObject();
user["cardId"] = aclData[i].cardId;
user["desc"] = aclData[i].desc;
}
// Convert the JSON array to a string
String response;
serializeJson(usersArray, response);
// Set the response content type to JSON
request->send(200, "application/json", response);
}
void handleCreateUser(AsyncWebServerRequest* request) {
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
if(request->hasParam("cardId", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No cardId'}");
request->send(response);
}
if(request->hasParam("desc", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No desc'}");
request->send(response);
}
String cardId = String(request->arg("cardId"));
String desc = String(request->arg("desc"));
if (acl.validateAccess(String(cardId))) {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'Duplicate ACL'}");
request->send(response);
} else {
acl.addUser(cardId, desc);
acl.saveToEEPROM();
request->send(201); // Create
}
}
// Handler for the '/users/update' endpoint to remove a user
void handleUpdateUser(AsyncWebServerRequest* request) {
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
if(request->hasParam("cardId", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No cardId'}");
request->send(response);
}
if(request->hasParam("newCardId", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No newCardId'}");
request->send(response);
}
if(request->hasParam("desc", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No desc'}");
request->send(response);
}
String cardId = String(request->arg("cardId"));
String newCardId = String(request->arg("newCardId"));
String desc = String(request->arg("desc"));
acl.updateUser(cardId, newCardId, desc);
acl.saveToEEPROM();
request->send(201); // Created
}
// Handler for the '/users/remove' endpoint to remove a user
void handleRemoveUser(AsyncWebServerRequest* request) {
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
if(request->hasParam("cardId", true)) {} //This is important, otherwise the sketch will crash if there is no body
else {
AsyncWebServerResponse *response = request->beginResponse(400, "application/json", "{'msg':'No cardId'}");
request->send(response);
}
String cardId = String(request->arg("cardId"));
acl.removeUser(cardId);
acl.saveToEEPROM();
request->send(201); // Created
}
#endif
#endif
#endif
void handleInterrupt(int bitValue) {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = micros();
if (interruptTime - lastInterruptTime > 200) {
cardData <<= 1;
cardData |= bitValue;
newDataAvailable = true;
bitCount++; // Increment the bit count
}
lastInterruptTime = interruptTime;
lastDataTime = millis(); // Update the time of last received data
}
void handleData0Interrupt() {
handleInterrupt(0);
}
void handleData1Interrupt() {
handleInterrupt(1);
}
void setup() {
#ifdef SERIAL_DEBUG
Serial.begin(9600);
#endif
#ifdef WEB_SERVER
WiFi.disconnect(true);
#endif
#ifdef LOCAL_ACL
acl.loadFromEEPROM();
#endif
// Initialize SPIFFS
#ifdef WEB_SERVER
if(!SPIFFS.begin(true)){
#ifdef SERIAL_DEBUG
Serial.println("An Error has occurred while mounting SPIFFS");
#endif
return;
}
#ifdef WEB_OTA_UPDATE
AsyncElegantOTA.begin(&server, http_username, http_password);
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.begin(&server);
#endif
#endif
pinMode(DATA0_PIN, INPUT_PULLUP);
pinMode(DATA1_PIN, INPUT_PULLUP);
#ifdef RELAY1
pinMode(RELAY1_PIN, OUTPUT);
#endif
attachInterrupt(digitalPinToInterrupt(DATA0_PIN), handleData0Interrupt, FALLING);
attachInterrupt(digitalPinToInterrupt(DATA1_PIN), handleData1Interrupt, FALLING);
#ifdef WIFI
WiFi.onEvent(WiFiStationConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.onEvent(WiFiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
connectToWiFi();
#endif
#ifdef RELAY1
#ifdef SERIAL_DEBUG
Serial.println("Enabling Relay: Locking Door");
#endif
lockDoor();
#endif
#ifdef RELAY2
pinMode(RELAY2_PIN, OUTPUT);
#endif
#ifdef BUZZER
pinMode(ALARM_PIN, OUTPUT);
// Do not set to low or it will constantly beep.
digitalWrite(ALARM_PIN, HIGH);
#endif
#ifdef WEB_SERVER
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send(SPIFFS, "/index.html", String(), false, processor);
});
server.onNotFound([](AsyncWebServerRequest *request){
request->send(404, "text/plain", "The content you are looking for was not found.");
});
// Send a GET request to <ESP_IP>/gpio?output=<inputMessage1>&state=<inputMessage2>
server.on("/gpio", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage1;
String inputMessage2;
if(!request->authenticate(http_username, http_password))
return request->requestAuthentication();
// GET input1 value on <ESP_IP>/gpio?output=<inputMessage1>&state=<inputMessage2>
if (request->hasParam("output") && request->hasParam("state")) {
inputMessage1 = request->getParam("output")->value();
inputMessage2 = request->getParam("state")->value();
digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
}
else {
inputMessage1 = "No message sent";
inputMessage2 = "No message sent";
}
#ifdef SERIAL_DEBUG
Serial.print("GPIO: ");
Serial.print(inputMessage1);
Serial.print(" - Set to: ");
Serial.println(inputMessage2);
request->send(200, "text/plain", "OK");
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.print("GPIO: ");
WebSerial.print(inputMessage1);
WebSerial.print(" - Set to: ");
WebSerial.println(inputMessage2);
#endif
});
#ifdef LOCAL_ACL
#ifdef LOCAL_ACL_API
server.on("/users", HTTP_GET, handleListUsers);
server.on("/users/create", HTTP_POST, handleCreateUser);
server.on("/users/update", HTTP_POST, handleUpdateUser);
server.on("/users/remove", HTTP_POST, handleRemoveUser);
#endif
#endif
server.begin();
#endif
}
void loop() {
if (newDataAvailable) {
newDataAvailable = false;
lastDataTime = millis(); // Reset the time of last received data
}
if (millis() - lastDataTime >= displayDelay && cardData != 0 && bitCount == maxBitCount) {
uint64_t facilityID = (cardData >> 17) & 0xFFFF;
uint64_t cardID = (cardData >> 1) & 0xFFFF;
#ifdef SERIAL_DEBUG
Serial.print("Facility ID (Decimal): ");
Serial.println(facilityID);
Serial.print("Facility ID (Binary): ");
Serial.println(facilityID, BIN);
Serial.print("Card ID (Decimal): ");
Serial.println(cardID);
Serial.print("Card ID (Binary): ");
Serial.println(cardID, BIN);
Serial.print("Card Data (Binary): ");
Serial.println(cardData, BIN);
Serial.print("Total Bits Received: ");
Serial.println(bitCount);
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.print("Facility ID (Decimal): ");
WebSerial.println(facilityID);
WebSerial.print("Facility ID (Binary): ");
WebSerial.println(facilityID, BIN);
WebSerial.print("Card ID (Decimal): ");
WebSerial.println(cardID);
WebSerial.print("Card ID (Binary): ");
WebSerial.println(cardID, BIN);
WebSerial.print("Card Data (Binary): ");
WebSerial.println(cardData, BIN);
WebSerial.print("Total Bits Received: ");
WebSerial.println(bitCount);
#endif
String fullCardID = String(facilityID)+":"+String(cardID);
#ifdef SERIAL_DEBUG
Serial.print("Full Card (Tinance2): ");
Serial.println(fullCardID);
#endif
#ifdef WEB_SERIAL_DEBUG
WebSerial.print("Full Card (Tinance2): ");
WebSerial.println(fullCardID);
#endif
cardData = 0; // Reset the card data
lastDataTime = millis(); // Reset the time of last received data
bitCount = 0; // Reset the bit count
#ifdef LOCAL_ACL
if (acl.validateAccess(String(cardID))) {
#ifdef SERIAL_DEBUG
Serial.println("LOCAL_AUTH: Access granted!");
#endif
// Perform actions for authorized access
#ifdef LATCH_DOOR
unlockDoor(false);
delay(RELAY_DELAY);
lockDoor();
#endif
#ifdef TOGGLE_DOOR
toggleDoor();
delay(RELAY_DELAY);
#endif
} else {
#ifdef SERIAL_DEBUG
Serial.println("LOCAL_AUTH: Access denied!");
#endif
// Perform actions for denied access
#ifdef BUZZER
denied_beep();
#endif
}
#endif
}
}

3
upload_params.ini.dist Normal file
View file

@ -0,0 +1,3 @@
[env]
upload_protocol = custom
upload_url = http://admin:admin@remotehost/update