Simple system to track hours worked - python

I am a total beginner and I think I underestimated my little project.
I am trying to implement a simple attendance system that will give me the hours worked per day and later per month.
I am using a Raspberry pi 3 b+ and RC522 Rfid reader plus 16x2 lcd display.
The data ist stored in a database using MariaDB.
The idea is to use it for student workers in a restaurant to clock in their hours.
The employees still write down their hours, but if it works it could replace the paperwork, we will see.
I know that there will be some concerns about legality, but thats up for the lawyers once I am done.
However my issue is, that right now I am unable to clock in and clock out multiple users.
It does work for one user.
If I clock in User 1 it waits until there is a new clock out info, doesn't matter which user it is.
So user 1 clock in, user 2 wants to clock in but he is registered to clock out and only then the database entry is transferred. I think it somehow would need to update the entries instantly.
I think you can get the idea from the picture
phpMyAdminScreenshot
I think I need to get more info from the database and compare it to what I have.
But I hit a wall now and I am unable to find a solution for my problem.
The code I have right now:
'#!/usr/bin/env python
import time
import datetime
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
import mysql.connector
import drivers
db = mysql.connector.connect(
host="localhost",
user="xxx",
passwd="xxx",
database="attendancesystem"
)
cursor = db.cursor()
reader = SimpleMFRC522()
display = drivers.Lcd()
sign_in = 0
sign_out= 1
try:
while True:
display.lcd_clear()
display.lcd_display_string("Transponder", 1)
display.lcd_display_string("platzieren", 2)
id, text = reader.read()
ts = time.time()
timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
cursor.execute("Select id, name FROM users WHERE rfid_uid="+str(id))
result = cursor.fetchone()
display.lcd_clear()
#if cursor.rowcount >= 1:
#display.lcd_display_string("Willkommen " + result[1], 2)
#cursor.execute("INSERT INTO attendance (user_id) VALUES (%s)", (result[0],) )
if sign_in == 0:
sign_in = (sign_in +1) % 2
sign_out = (sign_out +1) % 2
cursor.execute(f"INSERT INTO attendance (user_id, clock_in, signed_in) VALUES (%s, %s, %s)", (result[0], timestamp, sign_in) )
display.lcd_display_string(f"Angemeldet " + result[1], 1)
elif sign_in == 1:
sign_out = (sign_out +1) % 2
sign_in = (sign_in +1) % 2
cursor.execute(f"INSERT INTO attendance (user_id, clock_out, signed_in) VALUES (%s, %s, %s)", (result[0], timestamp, sign_in) )
display.lcd_display_string (f"Abgemeldet " + result[1], 1)
db.commit()
else:
display.lcd_display_string("Existiert nicht.", 1)
time.sleep(2)
finally:
GPIO.cleanup()'
My idea was to get one more database entry called signed_in and either have it as 0 or as 1.
The signed_in status does update how I want it to, but I don't know how to continue from here.
And I fail to be able to update the table I want to.
My idea was to get user_id and check for this id the last status of the signed_in row, if it is 1 the timestamp will update the clock_out row and signed_in to 0.
If it is 0 and clock_out is not NULL it will start a new row with the clock_in timestamp and switch signed_in to 1.
I didn't have any luck with updating any database values, so I reverted back to INSERT.

I would expect the database would only store events. These events would be from when they touched the RFID. The card would have an ID/employee number.
Processing the database information would allow the calculation of who is on shift and the length of shifts worked etc.
If there was an odd number of entries for an employee then you could assume they are on shift. Even number of entries would mean they have completed the shift.
I don't have your hardware or database so I've created random RFID read events and used sqlite3.
import datetime
import random
import sqlite3
import time
class SimpleMFRC522:
#staticmethod
def read():
while random.randint(0, 30) != 10: # Create some delay/blocking
time.sleep(1)
employee_id = random.randint(0, 5)
return employee_id, f'employee_{employee_id}'
class MyDatabase:
def __init__(self):
self.db = sqlite3.connect('/tmp/my-test.db')
with self.db:
self.db.execute("""
CREATE TABLE IF NOT EXISTS workers (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
empl_no INTEGER,
name TEXT,
tap_event timestamp);
""")
self.db.commit()
def insert_event(self, empl_no, name, timestamp):
sqlite_insert_with_param = """INSERT INTO 'workers'
('empl_no', 'name', 'tap_event')
VALUES (?, ?, ?);"""
self.db.cursor()
self.db.execute(sqlite_insert_with_param, (empl_no, name, timestamp))
self.db.commit()
def worker_in(self, empl_no):
sql_cmd = f"SELECT COUNT(*) FROM workers WHERE empl_no = {empl_no};"
cursor = self.db.execute(sql_cmd)
event_count, = cursor.fetchone()
return event_count % 2 == 1 and event_count > 0
def last_shift(self, empl_no):
sql_cmd = f"""SELECT tap_event FROM
(SELECT * FROM workers WHERE empl_no = {empl_no} ORDER BY tap_event DESC LIMiT 2)
ORDER BY tap_event ASC ;"""
cursor = self.db.execute(sql_cmd)
tap_in, tap_out = cursor.fetchall()
start = datetime.datetime.fromtimestamp(tap_in[0])
end = datetime.datetime.fromtimestamp(tap_out[0])
return end - start
def main():
db = MyDatabase()
reader = SimpleMFRC522()
while True:
empl_no, name = reader.read()
print(f"adding tap event for {name}")
db.insert_event(empl_no, name, datetime.datetime.timestamp(datetime.datetime.now()))
if db.worker_in(empl_no):
print(f"\t{name} has started their shift")
else:
time_worked = db.last_shift(empl_no)
print(f"\t{name} has left the building after {time_worked}")
if __name__ == '__main__':
main()
This gave me a transcript as follows
adding tap event for employee_5
employee_5 has started their shift
adding tap event for employee_0
employee_0 has left the building after 0:02:10.154697
adding tap event for employee_3
employee_3 has left the building after 0:07:02.465903
adding tap event for employee_3
employee_3 has started their shift
adding tap event for employee_2
employee_2 has left the building after 0:06:27.403874
adding tap event for employee_2
employee_2 has started their shift

Related

MySQL update or insert based on fetchall results in Python

I need to set some user meta in my wordpress through local python script. Hence I can't use the WP update_user_meta for it - it has to be done manually.
import mysql.connector as mysql
cnx = mysql.connect(host=HOST, database=DATABASE, user=USER, password=PASSWORD)
cursor = cnx.cursor()
get_meta = ("SELECT * FROM `ff_usermeta` WHERE `user_id`= 1 AND (`meta_key`='nickname' OR `meta_key`='info' OR `meta_key`='bg' OR `meta_key`='avatar' OR `meta_key`='profile_updated')")
cursor.execute(get_meta)
meta = cursor.fetchall()
#some processing of the result
cursor.execute(q, (...))
cnx.commit()
cursor.close()
cnx.close()
Now I need to check if the result has meta with each of the keys.
If the key already exists for this user, it needs to run UPDATE for this meta.
If this user still has no meta of this key, it has to INSERT new row.
if(there's no 'nickname' in meta_key on either of 5 or less rows):
q = ("INSERT INTO `ff_usermeta` ...")
else:
q = ("UPDATE `ff_usermeta` ...")
...and 4 more times like that?.. Seems like a good place for a cycle, but I don't really like the idea to make it 5x queues, especially since there might be more fields in the future.
I was thinking along the lines of searching the fetchall result for matches in meta_key, and if found, adding required data to one array, if not - to another. And then just running one update and one insert at the end, assuming both are not empty. If I were to write it in semi-php style, it would look roughly like this:
if(in_array("nickname", meta))
for_update .= "`nickname`='"+data[0]+"', "
else:
fields .= "`nickname`, "
vals .= "'"+data[0]+"', "
if(in_array("bg", meta)):
for_update .= "`bg`='"+data[1]+"', "
else:
fields .= "`bg`, "
vals .= "'"+data[1]+"', "
if(for_update):
update = ("UPDATE `ff_usermeta` SET "+for_update+" WHERE 1")
if(fields):
insert = ("INSERT INTO `ff_usermeta`("+fields+") VALUES ("+vals+")")
But absolutely no clue how to translate it correctly to python. I had to google it up to things like "why dot not working to add one string to another". Any advice? Or perhaps there is a better way? Thanks!
It is not complete, you can not update your rows in that way.
But with this you can start to make your query
The frist select gets exactly 1 row, if the user_id exists.
The user_id doesn't seem the right choice for this, but to get what you can do it is enough.
If the query doesn't have an entry, the it will insert some data you get from anywhere
The update as the insert are in that form wrong as you have to insert 5 new orws or update max 5 rows, but that is more for you to programm
import mysql.connector as mysql
HOST = "localhost"
DATABASE = ""
USER = "root"
PASSWORD = "mypassword"
cnx = mysql.connect(host=HOST, database=DATABASE, user=USER, password=PASSWORD)
cnx = mysql.connect(host=HOST, database=DATABASE, user=USER, password=PASSWORD)
cursor = cnx.cursor()
user_id = 1
get_meta = ("""SELECT umeta_id, user_id , MAX(IF( `meta_key`='nickname', meta_value,'')) AS 'nickname' , MAX(IF( `meta_key`='info', meta_value,'')) AS 'info' , MAX(IF( `meta_key`='bg', meta_value,'')) AS 'bg' , MAX(IF( `meta_key`='avatar', meta_value,''NULL'')) AS 'avatar' , MAX(IF (`meta_key`='profile_updated', meta_value,'')) AS 'profile_updated' FROM `ff_usermeta` WHERE `user_id`= %s GROUP BY umeta_id, user_id:""")
result = cursor.execute(get_meta,(user_id,))
if result > 0:
data = cursor.fetchone()
for_update = "";
#some processing of the result
if not data["nickname"]:
for_update += "`nickname`='"+data["nickname"]+"', "
if not data["bg"]:
for_update += "`bg`='"+data["bg"]+"', "
query = ("UPDATE `ff_usermeta` SET "+for_update+" WHERE user_id = " + user_id)
else:
#here are no data to be gathered as there is no user_id present add new user
nickname = ""
bg= ""
info = ""
avatar = ""
profile_updated = ""
fields= ""
vals = ""
fields += "`nickname`,`info`, `bg`,`avatar`,`profile_updated`"
vals += "'"+nickname+"', "+"'"+info+"', "+"'"+bg+"', "+"'"+avatar+"', "+"'"+profile_updatedfo+"'"
query = ("INSERT INTO `ff_usermeta`("+fields+") VALUES ("+vals+")")
cursor.execute(query)
cnx.commit()
cursor.close()
cnx.close()
I tried my best to adapt the suggestion above, but couldn't figure out how to make it work. Eventually I went another way, and it seems to work somehow, so I'll post the full code in case anyone would find it useful.
What it does: checks the queue in table with validation request, then parses a page (separate function) and updates user profile accodringly.
import mysql.connector as mysql
import time
from datetime import datetime
cnx = mysql.connect(host=HOST, database=DATABASE, user=USER, password=PASSWORD)
while True: #endless loop as a temporary scheduler
cursor = cnx.cursor()
#getting first request in the queue - 0: id, 1: url, 2: parse, 3: status, 4: user, 5: user_page, 6: req_date, 7: action
cursor.execute("SELECT * FROM `ff_qq` WHERE status = 0 LIMIT 1")
row = cursor.fetchone()
if row:
status = 1 #processed
if row[7] == "verify":
get_user = ("SELECT * FROM `ff_users` WHERE ID = %s LIMIT 1")
cursor.execute(get_user, (row[4],))
user = cursor.fetchone() #0 - ID, 5 - user_url, 8 - user_status, 9 - display_name
#separate function that returns data to insert into mysql
udata = verify(row) #0 - nickname, 1 - fb_av, 2 - fb_bg, 3 - fb_info, 4 - owner
ustat = row[1].split("/authors/")
if udata['owned'] or user[8] == ustat[1]:
update_user = ("UPDATE `ff_users` SET user_status = %s, display_name = %s, user_url = %s WHERE ID = %s LIMIT 1")
cursor.execute(update_user, (ustat[1], udata['nickname'], row[1], user[0]))
status = 2 #success
get = ("SELECT `meta_value` FROM `ff_usermeta` WHERE `user_id`= %s AND `meta_key`='ff_capabilities' LIMIT 1")
cursor.execute(get, (row[4],))
rights = cursor.fetchone()
if rights == 'a:1:{s:10:"subscriber";b:1;}':
promote = ("UPDATE `ff_usermeta` SET `meta_value` = 'a:1:{s:6:\"author\";b:1;}' "
"WHERE `user_id` = %s AND `meta_key`='ff_capabilities' LIMIT 1")
cursor.execute(promote, (row[0],))
#list of meta_key values in same order as returned data
ff = ['nickname', 'fb_av', 'fb_bg', 'fb_info']
for x in range(0,3): #goes through each one of the above list
if udata[ff[x]]: #yes this actually works, who would've thought?..
#current meta_key added directly into the string
get = ("SELECT `meta_value` FROM `ff_usermeta` WHERE `user_id`= %s AND `meta_key`='" + ff[x] + "' LIMIT 1")
cursor.execute(get, (row[4],))
meta = cursor.fetchone()
if(meta): #update if it exists, otherwise insert new row
qq = ("UPDATE `ff_usermeta` SET `meta_value` = %s "
"WHERE `user_id` = %s AND `meta_key`='" + ff[x] + "' LIMIT 1")
else:
qq = ("INSERT INTO `ff_usermeta`(`meta_value`, `meta_key`, `user_id`) "
"VALUES ('%s','" + ff[x] + "','%s'")
cursor.execute(qq, (udata[ff[x]], row[0])) #same execute works for both
else:
status = 3 #verification failed
#update queue to reflect its status
update = ("UPDATE `ff_qq` SET status = %s WHERE id = %s LIMIT 1")
cursor.execute(update, (status, row[0]))
cnx.commit()
cursor.close()
now = datetime.now()
print(now.strftime("%d.%m.%Y %H:%M:%S"))
time.sleep(180) #sleep until it's time to re-check the queue
cnx.close()

GUI-displayed timestamps in python program not refreshing?

Ok so I've recently written a python-based punchclock program for my company to replace their old paper-and-stamp punchcard system.
The idea is that everyone has access key cards to get in the doors in the morning, so we're using an RFID reader connected to a Surface Pro just inside the door to clock in with. The reader is programmed so it'll read the card and issue a 'RETURN' event. When it hits, it'll trigger the numbers it just input(EmpID) to be run against a SQLite table(Emps) that returns the corresponding name(EmpName). Then, EmpName is used as input to a different table(Swipes), along with the theDate and theTime. The EmpName and theTime values are also displayed in the text beneath the Input box with an .Update() command.
It.. mostly works. It'll switch names when different cards are run over the reader, but it won't change time. When I check the DB with SQLiteStudio, my entrys have been made, but they all share the same timestamp.
Here's a snip of the frontend code:
import pysimplegui as sg
import datetime
import TimeclockBackEnd as be
layout = [[sg.Text('Swipe Your Card')],
[sg.InputText(size=(6,1), key='IN', focus=True)],
[sg.Text('', key='theName', size=(45,1))],
[sg.Text('', key='theTime', size=(45,1))],
[sg.Button('', key='RETURN', visible=False, bind_return_key=True)],
window = sg.Window('Clock!', layout)
rightNow = datetime.datetime.now()
theTime = rightNow.strftime("%H:%M:%S")
theDate = rightNow.strftime("%Y-%m-%d")
while True :
event, value = window.Read()
cardNumber = value['IN']
if event is None:
break
elif event is 'RETURN':
nameOfSwiper = be.Swipe(cardNumber)
be.submitToDB(nameOfSwiper, theDate, theTime)
window['theName'].Update(nameOfSwiper)
window['theTime'].Update(theTime)
window['IN'].Update('')
and here's the code it calls on in the backend(be):
def Swipe(EmpID):
con = sqlite3.connect('Timeclock.db')
cur = con.cursor()
cur.execute("SELECT EmpName FROM Emps WHERE EmpID=?", (EmpID,))
returnedValue = cur.fetchall()
con.commit()
con.close()
delisted = ''.join(map(str, returnedValue))
stripped = str(delisted).strip('()')
strippedAgain = str(stripped).strip(',')
swiperName = str(strippedAgain).strip("''")
return swiperName
def submitToDB(EmpName, theDate, theTime):
con = sqlite3.connect('Timeclock.db')
cur = con.cursor()
cur.execute("INSERT INTO Swipes VALUES (?, ?, ?)", (EmpName, theDate, theTime))
con.commit()
con.close()
Again, it writes to the DB just fine, the only thing I'm having an issue with is how theTime doesn't change from the value that was set on its initial swipe. This is the main working parts of the code but if you don't see anything wrong here, feel free to check my github, I've got the full thing there.
Thanks for helping out!

scheduling a task not working in Python

i'm trying to schedule a task every 5 seconds, here what i did:
conn = connect('mydatabase.db')
c = conn.cursor()
c.execute('CREATE TABLE IF NOT EXISTS RSSEntries (entry_id INTEGER PRIMARY KEY AUTOINCREMENT, title , url , date );')
def checkLink(linko):
c.execute("SELECT entry_id FROM RSSEntries WHERE url = ?", (linko,))
datas=c.fetchall()
if len(datas)==0:
return True
else:
return False
def storeData():
data = feedparser.parse("http://www...")
for i in range(len(data['entries'])):
if checkLink(data.entries[i].link) is True:
print "doesn't exist"
c.execute("insert into RSSEntries VALUES\
(NULL,'%s', '%s', '%s')" % (data.entries[i].title,data.entries[i].link, data.feed.updated))
else:
print "exist"
schedule.every(5).seconds.do(storeData)
conn.commit()
but the storeData method is not reachable..
if i run storeData() instead of schedule.every(5).seconds.do(storeData) the code work perfectly, what i'm doing wrong
any suggesting or other ways to do this task are welcome.
I think you are missing the scheduler loop at the end of your script:
while True:
schedule.run_pending()
time.sleep(1)
https://pypi.python.org/pypi/schedule

Python - failing to update 90% of rows

cur.execute("""SELECT match_own_goals.game_id, home_team, away_team, team, time
FROM football.match_own_goals
JOIN football.match_info
ON match_own_goals.game_id = match_info.game_id""")
e = cur.fetchall()
for game in e:
print game
time = game[4]
print type(time)
if game[3] == 1:
team_id = game[1]
else:
team_id = game[2]
cur.execute("""UPDATE football.match_own_goals
SET team_id = %s
WHERE time = %s AND game_id = %s""", (team_id, time, game[0]))
db.commit()
This has updated about 10% of the rows, no idea why. time is actually a float (it doesn't represent a time, more a point in time during a football match e.g. 87mins, 54mins).
Time is definately the problem as when I removed it it worked fine, but I really need it in there for other tables.
What am I doing wrong? Thanks
time = %s this is definitely an issue (you can not properly compare floats with equation operator, read more about it here).

Python - Continue Code Execution After Popping Up MsgBox?

I'm fairly new to Python. Here's a script I have that gathers info from our MySQL server hosting our Helpdesk tickets, and will pop up a message box (using EasyGUI's "msgbox()" function) whenever a new ticket arrives.
The issue is that I want my program to continue processing after the popup, regardless of whether the user clicks "OK" or not, even if that means message boxes could keep popping up over each other and must be dismissed one by one; that would be fine with me.
I looked into threading, and either it doesn't work or I did something wrong and need a good guide. Here's my code:
import MySQLdb
import time
from easygui import *
# Connect
db = MySQLdb.connect(host="MySQL.MyDomain.com", user="user", passwd="pass", db="db")
cursor = db.cursor()
# Before-and-after arrays to compare; A change means a new ticket arrived
IDarray = ([0,0,0])
IDarray_prev = ([0,0,0])
# Compare the latest 3 tickets since more than 1 may arrive in my time interval
cursor.execute("SELECT id FROM Tickets ORDER BY id DESC limit 3;")
numrows = int(cursor.rowcount)
for x in range(0,numrows):
row = cursor.fetchone()
for num in row:
IDarray_prev[x] = int(num)
cursor.close()
db.commit()
while 1:
cursor = db.cursor()
cursor.execute("SELECT id FROM Tickets ORDER BY id DESC limit 3;")
numrows = int(cursor.rowcount)
for x in range(0,numrows):
row = cursor.fetchone()
for num in row:
IDarray[x] = int(num)
if(IDarray != IDarray_prev):
cursor.execute("SELECT Subject FROM Tickets ORDER BY id DESC limit 1;")
subject = cursor.fetchone()
for line in subject:
# -----------------------------------------
# STACKOVERFLOW, HERE IS THE MSGBOX LINE!!!
# -----------------------------------------
msgbox("A new ticket has arrived:\n"+line)
# My time interval -- Checks the database every 8 seconds:
time.sleep(8)
IDarray_prev = IDarray[:]
cursor.close()
db.commit()
You can use Python GTK+
It offers non-modal using
set_modal(False)

Categories

Resources