How an autonomous flying machine actually works
Long before any of the swarm work, there was a tiny quadcopter hovering over my desk, and me not quite believing it could hold itself in the air.
My first real flight-controller project was Udacity's Flying Car and Autonomous Flight Engineer Nanodegree. The course is four projects, and they are more or less the four things an autonomous aircraft has to do: fly a shape (backyard flyer), plan a route through a city (3D motion planning), control the vehicle along that route (build a controller), and work out where it is in the first place (estimation and sensor fusion). I wrote mine in Python and the code is still on GitHub. It is a basic implementation, which is exactly what makes it a good way to explain this.
Underneath, it comes down to two jobs. Know where you are. Decide what to do about it. Everything else is detail.
State estimation: where am I?
You would think you could just read the sensors. You cannot, and the reason is worth sitting with.
A gyroscope measures how fast the drone is rotating. To get an actual angle you integrate that rate over time, and any small constant error in the measurement integrates too, piling up forever. Integrate the gyro alone and your sense of which way is up slowly rots, even sitting still on a desk. That is drift, and it is fatal.
An accelerometer feels gravity, which is an absolute, drift-free reference for which way is down. It also feels vibration and every movement the drone makes, so in the short term it is noisy and untrustworthy.
They fail in opposite directions: the gyro is smooth but drifts, the accelerometer is stable but noisy. So you fuse them, trusting the gyro moment to moment and letting the accelerometer pull the long-run drift back towards level. Add GPS and a magnetometer and you can do the same for position and heading, which is what the course's estimation project builds: an Extended Kalman Filter that holds a belief about the full state along with how uncertain it is, predicts that state forward with the physics on every tick, then corrects it against whatever measurements arrived, weighting each by how much it deserves to be trusted.
The cheap version is worth knowing too. A complementary filter does a smaller version of the job with almost no machinery: trust the gyro at high frequency, the accelerometer at low frequency, blend. It gives you attitude and not much more, and for "just stay level" that is plenty. The Crazyflie I later kept on my desk ships with both, and choosing between them is a neat summary of the trade: complementary filter to hold it level, EKF the moment you want it to know where it is in a room.

Whichever filter you run, the output is the same: one clean belief about position, velocity and attitude. That belief is the only thing the next job gets to see.
The control loop: what do I do about it?
The controller's question is narrower. Given where I am, and where I am supposed to be, what should the motors do?
The trick is that you never answer "be at that point in space" in one shot. You build a cascade: a stack of small controllers, each turning a goal into a goal for the next one down. My implementation is five functions, and reading them in order is the whole idea.
lateral_position_controlturns the horizontal position error into a desired acceleration.altitude_controldoes the vertical half, turning altitude error into a thrust, accounting for the tilt of the craft (a tilted drone spends part of its thrust sideways).roll_pitch_controllerturns that desired acceleration into a desired attitude. To accelerate forward, tilt forward. A quadcopter has no other way to move.body_rate_controlturns the desired attitude into the moments the motors have to produce. This is the innermost loop, fed straight from the gyro.yaw_controlhandles heading on its own, since where the craft points is decoupled from where it is going.
Two things make the cascade work. The first is that each loop is little more than a gain on an error, which keeps tuning tractable: in my controller the inner rate loops are stiff (Kp_p = Kp_q = 20) and the outer position loop is soft (Kp_pos = 6). The inner loop has to be fast, because rotational dynamics are twitchy and a quadcopter that loses its attitude flips. The outer loop can afford to be slow, because moving through space is comparatively sluggish. Fast and reflexive on the inside, slow and strategic on the outside.
The second is saturation. Every command is clamped before it leaves: a maximum tilt, a maximum ascent and descent rate, a maximum speed. Without those, the position loop cheerfully asks for a ninety-degree bank and the drone falls out of the sky doing exactly what it was told.
Production firmware is this, tuned harder. The Crazyflie runs the same cascade with a PID at each stage, the inner rate loop at 500 Hz and the outer position loop at 100 Hz, and there are fancier controllers again (Mellinger's geometric controller, INDI) once you want aggressive flight. The shape does not change.

Why I am telling you this
Squint at the two jobs and you get: sense, estimate, decide, act, and then do it again, forever. That is the shape DomeCommand runs, one level up. Where the quadcopter fuses a gyro and an accelerometer into which way is up, DomeCommand fuses radar and RF and cameras into what is out there. Where the cascade turns a setpoint into motor commands, the planner turns a commander's intent into tasks for a fleet.
The instinct that a hard autonomy problem is really a clean estimation problem stacked on a clean control problem came from a course assignment and a small quadcopter I could not stop tinkering with. It scales further than I expected.
The two diagrams above are from Bitcraze's open crazyflie-firmware documentation, a genuinely good place to learn this hands-on.
