9.5 - Implementation

9.5 - Implementation

CAD Design:

Before beginning any step in the fabrication process, the entirety of the design was modeled and assembled in SolidWorks to ensure that all components would interact and connect properly. The final CAD assembly is shown below in Figure 1.

Figure 1: Final Solidworks Assembly of Mechanism.

Fabrication And Assembly:

We employed the use of two different fabrication techniques for this project, the first being laser cutting. The parts that we chose to laser cut include all linkages, the two baseplates, the motor mount, the base spacers, and the gears. These parts are shown below in Figure 2. We cut these parts out of 6 mm acrylic using one of the Trotec Rubys at Texas Invention Works. We chose to laser cut our materials primarily due to the complex nature of our design. We are employing the use of six unique gears along with nine linkages that all need to mesh together perfectly in order to achieve proper motion. The laser cutter is able to create the very specific geometry we need for our parts not only accurately but repeatably. On top of this, all of these parts are two-dimensional, so a laser cutter is a perfect choice. We decided to use acrylic because it is very strong while also being transparent. The aspect of our design that we are the most proud of is arguably our gear train, and if we were to use a material like wood, this aspect would be hidden after assembly is completed.

image-20250501-180110.png
Figure 2: Laser Cut Components of the Build

The second fabrication technique we employed was 3D printing. This method was perfect for our components that had more three-dimensional features, such as the mouse mount, linkage spacers, and base feet. All of these components are shown in black in Figure 1, with the exception of the mouse mount, which is shown in more detail in Figure 3. All prints have a 20% infill and were printed from PLA filament.

image-20250501-180447.png
Figure 3: 3D Printed Mouse Mount

The assembly process was relatively simple. All components had geometry that allowed them to slot together, but Gorilla two-part epoxy was used to secure these connections. Bearings were placed in their corresponding spots in both the base plates and the linkages. They were held in place both via a press fit and by being sandwiched in place by washers and 3D printed spacers. The only hardware we used were M6 threaded rods, which were cut to size with a Dremel and then had washers and nuts secured to their ends, again with epoxy. The epoxy was also used to secure the spacers to the M6 rods in order to translate their rotational movement to the gears and linkages. The motor was fastened onto the motor mounting plate with six M3 screws, and the shaft was coupled with the shaft of the input gear using an M6-to-M6 coupler. The final connection was the mouse to the mouse mount, again shown in Figure 3. The mount utilized compliant geometry in order to clip onto the mouse; however, rubber bands were also used to further secure the mouse in place. The final assembly, minus the mouse and motor, is shown below in Figure 4.

image-20250501-181407.png
Figure 4: Full Assembly Prior to Attaching the Motor or Mouse

Electronics And Circuitry:

As seen in the Figure 5, we connect the 12V power supply's positive terminal to the VCC (12V input) on the L298N motor driver and the negative terminal to the GND on the L298N. Then, the GND of the L298N is connected to the GND pin on the Arduino Uno to ensure a common ground. For motor control, connect the OUT1 and OUT2 pins on the L298N to the two terminals of the Greartisan 12V gearbox motor. For direction control, connect IN1 on the L298N to digital pin 8 of the Arduino and IN2 to digital pin 9. For speed control, connect ENA (Enable A) on the L298N to PWM-capable digital pin 10 of the Arduino. Now, wire the 10kΩ potentiometer: connect one outer leg to 5V on the Arduino, the other outer leg to GND, and the center leg (wiper) to analog pin A0. This lets the Arduino read the analog voltage to control motor speed.

image-20250502-230719.png
Figure 5: Circuit Schematic

Component

Quantity

Notes

Arduino Uno

1

Microcontroller

Greartisan 12V Gearbox Motor

1

Brushed DC motor, verify current draw

L298N Motor Driver Module

1

Dual H-Bridge driver; supports up to 2A per channel (with heatsink)

Rotary Potentiometer (10kΩ)

1

Analog input to control motor speed

12V DC 2A Power Supply

1

Make sure it supplies enough current for the motor

Breadboard + Jumper Wires

1 set

For connections

Table 1: Circuitry Component List

Software Development:

Arduino:

Our Arduino code is designed to take an analog input from a potentiometer and use it to control the speed of a DC motor. The potentiometer provides a variable voltage that is read by the Arduino and converted into a speed signal for the motor via PWM (Pulse Width Modulation).

 

We begin by initializing the motor control pins — IN1, IN2, and EN — as outputs using the pinMode(pin_number, OUTPUT) function. These pins are used to send control signals from the Arduino to the motor driver (L298N).

IN1

IN2

Motor Behavior

LOW

LOW

Motor stops (brake)

HIGH

LOW

Motor rotates forward

LOW

HIGH

Motor rotates backward

HIGH

HIGH

Motor stops (brake)

Table 2: Setting direction of motor with IN1 and IN2

Note: "Forward" and "Backward" directions depend on how the motor is wired to the motor driver's output terminals (OUT1 and OUT2).

 

Once the motor direction is set (in this case, forward), the Arduino reads the analog value from the potentiometer using analogRead(), which returns a value from 0 to 1023. This value is then scaled to a PWM range of 0 to 255 using the map() function. The resulting value is written to the EN pin using analogWrite(), which adjusts the speed of the motor accordingly.

Code:

int potPin = A0;

int enPin = 10;

int in1 = 9;

int in2 = 8;

 

void setup() {

  pinMode(enPin, OUTPUT);

  pinMode(in1, OUTPUT);

  pinMode(in2, OUTPUT);

  digitalWrite(in1, HIGH);

  digitalWrite(in2, LOW); // sets direction

  Serial.begin(9600);

}

 

void loop() {

  int potValue = analogRead(potPin);     // 0–1023

  int speed = map(potValue, 0, 1023, 0, 255);

  analogWrite(enPin, speed);             // sets speed

  Serial.println(speed);

}

 

Mouse Tracking Code:

This mouse-tracking Python program uses Pygame to track mouse movements, display a trailing blur effect, and detect when the user is AFK (Away From Keyboard) based on mouse activity alone. It provides a real-time visual trail of the mouse and prints notifications when the user becomes or returns from being AFK.

 

Line-by-Line Explanation of the Mouse Tracker with AFK Detection in Pygame

 

import pygame

import sys

import time

 

pygame.init()

 

This section imports the necessary Python modules and initializes the Pygame library. pygame.init() prepares all Pygame subsystems such as graphics, sound, and input handling for use.


screen_width, screen_height = 3456, 2140

screen = pygame.display.set_mode((screen_width, screen_height))

pygame.display.set_caption("Mouse Tracker with Blur Trail + AFK Detection (Mouse Only)")

 

Here, we define the screen dimensions and create a display window using Pygame. We also set a custom title for the window that describes the program's functionality.


# Colors

background_color = (0, 0, 0)  # Black

trail_color = (0, 255, 0)     # Green

 

# Trail settings

trail = []

max_trail_length = 100  # Max number of points to keep

 

# AFK settings

afk_timeout = 4  # Seconds to be considered AFK

last_activity_time = time.time()

afk = True

 

This block initializes variables:

- Colors for the background and the trail.

- An empty list to store recent mouse positions.

- A maximum trail length to limit memory usage.

- AFK timeout (in seconds), last time the mouse moved, and a boolean flag to track AFK status.


clock = pygame.time.Clock()

 

Creates a clock object to manage frame rate and ensure smooth rendering.


while True:

    current_time = time.time()

   

    for event in pygame.event.get():

        if event.type == pygame.QUIT:

            pygame.quit()

            sys.exit()

 

The main game loop begins. It handles events, particularly checking if the user tries to close the window. If so, the program shuts down cleanly.


   # Get mouse position

    pos = pygame.mouse.get_pos()

 

    # If mouse moved, reset activity timer

    if not trail or pos != trail[-1]:

        last_activity_time = current_time

        if afk:

            print("User returned (mouse moved)!")

        afk = False

 

    trail.append(pos)

 

    # Limit the trail length

    if len(trail) > max_trail_length:

        trail.pop(0)

 

Here, the current mouse position is recorded. If the mouse has moved, the AFK timer is reset and the user is marked as active. The new position is appended to the trail list, and if the trail exceeds the set limit, the oldest point is removed to maintain consistent length.


   # Check for AFK

    if current_time - last_activity_time > afk_timeout and not afk:

        afk = True

        print("User is now AFK (no mouse movement).")

 

This condition checks how long it has been since the last movement. If the threshold is exceeded, the user is marked as AFK and a message is printed.


   # Clear screen

    screen.fill(background_color)

 

    # Draw trail with fading effect

    for idx, point in enumerate(trail):

        alpha = int(255 * (idx / max_trail_length))

        s = pygame.Surface((10, 10), pygame.SRCALPHA)  # Transparent surface

        pygame.draw.circle(s, (trail_color[0], trail_color[1], trail_color[2], alpha), (5, 5), 5)

        screen.blit(s, (point[0]-5, point[1]-5))

 

The screen is cleared each frame and the mouse trail is redrawn with a fading effect. Older points in the trail have lower alpha values (more transparent), creating a smooth blur effect.


   # Optionally display AFK status

    if afk:

        font = pygame.font.SysFont("Comic Sans MS", 500)

        afk_text = font.render("AFK!", True, (255, 0, 0))

        screen.blit(afk_text, (screen_width//2 - afk_text.get_width()//2,

                               screen_height//2 - afk_text.get_height()//2))

 

If the user is AFK, a large red "AFK!" message is displayed in the center of the screen using Pygame's text rendering system.


   pygame.display.update()

    clock.tick(100)  # 100 FPS

 

The screen is updated to show changes, and the loop is throttled to run at 100 frames per second for smoother visuals and performance efficiency.

 

The full code is provided below.

 

 

import pygame

import sys

import time

 

# Initialize pygame

pygame.init()

 

# Screen settings

screen_width, screen_height = 3456, 2140

screen = pygame.display.set_mode((screen_width, screen_height))

pygame.display.set_caption("Mouse Tracker with Blur Trail + AFK Detection (Mouse Only)")

 

# Colors

background_color = (0, 0, 0)  # Black

trail_color = (0, 255, 0)     # Green

 

# Trail settings

trail = []

max_trail_length = 100  # Max number of points to keep

 

# AFK settings

afk_timeout = 4  # seconds to be considered AFK

last_activity_time = time.time()

afk = True

 

clock = pygame.time.Clock()

 

while True:

    current_time = time.time()

   

    for event in pygame.event.get():

        if event.type == pygame.QUIT:

            pygame.quit()

            sys.exit()

 

    # Get mouse position

    pos = pygame.mouse.get_pos()

 

    # If mouse moved, reset activity timer

    if not trail or pos != trail[-1]:

        last_activity_time = current_time

        if afk:

            print("User returned (mouse moved)!")

        afk = False

 

    trail.append(pos)

 

    # Limit the trail length

    if len(trail) > max_trail_length:

        trail.pop(0)

 

    # Check for AFK

    if current_time - last_activity_time > afk_timeout and not afk:

        afk = True

        print("User is now AFK (no mouse movement).")

 

    # Clear screen

    screen.fill(background_color)

 

    # Draw trail with fading effect

    for idx, point in enumerate(trail):

        # Alpha depending on how old the point is

        alpha = int(255 * (idx / max_trail_length))

        s = pygame.Surface((10, 10), pygame.SRCALPHA)  # small transparent surface

        pygame.draw.circle(s, (trail_color[0], trail_color[1], trail_color[2], alpha), (5, 5), 5)

        screen.blit(s, (point[0]-5, point[1]-5))

 

    # Optionally display AFK status

    if afk:

        font = pygame.font.SysFont("Comic Sans MS", 500)

        afk_text = font.render("AFK!", True, (255, 0, 0))

        screen.blit(afk_text, (screen_width//2 - afk_text.get_width()//2, screen_height//2 - afk_text.get_height()//2))

 

    pygame.display.update()

    clock.tick(100)  # 60 FPS