Update 34 files
- /nfc/README.md - /nfc/clean_old_logs.sh - /nfc/clockin.py - /nfc/emptycards.py - /nfc/sync_clocks.sh - /piservices/Dockerfile - /piservices/Portainer-lamp-stack.env - /piservices/Portainer-lamp-stack.yaml - /piservices/README.md - /piservices/avahi-alias.service - /piservices/clockpy.service - /piservices/emptycards.service - /piservices/nfc-sync.service - /piservices/test.sh - /www/404 - /www/get-table-data.php - /www/habiwan copy.png - /www/habiwan.png - /www/index.php - /www/log_swipe.php - /www/manage_data.php - /www/manifest.json - /www/names.csv - /www/network_audit.csv - /www/network-tracker.html - /www/nfc_debug.log - /www/scanner.html - /www/staff_names.php - /www/times.csv - /www/check_update.php - /www/dev.php - /www/favicon.png - /www/JsBarcode.all.min.js - /www/README.md
This commit is contained in:
1 parent
10824ab851
commit
a201d5d7ac
34 files changed
+9453
No files matched your search
@@ -0,0 +1,45 @@
|
||||
Use this folder as an example of what should be in your working "NFC" python folder on your pi.
|
||||
This was the folder that was generated after all the compliling of the NFC manufacturer software.
|
||||
(broadly explained in the main README.md...)
|
||||
This folder, in my example also has a few shell scripts that I run via services...
|
||||
in my example I mounted a "NAS" and a "www" folder on the pi's /etc/fstab so the scripts therein can copy
|
||||
names.csv and times.csv on those locations with simple cp commands...
|
||||
You could say that IF you want to implement your own version and want to do it my way, this a PREREQUISITE then:
|
||||
|
||||
mount on the pi's /etc/fstab 2 targets:
|
||||
- //192.168.X.X/NASFOLDER /media/NAS cifs credentials=/root/.NASCREDS,uid=1000,gid=1000,noauto,x-systemd.automount,x-systemd.mount-timeout=30 0 0
|
||||
- YOURUVMUSER@192.168.X.X:/home/YOURUVMUSER/docker/docker-compose-lamp/www /media/uvm fuse.sshfs noauto,x-systemd.automount,_netdev,allow_other,IdentityFile=/root/.ssh/id_rsa,StrictHostKeyChecking=accept-new,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 0 0
|
||||
|
||||
For the SMB CIFS I added the NASCREDS file with username and password in the RPi /root/ folder (root runs these scripts in services! that's why... room for improvement though)
|
||||
|
||||
For the SSHFS UVM www folder mount on the pi, first apt install sshfs then you got to exchange keys and have them in ../html/www/.ssh (I know is not optimal, I am working on hardening see TODO.md)
|
||||
|
||||
I also wanted to keep my samba mount for local dev on the php code an the UMV machine but not as guest so I had to run smbpasswd command on the UVM (UbuntuVM).
|
||||
|
||||
If you also want to keep the /etc/samba/smb.conf on the UVM you still want to keep samba with password secured:
|
||||
|
||||
[www]
|
||||
|
||||
path = /home/YOURUVMUSER/docker/docker-compose-lamp/www
|
||||
browseable = yes
|
||||
writable = yes
|
||||
read only = no
|
||||
public = no
|
||||
guest ok = no
|
||||
valid users = YOURUVMUSER
|
||||
force user = YOURUVMUSER
|
||||
create mask = 0664
|
||||
directory mask = 0775
|
||||
|
||||
- mount it manually once on the pi, it will ask for password then it should be using the ssh keys... this should definitely be improved for production.
|
||||
|
||||
The scripts on the pi run as a service by root, so they can copy names.csv and times.csv to the "NAS" and "UVM"
|
||||
|
||||
In my example I used a "NAS" wannabe... horrible consumer grade WD MyCloud for backups and where the Excel got it's data from
|
||||
and "UVM" which was a decomissioned now "upclycled" consumer-grade PC that I installed pve on...
|
||||
|
||||
If you want to use this like me, the rationale was to have the pi as lean as possible only processing the nfc card "scans" and copying the data over to
|
||||
the "webapp - UbuntuVM - PVE machine" and to the "NAS" for backups / disaster recovery... The processing goes to Excel clients or php / LAMP on the "PC"...
|
||||
|
||||
UPDATE:
|
||||
I modified the main python so that it uses mysql so most of the above is not needed anymore byt feel free to keep the csvs as raw hardcopies... I modified the php pages to use mysql too and the Docker compose to a Portainer stack that could be used on a Pi too... so the csv file syncin no more needed... anymore
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# added sudo crontab to the pi to trim old data at 2 am:
|
||||
# 0 2 * * * /bin/bash /home/YOURPIUSER/nfc/clean_old_logs.sh >/dev/null 2>&1
|
||||
|
||||
TIMES_FILE="/home/YOURPIUSER/nfc/times.csv"
|
||||
|
||||
# 1. Get the date 6 months ago (for testing)
|
||||
LOG_CUTOFF=$(date -d "6 months ago" +%Y-%m-%d)
|
||||
|
||||
# 2. Filter using universally compatible literal matching
|
||||
awk -v cutoff="$LOG_CUTOFF" '
|
||||
{
|
||||
if (match($0, /[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/)) {
|
||||
date_part = substr($0, RSTART, RLENGTH)
|
||||
if (date_part < cutoff) {
|
||||
next
|
||||
}
|
||||
}
|
||||
print $0
|
||||
}' "$TIMES_FILE" > "${TIMES_FILE}.tmp"
|
||||
|
||||
# 3. Safely replace the file
|
||||
mv "${TIMES_FILE}.tmp" "$TIMES_FILE"
|
||||
|
||||
# 4. Force the sync script to grab the new file inode
|
||||
systemctl restart nfc-sync.service
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
This example shows connecting to the PN532 with I2C (requires clock
|
||||
stretching support), SPI, or UART. SPI is best, it uses the most pins but
|
||||
is the most reliable and universally supported.
|
||||
After initialization, try waving various 13.56MHz RFID cards over it!
|
||||
Modified and enhanced by F.Javier "habiwan" Puig Diaz in May/June 2025
|
||||
Remember to adjust the paths!
|
||||
It writes the raw times.csv file which is then used with names.csv later during processing.
|
||||
This example is production ready and has been working since June 2025.
|
||||
We were using an excel file that lists the date swiped and name of employee,
|
||||
but now I developed a php webapp with the same functionality and nicer look.
|
||||
Copyleft by F.Javier Puig Diaz, July 2026
|
||||
UPDATE: mysql for lamp usage (pip3 install mysql-connector-python first of course...)
|
||||
"""
|
||||
import RPi.GPIO as GPIO
|
||||
import vcgencmd
|
||||
import mysql.connector
|
||||
from datetime import datetime
|
||||
import time # Imported time module directly for our debounce math
|
||||
from pn532 import *
|
||||
|
||||
# --- DATABASE CONFIGURATION ---
|
||||
db_config = {
|
||||
'host': 'db', # Or your Pi's IP/localhost
|
||||
'user': 'root',
|
||||
'password': 'rootpassword',
|
||||
'database': 'lampapp'
|
||||
}
|
||||
|
||||
# --- DEBOUNCE CONFIGURATION ---
|
||||
last_swipe_times = {}
|
||||
DEBOUNCE_SECONDS = 60
|
||||
|
||||
def get_cpu_temp():
|
||||
"""Fetches the CPU temperature using vcgencmd."""
|
||||
try:
|
||||
# Standard approach for vcgencmd on Raspberry Pi
|
||||
temp = vcgencmd.measure_temp()
|
||||
# Returns a string like "temp=56.0'C", we need to extract the float
|
||||
temp_value = float(temp.replace("temp=", "").replace("'C", ""))
|
||||
return temp_value
|
||||
except:
|
||||
return 0.0 # Fallback if reading fails
|
||||
|
||||
def ensure_tables_exist(cursor):
|
||||
"""Creates tables if they are missing."""
|
||||
# Create 'names' table
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS names (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
CardID VARCHAR(12) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) DEFAULT NULL
|
||||
)
|
||||
""")
|
||||
# Create 'times' table
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS times (
|
||||
UID VARCHAR(12) NOT NULL,
|
||||
temp FLOAT,
|
||||
timestamp DATETIME
|
||||
)
|
||||
""")
|
||||
|
||||
def log_to_db(uid_str, timestamp):
|
||||
try:
|
||||
conn = mysql.connector.connect(**db_config)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# --- NEW: Ensure structure exists before inserting ---
|
||||
ensure_tables_exist(cursor)
|
||||
|
||||
cpu_temp = get_cpu_temp()
|
||||
|
||||
sql = "INSERT INTO times (UID, temp, timestamp) VALUES (%s, %s, %s)"
|
||||
cursor.execute(sql, (uid_str, cpu_temp, timestamp))
|
||||
|
||||
conn.commit()
|
||||
cursor.close()
|
||||
conn.close()
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Database error: {err}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
pn532 = PN532_I2C(debug=False, reset=20, req=16)
|
||||
ic, ver, rev, support = pn532.get_firmware_version()
|
||||
print('Found PN532 with firmware version: {0}.{1}'.format(ver, rev))
|
||||
pn532.SAM_configuration()
|
||||
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
buzzer = 23
|
||||
GPIO.setup(buzzer, GPIO.OUT)
|
||||
|
||||
# Boot beep
|
||||
GPIO.output(buzzer, GPIO.HIGH)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(buzzer, GPIO.LOW)
|
||||
|
||||
print('Waiting for RFID/NFC card...')
|
||||
|
||||
while True:
|
||||
uid = pn532.read_passive_target(timeout=0.5)
|
||||
if uid is None:
|
||||
continue
|
||||
|
||||
# Convert list of hex to a clean string immediately
|
||||
uid_str = " ".join([hex(i) for i in uid])
|
||||
current_epoch = time.time()
|
||||
|
||||
# --- Per-Card Debounce Logic ---
|
||||
if uid_str in last_swipe_times:
|
||||
time_since_last = current_epoch - last_swipe_times[uid_str]
|
||||
if time_since_last < DEBOUNCE_SECONDS:
|
||||
# Ignore the card if it was swiped less than 60 seconds ago
|
||||
continue
|
||||
|
||||
# Record the new swipe time for this specific card
|
||||
last_swipe_times[uid_str] = current_epoch
|
||||
# -------------------------------
|
||||
|
||||
# Log to DB
|
||||
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
log_to_db(uid_str, current_time)
|
||||
|
||||
# Buzzer feedback
|
||||
GPIO.output(buzzer, GPIO.HIGH)
|
||||
time.sleep(0.1)
|
||||
GPIO.output(buzzer, GPIO.LOW)
|
||||
|
||||
except Exception as e:
|
||||
print(f"System error: {e}")
|
||||
finally:
|
||||
GPIO.cleanup()
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
This example shows connecting to the PN532 with I2C (requires clock
|
||||
stretching support), SPI, or UART. SPI is best, it uses the most pins but
|
||||
is the most reliable and universally supported.
|
||||
After initialization, try waving various 13.56MHz RFID cards over it!
|
||||
Modified and enhanced by F.Javier "habiwan" Puig Diaz in May/June 2025
|
||||
This optional set of script/service is to get an excel file file written with the codes of empty cards you swipe (whevever you get new cards)
|
||||
For example you could get an excel file written with the codes and matching names so a number can be written on empty cards
|
||||
uses the production names.csv that I got from prescanning 100 cards and getting their unique ID on column 1 and _UNASSIGNEDXX on column 2
|
||||
so that matching names to their unique ID is simple. I suggest writing with a pen a number to a label on those new empty cards...
|
||||
This uses the same production names.csv file, new names like _UNASSIGNEDXX should be column 2 column 1 the unique ID stripped as shown in the get-table-data.php example:
|
||||
Look for /** 2. PARSE AND MAP TIMESTAMPS (times.csv) **/ and see what $combinedData = []; does...
|
||||
... or use an EXCEL with the formulae shown in the main README.md ....
|
||||
GOAL is to see the results after swiping a card and writing a little number in the corner with the unassigned card number for future employees / temps...
|
||||
This way the empty cards that all look the same will have the labelled number matched to their unique ID for later processing...
|
||||
Remember to adjust the paths!
|
||||
"""
|
||||
|
||||
import RPi.GPIO as GPIO # used for nfc and buzzer
|
||||
import vcgencmd # added for CPU temperature readings
|
||||
from datetime import datetime # added for timestamps
|
||||
from time import sleep # added for buzzer
|
||||
from pathlib import Path # added to 'touch' the csv if it does not exist yet or gets deleted
|
||||
|
||||
from pn532 import *
|
||||
Path('/home/YOURPIUSER/nfc/emptycards.csv').touch()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
pn532 = PN532_I2C(debug=False, reset=20, req=16)
|
||||
|
||||
ic, ver, rev, support = pn532.get_firmware_version()
|
||||
print('Found PN532 with firmware version: {0}.{1}'.format(ver, rev))
|
||||
|
||||
# Configure PN532 to communicate with MiFare cards
|
||||
pn532.SAM_configuration()
|
||||
|
||||
#Disable warnings (optional)
|
||||
GPIO.setwarnings(False)
|
||||
#Select GPIO mode
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
#Set buzzer - pin 23 as output
|
||||
buzzer=23
|
||||
GPIO.setup(buzzer,GPIO.OUT)
|
||||
# debug Beep on boot
|
||||
GPIO.output(buzzer,GPIO.HIGH)
|
||||
sleep(0.01)
|
||||
GPIO.output(buzzer,GPIO.LOW)
|
||||
print('Waiting for RFID/NFC card...')
|
||||
while True:
|
||||
# Check if a card is available to read
|
||||
uid = pn532.read_passive_target(timeout=0.5)
|
||||
print('', end="")
|
||||
# Try again if no card is available.
|
||||
if uid is None:
|
||||
continue
|
||||
temp = vcgencmd.measure_temp()
|
||||
s = ["UID: ", [hex(i) for i in uid], temp, str(datetime.now())]
|
||||
with open("/home/YOURPIUSER/nfc/emptycards.csv", "a") as f:
|
||||
f.writelines(str(s))
|
||||
f.write("\n")
|
||||
print(s) # debug
|
||||
GPIO.output(buzzer,GPIO.HIGH)
|
||||
#print("Beep") # debug
|
||||
sleep(0.1) # Beep delay in seconds
|
||||
GPIO.output(buzzer,GPIO.LOW)
|
||||
sleep(1.7) # debounce
|
||||
f.close()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
GPIO.cleanup()
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Paths to the files we are watching
|
||||
TIMES_FILE="/home/YOURPIUSER/nfc/times.csv"
|
||||
NAMES_FILE="/home/YOURPIUSER/nfc/names.csv"
|
||||
|
||||
echo "Watching $TIMES_FILE and $NAMES_FILE for changes..."
|
||||
|
||||
# Loop indefinitely
|
||||
while inotifywait -e modify "$TIMES_FILE" "$NAMES_FILE"; do
|
||||
echo "Change detected! Copying files..."
|
||||
|
||||
# copy commands that happen after any swipe (or NFC-card name change):
|
||||
cp /home/YOURPIUSER/nfc/*.csv /media/NAS/PiClock/nfc/ # make sure you mount these 2 on your pi's /etc/fstab
|
||||
cp /home/YOURPIUSER/nfc/*.csv /media/uvm/ # see nfc/pifolder.md for more info
|
||||
|
||||
echo "Sync complete."
|
||||
done
|
||||
Reference in new issue
Block a user