Skip to content

Final Project

What Materials And Components Were Used?

The bill of materials that is included below displays all of the parts that are included in the final version of my final project.

Part Price Quantity Location
ATTiny1614 (processor) $0.91 1 Link
3.3V Fixed Regulator $0.55 1 Link
10 uF Capacitor $0.28 1 Link
100 nF Capacitor $0.36 1 Link
1 uF Capacitor $0.13 1 Link
499 Ω resistor $0.10 2 Link
SMD LED (orange) $0.61 2 Link
0 Ω jumper resistor $0.10 3 Link
SMD Header (Male) $0.33 / 6 3 Link
MFRC-522 RFID Module $2.56 1 Link
SparkFun FTDI Chip $15.50 1 Link
Raspberry Pi 4 Model B $75.50 1 Link
rPi Touch Display $69.71 1 Link
PrusaMent PLA $29.99 1 ROLL Link
TOTAL PRICE. $198.20 1 PROJ. N/A

Indexes

Final Slide & Video

Slide

Video

Part 1: Planning Final Project

The planning phase of my final project initially began during the first week of Fab Academy, where my peers and I were tasked with creating a tentative plan for our final project idea that we could hopefully develop elements for throughout the course of the program. I initially planned to create a suitcase that would track its owner using a combination of AI-based image tracking and a Bluetooth module. My documentation and sketches for this original idea are described in extensive detail on my week two page. I decided to change my project after conducting more research about the feasibility of the various elements of my project during week 11. As a following suitcase would require various inputs and a semi-advanced neural network in order to parse through and prioritize the large amounts of sensory data that would be received by the device, I decided to change my project idea to something that was more within the scope of other successful, but still very impressive final projects that have been executed very well in the past. The idea for the final project that I ultimately decided to create stemmed from my disdain for crytpocurrency-based credit cards that are becoming ever more popular throughout the United States. While Bitcoin's purpose is to provide a truly decentralized cryptocurrency that can be transacted without the oversihght of credit card monoliths or large government agencies that generally control currency in its entirety. In my opinion, chipped credit cards that allow for the transacting of Bitcoins through traditional credit card readers that can be found in most retail locations throughout the United States, harming the decentralization of the currency. My aim for my final project was to create a truly decentralized Bitcoin payment client, which uses a separate payment system in order to transact the currency rather than relying on the proprietary system's of credit card giants which are currently successfully monopolizing a currency that was never intended to be transacted in such a way. You can read more about the philosophy behind my final project in my week seventeen documentation.

The planning phase of my final project began with the drawing of several basic sketches, which detailed the rough dimensions of the outer enclosure of the device and several other sketches which display the basic configuration of electronics that I originally wanted to include on my board.

IMAGES OF FIRST SKETCH

IMAGES OF ELECTRONICS SKETCH

The initial plans for the internals of my project included a single RFID input board that would receive the unique UID's of different user's RFID cards that are received when the card is scanned through the small slit that is included in the final enclosure, which I initially planned to 3D print. I planned on networking this input board with a Raspberry Pi that would run all of the code for my interface, which would run all of the code to automate Bitcoin transactions, host my database, check the balances of accounts associated with user's UID's, and various other tasks on its backend. I also planned to network another board to the input board that would control some onboard lighting. I will also attach a small touchscreen LCD for the Pi (size TBD), that will allow users to interact with the final product upon its completion, allowing the device to become a fully portable package that will operate independently for an unlimited amount of time.

After creating the above rough plan for my final project, I began my work for the first element of the project by designing my RFID input board during the 11th week of the program. I began the design process of this component of my final project by launching my PCB design software of choice, EAGLE, and beginning to design a schematic that would eventually be used in a final .brd file to be used in the final milling of my board.

Part 2: Designing and Milling RFID Input Board

NOTE: The work that is documented below was originally accomplished during the 11th week of Fab Academy. For a more detailed explanation of my work for input week, please visit this page of my Fab Academy documentation website.

The first element of my final project that I designe was my RFID input board. For my final project, it needs to detect the scanning of RFID-enabled magnetic cards and detect the unique UID's of the cards and output them to serial. To accomplish this, my board needed a multitude of pins that enable the RFID module that I used to print UID's over serial to the Raspberry Pi that runs the remainder of my project.

I began the process of designing my input board by importing the footprint of the ATTiny328P microcontroller into the schematic design interface that is offered within EAGLE.

pg1

After placing the various components that would be necessary on my board, I discovered that there were many extra pins. Due to this excess of pins, I decided to search for a microcontroller that featured less pins than the ATTiny328P but would still allow the RFID module to function as intended.

pg1

I ultimately settled on using the ATTiny1614, as it featured all of the pins that would allow the RFID module to function and was significantly smaller than the 328, only featuring 16 pins rather than the 32 that are offered on the 1614, making it significantly cheaper per unit and also much easier to solder and route traces for due to the limited number of pins.

pg1

The process of designing the schematic for my ATTiny1614 RFID board was relatively straightforward. I knew that I would need to use several specific pins in order to enable several key features of the device. These included UPDI pins, GND/VCC, TX/RX, and all of the pins that are required for the RFID module that I decided to use. I referenced this guide when researching which pins to connect to my RFID module, as it very clearly describes which connections need to be made in order to allow the device to function properly.

After researching the MFRC-522 that I am using in my final project, I discovered that I needed to route 3.3V to the RFID module rather than the 5V that the ATTiny1614 runs on. In order to reduce the voltage of the 1614 to 3.3V for the module, I needed to add a fixed 3.3V regulator, which requires the addition of a 100nF capacitor on the in-line and 10 uF capacitor on the out-line for voltage smoothing. After connecting the 3.3V trace to my header array that would be used for RFID, the remainder of the process was relatively straightforward, as I simply needed to connect each pin of the 1614 to their respective header pins on the header array that I added to my board.

pg1

After completing the design of the schematic for my input board, I was ready to begin routing the traces of my board that would be used when I exported the .brd file to one of our milling machines. The process of routing the traces is quite simple, as by this point of the program I had already dealt with the board design feature of EAGLE countless times, though in slightly less complicated formats than my input board. Still, the process of designing this board was not incredibly time-consuming, though I did re-route the traces numerous times in order to decrese the usage of 0 ohm jumper resistors, decreasing the total cost of my board as a result of these reductions.

pg1

Next, I needed to route two 0 ohm jumper resistors in order to bring SDA and SCL from the ATTiny1614 to my 4x2 header pin array that would be used on my RFID module once my board was complete. The module will be located on an external board due to the location that it needs to be within my final project to consistently scan RFID cards.

pg1

After routing all of my traces that I had on the board after placing the 0 ohm resistors, I realized that the configuration of traces near the 4x2 was incredibly messy and could be optimized to improve the ease of soldering my board and also mitigate the likelihood of traces ripping off of my board.

pg1

I decided to clean up all of the traces on my board before adding the 2x1 headers for RDX and TDX serial communication that I will be using later on when creating my final program for my final project that will rely heavily on certain features that are uniquely available on the RPi.

pg1

When I designed my board, I used the above schematic as a reference for the routing of my traces through the regulator in order to safely power my RFID module via a 3.3V connection. The voltage regulator's datasheet estimates that the regulator can function for roughly 1,000 hours, which is likely far more time than I will use my input board for.

pg1

I made one board with my inaccurate .brd file, but never programmed it due to my discovery of the regulator schematic/pinout discontinuity that I described above.

pg1

Testing the inaccurate board's 3.3V trace yielded a voltage level of 0.403 V when tested with a multimeter, which is orders of magnitude lower than the desired 3.3V output that I need for my RFID module to function.

pg1

After re-designing my board in EAGLE by re-routing the traces to their correct pins on the regulator by referencing the actual pinout of the regulator that is included in the datasheet for the device, I was left with the above .brd file.

’image’
’pg’

The process of programming my board was relatively straightforward. For the initial testing of the board, I decided to simply use an example code that is provided in the base installation of the library that is designed for the MFRC-522 RFID module which I used to print some basic data about cards when they are scanned by the reader, as there is much more data stored within an RFID card than just the UID, which is really the only thing that I need for my final project. I apologize for the poor quality of this original video of my input board. I am not sure why I decided to orientate my lens vertically prior to recording it, but this is the only video that I have of the original session of me troubleshooting my input board, so I have no choice but to include this very poor-quality video.

/**
 * ----------------------------------------------------------------------------
 * This is a MFRC522 library example; see https://github.com/miguelbalboa/rfid
 * for further details and other examples.
 *
 * NOTE: The library file MFRC522.h has a lot of useful info. Please read it.
 *
 * Released into the public domain.
 * ----------------------------------------------------------------------------
 * This sample shows how to read and write data blocks on a MIFARE Classic PICC
 * (= card/tag).
 *
 * BEWARE: Data will be written to the PICC, in sector #1 (blocks #4 to #7).
 *
 *
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 *
 */

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN          11          // Configurable, see typical pin layout above
#define SS_PIN            6        // Configurable, see typical pin layout above

MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.

MFRC522::MIFARE_Key key;

/**
 * Initialize.
 */
void setup() {
    Serial.begin(9600); // Initialize serial communications with the PC
    while (!Serial);    // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
    SPI.begin();        // Init SPI bus
    mfrc522.PCD_Init(); // Init MFRC522 card

    // Prepare the key (used both as key A and as key B)
    // using FFFFFFFFFFFFh which is the default at chip delivery from the factory
    for (byte i = 0; i < 6; i++) {
        key.keyByte[i] = 0xFF;
    }

    Serial.println(F("Scan a MIFARE Classic PICC to demonstrate read and write."));
    Serial.print(F("Using key (for A and B):"));
    dump_byte_array(key.keyByte, MFRC522::MF_KEY_SIZE);
    Serial.println();

    Serial.println(F("BEWARE: Data will be written to the PICC, in sector #1"));
}

/**
 * Main loop.
 */
void loop() {
    // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
    if ( ! mfrc522.PICC_IsNewCardPresent())
        return;

    // Select one of the cards
    if ( ! mfrc522.PICC_ReadCardSerial())
        return;

    // Show some details of the PICC (that is: the tag/card)
    Serial.print(F("Card UID:"));
    dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
    Serial.println();
    Serial.print(F("PICC type: "));
    MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
    Serial.println(mfrc522.PICC_GetTypeName(piccType));

    // Check for compatibility
    if (    piccType != MFRC522::PICC_TYPE_MIFARE_MINI
        &&  piccType != MFRC522::PICC_TYPE_MIFARE_1K
        &&  piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
        Serial.println(F("This sample only works with MIFARE Classic cards."));
        return;
    }

    // In this sample we use the second sector,
    // that is: sector #1, covering block #4 up to and including block #7
    byte sector         = 1;
    byte blockAddr      = 4;
    byte dataBlock[]    = {
        0x01, 0x02, 0x03, 0x04, //  1,  2,   3,  4,
        0x05, 0x06, 0x07, 0x08, //  5,  6,   7,  8,
        0x09, 0x0a, 0xff, 0x0b, //  9, 10, 255, 11,
        0x0c, 0x0d, 0x0e, 0x0f  // 12, 13, 14, 15
    };
    byte trailerBlock   = 7;
    MFRC522::StatusCode status;
    byte buffer[18];
    byte size = sizeof(buffer);

    // Authenticate using key A
    Serial.println(F("Authenticating using key A..."));
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("PCD_Authenticate() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
        return;
    }

    // Show the whole sector as it currently is
    Serial.println(F("Current data in sector:"));
    mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
    Serial.println();

    // Read data from the block
    Serial.print(F("Reading data from block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Read() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block ")); Serial.print(blockAddr); Serial.println(F(":"));
    dump_byte_array(buffer, 16); Serial.println();
    Serial.println();

    // Authenticate using key B
    Serial.println(F("Authenticating again using key B..."));
    status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("PCD_Authenticate() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
        return;
    }

//    // Write data to the block
//    Serial.print(F("Writing data into block ")); Serial.print(blockAddr);
//    Serial.println(F(" ..."));
//    dump_byte_array(dataBlock, 16); Serial.println();
//    status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, 16);
//    if (status != MFRC522::STATUS_OK) {
//        Serial.print(F("MIFARE_Write() failed: "));
//        Serial.println(mfrc522.GetStatusCodeName(status));
//    }
//    Serial.println();

    // Read data from the block (again, should now be what we have written)
    Serial.print(F("Reading data from block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Read() failed: "));
        Serial.println(mfrc522.GetStatusCodeName(status));
    }
    Serial.print(F("Data in block ")); Serial.print(blockAddr); Serial.println(F(":"));
    dump_byte_array(buffer, 16); Serial.println();
//
//    // Check that data in block is what we have written
//    // by counting the number of bytes that are equal
//    Serial.println(F("Checking result..."));
//    byte count = 0;
//    for (byte i = 0; i < 16; i++) {
//        // Compare buffer (= what we've read) with dataBlock (= what we've written)
//        if (buffer[i] == dataBlock[i])
//            count++;
//    }
//    Serial.print(F("Number of bytes that match = ")); Serial.println(count);
//    if (count == 16) {
//        Serial.println(F("Success :-)"));
//    } else {
//        Serial.println(F("Failure, no match :-("));
//        Serial.println(F("  perhaps the write didn't work properly..."));
//    }
//    Serial.println();

    // Dump the sector data
    Serial.println(F("Current data in sector:"));
    mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
    Serial.println();

    // Halt PICC
    mfrc522.PICC_HaltA();
    // Stop encryption on PCD
    mfrc522.PCD_StopCrypto1();
}

/**
 * Helper routine to dump a byte array as hex values to Serial.
 */
void dump_byte_array(byte *buffer, byte bufferSize) {
    for (byte i = 0; i < bufferSize; i++) {
        Serial.print(buffer[i] < 0x10 ? " 0" : " ");
        Serial.print(buffer[i], HEX);
    }
}

The above code is an example from the Arduino MFRC-522 library that I installed into my Arduino IDE. The library can be downloaded from the base installation of the latest version of the Arduino IDE or from this link.

The last thing I needed to do to finish the work on my input for my final project was to modify the code for the input boar to make it only print out the UID's of the RFID cards rather than the sundry of data that it was previously printing with the example code that I used from the module's library.

#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("test");
  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")

The above program that I modified from one of the other programs included in the examples section of the RFID module allowed me to only print the UID's from the RFID cards, which when printed over serial will tell my Raspberry Pi which user data to use in any given use of the interface that will be running my final project. I've included a video below of the final form of my input board.

After verifying that the input of my final project functions as intended, I was ready to begin designing what became by far the most time-consuming portion, my Raspberry Pi interface that automatically signs and authories BTC transactions, which also includes a secure SQLite database to store user's information that is attached to unique UID's of the RFID's cards that are being used to scan with my input board.

pg1

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

Prior to milling the board that would be used in the final enclosure of my final project, I rerouted several traces to make the design slightly more sensible and efficinet. This did not reduce or increase the cost of the device, excpet maybe in electricity costs because the new file takes slightly less time to mill, but this change is likely infinitessimal and will not be added to my final bill of materials. I've included a picture of the final .brd file below, though a physical version of the board is not pictured below as I have not yet milled this file at the time of writing this section of my documentation. Final photos of the board will be included in the final construction phase of my project that is documented in extensive detail below.

pg1

During the CAD stage of my final project design, which is detailed on this page here. The rest of the information in this section below this point requires knowledge of my CAD, so I recommend that you click the link above or scroll down to the enclosure design section to familiarize yourself with some of the details of my enclosure that I will describe here. Due to the 15mm of space that I left on the bottom part of my enclosure, I would need to install horizontal headers in order to snugly fit them into the snap-fit mount that I designed for my input board on this design and allow space for jumper wires to connect during the system integration phase of the project. In order to accomplish this, I would need to either desolder my current vertical headers or install new horizontal headers onto my board by desoldering all of my header pins. While simply desoldering would have worked had there been no other problems with my board, there were several other small things that I wanted to change to really make my final input board the component of my masterpiece that I want it to be. As such, I returned to my board file and made a few changes to my design that revolutionized several small functionalities of the already incredibly streamlined device.

IMAGE OF NEW FINAL BOARD

I changed several things on this board. I re-routed the headers for the TX/RX pins to allow for more space for the GND header connection on the 6x1 header array that is located on the right side of the board. This will mitigate potential problems that can arise when milling, allowing for a faster production time and less likelihood of bridging traces when soldering. Additionally, I added a data-line connection to an LED which will allow for more advanced testing. The LED that I previously had is connecting to VCC and GND, so it is always powered when the board receives power, which only allows me to check whether or not the VCC and GND lines are fully connected. Adding a data-line to LED allows for troubleshooting of the microcontroller without the need for running more advanced RFID programs or connecting external devices to the various header pins that include programmable data connections. The final re-design of my board is pictured above.

REFLOW DESOLDERING IMAGES

Due to a shortage of 3.3V regulators in our lab, the one that was on my previous input board was the only one left in the CLS FabLab. As a new one would not arrive until the day after my final presentation data, my entire project's functionality was contingent on the successful desoldering of this tiny and fragile component. I decided to use a reflow soldering iron to remove the component from my prevoius input board and attach it to my new one. Fortunately, this worked incredibly easily and efficiently, and I was left with a fully functional, fully detached 3.3V regulator after roughly 30 seconds of heating the component directly with the reflow iron. I've attached various images above that detail the process of removing this component from my old input board.

Part 3: Developing Python Interface

NOTE: This work was originally accomplished during the interfaces and applications week of Fab Academy. For more extensive documentation, please visit this page to read about all of the work that I accomplished for my interface.

pg1

During the fifteenth week of my Fab Academy experience, I worked on the development process of the interface that is used in my final project. This interface is capable of a variety of operations that assist in the functioning of my final project. For example, the interface is capable of authorizing and signing Bitcoin transactions using user data that is stored in a SQLite database that is connected to the main Python script that runs the interface. If you want a more detailed flow chart of how the various components of my project interact with each other, please refer to the more detailed flow chart of how the various components of my project interact with each other that is included in the first part of the planning phase of my final project.

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

To begin the development process of the interface for my final project, I began familiarizing myself with the library that I built my entire interface on top of, the Guizero Python module, which allows for the simple development of barebones interfaces, which is precisely what I wanted to employ in my final project.

After browsing the documentation for the guizero library, 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. I've included some brief examples below that I commented out to display different declarations of guizero objects.

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
pg1

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.

pg1

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()
pg1

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. 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": "&#36;", "rate": "56,356.3549", "description": "United States Dollar", "rate_float": 56356.3549}, "GBP": {"code": "GBP", "symbol": "&pound;", "rate": "39,905.8785", "description": "British Pound Sterling", "rate_float": 39905.8785}, "EUR": {"code": "EUR", "symbol": "&euro;", "rate": "46,571.8209", "description": "Euro", "rate_float": 46571.8209}}}
pg1

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
pg1

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()
pg1

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.

pg1

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

pg1

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.

’wait’
’soon’

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.

pg1

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.

Implementing SQLite

pg1

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.

pg1

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)
pg1

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.

#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
from threading import Thread; import pyfiglet; import sqlite3; import re
import serial; import time
con = sqlite3.connect('/home/pi/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
from multiprocessing import Process
from concurrent.futures import ThreadPoolExecutor
executors_list = []
"""
███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
"""
if __name__ == '__main__':
    ser = serial.Serial('/dev/ttyUSB1', 9600, timeout=1)
    ser.flush()
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()
    print(data)
    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()
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"); poopBool = False
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 = ""; peeCount = 0
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, peePee, peeCount
    data = ser.readline().decode('utf-8').rstrip()
    if len(data) > 0:
        print(data)
        return data
    else:
        readScanner()
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 - double press protection activated!")
    #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 amount1setup(): # $5
    goBackDetails.hide(); goBackTransact.hide(); passes = 0
    global priceBanner, convertedBTC, scanCard, poopBool
    priceBanner.hide(); convertedBTC.hide(); scanCard.hide()
    global transActual, btcFloat, convertThis, data, testButtBox, testButt
    transActual = amt1; data = ""
    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 = ""
    readyToScan.show()
    testButtBox.show(); testButt.show(); 
    transActual = 5
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 == "":
        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 custom():
    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 len(data) <= 0:
        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(); custom()
    elif tString == "": #best conditional in this program :)
        warner.warn("You can't send  over the blockchain, goober!", " funny, so no take chance away, but please enter int next time or I'll tell your mom you said naughty word!")
    elif tString == "hole":
        warner.warn("Your binance interface permissions have been revoked for 1 week!", "You entered a value that I do not like, please come back in one week.")
        app.destroy()
    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, !", "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; readyToScan.hide()
    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(); readyToScan.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(); readyToScan.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)
    readyToScan.hide()
    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 go away. Thanks!")
warner = Window(app)
picture = Picture(app, image="/home/pi/Desktop/fab_gui/fabguiimages/btclogo.png")
img2 = Picture(app, image="/home/pi/Desktop/fab_gui/fabguiimages/img2.png")
amount1 = PushButton(app, command=amount1setup, text="$" + str(amt1), height=2, width=20) #button init
amount2 = PushButton(app, command=amount2, text="$" + str(amt2), height=2, width=20) #button init
readyToScan = PushButton(app, command=readScanner, text="Scan My Card!", height=2, width=20, align="bottom")
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(); readyToScan.hide()
goBackTransact.hide(); goBackCustom.hide(); transact.show()
masterWalletKey = PrivateKeyTestnet('cVoQzorn4itP178J6eGBHJV8nZWc6q64c5bd3rUiqSSZsvzy8iTq') #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('cPFN5ioUBHreupTR9KJG9RN5ewWoKXG5ViqaLi1LupUbk3tXXTyK') #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()
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
pg1

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.

pg1

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.

pg1

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.

pg1

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.

pg1

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.

TRANSACTION FROM VIDEO

Interfacing Input Board With My Interface

pg1

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)

After successfully printing RFID UID's over serial, I needed to integrate my work that I did with my input port and the interface that I detailed the development for above. In essence, I needed to find a way to transfer the UID's that are scanned on the RFID scanner only at specific times to the Pi, and then use those UID's to parse through my database of user information and find the correct row that corresponds to the unique UID of the user whose card is scanned. While this process seeemed relatively straightforward when I first began this process, as I knew precisely what I wanted to write prior to writing this integral feature, I ultimately had to modify many features of my project in order to resolve the many problems that I encountered with this portion of my code. All of my troubleshooting for this portion of my project is documented below in extensive detail. This final portion of my project allowed me to finally complete the majority of the software-side of the project.

Part 4: Integrating Input Board with Interface

The process of integrating my input board with the Python interface that I developed was relatively straightforward. My electronics for this portion of my final project consisted of my input board, an RFID module that is connected to the input board, an FTDI chip that is connected between the input board and the Pi which powers the Pi and reads serial data from the input board and sends it to the Pi, and a Raspberry Pi 4 Model B. Successfully integration of these components would be marked by the successful authorization and signing of a Bitcoin transaction using a magnetic RFID card as an input with my input board and sending a transaction using the details associated with that card that are stored in the database on the Raspberry Pi, all of this resulting from the navigation of my interface by a user. I began this process by ensuring that the serial communication of the UID's of the RFID cards was functioning as intended, as the entire functionality of my final project is contingent on the consistent functioning of this scanning mechanism. The wiring for this process was relatively simple, and I've included a basic diagram below that details the various connections that needed to be made to accomplish this task. The video of this aspect of my project working also provides a visual of the connections that were made between various components of the project. The video that is attached below displays the successful writing of UID's to the Serial port of the Pi after they are scanned by the RFID module on my input board and transmitted through the FTDI chip to the Pi via USB.

pg1

After getting the UID's onto the Pi, I needed to tell the Pi what to do with these UID's. First, I would need to check if the UID existed somewhere in the "UID" column of my database. This process is pretty simple, and simply requires iterating through the desired column until a match is found or every row has been searched. The code that I wrote to accomplish this portion of my project is included below.

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!")

The above code creates a local list in Python for each row of my database and checks the 0th index position for a match with the UID that is scanned by the Pi. The code for the definition of the UID exists elsewhere, but you can see the global definition of the "UID" variable in this function, which is obviously where the UID is stored after it is received by the Pi from the FTDI chip which receives the UID from the input board which receives the UID from the on-board FTDI chip. There is some extra logic in this function that does not simply parse through a row of the database, but I will explain the rest of this later if I have time, for now I am simply detailing the process of integrating my input board with my interface and not delving too deep into the extensive back-end of my interface.

After I successfully stored the UID in my Python code, searched my database for a matching entry, and grabbed all of the necessary data from the matching row of my database, I needed to accomplish Bitcoin transactions using the information received. As all transactions are routed to the same master wallet that is defined in the interface's code, I simply needed to automate transactions to this static master address. I accomplished this by creating a PrivateKeyTestnet object using the wIF formatted address of the matching row's WIF column and using various variables that are defined throughout the process of running the interface to determine the amount of BTC sent in the transaction. I've attached this code below.

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 - double press protection activated!")

One interesting problem that I debugged with the conditional exit at the bottom of the above code is the ability to destroy my interface by rapidly pressing on the 'enter UID' button in my interface. Doing so would repeatedly send money to the master wallet. In a real-world application of my device, this would be a positive for the reatiler and not the consumer, removing the incentive to abuse my interface in this manner, but I still felt that I would add some protection against consumers to prevent this from happening if they just feel like pressing the button a few extra times whilst interacting with my interface.

Once both of the above features were ready to go, I began testing my interface by connecting every electronic component, navigating through the interface, and scanning my RFID cards on the included scanner. I've included a video of the seamless operation of these various systems below as well as a link to the Bitcoin Testnet transaction that was created during this usage of my device. After this portion of my project was completed, I simply needed to place everything in a well thought-out enclosure with a smaller LCD screen to finish my work for the first version of my final project. As you will see below, this was by no means a simple task, but I still felt like the most difficult portion of my work was behind me after finishing this section of my work.

Part 5: Designing and Fabricating Enclosures

For the next part of my work on my final project, I needed to design an enclosure that would securely store all of my electronics and other features of my project inside. I wanted my final enclosure to consist of several key parts, including a large box to place all of the electronics in and various small features to secure boards, route cables, and mount other devices inside. I also wanted to add various unique design elements to the outside in order to add an extra amount of uniqueness to the otherwise relatively bland outer shell of the device. I began my work by designing all of the smaller components in Fusion that would eventually be placed inside of the larger outer shell fo the final device. I decided to 3D print my entire enclosure for several reasons. First, 3D-printing the device, as opposed to laser cutting it, would ensure the rigidity and stability of the enclosure, which is incredibly important for the long period of time that I intend to use this device for. Additionally, 3D-printed boxes are much more aesthetically pleasing than those that can easily be designed for laser cutting, as printing one contiguous body rather than cutting various components of a laser cut part and then assembling them is far more appealing. My project also features lots of small components on the interior of the main body. If I laser cut the design, I would have to either glue or screw in the cable ties and press fit board mounts that are going to printed during the same job as the remainder of the enclosure. Ultimately, given the scope and the purpose of my project, 3D-printing the device makes the most sense.

’image’
’pg’
pg1

I began the design process for the enclosure of my final project by first designing several snap fit cases for the various electronic components that are included in my project. The four boards that I am using and have designed cases for include my input board, Raspberry Pi, RFID module, and FTDI chip. I designed all of these cases in separate Fusion files and concatenated all of them in the final enclosure design that is documented at the bottom of this section of my final project documentation page. I began by designing a case for my input board. In order to accomplish this, I used calipers to measure the width and length of the board and created a rectangle in Fusion using dimensions slightly longer than those measured to allow for easier snapping and account for any errors in the caliper measurements. I also measured the height of the highest header pin to ensure that it was less than the 15 mm vertical space that I planned on including at the bottom of my final enclosure to allow for the easy mounting of boards to the bottom of the enclosure. After creating my rectangle with the proper dimensions, I used the offset tool to create another rectangle that would ultimately become the sides of the snap fit case. Next, I extruded the outer offset rectangle to a height roughly 3 mm above the extruded height of the inner rectangle, creating a small space for my board to snap snugly into place.

pg1

After exporting my final case file as an STL and generating GCODE for the Prusa MINI in the Prusa Slicer, I printed my file. The print time for this case was roughly 30 minutes on the most optimal quality settings for the MINI. After printing my case, I was ready to test it by attempting to snap a defunct prototype of my input board into the case.

pg1

Ultimately, this test print came out rather well. As the dimensions for my final input board are the same as the one that I tested on, I did not need to perform any further modifications to my file, and could ensure that this part would work when included in the design of my final project's enclosure.

pg1

Next, I began designing a snap-fit case for the FTDI chip that will be placed within the printed enclosure of my final project. For this component of my design, I would need to extrude rectangles to different heights within the case due to the strange configuration of the chip. To accomplish this, I created a rectangle width the actual dimensions of the entire FTDI chip. Next, I created a rectangle using the offset tool that wraps around the original rectangle, allowing me to create walls that will snap the FTDI chip into place. After this, I divided the original rectangle into two sections: one with the space that the header pins occupy and another with the space that is not occupied by the header pins on the chip. After doing this, I extruded each piece to its necessary height and created a bar across the header snap-in to increase the stability of the device and prevent it from inadvertently snapping out of place during use. I sliced my design in the Prusa Slicer on high detail, giving me a rough print time of 14 minutes and exported the GCODE to a Prusa MINI. After printing the snap-fit case, it worked perfectly with the FTDI chip that I plan on using in my final project.

meshfinal12
meshfinal13
pg1

After designing a working case for the FTDI chip that will be placed within the enclosure for my final project, I needed to design a small snap-fit case for the final electronic component of my final project, the Raspberry Pi 4 Model B that will run the interface and process all BTC transactions that happen with the usage of my device. The process of designing this case was relatively straightforward. I simply pulled the dimensions of the Raspberry Pi from online sources, created a rectangle with slightly greater dimensions than the dimensions of the Pi, used the offset tool to generate another rectangle that would be extruded into the walls of the device, and extruded the central rectangle and outer perimeter to the necessary heights. Slicing this model yieleded a print time of roughly 1 hour on speed settings due to the size of the device being slightly larger than those of the smaller FTDI chip and input board that I previously designed cases for. After printing the basic case that I designed for the Pi, I snapped it into place snugly, letting me know that I had printed a working form of my Pi case. The pictures that I've attached above display this simple case that will eventually be included in the final design of the enclosure for my BTC ATM.

pg1

After completing the design for all of the larger elements of my final enclosure, I was ready to begin designing the actual enclosure itself that would contain all of the cases and components that I detailed the design process for above. I wanted to make the device as compact as possible, whilst leaving ample space for all of the electronic components and room for organized cable management during the final construction phase of my project, as that is one of the major requirements for this assignment.

pg1

I decided to make the dimensions of my device 150mm x 100mm so that it would have room to contain all of the electronic components on the interior base and sides of the device. Making the device these dimensions would also allow it to be printed on our lab's Prusa MINI printers, which we have significantly more of in the lab. This was especially important as during the few weeks that I spent developing the physical components of my device, various summer camps were also operating within the same lab space that I was using, and the printers were frequently occupied by young students spending significant amounts of time printing trinkets on ultra highly-detailed settings, necessitating the usage of the smaller printers in order to rapidly iterate on my design for the enclosure of my device. As the base and the upper component that the LCD is mounted in would take up too much vertical space, I divided the design phase of my enclosure into two phases, the top half and bottom half of the device, and decided to connect them with simple vertical tabs that I decided to print on each component, allowing for easier access to the many small components that are included inside of the device and allowing for easier cable management as well, which will be hugely important during the final construction phase of the project.

pg1

I extruded the footprint that I designed to provide a rough idea of what my final print would look like. I was satisfied with these dimensions, so I continued the design process of the other more intricate elements of the final design for my enclosure that will be included in this box. I eventually conducted various aesthetic improvements to the outside of the enclosure to make it look like less of a simble box and boost it to my incredibly high standards for 3D-printed enclosures.

pg1

I added the snap-fit case for the FTDI chip that I designed to the base of the interior of my enclosure. I split the bodies of the base in order to increase the ease of viewing this element of my design.

pg1

Next, I added a 45 degree extruded triangle with the dimensions of the RFID module that will be place next to the hole for cards to be swiped with. The RFID module needs to be placed in incredibly close proximity to the cards when they are scanned in order to ensure consistent and accurate scanning of the RFID cards, which is obviously one of the most integral components of my design. I designed the snap-fitting for the RFID module in away that would allow the header pins at the back of the device to hang over the rectangle where the module is mounted. Doing this would allow me to route cables from my input board, which is mounted on the bottom of the device in this iteration of my CAD, to be routed directly upwards and into the male connectors of the headers instead of needing to be routed downwards onto the header pins if they were in a standard vertical configuration. Doing this improves the simplicity of the cable routing and improves the ease of managing wires once I place all of my devices and other wires into the device. Because my model is being printed on our lab's Prusa I3's and MINI's 45 degree overhangs can print without the need for supports, making this the most efficient close-proximity mounting option for my RFID module.

pg1

As the two sections of my model make the completed design quite tall (too tall to be printed on a single printer), I decided to split the design into two different halves. In order to connect the two pieces together, I decided to add some tabs along the perimeter of each component that would allow them to slot together snugly after printing each piece. To accomplish this, I drew a simple 3x5mm rectangle on the top of my base and extruded it down 5mm, I then extruded a rectangle of slightly smaller dimensions on the triangular upper piece to create tabs that will slot into these. One of the major design decisions that I made during this process was my choice to omit a sliding mechanism. While the CAD for a sliding mechanism likely would have taken less time than the tab system that I designed, I feared that users would accidentally slide the top off of my design during use of the interface, as repeated tapping motions could potentially dislodge the upper piece from the sliding mechanism in the lower portion of the design. Tabs prevent this from happening altogether, making them a much smarter choice for this application. Accidental sliding could have been potentially disastrous for the entirety of my design, as pulling the LCD would pull the Pi which would pull the FTDI which would pull the input board, potentially ripping traces on this fragile custom board and necessitating the replacing of components that require lengthy processes to re-fabricate. Making the design two halves also allows for more rapid protoyping of individual components and faster times to completed designs. If I notice a problem on the bottom part of the design, instead of needing to re-print the entire model, I can simply re-print the bottom half. Obviously, I do not want to do this repeatedly for such a large model, which is why spending significant amounts of time in CAD thinking through each design decision is crucial with such large prints, but it is still significantly more efficient with two bodies opposed to a single large body.

pg1

At this point, the final model for the first iteration of my base was completed, I exported the bodies of this portion of my design as an STL and opened the file in the Prusa Slicer. I sliced the model on speed settings, as classmate and Prusa enthusiast, Teddy Warner explained to me that the difference between speed settings and more precise quality prints is marginal with such large models, oftentimes actually reducing the quality of a print rather than increasing it with higher print times and more material usage. I sliced my model for a Prusa MINI, with 0.20mm of infill and a gyroid layer pattern to ensure maximum stability opposed to the typical grid layer generation structure that I have used in the past for smaller models such as the ones that I printed during the testing phases of the various snap-fit cases whose design processes I detailed above.

pg1

Slicing the model revealed a print time of 18 hours. I could not really do anything more to optimize this print time, so I exported the GCODE to one of our lab's Prusa MINI's and waited 18 hours for the model to finish printing. I will describe the post-printing process after my documentation for the top piece below so that I can immediately transition into what I did after my first models printed, but I've included an image directly below that displays what the final printed model looks like. I noticed various errors with this design after it printed so I did eventually end up re-designing it, but this first version of my design was incredibly useful to visualize my final project in a physical form and test the various components of the design that I added to the enclosure.

pg1 pg1

I did run into a problem with the fitment of the bottom component of my design after printing the top part whose design process is detailed below. As you can see in the above image, the two printed pieces of my project did not fit together perfectly due to errors that I made in Fusion with the dimensions of the tabs. I could have sanded the tabs on the triangular piece to force them into the bottom piece, but I wanted the final build of my final project to truly display my mastery of digital fabrication, and as such decided to re-design the tabs and restart the entirety of the 18 hour print due to this one small error. Rome wasn't built in a day, nor was the final enclosure for my project printed in a single print. I simply took the sketch of the rectangles that I originally extruded into the box to create the tabs and used the offset tool to slightly increase their area, then extruded the new rectangles downwards into the box to the same 5mm depth that I had used for the previous tabs, as I would not have to re-design the top part by extruding the same distance and extruding any more or less would not have granted any quantifiable merit to the structural integrity of my enclosure. As I had printed the top component at the time of this re-design, I also made sure to use the same color filament to ensure the continuity of filament color on my final project, a small oversight that I made during the printing of the upper triangular piece. Had there been no other egregious errors made during the CAD phase of my final project, I likely would have accepted a two-tone filament configuration, but I figured that I might as well load the same color filament into one of our printers if I were already going to re-print my modified design anyways.

pg1

Re-designing my enclosure meant that I also had to re-slice the model in the Prusa Slicer to generate gcode to print it on one of the Prusa MINI's in our lab. Slicing the model revealed a print time of roughly 19 hours. I exported the gcode and began the print. The image of the final assembled device is included below the following documentation on the design process of the upper triangle piece that I mounted my screen to.

The design process for the second half of my design was relatively simple compared to the first part, as it really only includes once component, this being a cutout for the 7" LCD that I will mount on the device. The Amazon link to the 7" LCD that I am using in my final project can be found here.

An image of the LCD and its dimensions is attached above. The device measures 3.94 x 2.99 x 0.79 in.

pg1

In order to provide the best user experience possible for my device, I wanted the part of my enclosure with the screen mounted to it to sit at 45 degrees in order to allow for maximum ease of viewing the interface that will be running on the screen. I began the design process for this component by drawing a rectangle over the dimensions of my completed base and creating a 45 degree triangle on the Y plane using the base of the rectangle that I drew as the base of the triangle.

pg1

I used the dimensions of the LCD that I found on Amazon to draw the mount for the LCD. I wanted the LCD to sit flush with the triangular top of the design, so I added small supports along the perimeter of the large rectangle for the LCD that would be used to hold it in place. Unfortunately, my LCD never arrived due to numerous shipping delays with the various carriers that Amazon put it on, which is why my substitute LCD will look strange in the images of the final assembly of my project. This problem certainly was out of my control, as the part was ordered with over two weeks remaining until the presentation day of my project and estimated a shipping time of just two days, though it still has not arrived 17 days after the initial placing of the order. After, I added the supports for the LCD, the CAD for this component of my design was complete, now all I needed to do was slice the model and print it out to have a completed physical model of my enclosure.

pg1

After exporting my design from Fusion 360 and importing it into the Prusa Slicer, I sliced the model using my gyroid infill speed settings that I like to use for large prints because it really does not compromise on print quality but optimizes the speed and material usage of large prints that generally consume a lot of the aforementioned resources. The slicer revealed a print time of 13 hours, which is very good considering the size of the model. After exporting the gcode from the slicer and starting the print, the model successfully finished printing 13 hours later.

pg1

The finished top piece printed as planned, revealing a large structure and a hole to mount the LCD when it eventually arrives. (6/20 - Still hasn't arrived)

pg1

After finishing the prints for both the top and bottom pieces of my enclosure, I snapped the two parts together, revealing the full body for the final enclosure of my cryptocurrency ATM. At this point, the only component of my final project remaining was the final assembly of the device, which is documented in extensive detail in the sixth and final section of the documentation for my final project.

Part 6: Final Elements

For the final portion of my project, I decided to create a vinyl sticker using one of the vinyl cutters that we have in the CLS FabLab as my project previously did not feature a subtractive design element. I decided that I wanted to break up the constant blue of the front structure by adding a white vinyl sticker of the BitCoin logo to the front of my box. While I intended for this task to be relatively simple, it certainly was not, and required me to explore the usage of a Fusion add-on called Shaper that proved immensely useful in the arduous process of exporting some of my sketch geometry from Fusion to complete this task.

I began the process of exporting my sketch geometry by attempting to use the project tool to project the logo that I wanted to export into a new sketch and then export the new sketch as a .DXF file that could then be imported into Silhouette's proprietary toolpath generation software and exported directly to a cutter. This process ultimately failed when I discovered that the geometry that I wanted to export was attached to various other components of my design that I did not want it to be attached to, and attempting a selection of just the section of the logo that I wanted would always select the components of the design that I did not want to export from Fusion along with the portion of the design that I actually did want to export. I tried various other tools to no avail.

pg1

As one of our instructors, Adam Harris, was visiting the CLS lab on this day to assist with the development of final projects, I decided to see if he knew of a potential solution ot my problem. Fortunately, Dr. Harris provided me with an incredibly useful extension that can be installed into Fusion 360 that is known as the Shaper Tool. Shaper functions like any native Fusion tool, but is selected in the 'tools' menu of the 'design' platform within the software as opposed to in the tab shere all of the other tools are generally stored. The Shaper tool allows for the selection and exporting of individual geometries, allowing me to export my Bitcoin logo friom Fusion as a .DXF file in just a few seconds.

pg1

I exported the sketch that contained the correct logo geometry from Fusion 360 as a .DXF file, which is a 2D file format that is accepted by the Silhouette Studio software for the cutting of vinyl stickers.

pg1

I imported the .DXF file of the logo into Silhouette Studio and began preparing to cut my sticker.

pg1

I decided to use the trace tool in the Silhouette Software to generate toolpaths for the vinyl cutter along the outer edges of the logo.

pg1

After tracing the logo, I exported it to the machine attached to the Mac that I was using the Silhouette software on and awaited the cutting of my sticker. After the sticker was complete, I used transfer tape to collect the sticker from the vinyl sheet that it was cut on and placed it in the correct position on the enclosure for my Bitcoin ATM.

pg1

Here's what my sticker looked like attach to the ATM. At this point, my requirement for the implementation of a subtractive design process in my final project had been satisfied, and I was ready to focus on the final assembly of my device. While everything that I planned on making work, with the exception of the LCD for the Pi as it had not shipped from DigiKey yet, was working, so this portion of the process simply entailed cramming everything into the box that I designed and hoping that it still worked once I turned everything on. The process was slightly more streamlined than what was described in the previous sentence, as the cable clips and embedded snap-fit cases that I designed for my 3D-print did help out a lot with the routing of cables and boards throughout my device.

After finishing the subtractive element of my final project, I was ready to begin assembling all of the internals of the device. Fortunately, this process was not that difficult as I designed many features within the 3D-printed enclosure that aided this process. This process was extended as I filmed many clips for my final project video that I presented to Neil during the presentation of my final project during the assembly phase of my project. I used all of these videos to create a cool "Michael Reeves-esque" cut of tossing my internals into their cases and then cutting to a fully assembled version of my project. My video is attached at the bottom of the page if you're curious about how this feature of my video ultimately turned out.

IMAGE OF FTDI IN CASE

I decided to begin the installation process by starting from the bottom of the case and working my way to the top, then coming back to complete all of the wiring again moving from bottom to top. I began by situating my FTDI in its snap-fit mount. This process was relatively simple, as I knew how to situate the board in its mount from the testing that I conducted during the development phase of my final project.

pg1

The next board that I mounted was my Raspberry Pi 4 Model B. Again, the process of mounting this component was relatively simple due to the testing that I performed with this microcomputer whilst designing all of the CAD for my project.

pg1

Next, I decided to route the Mini USB cable from the FTDI to the Pi, as I knew that the USB ports connected to both the Pi and FTDI would become much harder to access after more boards and more wiring was completed within the enclosure.

As the only Mini USB cables I could find were incredibly long, I needed to shorten one by cutting off the majority of the interior of the cable and then soldering the correct lines of the cable back together once the proper length had been achieved. This proces was somewhat time-consuming, but I did manage to successfully shorten the cable on the first try after about 10 minutes. The hyperlapse above displays the full length of the process of shortening this cable.

pg1

Next, I mounted my RFID input board into the snap-fit board case that I designed during the testing phase of my final project. The snapping of this board went seamlessly, but I made sure to be extra careful with the board to avoid inadvertent snapping of this board that generally takes me upwards of one hour to mill, solder, and debug each time I make it.

pg1

Next, I decided to route the 5V, GND, and TX/RX connections from the FTDI board to my input board as these connections would be incredibly difficult to make later on. During this process, I accidentally pulled the pad of my TX line on my input board, but managed to salvage the board by re-fastening the header with a large glob of solder that I connected to the TX trace on the board. Fortunately, I did not experience any issues with this connection throughout the remainder of my work on my final project, which certainly saved me a lot of time.

pg1
pg1

Next, I decided to mount the final component of my network, the RFID module, in its snap-fit mount beneath the slit that I designed for card insertion. The headers lined up perfectly with the edge of the mount, meaning wiring was incredibly simple from the headers on my input board to the headers on the RFID module.

pg1

Next, I wired the RFID module to the input board, referencing my board file and the marked headers on the RFID module to ensure that all of the connections that I made were accurate. After this, all of the internals for my project were mounted inside of the enclosure, with the exception of the LCD which still had not arrived at this point. Because there was very limited time remaining before the presentation of my final project, I needed a solution, so I decided to substitue in a larger rPi touchscreen that did not fit in the mount on the triangular upper piece of my enclosure just to have a fully embedded system that did not require a monitor or external peripherals and could begin operating as a fully-indepdendent device.

pg1

The wiring of the LCD was incredibly simple. I simply routed the ribbon cable from the display ribbon port on the Pi to the ribbon port on the back of the LCD's mainboard and connected the 5V/GND of the LCD's mainboard to their respective GPIO pins on the rPi 4, whose pinout is the same as the Pi 3's which I pretty much have memorized in its entirety over the past few years of my life.

pg1

I mounted the LCD with Nitto Tape behind the screen as it did not fit in the mount that I designed for the LCD that never shipped. I eventually want to create snap-fit mount for this lcd in a re-designed enclosure, but doing so in the final couple of days for my final project would not have been possible due to the large print times of my enclosures, and would have also necessitated various other modifications to each part of my enclosure that would have likely caused me to not complete my project in time.

After all of the wiring for my system was completed, it was time to begin testing the device as a fully indepdent system, which meant detaching everything except for the power and ethernet connection from the device and interacting with my interface through the onboard, slightly oversized, touchscreen that I mounted to the front of the triangular component that was initially intended to hold the properly-sized LCD in place.

It worked! The above video marked the first fully successful test of my transaction system. I've included a more in-depth video of the project working lower down on this page, but the above video was the first time the project worked as-intended without failure.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

How was my project evaluated?

During the presentation of my final project, Neil approved of my project and did not advise the revision of any elements of my project. The video during which I presented to Neil is attached below. As Vimeo does not allow videos that do not belong to you to be embedded, the link below will take you the the video of my presentation. I present at 1:07:48.

Link

Conclusion

It's hard to sum up the past six months of my work on Fab Academy in a single paragraph. While I will receive a diploma that represents the countless hours of hard work that I put into the program, I think the vast majority of the value that I ultimately extract from my participation in the program will be the various skills that I acquired and some of the values that the rigor of the program instilled upon me. I learned how to use a preponderance of softwares and machines throughout the course of the program, providing me with the capability to fabricate (almost) anything on my own, which is a super unique and valuable ability and definitely worth all of the time that I have put into the program on its own. Additionally, Fab Academy taught me about the importance of hard work. Prior to my participation in Fab, I never needed to devote a significant amount of time to any academic challenge in order to succeed. Fab caused a pivotal change in this dynamic, as the program is unique in that it truly is boundless in terms of what each individual student can accomplish during the assignment for any given week, meaning that the value of the program is unique to each student and is a function of the amount of time they're willing to put into the program. Some of the most fun I had during the program was during the few weeks where I took very deep dives into concepts that I was previously unfamiliar with, taking it upon myself to master the intricacies of new abilities that I found incredibly interesting or applicable. Overall, Fab is certainly something that I would recommend to other aspiring engineers and makers who are looking for a challenging but immensely valuable course that covers nearly every facet of digital fabrication in a concise but in-depth manner, and I am incredibly grateful for my instructors and parents for providing me with the to do Fab.

Final Project Files

All of the project files for my final Fab Academy project are stored within my GitLab repository. To make the process of downloading my files easier, I've stored the physical design files and code in separate locations. Download links for both are included here. To download my code, please click here. The total storage of all of my code for my final project totals roughly 100 KB. For an entire Python interface, Arduino code, and database this is relatively small, in part due to the incredibly small size of my database, as it currently only includes five rows of unique users. All of my CAD files are stored here. The total storage size of my CAD for my final project is 1.2MB and 328KB compressed. I've decided to store the compressed version of my CAD on my site in order to save storage.


Last update: June 27, 2021