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:
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:
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 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.
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:
What's Next?
I really enjoyed working on this project and I look forward to:
Downloads