15. Interface and Application Programming¶
Assignment
This week’s individual assignment was to write an application that interfaces a user with an input &/or output device that you made, and the group assignment was to compare as many tool options as possible.
Introduction
For this week’s individual assignment, I decided to write an interface that I will use for my final project that will allow for the seamless transaction of bitcoin on the ATM that I am fabricating for my final project. The interface needs to offer basic functionality including the ability to transact BTC and features that allow users to check the balance of the account tied to the unique magnetic UID linked to the RFID cards that I will be issuing to my peers once the project is completed. In order for this interface to work, I would need to setup serial communication between the RPi that will run my Python interface and the custom input board that I designed that reads UID’s and other information from RFID cards. I have documented the process of designing both the front end and back end of my interface over the course of the past week in extensive detail below. For the first part of my documentation, screenshots with code will include images of an IDLE environment due to issues that I encountered with my text editor of choice, Atom, in the displaying of ASCII and Unicode text. I eventually rectified these problems and returned to Atom around halfway through the development process.
Week 15 Individual Assignment
I began this week’s individual assignment by deciding which programming language I wanted to use. As I only had experience with three languages that can be used to create GUI’s, these being Java, Python, and Node.js, I decided to implement Python due to its greater ease of use and my greater experience with Python relative to my experience with Java and NODE.js. My basis for this decision was multifaceted. First, there is a wider array of Python modules that will allow me to automate the transact of cryptocurrency, and there is lots of existing documentation on how to interface RPi’s with C-based boards, which will be an integral component of my interface for the reading and parsing of RFID data. I also consider myself to be significantly more skilled in Python environments than Java, so using Python would certainly make my workflow for this week more efficient, and allow me to further strengthen my Python abilities whilst diving into an entirely new sector of the language.
After deciding which language to use, I began researching various Python modules that would allow me to easily construct a basic user interface. After reading through the documentation for various GUI design tools, including Tkinter and guizero. After browsing the features of each module, I figured that guizero would allow me to design a working GUI quickly without forcing me to sacrifice features that are important to the functionality of my final project, as the appearance of my GUI is not vital to the functionality of the project.
pip install guizero
After deciding that I wanted to use guizero, which is based on Tkinter but is simplified for the sake of usability, I downloaded the module and began reading through the documentation on how to implement guizero in a Python environment.
//image of guizero page for apps
I discovered that guizero uses ‘app’ objects to display interfaces and widgets to implement various features within an instance of an app that allow users to interact with the interface. Various widgets included in guizero are textboxes, text, buttons, and sliders, all of which I used at one point throughout the development of the first functional iteration of my interface. The declaration of app objects and widgets is relatively straightforward. The declaration of an app object simply requires a title, and all widgets are declared before a statement that makes the app object display all of the widgets that are passed a parameter declaring which ‘app’ they will appear in.
app = App(title=”app”) #will create an instance of object type ‘app’ called ‘app’
app.display() #displays the app and any elements declared prior to its declaration
The declaration of widgets can be done by declaring the object type of the widget and passing a series of parameters in the declaration statement. Various functions can be invoked to change the state, appearance, and value of widgets throughout the runtime of the interface, or after the initial ‘display’ statement of the app that the widgets exist in. An example of a widget declaration is included below.
text = text(app, text=”text”) #can pass more parameters to change font, size, etc, but the first two are required for a text object to display
I added several textboxes to the first iteration of my GUI, including welcome messages and instructions on various pages of the interface that direct users to perform various actions at certain points throughout the use of my interface. I tried to consider the integration of various features in the final format of my design, which will be a physical device.
After adding some basic front end features to my homepage, I decided to add a ‘PushButton’ object that would allow me to swap screens from my homepage to the section of the interface that would eventually allow for the selection of transaction amounts. Initially, this was the only feature that I planned to add to the interface, though I would eventually add two more buttons for various other features throughout the course of the development process. Each button would need to hide all elements of the page that it existed on, including itself, and then make all of the objects on the following page appear in a seamless transition. After adding a button to my homepage, I managed to switch between the homepage and the page that would eventually become the space for users to select and enter transaction amounts. On the final version of this homepage, I included five transaction presets, which I currently have set to $5, $10, $20, $50, and $100 worth of BTC. I also added a field that allows users to enter a custom value into a TextBox and a button which uses the current value within the TextBox in a Bitcoin transaction.
After adding some basic front end features to my homepage, I decided to add a ‘PushButton’ object that would allow me to swap screens from my homepage to the section of the interface that would eventually allow for the selection of transaction amounts. Initially, this was the only feature that I planned to add to the interface, though I would eventually add two more buttons for various other features throughout the course of the development process. Each button would need to hide all elements of the page that it existed on, including itself, and then make all of the objects on the following page appear in a seamless transition. After adding a button to my homepage, I managed to switch between the homepage and the page that would eventually become the space for users to select and enter transaction amounts. On the final version of this homepage, I included five transaction presets, which I currently have set to $5, $10, $20, $50, and $100 worth of BTC. I also added a field that allows users to enter a custom value into a TextBox and a button which uses the current value within the TextBox in a Bitcoin transaction.
amount1 = PushButton(app, command=amount1, text="$" + str(amt1), height=2, width=20) #button init
amount2 = PushButton(app, command=amount2, text="$" + str(amt2), height=2, width=20) #button init
amount3 = PushButton(app, command=amount3, text="$" + str(amt3), height=2, width=20) #button init
amount4 = PushButton(app, command=amount4, text="$" + str(amt4), height=2, width=20) #button init
amount5 = PushButton(app, command=amount5, text="$" + str(amt5), height=2, width=20) #button init
useCustomAmount = PushButton(app, command=intTest, text="Use Custom Amount", height=2, width=20)
#for testing custom amount and using if only ints
customAmount = TextBox(app, height=2, width=20) #for filling out custom value
customAmount.hide()
useCustomAmount.hide()
amount2.hide()
amount3.hide()
amount4.hide()
amount5.hide()
Next, I began to research the implementation of one of the first cryptocurrency modules that would be implemented in the final build of my interface, which would allow users to constantly view the current price of bitcoin in a small textbox located at the bottom of the interface, that I would eventually make update through the invocation of a function after every presss of a button on my GUI, so the price would constantly update when a user was interacting with the GUI as the price would update with each interaction made, whether it be through filling out a text box or pressing a button, I want the price to update constantly in order to provide users with accurate prices throughout the entirety of their experience. I decided to use the Coindesk Bitcoin API, A brief statement about their service pulled from their website is included below.
"CoinDesk provides a simple API to make its Bitcoin Price Index (BPI) data programmatically available to others. You are free to use this API to include our data in any application or website as you see fit, as long as each page or app that uses it includes the text “Powered by CoinDesk”, linking to our price page. CoinDesk data is made available through a number of HTTP resources, and data is returned in JSON format. Please do not abuse our service. Launched in September 2013, the CoinDesk Bitcoin Price Index (XBP) represents an average of bitcoin prices across leading global exchanges that meet criteria specified by the XBP. It is intended to serve as a standard retail price reference for industry participants and accounting professionals."
Details for how to implement the Coindesk API in Python are included on Coindesk's site. To download the JSON from the API, the Python 'Requests' module is required to grab the information from the Coindesk API. After the JSON is returned, the data can be parsed in string format, which requires the implementation of the JSON module. The requests and json modules can be downloaded with the two commands below.
pip install requests
pip install json
The JSON can be downloaded from the API using the below call to the requests module. I wrote a Python function to return the JSON. I implemented this function in another function that I wrote to parse through the JSON from the Coindesk API and put useful information, including the current time and BTC price in USD, as strings and integers.
def btc_method():
response = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
data = response.json()
return data
Here's what the JSON looks like when grabbed from the Coindesk API.
{"time": {"updated": "May 12, 2021 14:52:00 UTC", "updatedISO": "2021-05-12T14:52:00+00:00", "updateduk": "May 12, 2021 at 15:52 BST"}, "disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org", "chartName": "Bitcoin", "bpi": {"USD": {"code": "USD", "symbol": "$", "rate": "56,356.3549", "description": "United States Dollar", "rate_float": 56356.3549}, "GBP": {"code": "GBP", "symbol": "£", "rate": "39,905.8785", "description": "British Pound Sterling", "rate_float": 39905.8785}, "EUR": {"code": "EUR", "symbol": "€", "rate": "46,571.8209", "description": "Euro", "rate_float": 46571.8209}}}
Parsing through JSON's is very difficult, as it is impossible to reference the indices of a JSON in order to grab the information that you want, necessitating the conversion of the JSON into a String, which can be parsed through with incredible ease in every language. Fortunately, the Python 'json' module makes this conversion very easy. Invoking the json.dumps(""JSON"") function, will convert JSON data into a String. I implemented the json.dumps() function by calling the btc_method() function that I included in the above code sample, which returns the JSON from the API in a JSON called 'data'. By invoking json.dumps() against the function that I wrote previously, the JSON is converted into a String.
str2 = json.dumps(btc_method()) #cast api json as str
After returning the string from the JSON that I downloaded from the Coindesk API, I needed to parse through the string in order to isolate the information that I needed. The two datas that I needed to return included the time (UTC) and current price of Bitcoin (USD). These two data points are always found at the same location within the API, meaning that the indices of constant values within the JSON can be used to calibrate a substring-grabbing function that isolates the values that I want to extract from the JSON. By using the (str).find(STRING) function, I calibrated my unique searches for the time and BTC price by using text that always appears in the JSON from the Coindesk API. I used different text because I wanted to, but I could have used the same statement and simply found the difference between the indices of the time and price from the first index position of this text. As .find() returns the int value of the first index of the first occurrence of the string that it searches for, I found the difference between the first index of each calibration point and the first and last indices of the text that I wanted to extract from the string of data from the API. To find the indices of the constant text that I wanted to use to calibrate the searches of the JSON, I counted the indices manually and then wrote statements that would allow me to find the index of the time or BTC price.
timeLoc = str2.find("UTC", 0, len(str2) - 1) #finds the constant location of 'UTC' in JSON
realTime = str2[timeLoc - 9: timeLoc + 3] #uses the index grabbed by prior statement and subtracts to index of time
priceLoc = str2.find('description": "United States Dollar"') #finds location of text in JSON
heresThePrice = str2[priceLoc - 15: priceLoc - 6] #uses location to find price
This data is cool, but these statements only find the data, and do not format it and place it in a meaningful location within my interface. In order to accomplish this task, I needed to create a Text object that included the time, price, and some filler text to make the data make sense. I did this by writing some filler text and concatenating my data points in certain locations so a concise but useful sentence is generated with each call to the updateBTC() function that I wrote. As the time and price information is very important to the Bitcoin economy, I decided to make this text display at the bottom of the screen on each page of my GUI, making it the only Text object that is omnipresent throughout the entirety of a user's experience with the GUI.
btc_price = Text(app, text=str1 + realTime + " - $" + heresThePrice, size = 10, font="Times New Roman", color="black", align="bottom") #generates sentence and aligns to bottom of screen
btc_price.show()
By hiding the Text object at the beginning of the updateBTC() function and showing it again after updating the value, issues with the Text duplicating on the screen of my GUI could be eliminated. Because of how widgets work in guizero, I had to use this logic often throughout my program to prevent objects from displaying multiple times or in places that they are not meant to exist within.
After successfully extracting the data from the JSON that I downloaded from the Coindesk library, I continued the develop process of my GUI by fleshing out the frontend of my interface. I finished adding buttons for each window of the interface that I planned to add. I also added Unicode text to the transaction amounts pages that display the amount of a pre-set transaction. I tried to generate the Unicode locally using the amount of a custom transaction entered by a user by using the 'pyFiglet' module, but the fonts generated by this module were distorted beyond legibility in guizero, deeming this a useless feature. As such, I was only able to integrate Unicode text into the pre-set transaction values in my GUI, and I simply added a TextBox that displays the transaction value of a custom transaction. Adding the multi-line Unicode was relatively simple, as I created strings using the \nI operator, which allows strings to be printed in multiple lines.
figlet =('███████╗ ██╗ ██╗███████╗██████╗\nI██╔════╝ ██║ ██║██╔════╝██╔══██╗\nI███████╗ ██║ ██║███████╗██║ ██║\nI╚════██║ ██║ ██║╚════██║██║ ██║\nI███████║ ╚██████╔╝███████║██████╔╝\nI ╚══════╝ ╚═════╝ ╚══════╝╚═════╝')
Printing the unicode to a Python shell appears as follows.
I've included a screen-recording below of an interaction with all of the basic page-swapping functions in my GUI, without interacting with any of the backend features, which did not exist in any capacity upon the recording of this video. After finalizing the development of the front end of my project, I began focusing on the far more difficult back-end, which really constitutes the majority of my work for this week, as I first had to make the backend of my GUI work on a local Windows machine and then had to port the entirety of my project to a Raspberry Pi and make it work again with my input board via serial. I apologize of the poor quality of this video, I had not yet figured out how to utilize OBS on my Windows Bootcamp environment so I figured this would suffice for a non-final demonstration of the front-end of my GUI.
Back-End Development
As transacting Bitcoin automatically requires more than just the current Bitcoin price and the time, which is all the Coindesk API returns, I would need to implement another module to automate the actualy transacting of Bitcoins, which is where the Python 'Bit' module is implemented in my interface. The documentation for Bit is relatively detailed compared to comparable modules, though I could not find many examples of individuals who have used the module in the past. As such, I had to go through lots of trial and error in order to figure out how to implement the various functions within the module as efficiently and effectively as possible. Needless to say, this consumed the majority of my time throughout the past week, but I am incredibly proud of the final result that will be displayed at the end of this page.
The landing page for the Bit module documentation links to several tools that proved useful throughout the development of the transaction portion of the back-end of my interface, namely the 'transactions' portion and the explanation of the various 'key' objects that are included in the module.
Transacting Bitcoin obvious requires bitcoin, and due to the meteoric rise in the price of the cryptocurrency in recent months, I obviously will not be testing with real Bitcoin. Instead, I implemented a fork of the Bitcoin blockchain, called a testnet, that works the same way as the actual BTC blockchain but has no real value, meaning it is commonly used by developers who do not want to transact real BTC during testing. The Bitcoin testnet is supported by the Bit module, which allows for the generation of testnet wallets and transactions to be authorized on the testnet. Finally, while I have attempted to redact the private wIF formatted hashed private keys of the wallets that I used throughout the development of my interface this week, I am sure that I will expose them at some point throughout the following writing. I understand the importance of protecting private keys, but since I used the BTC testnet, whose coins do not have any value, the extra effort to hide these keys simply is not worth the benefit in my opinion. If you are reading this and considering implementing fragments of my work in a real-world environment, please take the necessary precautions to conceal these keys from potential bad-actors, as they can be used to access and disburse the contents of BTC wallets when exposed.
from bit import PrivateKeyTestnet
my_key = PrivateKeyTestnet()
print(my_key.version)
print(my_key.to_wif())
print(my_key.address)
print(my_key.balance_as('usd'))
print(my_key)
I first needed to generate testnet wallets. To accomplish this, I wrote a simple Python script that allowed me to generate and print wIF (Wallet-Input Format) testnet keys which can then be used to generate public addresses which can receive transactions. The private key that is used to authorize transactions is used by generating a PrivateKeyTestnet object. The PrivateKeyTestnet() function takes one parameter, the wIF formatted key, and decrypts it into a private wallet key that is used to authorize transactions from the unique wallet tied to the wIF address. Anyays, after running my script I printed the wIF formats for each of the five testnet addresses that I generated. One of which would be used as the 'master' wallet and the other four which would eventually be issued to my clients during later phases of testing. The Bit library allows for multiple transactions to be completed at once by using lists where each element of the list contains the necessary parameters for a transaction to occur. These parameters include the wallet that the BTC is being sent to, the amount, and the currency. Bit offers a multitude of currencies that can be used to complete transaction, including BTC, Satoshi Units, and a plethora of currencies from various nations. Using a PrivateKeyTestnet object allows for a list to be used in conjunction with the send() function to complete multiple transactions in tandem if the wallet tied to the PrivateKeyTestnet object has sufficient funds to complete all of the transactions.
However, in order to get the test coins to disburse to my various client wallets, I would first need to acquire some testnet bitcoin. Fortunately, various tBTC faucets exist that automatically send small amounts of tBTC when a user submits an address to the sites. The best faucet that I found sends about $800 worth of tBTC (assuming the same exchange rate of BTC) or roughly 0.01 tBTC, which is more than enough to complete many tests without having to worry about running out of test coins. Here is the link to the faucet that I used throughout the development process of my interface. It allows for extracting every 12 hours, but repeated withdraws really are not necessary as small amounts of test coins can be used during testing, meaning just one withdraw is generally enough to last for the entire development process, which it did for me.
Viewing the wallet of the tBTC faucet that sent me the test coins shows that the balance is roughly 77 tBTC. As this is somewhat small relative to the amount of requests the faucet likely receives, it is important that users send tBTC back after use to support other developer's development processes without forcing them to buy tBTC from various online marketplaces that exist. I have not sent my tBTC back yet because I will be incorporating this week's work into my final project. As such, I will probably keep some tBTC in the master wallet so my project will still function in the future, but I will send the majority of the coins back after the conclusion of Fab Academy to support the open-source development community.
After sending the test coins to my wallet, I wrote a Python script using the Bit library that allowed me to check the balance of my wallet and verify that the transaction actually went through.
from bit import Key, PrivateKeyTestnet
master = PrivateKeyTestnet('REDACTED WIF') #generate PrivateKeyTestnet object for master wallet
print("Master Balance - $" + str(master.balance_as('usd'))) #print formatted address value in USD
After verifying that the funds from the tBTC faucet had entered the wallet that I planned to use as the master wallet for my payment service, I needed to disburse some of the funds to my client wallets that I created by running the same wallet generation that I included above in order to test the features that I planned on adding to my GUI. To do this efficiently, I fed the .send() function in the bit library an array that contained each client wallet that I had created, which at this point includes one for each of my peers, and entered amounts and the currency in the two columns of each row of the array that I submitted in order to specify the amount and the currency that I would be using. I sent each user roughly $50 USD worth of tBTC, assuming the tBTC actually did have the same value as actual BTC, which they do not.
from bit import Key, PrivateKey, PrivateKeyTestnet
master_key = PrivateKeyTestnet('REDACTED WIF')
sends = [
#('moZuqP9FR6h4paa6WgWGSddAecDo1P5oZn', 840, 'usd'), #send to master
('n2hFkWNbU2i9qjaU7u6nviyCjriorRjJfj', 100, 'usd'), #Graham Smith
('n1WHv9gACctTwCYjYExvJoJhHXVcUCaxD2', 70, 'usd'), #Teddy Warner
('n2hjUcAzX1rwmR2hKHvBaHFt2TE4RkPgq9', 70, 'usd'), #Drew Griggs
('mtXj1BeXmktkDChJY9BzFzWRe1QFUjETmw', 20, 'usd'), #Charles De Mey
('moEhWasKmKS9diNpXkTnyc6wT94oTQw5cq', 1, 'usd') #Grunt Flayscher
]
master_key.send(sends)
print("successfully sent")
After running the above script and seeing the "successfully sent" print that follows the call to the .send() function, I checked each client's wallet using an improved balance checking script that I wrote, which is used to print the value of the master wallet as well as each client's wallet.
from bit import Key, PrivateKeyTestnet
teddy = PrivateKeyTestnet('REDACTED')
charles = PrivateKeyTestnet('REDACTED')
drew = PrivateKeyTestnet('REDACTED')
graham = PrivateKeyTestnet('REDACTED')
grunt = PrivateKeyTestnet('REDACTED')
master = PrivateKeyTestnet('REDACTED')
clientWallet1 = PrivateKeyTestnet('REDACTED')
print("Graham Balance - $" + str(graham.balance_as('usd')))
print("Teddy Balance - $" + str(teddy.balance_as('usd')))
print("Drew Balance - $" + str(drew.balance_as('usd')))
print("Master Balance - $" + str(master.balance_as('usd')))
print("Client Balance - $" + str(clientWallet1.balance_as('usd')))
After successfully configuring several client wallets, I needed a way to access the various details that are associated with each wallet as well as the unique RFID UID that I planned on linking to each account. To accomplish this, I needed to create a database that contained users' names, tBTC addresses, wIF formatted private keys, and most importantly, the UID's for their cards. Each row of my database would contain a different users information, and each column would contain a different field of information that would either be used to view a users' account information within my interface or could also be used to compelete a transaction through the 'Do BTC Transaction' portal that I included in my interface. As I did not know how to create databases prior to this week, I decided to teach myself how to create databases with SQLite and access them in a Python envrionment.
Learning and Implementing SQLite
SQLite comes in the base installation of Python, so I did not need to use Pip to install additional modules in order to employ SQLite within my GUI. Only the following 'import' line is needed to include all of SQLite's functions and features in any Python script. Other setup is required to connect to databases that I will detail in the following paragraphs. I first familiarized myself with SQLite 3 using this guide. While this guide does show how to connect to a database using SQLite3 in Python, it does not describe how to implement some of the more advanced functions that I would need to use for my database to properly function with my GUI. All SQLite code in my GUI is entirely original, and I used the official SQLite3 website as a reference throughout the development process. It offers many details on various operations including traversals, which would be integral to the final functioning state of my interface and database.
import sqlite3 #add SQLite3 to Python
I first created a script that generated a database that I called binanceUSERS.db and planned on injecting several users' information into a table, 'users,' that I instantiated within the binanceUSERS database. The different columns of the database included text fields that I planned on injecting wIF-formatted private keys, addresses, names, and UID's into. Below is the script that I originally used to create my database.
import sqlite3
con = sqlite3.connect('binanceUSERS.db')
cur = con.cursor()
#create table for db
cur.execute('''CREATE TABLE users
(uid text, name text, wif text, address text)''')
After injecting the basic information into my database, I printed out the four columns into a Python shell using the following code.
import sqlite3
#for printing DB values
con = sqlite3.connect('binanceUSERS.db')
cur = con.cursor()
for row in cur.execute('SELECT * FROM users ORDER BY name'):
print(row)
After successfully printing the contents of my database to a Python shell, I began injecting user data into each cell of the database using pre-generated addresses, wIFs, and names that I disucssed earlier on. UID's were filled with filler numbers that would be replaced after I acquired the UID's of some of my RFID cards from my input board that scans RFID cards and prints their UID's to serial. As I had not acquired enough UID's at this point in the development process, I simply input six zeros in each UID row, as I would not be interacting with the UID's for awhile and could simply reference rows of the database using other unique information like wIF's, names, or tBTC addresses during testing.
DB Browser
I also discovered a useful software that allows for the browsing and modifying of SQLite databases through a GUI, which makes the process of adding rows and modifying data immensely easier than doing it through Python scripts that must be modified for each unique process that I want to do, which is incredibly time-consuming. DBBrowser allows for quick modification without any loss of functionality, making it a much better option than bare Python code to inject, modify, and view information within the database, particularly due to the small size of the data that I am using which makes manually entering data feasible rather than automating it with Python. Still, I am glad that I learned how to automate the entry of data into databases for future endeavors that might require the entry of larger datasets, which would make manual entry and modification impossible or incredibly time-consuming due to the size and amount of data that can be crammed into larger SQLite databases. DBBrowser can be downloaded here.
DBBrowser features a very intuitive interface that allows users to quickly navigate between different tables and sections of databases, which is incredibly helpful in a multitude of circumstances, oftentimes where quick edits are required which would generally take minutes to create a unique Python script for. While the above image is from the SQLite website and is obviously not of my database and is from 2013, the interface has not changed at all in the eight years between the capture of this image and the viewing of my own database on DBBrowser.
After creating my database, I needed to connect it to my main interface Python script to allow for the reading of data from the database during the actual running of the interface. In order to do this, I would need to traverse the database and check for matches in each cell of the database that matched the data that I was searching for. To begin this, I decided to add a feature that allows users to check the details of their account using a scan of an RFID card. Obviously, this would require a traversal of the UID column of the database until a match for the UID given by the card was found. As I did not have UID's ready yet, I decided to use the 'names' column during the initial testing of this feature by adding a button at the top of the 'check details' page that allows for the entry of custom name values that can then be cross-reference through a traversal of the array and return user's account information if the matching name that is given by the text box is found somewhere in the 'names' column of the array. I first created the above script that allowed for the printing of individual rows of my database to a Python shell.
import sqlite3
con = sqlite3.connect('binanceUSERS.db')
cur = con.cursor()
for row in cur.execute('SELECT * FROM users ORDER BY name'):
print(row)
After creating the above printing script, I wrote some code that would traverse my database for a matching name as the one entered into the 'use custom details' field that I added to the top of the 'check details' page and return the values from the matching row of the database in a readable format. The below code uses Python lists to add each value of each row of the database to each cell of the array. Once the program detects a value in the column that it is supposed to traverse (by checking a certain column of the local Python list,) it sets that vals_list list object to the permanent vals_list_real list, which is then formatted and its information is printed to the GUI in a readable, intuitive format. If a value is not detected that matches the one submitted by the user, an error message reads on the screen in the form of a popup that can be clicked out of by the user. These popups were eventually removed in the final Linux version of the software due to repeated bugs and questionable merit. I've also included a vide of the feature functioning by testing various user's names in the section of the interface and displaying their details within the GUI.
def useCustomDetails():
global accountName, accountUID, accountAddress, accountBalanceBTC, accountBalanceUSD; global wallet, UID, name
wallet = ""; UID = ""; name = ""; stop = False; vals_list_real = []; aString = customDetails.value; castResult = ""
rows = cur.execute('''SELECT * FROM users'''); data = ""
#while data == "":
#readScanner()
#UNCOMMENT ABOVE WHEN PI IS READY
for row in rows:
#print(row)
if stop:
break
else:
vals_list = []
for col in row:
castResult = str(col); castResult = castResult[0:len(castResult)].replace("(","")
castResult = castResult[0:len(castResult)].replace("'",""); castResult = castResult[0:len(castResult)].replace(",","")
vals_list.append(col); arrLen = len(vals_list)
if arrLen == 4:
print(vals_list)
if vals_list[1] == aString:
print("will show all account details for this wallet organized"); stop = True
if stop:
print("value found - showing details for " + vals_list[1] + "!")
scanCard.hide(); customDetails.hide(); useCustomDetails.hide()
wallet = vals_list[3]; UID = vals_list[0]; name = vals_list[1]
accountName = Text(app, text="Account Owner: " + vals_list[1], size=12, font="Times New Roman")
accountUID = Text(app, text="Your Unique UID: " + vals_list[0], size=12, font="Times New Roman")
accountAddress = Text(app, text="Your BTC Address: " + vals_list[3], size=12, font="Times New Roman")
accWif = PrivateKeyTestnet(vals_list[2]); accBal = accWif.get_balance('btc'); accBalUSD = accWif.balance_as('usd')
accountBalanceBTC = Text(app, text="Your BTC Balance: " + str(accBal), size=12, font="Times New Roman")
accountBalanceUSD = Text(app, text="Your BTC Balance (USD): $" + str(accBalUSD) + " USD", size=12, font="Times New Roman")
else:
print("no account found - please try again"); warner.warn("Please Try Again", "We could not find an account with that UID, please try again or go away. Thanks!")
After managing to successfully traverse my database and print out the details of unique accounts, I decided to begin writing the code for the core feature of my interface, this being the ability to automate bitcoin transactions through the scanning of an RFID card. As before, I still did not have the UID's of my RFID cards yet, so instead of having all of the values simply be '000000' for the UID's within my database as before, I changed all of them to unique values to allow for accurate traversals of the 'UID' column of my database. After a match is found for a UID, the bit library will take the address from the second index position of the list that is generated by the code snippet from the above details page code that contains the wIF formatted private key of the UID belonging to the user that is using the interface.
Here's the full function that I wrote that takes the UID of the card that is scanned, checks if it is in my database, and autonomously authorizes a transaction using the information stored in the same row of the database as the UID to the master address.
def awaitCard():
global wallet, name, UID, bString, cardReceived, sending
global sending, transActual, btcFloat, convertThis, accWif, btcFloat, bString
wallet = ""; UID = ""; name = ""; stop = False; cardReceived = False; vals_AMOUNTS = []
castResult = "";
rows = cur.execute('''SELECT * FROM users''')
for row in rows:
#print(row)
if stop:
break
else:
vals_LISP = []
for col in row:
castResult = str(col); castResult = castResult[0:len(castResult)].replace("(","")
castResult = castResult[0:len(castResult)].replace("'",""); castResult = castResult[0:len(castResult)].replace(",","")
vals_LISP.append(col); arrLen = len(vals_LISP)
if arrLen == 4:
print(vals_LISP)
print(bString)
if vals_LISP[0] == bString:
print("will show all account details for this wallet organized"); stop = True
if stop:
print("value found - showing details for " + vals_LISP[1] + "!")
scanCard.hide(); customDetails.hide(); useCustomDetails.hide()
wallet = vals_LISP[3]; UID = vals_LISP[0]; name = vals_LISP[1]
accWif = vals_LISP[2]; local_key = PrivateKeyTestnet(accWif); accBal = local_key.get_balance('btc');
accBalUSD = local_key.balance_as('usd'); cashIntStr = str(accBalUSD)
castIntStr = cashIntStr[0:len(cashIntStr)].replace(".","")
cashFloat = float(castIntStr)
if float(transActual) < cashFloat: #check if account balance exceeds transaction amount
cardNow()
return
else:
print("insufficient funds"); warner.warn("You are an indigent wastrel!", "Insufficient Funds!")
return
elif stop == False:
print("no account found - please try again"); warner.warn("Please Try Again", "We could not find an account with that UID, please try again or go away. Thanks!")
Here's the code that differs significantly from the code that I shared in the previous explanation of the 'check details' feature. This is the part of the code that uses the wIF formatted address to generate a unique PrivateKeyTestnet() object and automatically authorize and sign a transaction for an amount specified by the user in the previous section of the GUI. The following function will only execute under the following conditions: UID is in database, checkBalanceUSD of the wallet exceeds the amount of the transaction being attempted.
def cardNow():
updateBTC()
origBal = masterWalletKey.balance_as('usd')
origInt = int(origBal[0:len(origBal) - 2].replace(".",""))
global sending, transActual, btcFloat, convertThis
global wallet, name, UID, bString, cardReceived, accWif, success
global testButt, testButtBox, poopCount
convertedBTC.hide(); priceBanner.hide(); scanCard.hide(); success.hide()
sending.hide(); testButt.hide(), testButtBox.hide()
print("time to send BTC"); print("SENDING $" + str(transActual)); print(accWif)
sending = Text(app, text="Sent $" + str(transActual) + " BTC. Please come back again soon!", size=20, font="Times New Roman"); sending.show(); img2.show()
master_key = PrivateKeyTestnet(accWif)
#old_balance = master_key.balance_as('usd')
if poopCount == 0:
sends = [
('moZuqP9FR6h4paa6WgWGSddAecDo1P5oZn', transActual, 'usd') #leftover="mtXj1BeXmktkDChJY9BzFzWRe1QFUjETmw", fee=0) #sends transaction amount to master wallet
]
master_key.send(sends)
poopCount = poopCount + 1
print("success!")
time.sleep(5)
newBal = masterWalletKey.balance_as('usd')
newInt = int(newBal[0:len(origBal) - 2].replace(".",""))
anInteger = newInt - origInt
print("TOTAL RECEIVED BY MASTER - $" + str(anInteger))
else:
print("extra loop attempted")
#new_balance = master_key.balance_as('usd')
#diff = old_balance - new_balance
#print("Total Transacted $" + diff)
bString = ""; stop = False
#print("Total Amount Transacted $" + str(new_value - old_value))
#print("successfully transacted " + str(transActual) + " BTC - EPIC")
#success = Text(app, text="Successfully transacted $" + str(transActual), size=25, align="top", font="Times New Roman")
goBack.show()
#time.sleep(30000)
#goBackAmounts() #call go back transact function after 10 seconds no matter what, overridden by button press
Here's the portion of the above function that actually sends the tBTC in the amount specified by the user to the master address.
if poopCount == 0:
sends = [
('moZuqP9FR6h4paa6WgWGSddAecDo1P5oZn', transActual, 'usd') #leftover="mtXj1BeXmktkDChJY9BzFzWRe1QFUjETmw", fee=0) #sends transaction amount to master wallet
]
master_key.send(sends)
poopCount = poopCount + 1
print("success!")
time.sleep(5)
newBal = masterWalletKey.balance_as('usd')
newInt = int(newBal[0:len(origBal) - 2].replace(".",""))
anInteger = newInt - origInt
print("TOTAL RECEIVED BY MASTER - $" + str(anInteger))
else:
print("extra loop attempted")
After this part of the code worked, the entirety of the interface (without my input board and without a functioning casino :( unfortunately) was functioning as intended. The next step was to port my code over to a Pi and then setup all of the serial inputs to make the interface work with my RFID input board that I designed and fabricated during week 11 of Fab Academy. I would also need to modify the code for the RFID module in order to only send the UID over serial without all of the extra 'filler' information in order to make the serial data easier to parse in my Python script, as if I only send the UID over serial, the only thing that can possibly be read by the Pi is a UID instead of inadvertently reading characters that are not actually part of a valid UID. I've included a video below of me navigating the interface and authorizing transactions using temporary UID values that I used for testing. I use all of the features of my interface multiple times to prove that there are no bugs that appear after the first use of a feature and that the use of certain features do not affect the functionality of other features within the GUI. I've also included the entire code at this point in copy-pastable format below. This code will ONLY work on Windows installations with Python 3.8.7 and all of the modules that I've documented the use of throughout this page. Modules can be gathered by searching this page for 'pip install' and running all of the commands that appear in a Python shell or Windows Terminal. I hope you enjoy the following video and the extensive documentation that is included below that details the latter half of my arduous undertaking for this week's assignment.
Code for Windows 10 (No RFID/Serial) I had to divide the following code into several pieces because markdown kept breaking. Simply concatenate the following code segments in the order that they appear in and the code will work properly.
#pip install guizero, requests, threading, beautifulSoup
from guizero import App, Text, TextBox, PushButton, Slider, Picture, Window
app = App(title="Fab Test Interface" , bg = "#C7DBE4")
import requests; import json; import time
import threading; import pyfiglet; import sqlite3; import re
import serial
con = sqlite3.connect('C:/Users/jack/Desktop/fab_gui/SQlite stuff/binanceUSERS.db'); cur = con.cursor() #SQLite connect
from pyfiglet import Figlet; from bit import PrivateKeyTestnet
from bit import Key, PrivateKey, PrivateKeyTestnet
"""
███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
"""
#serial = serial.Serial("/dev/tty50", baudrate = 115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
count = 0; priceInt = 0; conversionFactor = 0
tString = ""; tAmount = 0; failureCounter = 5
failureStr = ""; failArr = ["one", "two", "three", "four"]
amt1 = 5; amt2 = 10; amt3 = 20; amt4 = 50; amt5 = 100
transActual = 0; convertThis = 0; btcFloat = 0; poopCount = 0
wallet = ""; UID = ""; name = ""; data = ""; accWif = ""
def btc_method():
response = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
data = response.json()
return data
def updateBTC():
print("")
global count, btc_price, priceInt, conversionFactor
if count >= 1:
btc_price.hide()
if count == 0:
print("countVarInit")
elif count == 0:
print("Welcome to Binance GUI")
str1 = "Bitcoin Price USD - Last Updated: "
str2 = json.dumps(btc_method()) #cast api json as str
if count == 0:
print(str2) #print json for testing and counting indices
timeLoc = str2.find("UTC", 0, len(str2) - 1)
realTime = str2[timeLoc - 9: timeLoc + 3]
if count == 0:
print(timeLoc) #print index of constant portion of json cast as str with API data
print("Time: " + str(realTime)) #print the actual time using index of calibration point
priceLoc = str2.find('description": "United States Dollar"')
heresThePrice = str2[priceLoc - 15: priceLoc - 6]
priceInt = int(heresThePrice[0:6].replace(",","").replace(".","")) #to be implemented when calculating conversion rate
print("Formatted BTC Price: " + str(heresThePrice)) #print proper formatted price as str, only on first run to reduce loads
print("BTC Price as integer: " + str(priceInt)) #print without commas or decimals without cents AS INT
#if price drops below 10k or above 100k, increment/decrement LOWER BOUND x1 - NOT WRITING THIS LOGIC B/C IMPROBABLE
btc_price = Text(app, text=str1 + realTime + " - $" + heresThePrice, size = 10, font="Times New Roman", color="black", align="bottom")
btc_price.show()
count+= 1
def repeatCall():
threading.Timer(60.0, updateBTC).start()
print("repeatedly updating...")
updateBTC()
"""
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█ ▄▄█ ▄▄▀█ ▄▀███ ▄▄▀█▄ ▄█▀▄▀███ ▄▄█▄ ▄█ ██ █ ▄▄█ ▄▄
█ ▄▄█ ██ █ █ ███ ▄▄▀██ ██ █▀███▄▄▀██ ██ ██ █ ▄██ ▄█
█▄▄▄█▄██▄█▄▄████▄▄▄▄██▄███▄████▄▄▄██▄███▄▄▄█▄███▄██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
░▄▀▒░█▒█░█░▀█▀▒██▀▒█▀▄░▄▀▄
░▀▄█░▀▄█░█░█▄▄░█▄▄░█▀▄░▀▄▀.init()
"""
welcome_message = Text(app, text="Binance Interface", size=40, font="Times new roman", color="orange", align="top")
tAmount = Text(app, text = "Please Select Transaction Amount", size=25, font="Times New Roman", color="black", align="top"); tAmount.hide()
priceBanner = Text(app, text = "test")#below text = shitty debug but it works
convertedBTC = Text(app, text = "test"); scanCard = Text(app, text = "test")
accountName = Text(app, text = "test"); accountUID = Text(app, text = "test")
accountAddress = Text(app, text = "test"); accountBalanceBTC = Text(app, text = "test")
success = Text(app, text = "test"); success.hide()
accountBalanceUSD = Text(app, text = "test"); sending = Text(app, text = "test")
accountName.hide(); accountUID.hide(); accountAddress.hide(); accountBalanceBTC.hide(); accountBalanceUSD.hide()
scanCard.hide(); convertedBTC.hide(); priceBanner.hide()
customBTCprice = Text(app, text = "test"); customPrice = Text(app, text = "test")
customBTCprice.hide(); customPrice.hide(); scanCard.hide(); sending.hide()
accWif = ""; cardReceived = False; bString = ""
def hideAll():
tAmount.hide(); amount1.hide(); amount2.hide(); amount3.hide()
amount4.hide(); amount5.hide(); useCustomAmount.hide(); customAmount.hide()
def say_my_name():
welcome_message.value = my_name.value
def readScanner():
global data
data = ser.read()
sleep(0.03)
print(data)
def test_button():
print("button press detected"); updateBTC()
def cardNow():
updateBTC()
origBal = masterWalletKey.balance_as('usd')
origInt = int(origBal[0:len(origBal) - 2].replace(".",""))
global sending, transActual, btcFloat, convertThis
global wallet, name, UID, bString, cardReceived, accWif, success
global testButt, testButtBox, poopCount
convertedBTC.hide(); priceBanner.hide(); scanCard.hide(); success.hide()
sending.hide(); testButt.hide(), testButtBox.hide()
print("time to send BTC"); print("SENDING $" + str(transActual)); print(accWif)
sending = Text(app, text="Sent $" + str(transActual) + " BTC. Please come back again soon!", size=20, font="Times New Roman"); sending.show(); img2.show()
master_key = PrivateKeyTestnet(accWif)
#old_balance = master_key.balance_as('usd')
if poopCount == 0:
sends = [
('moZuqP9FR6h4paa6WgWGSddAecDo1P5oZn', transActual, 'usd') #leftover="mtXj1BeXmktkDChJY9BzFzWRe1QFUjETmw", fee=0) #sends transaction amount to master wallet
]
master_key.send(sends)
poopCount = poopCount + 1
print("success!")
time.sleep(5)
newBal = masterWalletKey.balance_as('usd')
newInt = int(newBal[0:len(origBal) - 2].replace(".",""))
anInteger = newInt - origInt
print("TOTAL RECEIVED BY MASTER - $" + str(anInteger))
else:
print("extra loop attempted")
#new_balance = master_key.balance_as('usd')
#diff = old_balance - new_balance
#print("Total Transacted $" + diff)
bString = ""; stop = False
#print("Total Amount Transacted $" + str(new_value - old_value))
#print("successfully transacted " + str(transActual) + " BTC - EPIC")
#success = Text(app, text="Successfully transacted $" + str(transActual), size=25, align="top", font="Times New Roman")
goBack.show()
#time.sleep(30000)
#goBackAmounts() #call go back transact function after 10 seconds no matter what, overridden by button press
def awaitCard():
global wallet, name, UID, bString, cardReceived, sending
global sending, transActual, btcFloat, convertThis, accWif, btcFloat, bString
wallet = ""; UID = ""; name = ""; stop = False; cardReceived = False; vals_AMOUNTS = []
castResult = "";
rows = cur.execute('''SELECT * FROM users''')
for row in rows:
#print(row)
if stop:
break
else:
vals_LISP = []
for col in row:
castResult = str(col); castResult = castResult[0:len(castResult)].replace("(","")
castResult = castResult[0:len(castResult)].replace("'",""); castResult = castResult[0:len(castResult)].replace(",","")
vals_LISP.append(col); arrLen = len(vals_LISP)
if arrLen == 4:
print(vals_LISP)
print(bString)
if vals_LISP[0] == bString:
print("will show all account details for this wallet organized"); stop = True
if stop:
print("value found - showing details for " + vals_LISP[1] + "!")
scanCard.hide(); customDetails.hide(); useCustomDetails.hide()
wallet = vals_LISP[3]; UID = vals_LISP[0]; name = vals_LISP[1]
accWif = vals_LISP[2]; local_key = PrivateKeyTestnet(accWif); accBal = local_key.get_balance('btc');
accBalUSD = local_key.balance_as('usd'); cashIntStr = str(accBalUSD)
castIntStr = cashIntStr[0:len(cashIntStr)].replace(".","")
cashFloat = float(castIntStr)
if float(transActual) < cashFloat: #check if account balance exceeds transaction amount
cardNow()
return
else:
print("insufficient funds"); warner.warn("You are an indigent wastrel!", "Insufficient Funds!")
return
elif stop == False:
print("no account found - please try again"); warner.warn("Please Try Again", "We could not find an account with that UID, please try again or go away. Thanks!")
def amount1(): # $5
goBackDetails.hide(); goBackTransact.hide()
global priceBanner, convertedBTC, scanCard
priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
global transActual, btcFloat, convertThis, data, testButtBox, testButt
transActual = amt1
print("Will send $" + str(transActual)) #DEBUGGING
print("will send first specified amount")
updateBTC(); hideAll()
btcFloat = float(transActual / priceInt) #useful variable
custom_fig = Figlet(font='banner3'); goBack.show()
#custom_fig.renderText
figlet =('███████╗ ██╗ ██╗███████╗██████╗\nI██╔════╝ ██║ ██║██╔════╝██╔══██╗\nI███████╗ ██║ ██║███████╗██║ ██║\nI╚════██║ ██║ ██║╚════██║██║ ██║\nI███████║ ╚██████╔╝███████║██████╔╝\nI ╚══════╝ ╚═════╝ ╚══════╝╚═════╝')
print(figlet); print(btcFloat)
convertThis = str(btcFloat)
convertThis = convertThis[0:len(convertThis) - 1].replace(".","") #for removing extra decimal from float cast as str
convertThis = "0.0000" + convertThis[0:7]
print(convertThis)
priceBanner = Text(app, text=figlet, color="black", align="top")
convertedBTC = Text(app, text="Will send " + convertThis + " BTC", align="top", font="Times New Roman", size=17)
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25); data = ""
testButtBox.show(); testButt.show()
transActual = 5
while data == "":
readScanner()
awaitCard()
print("all done")
def amount2():
goBackDetails.hide(); goBackTransact.hide()
global priceBanner, convertedBTC, scanCard
priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
global transActual, btcFloat, convertThis, data, testButtBox, testButt
transActual = amt2
print("will send second specified amount")
updateBTC(); hideAll()
btcFloat = float(transActual / priceInt); goBack.show()
testButtBox.show(); testButt.show()
figlet=(' ██╗ ██████╗ ██╗ ██╗███████╗██████╗ \nI███║██╔═████╗ ██║ ██║██╔════╝██╔══██╗\nI╚██║██║██╔██║ ██║ ██║███████╗██║ ██║ \nI██║████╔╝██║ ██║ ██║╚════██║██║ ██║ \nI██║╚██████╔╝ ╚██████╔╝███████║██████╔╝ \nI╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝')
print(figlet); print(btcFloat)
convertThis = str(btcFloat)
convertThis = convertThis[0:len(convertThis) - 1].replace(".","") #for removing extra decimal from float cast as str
convertThis = "0." + convertThis[1:9]
print(convertThis)
priceBanner = Text(app, text=figlet, color="black", align="top")
convertedBTC = Text(app, text="Will send " + convertThis + " BTC", font="Times New Roman", size=17)
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25)
data = ""
transActual = 10
while data == "":
readScanner()
awaitCard()
print("all done")
def amount3():
goBackTransact.hide()
global priceBanner, convertedBTC, scanCard
priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
global transActual, btcFloat, convertThis, data, testButtBox, testButt
transActual = amt3
print("will send third specified amount")
testButtBox.show(); testButt.show()
updateBTC(); hideAll()
btcFloat = float(transActual / priceInt)
goBack.show()
figlet=('██████╗ ██████╗ ██╗ ██╗███████╗██████╗ \nI╚════██╗██╔═████╗ ██║ ██║██╔════╝██╔══██╗\nI █████╔╝██║██╔██║ ██║ ██║███████╗██║ ██║\nI██╔═══╝ ████╔╝██║ ██║ ██║╚════██║██║ ██║\nI███████╗╚██████╔╝ ╚██████╔╝███████║██████╔╝\nI╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚═════╝')
print(figlet); print(btcFloat)
convertThis = str(btcFloat)
convertThis = convertThis[0:len(convertThis) - 1].replace(".","") #for removing extra decimal from float cast as str
convertThis = "0." + convertThis[1:9]; print(convertThis)
priceBanner = Text(app, text=figlet, color="black", align="top")
convertedBTC = Text(app, text="Will send " + convertThis + " BTC", font="Times New Roman", size=17)
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25)
data = ""
while data == "":
readScanner()
def amount4():
goBackTransact.hide()
global priceBanner, convertedBTC, scanCard
priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
global transActual, btcFloat, convertThis, data, testButtBox, testButt
transActual = amt4
print("will send fourth specified amount -currently + $" + str(amt4))
updateBTC(); hideAll(); testButtBox.show(); testButt.show()
btcFloat = float(transActual / priceInt)
asskey = (''); goBack.show(); figlet = "test val"
print(figlet)
print("Actual send amount: " + str(btcFloat))
convertThis = str(btcFloat)
convertThis = convertThis[0:len(convertThis) - 1].replace(".","") #for removing extra decimal from float cast as str
convertThis = "0." + convertThis[1:9]
print("Amount as string: " + convertThis)
priceBanner = Text(app, text=figlet, color="black", align="top")
convertedBTC = Text(app, text="Will send " + convertThis + " BTC", font="Times New Roman")
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25)
data = ""
while data == "":
readScanner()
def amount5():
goBackTransact.hide()
global priceBanner, convertedBTC, scanCard
priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
global transActual, btcFloat, convertThis, testButtBox, testButt
transActual = amt5
print("will send fifth specified amount")
updateBTC(); hideAll(); testButtBox.show(); testButt.show()
btcFloat = float(transActual / priceInt)
asskey = (''); goBack.show(); print(btcFloat)
convertThis = str(btcFloat)
convertThis = convertThis[0:len(convertThis) - 1].replace(".","") #for removing extra decimal from float cast as str
convertThis = "0." + convertThis[1:15]; print(convertThis)
priceBanner = Text(app, text="test", color="black", align="top")
convertedBTC = Text(app, text="Will send " + convertThis + " BTC", font="Times New Roman")
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25)
data = ""
while data == "":
readScanner()
def customSex():
goBackTransact.hide(); goBack.hide(); goBackCustom.show()
global priceBanner, convertedBTC, transActual, btcFloat, convertThis, customAmount, customBTCprice, customPrice #import global textboxes
global amount1, amount2, amount3, amount4, amount5, scanCard
priceBanner.hide(); convertedBTC.hide(); #hide local textboxes
customPrice.hide(); customBTCprice.hide(); scanCard.hide()
print(transActual); updateBTC(); hideAll()
amount1.hide(); amount2.hide(); amount3.hide(); amount4.hide(); amount5.hide()
btcFloat = float(transActual / priceInt); asskey = (''); useCustomAmount.hide()
customPrice = Text(app, text="Sending $" + str(transActual) + " BTC", font="Times New Roman")
convertThis = str(btcFloat); customBTCprice = Text(app, text = "Transacting " + str(btcFloat) + " BTC", font="Times New Roman")
scanCard = Text(app, text="Please Scan Card", align="top", font="Times New Roman", size=25); data = ""
while data == "":
readScanner()
def amounts():
picture.hide(); welcome_message.hide()
transact.hide(); btcCasino.hide(); accountDetails.hide()
tAmount.show(); amount1.show()
amount2.show(); amount3.show()
amount4.show(); amount5.show()
useCustomAmount.show(); customAmount.show()
goBackTransact.show(); goBackDetails.hide()
updateBTC(); customDetails.hide(); useCustomDetails.hide()
def details():
global scanCard; scanCard.hide()
print("entering display details portal")
scanCard = Text(app, text="Please Scan Card...", font="Times New Roman", size=40)
transact.hide(); picture.hide(); welcome_message.hide(); btcCasino.hide()
accountDetails.hide(); goBackDetails.show()
customDetails.show(); useCustomDetails.show(); updateBTC()
def casinoStart():
print("casino coming soon")
"""transact.hide()
btcCasino.hide()
accountDetails.hide()"""
updateBTC(); customDetails.hide()
def intTest():
updateBTC()
global tString, tAmount, failureCounter, failStr, failArr, customAmount, useCustomAmount, transActual
"""global tAmount
global failureCounter """
print("warn if no ints/chars")
print(customAmount.value) #print value of textbox
tString = customAmount.value #string value for textBox
if tString.isnumeric(): #test if input has non-#'s
print(int(tString)) #print tString cast as int
tAnal = int(tString) #int value for cast non-numerical string
updateBTC(); customAmount.hide(); useCustomAmount.hide()
transActual = tAnal; customAmount.hide(); customSex()
elif failureCounter == 1: #stop program if too many non-numericals submitted to save bandwidth and bc I have nothing better to do with my time
warner.warn("Try again later, moron!", "I gave you five tries and will no longer waste bandwidth on you. Please re-launch the app to try again.")
app.destroy() ##shutdown app
else: #begin decrementing non-numerical counter
warner.warn("It says AMOUNT. Enter an integer, dumbass!", "You entered a value that I do not like, you have " + failArr[failureCounter - 2] + " more chance(s) to do it right!")
failureCounter = failureCounter - 1
print(failureCounter)
updateBTC()
def goBack(): #send page --> amounts
global priceBanner, convertedBTC, amounts, customBTCprice, customPrice, scanCard
global success, testButt, testButtBox, sending, poopCount, img2
success.hide(); testButt.hide(); testButtBox.hide(); sending.hide()
amounts(); priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
goBack.hide(); img2.hide(); goBackTransact.show(); poopCount = 0
print("this")
def goBackDetails(): #go home from details page
welcome_message.show(); picture.show(); scanCard.destroy()
goBackDetails.hide(); transact.show(); accountDetails.show()
btcCasino.show(); customDetails.hide(); useCustomDetails.hide()
accountName.hide(); accountUID.hide(); accountAddress.hide(); accountBalanceBTC.hide(); accountBalanceUSD.hide(); print("this")
def goBackAmounts(): #go back to home from ammounts
global success, testButt, testButtBox
welcome_message.show(); picture.show(); amount1.hide()
amount2.hide(); amount3.hide(); amount4.hide(); amount5.hide(); success.hide()
testButt.hide(); testButtBox.hide() #for when press comes through transaction
transact.show(); accountDetails.show();
btcCasino.show(); goBackTransact.hide(); hideAll()
accountName.hide(); accountUID.hide(); accountAddress.hide(); #accountBalanceBTC.hide(); accountBalanceUSD.hide()
def goBackCustom():
global priceBanner, convertedBTC, transActual, btcFloat, convertThis, customAmount, customBTCprice, customPrice #import global textboxes
global amount1, amount2, amount3, amount4, amount5, tAmount, scanCard; tAmount.show()
convertedBTC.hide(); customPrice.hide(); customBTCprice.hide();
goBackCustom.hide(); scanCard.hide(); goBackTransact.show()
amount1.show(); amount2.show(); amount3.show(); amount4.show(); amount5.show()
useCustomAmount.show(); customAmount.show();
def customUID():
global bString
bString = testButtBox.value;
print("UID to send " + bString)
awaitCard()
def useCustomDetails():
global accountName, accountUID, accountAddress, accountBalanceBTC, accountBalanceUSD; global wallet, UID, name
wallet = ""; UID = ""; name = ""; stop = False; vals_list_real = []; aString = customDetails.value; castResult = ""
rows = cur.execute('''SELECT * FROM users'''); data = ""
#while data == "":
#readScanner()
#UNCOMMENT ABOVE WHEN PI IS READY
for row in rows:
#print(row)
if stop:
break
else:
vals_list = []
for col in row:
castResult = str(col); castResult = castResult[0:len(castResult)].replace("(","")
castResult = castResult[0:len(castResult)].replace("'",""); castResult = castResult[0:len(castResult)].replace(",","")
vals_list.append(col); arrLen = len(vals_list)
if arrLen == 4:
print(vals_list)
if vals_list[1] == aString:
print("will show all account details for this wallet organized"); stop = True
if stop:
print("value found - showing details for " + vals_list[1] + "!")
scanCard.hide(); customDetails.hide(); useCustomDetails.hide()
wallet = vals_list[3]; UID = vals_list[0]; name = vals_list[1]
accountName = Text(app, text="Account Owner: " + vals_list[1], size=12, font="Times New Roman")
accountUID = Text(app, text="Your Unique UID: " + vals_list[0], size=12, font="Times New Roman")
accountAddress = Text(app, text="Your BTC Address: " + vals_list[3], size=12, font="Times New Roman")
accWif = PrivateKeyTestnet(vals_list[2]); accBal = accWif.get_balance('btc'); accBalUSD = accWif.balance_as('usd')
accountBalanceBTC = Text(app, text="Your BTC Balance: " + str(accBal), size=12, font="Times New Roman")
accountBalanceUSD = Text(app, text="Your BTC Balance (USD): $" + str(accBalUSD) + " USD", size=12, font="Times New Roman")
else:
print("no account found - please try again"); warner.warn("Please Try Again", "We could not find an account with that UID, please try again or fuck off. Thanks!")
warner = Window(app)
picture = Picture(app, image="C:/Users/jack/Desktop/fab_gui/fabguiimages/btclogo.png")
img2 = Picture(app, image="C:/Users/jack/Desktop/fab_gui/fabguiimages/img2.png")
amount1 = PushButton(app, command=amount1, text="$" + str(amt1), height=2, width=20) #button init
amount2 = PushButton(app, command=amount2, text="$" + str(amt2), height=2, width=20) #button init
amount3 = PushButton(app, command=amount3, text="$" + str(amt3), height=2, width=20) #button init
amount4 = PushButton(app, command=amount4, text="$" + str(amt4), height=2, width=20) #button init
amount5 = PushButton(app, command=amount5, text="$" + str(amt5), height=2, width=20) #button init
goBackDetails = PushButton(app, command=goBackDetails, text="<-- Go Back", height=2, width=20, align="bottom")
goBack = PushButton(app, command=goBack, text="<-- Go Back", height=2, width=20, align="bottom")
goBackTransact = PushButton(app, command=goBackAmounts, text="<-- Go Back", height=2, width=20, align="bottom")
goBackCustom = PushButton(app, command=goBackCustom, text="<-- Go Back", height=2, width=20, align="bottom")
customAmount = TextBox(app, height=2, width=20) #for filling out custom value
customDetails = TextBox(app, height=2, width=20) #placeholder for cards
testButtBox = TextBox(app, height=2, width=20) #field for custom UID
useCustomAmount = PushButton(app, command=intTest, text="Use Custom Amount", height=2, width=20) #for testing custom amount and using if only ints
useCustomDetails = PushButton(app, command=useCustomDetails, text="use custom details", height=2, width=20) #for testing without UID's
testButt = PushButton(app, command=customUID, text="Enter UID Manually", height=2, width=20)
transact = PushButton(app, command=amounts, text="Do BTC Transaction!", height=2, width=20) #enter BTC transaction interface
accountDetails = PushButton(app, command=details, text="View Account Details", height=2, width=20) #view account details
btcCasino = PushButton(app, command=casinoStart, text="BTC Casino (coming soon)", height=2, width=20) #placeholder for casino
amount1.hide() #hide following for beginning stage
customAmount.hide(); useCustomAmount.hide(); useCustomDetails.hide()
amount2.hide(); amount3.hide(); amount4.hide(); #gif.hide()
amount5.hide(); warner.hide(); goBack.hide(); testButt.hide(); testButtBox.hide()
customDetails.hide(); goBackDetails.hide(); img2.hide()
goBackTransact.hide(); goBackCustom.hide(); transact.show()
masterWalletKey = PrivateKeyTestnet('REDACTED') #info for master address and prints
print("Public Master Address: " + str(masterWalletKey.address)); print("Master Key wIF format: " + str(masterWalletKey.to_wif()));
print("Master Value BTC (satoshi): " + str(masterWalletKey.get_balance())); print("Master Value BTC (actual): 0.0" + str(masterWalletKey.get_balance()))
print("Master Value USD: " + str(masterWalletKey.balance_as('usd'))); print("")
clientWallet1 = PrivateKeyTestnet('REDACTED') #info for first client wallet and prints
print("Client Address #1: " + str(clientWallet1.address)); print("Client Wallet WIF: " + str(clientWallet1.to_wif()))
print("Client #1 Value BTC (satoshi): " + str(clientWallet1.get_balance())); print("Client #1 Value BTC (actual): 0.0" + str(clientWallet1.get_balance()))
print("Client #1 Value USD: " + str(clientWallet1.balance_as('usd'))); app.display()
goBackDetails = PushButton(app, command=goBackDetails, text="<-- Go Back", height=2, width=20, align="bottom")
goBack = PushButton(app, command=goBack, text="<-- Go Back", height=2, width=20, align="bottom")
goBackTransact = PushButton(app, command=goBackAmounts, text="<-- Go Back", height=2, width=20, align="bottom")
goBackCustom = PushButton(app, command=goBackCustom, text="<-- Go Back", height=2, width=20, align="bottom")
customAmount = TextBox(app, height=2, width=20) #for filling out custom value
customDetails = TextBox(app, height=2, width=20) #placeholder for cards
testButtBox = TextBox(app, height=2, width=20) #field for custom UID
useCustomAmount = PushButton(app, command=intTest, text="Use Custom Amount", height=2, width=20) #for testing custom amount and using if only ints
useCustomDetails = PushButton(app, command=useCustomDetails, text="use custom details", height=2, width=20) #for testing without UID's
testButt = PushButton(app, command=customUID, text="Enter UID Manually", height=2, width=20)
transact = PushButton(app, command=amounts, text="Do BTC Transaction!", height=2, width=20) #enter BTC transaction interface
accountDetails = PushButton(app, command=details, text="View Account Details", height=2, width=20) #view account details
btcCasino = PushButton(app, command=casinoStart, text="BTC Casino (coming soon)", height=2, width=20) #placeholder for casino
amount1.hide() #hide following for beginning stage
customAmount.hide(); useCustomAmount.hide(); useCustomDetails.hide()
amount2.hide(); amount3.hide(); amount4.hide(); #gif.hide()
amount5.hide(); warner.hide(); goBack.hide(); testButt.hide(); testButtBox.hide()
customDetails.hide(); goBackDetails.hide(); img2.hide()
goBackTransact.hide(); goBackCustom.hide(); transact.show()
masterWalletKey = PrivateKeyTestnet('REDACTED') #info for master address and prints
print("Public Master Address: " + str(masterWalletKey.address)); print("Master Key wIF format: " + str(masterWalletKey.to_wif()));
print("Master Value BTC (satoshi): " + str(masterWalletKey.get_balance())); print("Master Value BTC (actual): 0.0" + str(masterWalletKey.get_balance()))
print("Master Value USD: " + str(masterWalletKey.balance_as('usd'))); print("")
clientWallet1 = PrivateKeyTestnet('REDACTED') #info for first client wallet and prints
print("Client Address #1: " + str(clientWallet1.address)); print("Client Wallet WIF: " + str(clientWallet1.to_wif()))
print("Client #1 Value BTC (satoshi): " + str(clientWallet1.get_balance())); print("Client #1 Value BTC (actual): 0.0" + str(clientWallet1.get_balance()))
print("Client #1 Value USD: " + str(clientWallet1.balance_as('usd'))); app.display()
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
Porting My Interface to RPi
As my final project will run inside of a confined space, I obviously cannot continue to use a large PC or a laptop to run my interface. As such, I needed to configure a Pi with Python 3.8.7 (the only version that is compatible with all of the modules that are used in my interface, largely due to the lack of support for the Bit library in recent Python builds) on a Raspberry Pi running a base installation of Raspbian. I encountered numerous problems whilst attempting to configure Python 3.8.7 on the Pi, as most of the guides that I followed actually did not end up working on my system. I followed the entirety this tutorial and this one to no avail before this YouTube tutorial successfully guided me through the process of configuring an IDLE environment for Python 3.8.7 on a Raspberry Pi. The installation took a very long time (roughly two hours) given the fact that I had just booted this RPi for the first time several minutes before attempting to install Python, so all of the dependencies that are required for a successful installation of Python 3.8.7 needed to be downloaded because literally nothing had ever been downloaded on this Pi prior to my efforts in downloading Python. This Pi is now loaded with all of the software required for downloading lots of different softwares, which I suppose is one of the benefits of following multiple tutorials before it actually ended up working.
After getting Python 3.8.7 to run on the RPi I needed to install all of the same modules that I used on the Windows version of my interface that do not come in the base installation of Python. These modules included Bit, Json, and Requests, and can all be installed through pip using 'pip install (module name)' if you want to run my interface on a personal machine.
At this point, I discovered that Python 3.7.3 would also work with my code, so I decided to use that instead since I had already gotten that working before and did not feel like installing the modules again for 3.8.7 which would really function the same way as Python 3.7.3.
After downloading all of my modules on the Pi, I continued to experience issues with the download for the Bit library, which was obviously very necessary for my interface to function, so I needed to find a way to actually install the module before I could proceed.
After a brief period of debugging, I discovered that the module likely was failing to install because I was either not installing the module to the correct version of Python, as it is only compatible for several versions of the language. I also discovered that I did not have some of the modules on the Pi that come in the base installation of Python on Windows that are required for the module to successfully install. After changing the version of Python that Pip would download to to 3.7.3, I redownloaded all of the necessary modules that I described above and began working trying to run my interface on the Pi.
After downloading the modules on the Pi, I figured it was time to test the performance of my interface on the inferior hardware of the RPi to see how well it would hold up. I really did not have a contingency plan for optimizing my software if it did not run well, but it did so I ultimately did not need to do anything to optimize my interface as it ran seamlessly even on a Raspberry Pi. The only thing I had to change in my code for this basic build to work were the paths to my database and two images that display at different points throughout the use of the interface. Other than that, the code is unchanged from the one that I shared above that worked on Windows 10.
Interfacing Input Board With My Interface
Next, it was time to begin adding serial reading elements to my interface in order to detect the UID's from RFID cards through my ATTiny1614 input board and transmit them to the Pi via serial. I began by adding some code to my interface that would allow me to read UID's via serial. I had already decided to only print UID's through serial when a card was scanned, so that is why there are no parsing elements in my code, as only UID's are being trasmitted so no parsing is necessary to accurately receive the necessary information.
import serial # import library to read serial
serial = serial.Serial("/dev/tty50", baudrate = 115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1)
def readScanner():
global data
data = ser.read()
sleep(0.03)
print(data)
After writing the above script, I wrote some Arduino code to run on my input board that would be used to only send the UID of the RFID card from the card, excluding all of the excess information that is sent in the example programs that are included in the RFID library. I decided not to modify the example code because of how long it was, so I decided to browse the documentation for the RFID library that I am using to figure out how to print the UID of a card. I've included a video below of the program successfully sending the UID after a brief formatting statement. I later decided to remove the "Card Dected..." print statement from the code to make parsing the data easier on the Pi, as I would not have to remove this statement everytime I checked a UID, and could instead just grab a single line of Serial data each time I wanted to collect data on the Pi.
#include <SPI.h>
#include <MFRC522.h>
#define RST_PIN 11
#define SS_PIN 6
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance.
MFRC522::MIFARE_Key key;
void setup() {
Serial.begin(9600);
while(!Serial);
delay(1000);
Serial.print("We Out Here");
SPI.begin(); //initialize SPI bus
mfrc522.PCD_Init(); //Init MFRC522 card reader
delay(5000);
Serial.print("Test Print");
}
unsigned long getID(){
if ( ! mfrc522.PICC_ReadCardSerial()) { //Since a PICC placed get Serial and continue
return -1;
}
unsigned long hex_num;
hex_num = mfrc522.uid.uidByte[0] << 24;
hex_num += mfrc522.uid.uidByte[1] << 16;
hex_num += mfrc522.uid.uidByte[2] << 8;
hex_num += mfrc522.uid.uidByte[3];
mfrc522.PICC_HaltA(); // Stop reading
return hex_num;
}
void loop(){
if(mfrc522.PICC_IsNewCardPresent()) {
unsigned long uid = getID();
if(uid != -1){
Serial.println(uid);
}
}
Serial.println("test")
}
After writing the code for the Arduino to print the UID's, I would need to parse that data on a Pi in order to receive the UID of the card. For now, this hero shot will satisfy the requirements for the interface week assignment, but I will be returning to complete some further work on my interface during the development phase of my final project in a few weeks. Until then, enjoy the video that I've included below of cards being scanned on my RFID input board and their UID values being printed in a Python shell running on a Pi.
The code that I wrote to read the UID's from the Arduino on the Pi was relatively simple. I simply initialized an instance of the Serial library and read from the port of the FTDI board that I connected to the TX/RX, 5V, and GND pins of my input board. The code simply needed to read a line of serial data each time a new UID was submitted.
import serial
import time
if __name__ == '__main__':
ser = serial.Serial('/dev/ttyUSB1', 9600, timeout=1)
ser.flush()
while True:
line = ser.readline().decode('utf-8').rstrip()
if len(line) > 0:
print(line)
The above video displays the final working version of my interface running externally (without my enclosure that I designed for my final project) whilst receiving input from the input board that I designed during week 11. This was the final state of my interface and input board functioning in tandem to accomplish a truly glorious task of automatically sending bitcoin based on RFID input from the input board.
Group Work
The group assignment for interface week was to research and compare different tools that can be used to create interfaces. For my contribution to this week's work, I conducted the research on various tools that we wrote about on the group site and helped with the writing process. The group site page for this week's group assignment can be accessed here.
Conclusion
The development process of my interface required a significant devotion of time and I learned a significant amount of new applications of Python throughout this process. While my knowledge of basic Python syntax and the interactions with various data structures were relatively good prior to this week's assignment, I learned how to apply various libraries that my previous work has never allowed me to explore. I learned a great deal about the development of intuitive interfaces this week and how to integrate them with physical input devices to accomplish tasks in a digital space. I will be returning to this week's work during the development process of my final project, but for now the above video will suffice.
Files
Because these files are being used in my final project, they are continuously being updated. If you would like to download them, please visit the GitLab repository where they are being stored. Thanks!