A tiny drone mosquito that takes off from its pad, flies around its net by itself, and radios back every time the MS-2000 laser tags its glowing white window. This guide goes from a box of parts to a working target, one step at a time.
Who does what
Addie builds, tests, and codes the micro:bit. A grown-up handles batteries, soldering, the printer, and the Python code. Steps with a grown-up tag need one right there.
The big picture
Four computers work as a team. The drone flies. The Pi in the control box tells it where to go. The XIAO in the body feels the hits. The micro:bit tells the MS-2000.

The whole drone

Pulled apart
Get these ready
| Tool | For |
|---|---|
| Digital calipers | Measuring the real parts on day one |
| Kitchen scale (1 g) and a jewelry scale (0.1 g) if you have one | Weighing every part and the push test |
| Soldering iron, solder, heat shrink, 30 AWG wire | Sensor and battery wires grown-up |
| Small Phillips screwdriver, M2 and M2.5 screws | The control box and the Pi |
| Laptop with Chrome | Flashing the drone, MakeCode, Python |
| Hot glue and CA glue | Wire strain relief, the window |
| 7 US nickels | Test weights, 5.000 g each |
| Flashlight | Testing the hit sensor |
What is in the boxes
Full list with stores and prices on the Build page.
A drone whose frame is its circuit board: an ESP32-S3 computer, a motion sensor, four motor drivers and WiFi. Comes with 4 motors and 55 mm props.
Its job: Flies. Makes its own WiFi network that the control box and the mosquito body join.
100 x 100 mm · about 45 g · no battery included
A small board that plugs in underneath: a floor camera (PMW3901) and an invisible laser range finder (VL53L1X), plus 4 colored lights.
Its job: Lets the drone hold still without anyone steering.
46 x 44 mm · 8 g · arrow points to the USB-C port
Single-cell lithium polymer, 3.7 V, 500 to 650 mAh, 20C or more, MX2.0 plug.
Its job: The fuel tank. One flies, one cools, one charges.
CHECK THE PLUG POLARITY before the first plug-in
A thumbnail-size WiFi computer. One spare.
Its job: Lives in the body, watches the light sensor, and sends HIT over the drone WiFi.
21 x 17.8 mm · about 1.5 g
A tiny light sensor on a board with 3 pins: VIN, GND, OUT. Same sensor as the MS-2000 pod. One spare.
Its job: Feels the red laser dot glowing in the white window.
7.8 x 10.6 mm
A small Linux computer.
Its job: Runs the flight script, hears the HIT messages, and talks to the micro:bit.
65 x 30 mm · needs a 32 GB microSD card
The same little computer as the MS-2000 turret and wand.
Its job: Shows the status and radios GO, HIT and STOP on group 7. Saves each run.
Plugs into the Pi by USB
A big button with a light inside.
Its job: Press to fly. The light means the drone is ready.
Adafruit 3430
Anker 321 power bank, USB cable with switch, USB-C panel port, OTG cable, micro:bit cable, jumper wires.
Its job: Powers the Pi. The switch is the main on/off; the panel port charges the bank.
Same bank as the MS-2000
A mesh cube about 24 x 24 x 36 inches.
Its job: The flight zone. Front rolled open toward the turret.
Holds the flight mat and the pad
Prop guard with legs, mosquito body, white hit window, launch pad, control box, box floor, fit check.
Its job: Protect the props, hold the sensor, catch the laser, start every run in the same spot.
About 5 h 45 min · about 205 g
Where every wire goes
Small parts
| Where | What | How many |
|---|---|---|
| Box floor into the corner posts | M2 x 8 | 4 |
| Pi onto its posts | M2.5 x 6 | 4 |
| GO button | its own plastic nut | 1 |
| Guard onto the motors | press fit, no screws | 4 collars |
| Body onto the battery | 10 mm hook-and-loop strap | 1 |
| Window into the body | press fit, then a drop of glue once it works | 1 |
Part A · Dec 1 to 6
Find surprises on day one, fly the plain drone, then print the small test first.
Day 1 · about 1 hour · grown-up
Same as Dusty: a wrong or missing part gets found now, not on build day. Four numbers in the 3D model are guesses until we measure.
Every part checked in, four measurements and three weights written down.
Journal: the date, what arrived, the numbers you measured, and anything different from the plan.
Day 2 · 30 minutes · grown-up
Prove the drone works before anything gets changed. If it will not fly now, it is the drone, not our parts.
The motion sensor has to learn what "not moving" feels like. If it gets bumped while it learns, the drone thinks it is tipping and drifts.
The plain drone hovers for 2 minutes.
Day 3 · 30 minutes · grown-up
The positioning module needs newer firmware on the drone. Firmware is the program that lives inside the drone.
Module plugged in and its lights flash at startup.
Day 3 · 10 minutes of printing
A 10 minute print tells us which motor collar size grips best before the 33 minute guard gets printed.
Collar size picked; button and sensor fit.
Journal: which collar won and how it felt in the other two.
Days 4 to 6 · about 1 hour 10 minutes · grown-up
Three small prints: the guard, the white window, and the body. Then weigh them, because every gram counts.

The body with the white window
Three parts printed, cleaned, and weighed.
Journal: the real weights next to the targets.
Part B · Dec 7 to 13
Before building more, find out how much the drone can really carry. The answer can still change the design.
Dec 7 to 13 · two tests, 18 flights · grown-up
Our math says the four props push about 120 g and the full drone weighs about 87 g. The experiment checks if the math is right.
import time, cflib.crtp from cflib.crazyflie import Crazyflie URI = "udp://192.168.43.42" # the LiteWing address on its own WiFi cflib.crtp.init_drivers() cf = Crazyflie(); cf.open_link(URI); time.sleep(1) cf.commander.send_setpoint(0, 0, 0, 0) # unlock for pct in (25, 50, 75, 100): thrust = int(10000 + (60000 - 10000) * pct / 100) t0 = time.time() while time.time() - t0 < 3: # keep sending, or the drone stops cf.commander.send_setpoint(0, 0, 0, thrust); time.sleep(0.05) cf.commander.send_setpoint(0, 0, 0, 0); input(f"{pct}%: read the scale, press Enter") cf.close_link()
Drone strapped down, guard on, hands clear. LiteWing thrust runs from 10000 to 60000.
18 flights done, the push measured, the decision made.
Journal: every flight time, the push numbers, the graphs, and the conclusion.
Part C · Dec 14 to 20
First from the laptop, over a flight mat, in the net.
About 1 hour
The floor camera sees movement by watching the floor slide by. A plain or shiny floor gives it nothing to see, and the drone drifts.
It takes hundreds of tiny pictures of the floor every second. When the pattern slides left in the picture, the drone is moving right, so it pushes back. That is called optical flow. A computer mouse works the same way.
Net up, mat down, ready to fly.
About 2 evenings · grown-up
The laptop sends "stay at 40 cm, do not move" 10 times a second, and the drone uses its floor camera and range finder to obey.
import time, cflib.crtp from cflib.crazyflie import Crazyflie from cflib.crazyflie.syncCrazyflie import SyncCrazyflie URI, HEIGHT, SPEED = "udp://192.168.43.42", 0.4, 0.2 # meters, meters per second cflib.crtp.init_drivers() def hold(cf, vx, vy, z, seconds): for _ in range(int(seconds * 10)): # 10 times a second cf.commander.send_hover_setpoint(vx, vy, 0, z) # yaw rate 0: window keeps facing the turret time.sleep(0.1) with SyncCrazyflie(URI, cf=Crazyflie()) as scf: cf = scf.cf cf.commander.send_setpoint(0, 0, 0, 0) # unlock for i in range(1, 21): # climb over 2 seconds hold(cf, 0, 0, HEIGHT * i / 20, 0.1) for vx, vy in [(SPEED, 0), (0, SPEED), (-SPEED, 0), (0, -SPEED)] * 3: hold(cf, vx, vy, HEIGHT, 2) # 40 cm each side for i in range(20, 0, -1): # come down slowly hold(cf, 0, 0, HEIGHT * i / 20, 0.1) cf.commander.send_stop_setpoint()
Based on LiteWing's own position hold examples. The final version lives on the Pi (step 16).
It takes off, flies the square inside the zone, and lands, 3 times in a row.
Journal: how far it drifted each time.
Part D · Dec 21 to Jan 3
The light sensor feels the laser, and the XIAO tells the control box.
About 1 hour · grown-up
Three short wires from the sensor, two from the battery. Everything stays inside the body.
Nothing shorted: with the battery in, the XIAO light comes on and nothing gets warm.
About 1 evening · grown-up
The XIAO learns how bright the room is, then shouts HIT when the light jumps. It waits 1 second before it can shout again, like the MS-2000.
#include <WiFi.h> #include <WiFiUdp.h> const char* SSID = "LiteWing_xxxx"; // the drone WiFi name const char* PASS = "........"; // and password, from the LiteWing guide const int JUMP = 150; // how big a jump counts as a hit: set in the flashlight test WiFiUDP udp; long base = 0; unsigned long lastHit = 0; void setup() { WiFi.begin(SSID, PASS); while (WiFi.status() != WL_CONNECTED) delay(200); for (int i = 0; i < 200; i++) { base += analogRead(A0); delay(5); } base /= 200; // normal room light } void loop() { int v = analogRead(A0); if (v > base + JUMP && millis() - lastHit > 1000) { udp.beginPacket(IPAddress(192, 168, 43, 255), 5005); // shout to everyone on the drone WiFi udp.print("HIT"); udp.endPacket(); lastHit = millis(); } else if (v < base + JUMP / 2) { base = (base * 31 + v) / 32; // slowly follow the room light } delay(5); // 200 checks a second }
Upload with the Arduino IDE (board: XIAO_ESP32C3). The laptop can listen on port 5005 to see the HITs.
10 laser flicks, 10 HITs, and no HIT when nothing happens for a minute.
Journal: the JUMP number you picked and why.
30 minutes
Guard, body, window, wings. Then weigh the whole thing.

From above: guard around all four props, body in the middle
It all fits, nothing touches a prop, and the weight is written down.
Part E · Dec 26 to Jan 3
The big GO button, the Pi that flies the drone, and the micro:bit that talks to the turret.
About 4 hours 25 minutes of printing · grown-up
Three bigger prints. Good for overnight.
Three parts printed; the drone sits in the pad cups.
About 1 evening · grown-up
The Pi is a small Linux computer. It joins the drone WiFi like a phone would, and runs the flight script by itself when it turns on.
Power on the Pi with the drone on: the Pi joins the drone WiFi by itself.
About 1 hour · grown-up
Follow the control box wiring map.
Switch on: the Pi boots and the micro:bit lights up.
Addie · about 1 hour in MakeCode
Whatever the Pi sends over USB, the micro:bit radios on group 7, the same words the MS-2000 wand uses. It also counts hits and saves each run.
MakeCode for micro:bit. Add the Datalogger extension.
GO, HIT and STOP from the laptop show up on the micro:bit and on the MS-2000.
Journal: a screenshot of your code.
About 1 evening · grown-up
One script on the Pi ties it all together. It waits for the button, flies the run, passes every HIT to the micro:bit, and lands.
import socket, time, serial, threading from gpiozero import Button, LED # ... hold() and the pattern from step 8 ... button, light = Button(17, hold_time=1.5), LED(27) mb = serial.Serial("/dev/ttyACM0", 115200) # the micro:bit say = lambda word: mb.write((word + "\n").encode()) hits = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); hits.bind(("", 5005)) def listen(): # HIT from the XIAO -> micro:bit while True: data, _ = hits.recvfrom(16) if data == b"HIT": say("HIT") threading.Thread(target=listen, daemon=True).start() while True: light.on(); button.wait_for_press(); light.off() if battery_volts() < 3.8: light.blink(); continue # too low to fly a full run say("GO"); fly_run(seconds=30); say("STOP") # button.when_held lands right away
The full file lives in the repo with the other tools.
Press GO, full run, lands in the cups. The emergency landing works.
Part F · Jan 4 to 24
Twenty flights on its own, then the MS-2000 gets its turn. Only on days with no MS-2000 experiment.
Jan 4 to 15 · about 3 evenings
An engineer does not trust one good flight. Twenty in a row shows if it really works.
18 of 20 runs land on the pad, and every flashlight hit reaches the micro:bit.
Journal: the 20 results in a table.
Jan 16 to 24 · after the MS-2000 experiments are done
The fun part. Can the turret hit something that flies by itself?
10 runs recorded, and the stuffed mosquito learned again.
Journal: hits per run and time to first hit, next to the MS-2000 results.
When it misbehaves
Find what you see, try the fix. Write every problem in the journal.
| What you see | Most likely cause | What to do |
|---|---|---|
| Drone will not connect | Wrong WiFi | Join the drone WiFi, turn off mobile data and VPN, restart the app. |
| Slow green blinking forever | Still calibrating | Put it on a flat floor and do not touch it. Power cycle. |
| Flips on takeoff | Prop on the wrong motor | Check each prop matches the spin mark on the board. |
| Drifts in one direction | Floor camera cannot see | Use the flight mat. More contrast, matte paper, better light. |
| Climbs slowly, lands itself early | Too heavy or battery tired | Weigh it. Fresh battery. See the Can it fly? decision rules. |
| Wobbles, then drops | Battery voltage sag | Battery rated under 20C, or worn out. Swap it. |
| HIT with no laser | JUMP too small, or room lights flicker | Make JUMP bigger. Try away from bright lamps. |
| Laser hits but no HIT | JUMP too big, or XIAO not on WiFi | Make JUMP smaller. Check the XIAO joins the drone WiFi. |
| micro:bit shows nothing | USB cable | Some micro-USB cables only charge. Use a data cable through the OTG cable. |
| Turret ignores the runs | Radio group | Both programs say radio set group 7? |
| Guard rubs a prop | Collar not all the way up | Push the collar up until it stops. Check the slit faces the middle. |
Words to know
Everything else