Python GUI Bluetooth Programming With Tkinter
July 4, 2022Python has a lot of GUI frameworks, but Tkinter is the only framework that’s built into the Python standard library. Tkinter has several strengths. It’s cross-platform, so the same code works on Windows, macOS, and Linux. Visual elements are rendered using native operating system elements, so applications built with Tkinter look like they belong on the platform where they’re run.
Tkinter is lightweight and relatively painless to use compared to other frameworks. This makes it a compelling choice for building GUI applications in Python, especially for applications where a modern sheen is unnecessary, and the top priority is to quickly build something functional and cross-platform.
In this article, we will try to create a simple Python GUI application that can scan for nearby Bluetooth devices using Pyserial and shows the list on the screen.
Requirments
- Bluetooth Low Energy USB dongle BleuIO (https://www.bleuio.com)
- Pyserial.
Instructions
- Connect the BleuIO to your computer. The script uses pyserial to connect to the Bluetooth USB dongle BleuIO.
- Update the script and write the correct COM port (line 25), where the dongle is connected.
- After connecting to the dongle, we put the dongle into the central role using AT+CENTRAL so that it can scan for nearby Bluetooth devices.
- Then we do a simple Gap scan using AT+GAPSCAN=3 command to scan for nearby Bluetooth devices for 3 seconds.
- After that, we read the output from the serial port and filter the device to get the unique number of devices.
- Then we add a timestamp when the scan was completed.
- Finally, we sort the result by RSSI value before printing it out on screen.
- ‘Scan again’ button will do the whole process again.
Here is the final script file.
# Gjort av William
# 2022-06-16
# Smart Sensors Devices AB
#
# libraries that is necessary for tkinter to work
import tkinter as tk
from tkinter import ttk
# this is imported for the dongle and also for the "time.sleep()" commands
import serial
import time
# this is the library that is uesd to check the current time
import datetime
now = datetime.datetime.now()
# this is what creates the main window
main_window = tk.Tk()
#changes the titel of the window
main_window.title('Scan for nearby Bluetooth devices')
# sets your port for the dongle
your_com_port = "COM18"
connecting_to_dongle = True
#changes the size of the screens window
window_width = 900
window_height = 500
# get the screen dimension
screen_width = main_window.winfo_screenwidth()
screen_height = main_window.winfo_screenheight()
# find the center point
center_x = int(screen_width/2 - window_width / 2)
center_y = int(screen_height/2 - window_height / 2)
# set the position of the window to the center of the screen
main_window.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')
# apply the grid layout
main_window.grid_columnconfigure(1, weight=1)
main_window.grid_rowconfigure(1, weight=1)
# create the text widget
text = tk.Text(main_window, height=30, width=30)
text.grid(row=1, column=1, sticky=tk.EW)
# this is the part of the code that communicates whit the dongle
print("Connecting to dongle...")
while connecting_to_dongle:
try:
console = serial.Serial(
port=your_com_port,
baudrate=57600,
parity="N",
stopbits=1,
bytesize=8,
timeout=0,
)
if console.is_open.__bool__():
connecting_to_dongle = False
except:
print("Dongle not connected. Please reconnect Dongle.")
time.sleep(5)
print("Connected to Dongle.")
console.write(str.encode("AT+CENTRAL"))
console.write("\r".encode())
print("Putting dongle in Central role.")
time.sleep(0.1)
console.write(str.encode("AT+GAPSCAN=3"))
console.write("\r".encode())
time.sleep(0.1)
print("Looking for nearby Bluetooth devices ...")
dongle_output2 = console.read(console.in_waiting)
time.sleep(3)
print("Scan Complete!")
filtered = []
for dev in dongle_output2.decode().splitlines():
if len(dev)>20:
filtered.append(dev.split(maxsplit=1)[1])
seen = set()
out = []
for elem in filtered:
prefix = elem.split(' ')[1]
if prefix not in seen:
seen.add(prefix)
out.append(elem)
# sort list
out.sort(key=lambda x:int(x.split()[3]), reverse=True)
# writes out the amount of bluetooth devices found on the main screen
text.insert('0.5', 'Amount of devices found: ' + str(len(out)) + '\n\n')
# funktion to get the current time
def get_time():
return now.strftime('%H:%M:%S')
# prints out the time of the scan on the main screen
text.insert('1.0','The time of the scan: ' + str(get_time()) + '\n\n')
# writes out the results on the main screen
for i in range(0,len(out)):
position = f'{i+5}.{len(out[i])}'
tempStr = out[i] + "\n"
text.insert(position,f' {tempStr}')
# is supposed to delet everyting on the list
out.clear()
#the funktion for the scan button
def button_clicked():
# enables the programe to change the results on the main screen to the new ones after the user presses the scan button
text['state'] = 'normal'
# update the current time.
now = datetime.datetime.now()
# funktion to get the current time
def get_time():
return now.strftime('%H:%M:%S')
# this simply puts a emty row betwen the results and the rest of the output on kommandotolken
print()
# this delets the previous output that is on the main screen
text.delete('0.0', tk.END)
# this is the part of the code that communicates whit the dongle
console.write(str.encode("AT+GAPSCAN=3"))
console.write("\r".encode())
time.sleep(0.1)
dongle_output2 = console.read(console.in_waiting)
time.sleep(3)
filtered = []
for dev in dongle_output2.decode().splitlines():
if len(dev)>20:
filtered.append(dev.split(maxsplit=1)[1])
seen = set()
out = []
for elem in filtered:
prefix = elem.split(' ')[1]
if prefix not in seen:
seen.add(prefix)
out.append(elem)
# sort list
out.sort(key=lambda x:int(x.split()[3]), reverse=True)
#writes out the time of the scan on the main screen
text.insert('1.0','The time of the scan: ' + str(get_time()) + '\n\n')
# writes out the amount of bluetooth devices found on the main screen
text.insert('0.0', 'Amount of devices found: ' + str(len(out)) + '\n\n')
# writes out the results on the main screen
for i in range(0,len(out)):
position = f'{i+5}.{len(out[i])}'
tempStr = out[i] + "\n"
text.insert(position,f' {tempStr}')
# makes it so that you cant edite the results on the main screen
text['state'] = 'disabled'
#what calls the function for the scan button. also fixes what the user will see as the buttons name.
main_button = ttk.Button(
main_window,
text='Scan again',
command=lambda: button_clicked()
)
# creat the scan button
main_button.grid(row=0, column=1, sticky=tk.EW)
# just an exit button.
exit_button = ttk.Button(
main_window,
text='Exit',
command=lambda: main_window.quit()
)
# this determens were the exit button is located
exit_button.grid(row=2, column=1, sticky=tk.EW)
# makes it so that you cant edite the results on the main screen
text['state'] = 'disabled'
# keeps the main window open
main_window.mainloop()
time.sleep(1)
console.close()
Source code is available at https://github.com/smart-sensor-devices-ab/python_gui_tkinter_bluetooth.git
Run the script
To run the script we use start
pythonw Scan_ble.pyw
Note : Scan_ble.pyw is the file name
Output
After running the script, we see a total 25 devices found nearby. We can scan again using the ‘Scan again’ button