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.
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.
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.
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.
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.
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