Warning: The software for this is a little confusing and complex! If you're a total noob, you may find that part confusing. Don't say I didn't warn you!
Required
3D Printer (or a nice friend with one)
Various M3 Hardware
Various M2 Hardware
28BYJ-48/ULN2003 kit (like this)
An Arduino Nano
A micro USB breakout board
A breadboard
Any USB webcam (this is the one I used. If you use your own, the mounting may need to be modified).
A computer connected to the internet that you're okay with leaving on forever
Some 5x10x4mm ball bearings (you can get them here)
Wire
Small Zipties
Optional (but recommended)
A cheap tripod
Soldering equipment (breadboards tend to unwire themselves) and a permaproto (basically a permanent breadboard)
Download the files here! Then, get printing. You should have the following bits:
1x azimuth_part (img 1)
1x bearing_support (img 2)
1x camera_mount (img 3)
2x shaft_collar (img 4)
1x elevation_part (img 5)
1x tripod_interface (img 6)
Install a shaft collar on each motor. You'll need a short M2 screw, and a M2 nut. Don't tighten it too much! The shaft collar will crack.
2. Screw one motor into the tripod interface.
3. Take that assembly, and screw it into the azimuth part by the shaft collar's M2 mounting points.
4. Add the 2nd motor. Important: install it on the side further from the azimuth pivot!
5. Screw the elevation part to the 2nd motor's shaft collar.
6. Ziptie the camera to the camera mount without it mounted on the elevation part!
7. Attach the bearing support to the other side fo the azimuth part with some screws + a bearing to stabalize the elevation axis.
8. Screw on the camera assembly!
9. CELEBRATE. Your spyware-free camera is starting to take shape.
Time to wire this sucker up. Follow the image below! This circuit was wired on a permaproto breadboard - which is identical to a breadboard, just in PCB form. So, if you're using a breadboard, it will still work if you copy the layout!
VERY IMPORTANT: THE POWER WIRES TO THE STEPPER DRIVERS ARE NOT SOLDERED TO THE ARDUINO AT ALL. THEY ARE THREADED THROUGH THE BREADBOARD HOLES FOR NEATNESS. IF YOU MAKE THIS CONNECTION ELECTRICALLY IT WILL EXPLODE!!!!!!!!!!
I won't sugarcoat it; this is a little involved. But I will try my best to explain!
Part 1: Arduino Firmware
Flash the firmware on the board via the Arduino IDE. You can download the code here. Pro nerd tip: the firmware on this camera is pretty freakin dumb. If the camera receives the right ascii character over serial, it incrementally pivots the respective axis. You do not need to connect this to the web; you can also just plug into your pc, open the Arduino serial monitor, spam "L + enter" or "R + enter" etc and watch it go!
Part 2: Tailscale
Install Tailscale from the intervebz
Make an account and add both the host PC and your remote device to the network. Make note of your host PC's IP address.
Make sure there's a green "connected" indication next to your remote device, AND the host PC.
Part 3: Host PC Software
Download python 3.12.
Download this zip file (contains python code and requirements.txt)
Install all the requirements
Plug the webcam, and the Arduino, into your computer. Also, plug the external USB power jack into a 5v wall brick.
Open app.py and make sure the COM port matches that of your arduino. You can figure out what com port the arduino is on by checking the device manager.
6. Moment of truth. Run app.py. If it errors out, or closes instantly, make sure you're running it with python 3.12, and have the libraries installed. You should run it in VSCode or something similar, else, you may not have a chance to see the error messages and debug.
Part 4: Moment of Truth
If, and only if
The app is running, with the correct COM port selected
The arduino is plugged in
The webcam is plugged in
The arduino is powered via the external power port
The host PC is on Tailscale
YOU are on Tailscale (your phone is, or your other computer)
You can now access the camera and control it!
Go to http://<your_computer's_tailscale_IP_from_earlier>:5000
Login (user: 1234, pass: 1234, you can change them in app.py)