Inspiration

In the time of working from home; I find myself swinging between being procrastinate or workaholic.

I'm also not used to using the common productivity tools, such as the time trackers, and I always forget to start or to stop them, so I thought It would be cool if I had something like smart sticky notes that can track the time I work on each task.

I then realized that the closest thing to my sticky note thing is the clock card machine that records start and end times for hourly employees, and the closest thing to a smart sticky note or time card is an RFID card

I started to plan for the device story board and I came up with the following: The user would interact with the time tracker only and simply using the RFID card and the device would only show the task name and the time count. The device would be connected to the computer; where I gets its power and saves the data. Any further controls for resetting the time count or changing the names of the task shall be done from the computer interface, as well.

  Proof-of-concept

Before starting the work, I liked to make sure that two things are working:

  • The removal of the RFID card can be signaled using code
  • A complex Python GUI would be nicely designed without waste of time


Starting with the RFID thing

The normal use of the RFID card for most applications is to read the card ID in order to unlock a series of instructions. This story is somehow different in my case. I need to detect the availability of the card at any given moment, or get a signal of its removal in order to track the time correctly.

I tried many RFID libraries but I didn't get what I wanted.... except for this library that has bool is_tag_near() which returns whether a tag is currently held near the antenna.

I wired the 125kHz reader to the arduino and uploaded the ready example.

The example worked like a charm. It registered the ID of the card, and also made the LED 13 on while the card is near the reader. I'm really reliefed it finally worked.



Secondly, the Python GUI

In a previous assignment I used python tkinter to build a GUI, but it was a slow process because I can't see the results of my edits for elements positions until I run the code.

After doing some search I found that there is another GUI library called PyQT- which has many design software that simplifies GUI application development. I used Qt Designer to make my GUI and it took me like two minutes to figure out how to use it to generate the python code for the GUI.

The process was easy and quick, but the generated code didn't compile directly because the program generates a class and you should write the main code that uses that class. I got the code running as follows:


Qt5 import QtCore, QtGui, QtWidgets
import sys

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(340, 233)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
        MainWindow.setSizePolicy(sizePolicy)
        MainWindow.setMinimumSize(QtCore.QSize(340, 233))
        MainWindow.setMaximumSize(QtCore.QSize(340, 233))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setGeometry(QtCore.QRect(120, 170, 141, 22))
        self.comboBox.setObjectName("comboBox")
        self.label1 = QtWidgets.QLabel(self.centralwidget)
        self.label1.setEnabled(False)
        self.label1.setGeometry(QtCore.QRect(17, 15, 71, 21))
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(True)
        font.setWeight(75)
        self.label1.setFont(font)
        self.label1.setObjectName("label1")
        self.label2 = QtWidgets.QLabel(self.centralwidget)
        self.label2.setEnabled(False)
        self.label2.setGeometry(QtCore.QRect(17, 45, 81, 16))
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(True)
        font.setWeight(75)
        self.label2.setFont(font)
        self.label2.setObjectName("label2")
        self.label3 = QtWidgets.QLabel(self.centralwidget)
        self.label3.setEnabled(False)
        self.label3.setGeometry(QtCore.QRect(17, 70, 101, 21))
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(True)
        font.setWeight(75)
        self.label3.setFont(font)
        self.label3.setObjectName("label3")
        self.lbl_name = QtWidgets.QLineEdit(self.centralwidget)
        self.lbl_name.setEnabled(False)
        self.lbl_name.setGeometry(QtCore.QRect(117, 45, 201, 20))
        self.lbl_name.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        self.lbl_name.setObjectName("lbl_name")
        self.lbl_id = QtWidgets.QLabel(self.centralwidget)
        self.lbl_id.setEnabled(False)
        self.lbl_id.setGeometry(QtCore.QRect(120, 18, 191, 16))
        self.lbl_id.setObjectName("lbl_id")
        self.lbl_tracker = QtWidgets.QLabel(self.centralwidget)
        self.lbl_tracker.setEnabled(False)
        self.lbl_tracker.setGeometry(QtCore.QRect(120, 73, 191, 16))
        self.lbl_tracker.setObjectName("lbl_tracker")
        self.btn_refresh_com = QtWidgets.QPushButton(self.centralwidget)
        self.btn_refresh_com.setGeometry(QtCore.QRect(268, 170, 51, 23))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.btn_refresh_com.sizePolicy().hasHeightForWidth())
        self.btn_refresh_com.setSizePolicy(sizePolicy)
        self.btn_refresh_com.setObjectName("btn_refresh_com")
        self.btn_reset = QtWidgets.QPushButton(self.centralwidget)
        self.btn_reset.setEnabled(False)
        self.btn_reset.setGeometry(QtCore.QRect(278, 70, 41, 23))
        self.btn_reset.setObjectName("btn_reset")
        self.label4 = QtWidgets.QLabel(self.centralwidget)
        self.label4.setGeometry(QtCore.QRect(17, 127, 101, 21))
        font = QtGui.QFont()
        font.setPointSize(8)
        font.setBold(True)
        font.setWeight(75)
        self.label4.setFont(font)
        self.label4.setObjectName("label4")
        self.lbl_status_card = QtWidgets.QLabel(self.centralwidget)
        self.lbl_status_card.setGeometry(QtCore.QRect(120, 130, 251, 16))
        self.lbl_status_card.setObjectName("lbl_status_card")
        self.line = QtWidgets.QFrame(self.centralwidget)
        self.line.setGeometry(QtCore.QRect(20, 110, 301, 16))
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.lbl_status_port = QtWidgets.QLabel(self.centralwidget)
        self.lbl_status_port.setGeometry(QtCore.QRect(120, 150, 251, 16))
        self.lbl_status_port.setObjectName("lbl_status_port")
        self.btn_refresh_com.raise_()
        self.comboBox.raise_()
        self.label1.raise_()
        self.label2.raise_()
        self.label3.raise_()
        self.lbl_name.raise_()
        self.lbl_id.raise_()
        self.lbl_tracker.raise_()
        self.btn_reset.raise_()
        self.label4.raise_()
        self.lbl_status_card.raise_()
        self.line.raise_()
        self.lbl_status_port.raise_()
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 340, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Task Tracker "))
        self.label1.setText(_translate("MainWindow", "Task ID:"))
        self.label2.setText(_translate("MainWindow", "Task Name:"))
        self.label3.setText(_translate("MainWindow", "Task Tracker:"))
        self.lbl_id.setText(_translate("MainWindow", "0x0000"))
        self.lbl_tracker.setText(_translate("MainWindow", "0 Hours, 0 Minutes"))
        self.btn_refresh_com.setText(_translate("MainWindow", "Refresh"))
        self.btn_reset.setText(_translate("MainWindow", "Reset"))
        self.label4.setText(_translate("MainWindow", "Status:"))
        self.lbl_status_card.setText(_translate("MainWindow", "Card not available"))
        self.lbl_status_port.setText(_translate("MainWindow", "Device disconnected. Select COM Port"))

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    app.exec()

  Function Prototyping

The simplest function, and yet the most vital one in this project, is to have a proper serial communication between Python and Arduino. I've done this one many iterations as follows:

Serial Ports listing in Python GUI

I looked up the code for listing the serial ports, so that I can add them to the ports menu in the GUI. It looked like this:

                        
import sys
import glob
import serial


def serial_ports():
    """ Lists serial port names

        :raises EnvironmentError:
            On unsupported or unknown platforms
        :returns:
            A list of the serial ports available on the system
    """
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result


if __name__ == '__main__':
    print(serial_ports())

I merged the code with the GUI code and I successfully listed the ports in the comboBox.

Connecting to a Serial Port with a handshake

Sending serial data to a device is a simple thing to do with Python, but I needed to make sure that I'm connecting to the right device. I also needed to know if I was disconnected in order to stop counting time indefinitely. This required some sort of regular handshakes between the GUI and the Arduino side.

I made a flow-chart for the process I imagined.

Here's the Python implementation

                          
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import glob
import serial
import time

class Ui_MainWindow(object):
currentCOM = ''
openCOM = False
verifiedCOM = False
monitor = []

def setupUi(self, MainWindow):
    MainWindow.setObjectName("MainWindow")
    MainWindow.resize(340, 233)
    sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
    sizePolicy.setHorizontalStretch(0)
    sizePolicy.setVerticalStretch(0)
    sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
    MainWindow.setSizePolicy(sizePolicy)
    MainWindow.setMinimumSize(QtCore.QSize(340, 233))
    MainWindow.setMaximumSize(QtCore.QSize(340, 233))
    self.centralwidget = QtWidgets.QWidget(MainWindow)
    self.centralwidget.setObjectName("centralwidget")
    self.label1 = QtWidgets.QLabel(self.centralwidget)
    self.label1.setEnabled(False)
    self.label1.setGeometry(QtCore.QRect(17, 15, 71, 21))
    font = QtGui.QFont()
    font.setPointSize(8)
    font.setBold(True)
    font.setWeight(75)
    self.label1.setFont(font)
    self.label1.setObjectName("label1")
    self.label2 = QtWidgets.QLabel(self.centralwidget)
    self.label2.setEnabled(False)
    self.label2.setGeometry(QtCore.QRect(17, 45, 81, 16))
    font = QtGui.QFont()
    font.setPointSize(8)
    font.setBold(True)
    font.setWeight(75)
    self.label2.setFont(font)
    self.label2.setObjectName("label2")
    self.label3 = QtWidgets.QLabel(self.centralwidget)
    self.label3.setEnabled(False)
    self.label3.setGeometry(QtCore.QRect(17, 70, 101, 21))
    font = QtGui.QFont()
    font.setPointSize(8)
    font.setBold(True)
    font.setWeight(75)
    self.label3.setFont(font)
    self.label3.setObjectName("label3")
    self.lbl_name = QtWidgets.QLineEdit(self.centralwidget)
    self.lbl_name.setEnabled(False)
    self.lbl_name.setGeometry(QtCore.QRect(117, 45, 201, 20))
    self.lbl_name.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
    self.lbl_name.setObjectName("lbl_name")
    self.lbl_id = QtWidgets.QLabel(self.centralwidget)
    self.lbl_id.setEnabled(False)
    self.lbl_id.setGeometry(QtCore.QRect(120, 18, 191, 16))
    self.lbl_id.setObjectName("lbl_id")
    self.lbl_tracker = QtWidgets.QLabel(self.centralwidget)
    self.lbl_tracker.setEnabled(False)
    self.lbl_tracker.setGeometry(QtCore.QRect(120, 73, 191, 16))
    self.lbl_tracker.setObjectName("lbl_tracker")
    
    self.comboBox = QtWidgets.QComboBox(self.centralwidget)
    self.comboBox.setGeometry(QtCore.QRect(120, 170, 141, 22))
    self.comboBox.setObjectName("comboBox")
    self.comboBox.activated.connect(self.connectSerialPort)
    self.updateSerialPorts()

    self.btn_refresh_com = QtWidgets.QPushButton(self.centralwidget)
    self.btn_refresh_com.setGeometry(QtCore.QRect(268, 170, 51, 23))
    self.btn_refresh_com.setObjectName("btn_refresh_com")
    self.btn_refresh_com.clicked.connect(self.updateSerialPorts)

    self.timer = QtCore.QTimer()
    self.timer.timeout.connect(self.mainLoop)
    self.timer.start(200)

    self.btn_reset = QtWidgets.QPushButton(self.centralwidget)
    self.btn_reset.setEnabled(False)
    self.btn_reset.setGeometry(QtCore.QRect(278, 70, 41, 23))
    self.btn_reset.setObjectName("btn_reset")
    
    self.label4 = QtWidgets.QLabel(self.centralwidget)
    self.label4.setGeometry(QtCore.QRect(17, 127, 101, 21))
    font = QtGui.QFont()
    font.setPointSize(8)
    font.setBold(True)
    font.setWeight(75)
    self.label4.setFont(font)
    self.label4.setObjectName("label4")
    self.lbl_status_card = QtWidgets.QLabel(self.centralwidget)
    self.lbl_status_card.setGeometry(QtCore.QRect(120, 130, 251, 16))
    self.lbl_status_card.setObjectName("lbl_status_card")
    self.line = QtWidgets.QFrame(self.centralwidget)
    self.line.setGeometry(QtCore.QRect(20, 110, 301, 16))
    self.line.setFrameShape(QtWidgets.QFrame.HLine)
    self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
    self.line.setObjectName("line")
    self.lbl_status_port = QtWidgets.QLabel(self.centralwidget)
    self.lbl_status_port.setGeometry(QtCore.QRect(120, 150, 251, 16))
    self.lbl_status_port.setObjectName("lbl_status_port")
    self.btn_refresh_com.raise_()
    self.comboBox.raise_()
    self.label1.raise_()
    self.label2.raise_()
    self.label3.raise_()
    self.lbl_name.raise_()
    self.lbl_id.raise_()
    self.lbl_tracker.raise_()
    self.btn_reset.raise_()
    self.label4.raise_()
    self.lbl_status_card.raise_()
    self.line.raise_()
    self.lbl_status_port.raise_()
    MainWindow.setCentralWidget(self.centralwidget)
    self.menubar = QtWidgets.QMenuBar(MainWindow)
    self.menubar.setGeometry(QtCore.QRect(0, 0, 340, 21))
    self.menubar.setObjectName("menubar")
    MainWindow.setMenuBar(self.menubar)
    self.statusbar = QtWidgets.QStatusBar(MainWindow)
    self.statusbar.setObjectName("statusbar")
    MainWindow.setStatusBar(self.statusbar)

    self.retranslateUi(MainWindow)
    QtCore.QMetaObject.connectSlotsByName(MainWindow)

def retranslateUi(self, MainWindow):
    _translate = QtCore.QCoreApplication.translate
    MainWindow.setWindowTitle(_translate("MainWindow", "Task Tracker "))
    self.label1.setText(_translate("MainWindow", "Task ID:"))
    self.label2.setText(_translate("MainWindow", "Task Name:"))
    self.label3.setText(_translate("MainWindow", "Task Tracker:"))
    self.lbl_id.setText(_translate("MainWindow", "0x0000"))
    self.lbl_tracker.setText(_translate("MainWindow", "0 Hours, 0 Minutes"))
    self.btn_refresh_com.setText(_translate("MainWindow", "Refresh"))
    self.btn_reset.setText(_translate("MainWindow", "Reset"))
    self.label4.setText(_translate("MainWindow", "Status:"))
    self.lbl_status_card.setText(_translate("MainWindow", "Card not available. Insert a card."))
    self.lbl_status_port.setText(_translate("MainWindow", "Device disconnected. Select COM Port"))

def connectSerialPort(self):
    try:
        self.monitor.close()
    except:
        pass
    availablePorts = self.serial_ports()
    if (str(self.comboBox.currentText()) in availablePorts):
        self.currentCOM = str(self.comboBox.currentText())
    else:
        self.updateSerialPorts()

def updateSerialPorts(self):
    availablePorts = self.serial_ports()
    self.comboBox.clear()
    if (len(availablePorts)):
        self.comboBox.addItem("Select COM Port")
        for item in availablePorts:
            self.comboBox.addItem(item)

def serial_ports(self):
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(256)]
    elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
        # this excludes your current terminal "/dev/tty"
        ports = glob.glob('/dev/tty[A-Za-z]*')
    elif sys.platform.startswith('darwin'):
        ports = glob.glob('/dev/tty.*')
    else:
        raise EnvironmentError('Unsupported platform')

    result = []
    for port in ports:
        try:
            s = serial.Serial(port)
            s.close()
            result.append(port)
        except (OSError, serial.SerialException):
            pass
    return result

def mainLoop(self):
    if (self.currentCOM == ''):
        self.lbl_status_port.setText("Device disconnected. Select COM Port")
        self.lbl_status_port.setStyleSheet("color: red")
        self.lbl_status_card.setText("Card not available. Insert a card")
        self.lbl_status_card.setStyleSheet("color: red")
        #print("No COM")
    else:
        if (self.openCOM == False):
            self.lbl_status_port.setText("Verifying device on " + self.currentCOM)
            self.lbl_status_port.setStyleSheet("color: orange")
            #print("Trying COM " + self.currentCOM)
            self.monitor = serial.Serial(self.currentCOM,115200,timeout=5)
            time.sleep(2) #waiting for the arduino bootloader to load the main program
            self.openCOM = True
        else:
            if (self.verifiedCOM == False):
                self.monitor.write(bytes(b'\x0A'))
                data = self.monitor.read(1)
                if (data == bytes(b'\x0B')):
                    self.verifiedCOM = True
                    self.lbl_status_port.setText("Device connected on " + self.currentCOM)
                    self.lbl_status_port.setStyleSheet("color: green")
                    #print("Device connected on " + self.currentCOM)
                #self.monitor.close()
                if (self.verifiedCOM == False):
                    self.revokeConnection()
                    #print("Wrong Device")
            else:
                try:
                    # send a dump byte to check the connection
                    self.monitor.write(bytes(b'\x00'))
                    #print(self.currentCOM + " is active" )
                    self.trackTheCard()
                except:
                    self.saveData()
                    self.revokeConnection()
                    print("Connection Interrupted.") 

def trackTheCard(self):
    pass 

def saveData(self):
    pass 

def enableUI(self):
    self.label1.setEnabled(True)
    self.label2.setEnabled(True)
    self.label3.setEnabled(True)
    self.lbl_name.setEnabled(True)
    self.lbl_id.setEnabled(True)
    self.lbl_tracker.setEnabled(True)
    self.btn_reset.setEnabled(True)

def disableUI(self):
    self.label1.setEnabled(False)
    self.label2.setEnabled(False)
    self.label3.setEnabled(False)
    self.lbl_name.setEnabled(False)
    self.lbl_name.clear()
    self.lbl_id.setEnabled(False)
    self.lbl_id.setText("0x0000")
    self.lbl_tracker.setEnabled(False)
    self.lbl_tracker.setText("0 Hours, 0 Minutes")
    self.btn_reset.setEnabled(False)

def revokeConnection(self):
    self.currentCOM = ''
    self.openCOM = False
    self.verifiedCOM = False
    self.disableUI()
    self.updateSerialPorts()
    try:
        self.monitor.close()
    except:
        pass
    

if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
app.exec()

Here's the Arduino implementation

  
#include <SoftwareSerial.h>
SoftwareSerial mySerial(0, 1); // RX, TX

void setup() {
    mySerial.begin(115200);
}

void loop() {
    if (mySerial.available()>0)
    {
    byte msg = mySerial.read();
    if (msg == 0x0A) mySerial.write(byte(0x0B));
    }
}

Here's a demo for the result.

You can see that the Arduino is receiving data after the connection is established.


Finalizing the Code

The components list used in the final prototype are the following:

  • MCU: Arduino
  • RFID Reader: RDM6300
  • LCD: 16x02 LCD with I2C interface

The rest of work was to get the Arduino send the existing card ID, and the python would take the right decisions to manage the data on the GUI and the Arduino screen. You can find the final Arduino and Python codes attached in the Downloads section below.

The UI also was fully functional

  Electronics Production

Verification of the components

The circuit is pretty straightforward, but I needed to decide which MCU I should use. I tried to compile the code for ATtiny44, but I found out that the code is too large.

I decided to used the ATmega328p instead. I thought it would be a good Idea to test the circuit on a breadboard on a THT package of the ATmega.

I tried to make the minimum number of wires.

  • I connected only one GND and one VCC pins
  • I didn't connect the AVCC pin since I'm not using the analog functionality
  • I decided to enable and use the internal 8MHz crystal
  • I didn't use pull-up resistors with the I2C pins because I read that the I2C library enables the internal pull-ups.

I compiled the code for the ATmega328p with the internal crystal using this board core

I used AVRDUDESS and USBasp to burn the fuses and upload the code to the ATmega, then mounted it in place on the breadboard. Everything worked perfectly!



Eagle Design

I transformed the breadboard wiring into a schematic on Eagle.

Here's the final Bill of materials

Part Quantity Price
ATMEGA328P-AU IC MCU 8BIT 32KB FLASH 32TQFP 1 2$
JANSANE 16x2 1602 LCD + I2C Module Interface Adapter 1 9.99$
RDM6300 125Khz EM4100 RFID Reader Module UART Output 1 7.88$
6 Positions Header Connector 0.100" SMD 1 0.86$
JST CONN HEADER VERT 2POS 2.5MM 1 0.15$
JST CONN HEADER VERT 4POS 2.5MM 1 0.21$
RES 4.99K OHM 1-4W 1% 1206 SMD 2 0.2$
SMT RT Angle Male Header 0.1" (36pos) 1 3.3$

I didn't have an idea yet about the final enclosure shape, but I thought it would be cool to make the PCB in the shape and the size of the LCD; with the same mounting holes.

The smallest traces I used were 12mil traces with 0.45mm spacing between traces.


PCB Production

I milled the PCB on the MDX-20 modela, using 1/64 endmills

I then soldered the components

I programmed the ATmega328p using the ISP header, and the circuit worked like a charm!


I really liked how the PCB looked :D.. I gave it a photo session :D :D

  Enclosure Prototyping

I didn't have a certain idea about how my project should look like. I got my working circuits and tried to put them in different formations to get any inspirations.

I liked the compact version. I used my Wacom and Inkodo to make some outlines around the formation I got.

I then used Fusion360 to design some draft devices to get more insights.

Now I'm settled on the Idea. I liked the last design, and the next thing to do is to make it real!

  Enclosure Production

The requirement now is to divide the device into separate parts that could hold the components and fit together. I tried imagin how to start this and it took me a full day! I came designed the parts using Fusion360, as well, to design the parts, the fits, and the mounts.

  • The RFID would be sandwiched inside the base part and a cover part
  • The PCB will be mounted on the cover using two screws, which will hold the PCB, the RFID cover, and the base together.
  • The LCD would be mounted on a detachable cover that would be mounted to the base using screws, as well.

Here's a video of how my final parts would fit together. I changed the colors according to the filament options available in the lab.


I used PRUSA Slicer to prepare the files for printing. I used PLA filament in two different colors: White and Wood. I chose small layer height because I had plenty of time and I really wanted the project to look cool

The retraction settings seemed to be messed up, but I decided to proceed with my printings and do some post-processing and sanding when I finish because I didn't want to waste the printing materials.

After I sanded the parts they were nice and clean. I insetred the nuts using a soldering iron into the LCD cover, then proceeded with my assembly as follows:

The design was beautiful when it worked. I literally felt like a proud dad :D

  Finalizing

Now everything is working just fine, but I didn't give the project any identity.

My friend, and Fab Lab Egypt's Manager, Omar El-Safty, suggested to name it "deeds". and I really liked the idea because I can treat the task cards as deeds to myself.

I made a logo and added updated the codes to show the name on the GUI and the LCD.

Cool, isn't it? :D

You can see a full demo for the complete project functionality in the presentation video:





Using the mentioned lisence will make other users able to:
  • Copy and redistribute the material in any medium or format
  • Remix, transform, and build upon the material
Under the following terms:
  • An appropriate credit must be given to the creator and the lisence
  • No commercial use for the material
  • Remixed versions must be distributed under the same license as the original

  What's Next?

I really enjoyed working on this project and I look forward to:

  • Add integration to an existing task management tool
  • Add an internal battery to make it usable without being connected to a PC
  • Replace the FTDI port with a USB B port
  • Add the sound notifications capability
  • and also make some tutorials based on it
  • Add rubber or silicon feet for more stability