diff --git a/app/__init__.py b/app/__init__.py
index b6cefa6..2ec71df 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -8,6 +8,8 @@ from typing import Optional
from flask import Flask, Response, abort, send_from_directory, render_template
from markdown import markdown
+from .latex import MarkdownLatex
+
basedir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..'))
templates_dir = os.path.join(basedir, 'templates')
static_dir = os.path.join(basedir, 'static')
@@ -66,7 +68,7 @@ def get_page(page: str, title: Optional[str] = None, skip_header: bool = False):
author_email=re.match(r'(.+?)\s+<([^>]+)>', metadata['author'])[2] if 'author' in metadata else None,
published=(metadata['published'].strftime('%b %d, %Y')
if metadata.get('published') else None),
- content=markdown(f.read(), extensions=['fenced_code', 'codehilite']),
+ content=markdown(f.read(), extensions=['fenced_code', 'codehilite', MarkdownLatex()]),
skip_header=skip_header)
diff --git a/app/latex.py b/app/latex.py
new file mode 100644
index 0000000..290f67a
--- /dev/null
+++ b/app/latex.py
@@ -0,0 +1,243 @@
+"""
+Licensed under Public Domain Mark 1.0.
+See https://creativecommons.org/publicdomain/mark/1.0/
+Author: Justin Bruce Van Horne
+
+Python-Markdown LaTeX Extension
+Adds support for $math mode$ and %text mode%. This plugin supports
+multiline equations/text.
+The actual image generation is done via LaTeX/DVI output.
+It encodes data as base64 so there is no need for images directly.
+All the work is done in the preprocessor.
+"""
+
+import base64
+import hashlib
+import json
+import os
+import re
+import tempfile
+from subprocess import call as rawcall, PIPE
+
+import markdown
+
+
+def call(*args, **kwargs):
+ """
+ Proxy to subprocess.call(), removes timeout argument in case of
+ Python2 because that was only implemented in Python3.
+ """
+ return rawcall(*args, **kwargs)
+
+
+# Defines our basic inline image
+img_expr = ''
+
+# Defines multiline expression image
+multiline_img_expr = '''
+
'''
+
+# Base CSS template
+img_css = """"""
+
+# Cache and temp file paths
+tmpdir = tempfile.gettempdir() + '/markdown-latex'
+cache_file = tmpdir + '/latex.cache'
+
+
+class LaTeXPreprocessor(markdown.preprocessors.Preprocessor):
+ # These are our cached expressions that are stored in latex.cache
+ cached = {}
+
+ # Basic LaTex Setup as well as our list of expressions to parse
+ tex_preamble = r"""\documentclass[14pt]{article}
+\usepackage{amsmath}
+\usepackage{amsthm}
+\usepackage{amssymb}
+\usepackage{bm}
+\usepackage{graphicx}
+\usepackage[usenames,dvipsnames]{color}
+\pagestyle{empty}
+"""
+
+ # Math TeX extraction regex
+ math_extract_regex = re.compile(r'(.+?)((\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n))(.*)', re.MULTILINE | re.DOTALL)
+
+ # Math TeX matching regex
+ math_match_regex = re.compile(r'\s*(\\\(.+?\\\))|(\$\$\n.+?\n\$\$\n)\s*', re.MULTILINE | re.DOTALL)
+
+ def __init__(self, *_, **__):
+ if not os.path.isdir(tmpdir):
+ os.makedirs(tmpdir)
+ try:
+ with open(cache_file, 'r') as f:
+ self.cached = json.load(f)
+ except (IOError, json.JSONDecodeError):
+ self.cached = {}
+
+ self.config = {
+ ("general", "preamble"): "",
+ ("dvipng", "args"): "-q -T tight -bg Transparent -z 9 -D 200",
+ ("delimiters", "text"): "%",
+ ("delimiters", "math"): "$",
+ ("delimiters", "preamble"): "%%"}
+
+ def _latex_to_base64(self, tex):
+ """Generates a base64 representation of TeX string"""
+
+ # Generate the temporary file
+ tmp_file_fd, path = tempfile.mkstemp(dir=tmpdir)
+ with os.fdopen(tmp_file_fd, "w") as tmp_file:
+ tmp_file.write(self.tex_preamble)
+ tmp_file.write(tex)
+ tmp_file.write('\n\\end{document}')
+
+ # compile LaTeX document. A DVI file is created
+ status = call(('latex -halt-on-error -output-directory={:s} {:s}'
+ .format(tmpdir, path)).split(),
+ stdout=PIPE, timeout=10)
+
+ # clean up if the above failed
+ if status:
+ self._cleanup(path, err=True)
+ raise Exception("Couldn't compile LaTeX document." +
+ "Please read '%s.log' for more detail." % path)
+
+ # Run dvipng on the generated DVI file. Use tight bounding box.
+ # Magnification is set to 1200
+ dvi = "%s.dvi" % path
+ png = "%s.png" % path
+
+ # Extract the image
+ cmd = "dvipng %s %s -o %s" % (self.config[("dvipng", "args")], dvi, png)
+ status = call(cmd.split(), stdout=PIPE)
+
+ # clean up if we couldn't make the above work
+ if status:
+ self._cleanup(path, err=True)
+ raise Exception("Couldn't convert LaTeX to image." +
+ "Please read '%s.log' for more detail." % path)
+
+ # Read the png and encode the data
+ try:
+ with open(png, "rb") as png:
+ data = png.read()
+ return base64.b64encode(data)
+ finally:
+ self._cleanup(path)
+
+ @staticmethod
+ def _cleanup(path, err=False):
+ # don't clean up the log if there's an error
+ extensions = ["", ".aux", ".dvi", ".png", ".log"]
+ if err:
+ extensions.pop()
+
+ # now do the actual cleanup, passing on non-existent files
+ for extension in extensions:
+ try:
+ os.remove("%s%s" % (path, extension))
+ except (IOError, OSError):
+ pass
+
+ def run(self, lines):
+ """Parses the actual page"""
+ # Re-creates the entire page so we can parse in a multine env.
+ page = "\n".join(lines)
+
+ # Adds a preamble mode
+ self.tex_preamble += self.config[("general", "preamble")] + "\n\\begin{document}\n"
+
+ # Figure out our text strings and math-mode strings
+ tex_expr = self.math_extract_regex.findall(page)
+
+ # No sense in doing the extra work
+ if not len(tex_expr):
+ return page.split("\n")
+
+ # Parse the expressions
+ new_cache = {}
+ new_page = ''
+ n_multiline_expressions = 0
+
+ while page:
+ m = self.math_extract_regex.match(page)
+ if not m:
+ new_page += page
+ break
+
+ new_page += m.group(1)
+ math_match = self.math_match_regex.match(m.group(2))
+ if not math_match:
+ new_page += m.group(2)
+ else:
+ expr = m.group(2)
+ is_multiline = math_match.group(2) is not None
+ tex_hash = self.hash(expr)
+ if tex_hash in self.cached:
+ data = self.cached[tex_hash]
+ else:
+ data = self._latex_to_base64(expr).decode()
+ new_cache[tex_hash] = data
+
+ if is_multiline and n_multiline_expressions > 0:
+ new_page += '
'
+ new_page += (multiline_img_expr if is_multiline else img_expr) % ('true', expr, tex_hash, data)
+
+ if is_multiline:
+ new_page += ''
+ n_multiline_expressions += 1
+
+ page = m.group(5)
+
+ if n_multiline_expressions > 0:
+ new_page += '
'
+
+ # Cache our data
+ self.cached.update(new_cache)
+ with open(cache_file, 'w') as f:
+ json.dump(self.cached, f)
+
+ # Make sure to re-split the lines
+ return new_page.split("\n")
+
+ @staticmethod
+ def hash(tex: str) -> str:
+ return hashlib.sha1(tex.encode()).hexdigest()
+
+
+class LaTeXPostprocessor(markdown.postprocessors.Postprocessor):
+ """This post processor extension just allows us to further
+ refine, if necessary, the document after it has been parsed."""
+
+ # noinspection PyMethodMayBeStatic
+ def run(self, text):
+ # Inline a style for default behavior
+ text = img_css + text
+ return text
+
+
+class MarkdownLatex(markdown.Extension):
+ """Wrapper for LaTeXPreprocessor"""
+
+ def extendMarkdown(self, md):
+ # Our base LaTeX extension
+ md.preprocessors.add('latex',
+ LaTeXPreprocessor(self), ">html_block")
+ # Our cleanup postprocessing extension
+ md.postprocessors.add('latex',
+ LaTeXPostprocessor(self), ">amp_substitute")
diff --git a/static/img/adafruit-16-pwm.jpg b/static/img/adafruit-16-pwm.jpg
new file mode 100644
index 0000000..5cf8ce9
Binary files /dev/null and b/static/img/adafruit-16-pwm.jpg differ
diff --git a/static/img/brushless-motor-1.png b/static/img/brushless-motor-1.png
new file mode 100644
index 0000000..e59c152
Binary files /dev/null and b/static/img/brushless-motor-1.png differ
diff --git a/static/img/drone-lift.gif b/static/img/drone-lift.gif
new file mode 100644
index 0000000..9fe8a0d
Binary files /dev/null and b/static/img/drone-lift.gif differ
diff --git a/static/img/drone-schema.png b/static/img/drone-schema.png
new file mode 100644
index 0000000..9842ba0
Binary files /dev/null and b/static/img/drone-schema.png differ
diff --git a/static/img/drone-warning.png b/static/img/drone-warning.png
new file mode 100644
index 0000000..afafb2f
Binary files /dev/null and b/static/img/drone-warning.png differ
diff --git a/static/img/esc-1.jpg b/static/img/esc-1.jpg
new file mode 100644
index 0000000..5ce61aa
Binary files /dev/null and b/static/img/esc-1.jpg differ
diff --git a/static/img/esc-pwm.png b/static/img/esc-pwm.png
new file mode 100644
index 0000000..d01d7a9
Binary files /dev/null and b/static/img/esc-pwm.png differ
diff --git a/static/img/lipo-1.jpg b/static/img/lipo-1.jpg
new file mode 100644
index 0000000..98d995c
Binary files /dev/null and b/static/img/lipo-1.jpg differ
diff --git a/static/img/pitch-roll-yaw-1.png b/static/img/pitch-roll-yaw-1.png
new file mode 100644
index 0000000..a54bcdc
Binary files /dev/null and b/static/img/pitch-roll-yaw-1.png differ
diff --git a/static/img/pitch-roll-yaw-2.png b/static/img/pitch-roll-yaw-2.png
new file mode 100644
index 0000000..f5c712a
Binary files /dev/null and b/static/img/pitch-roll-yaw-2.png differ
diff --git a/static/img/propeller-1.png b/static/img/propeller-1.png
new file mode 100644
index 0000000..945d220
Binary files /dev/null and b/static/img/propeller-1.png differ
diff --git a/static/img/propeller-schema.png b/static/img/propeller-schema.png
new file mode 100644
index 0000000..8554550
Binary files /dev/null and b/static/img/propeller-schema.png differ
diff --git a/static/img/pwm-1.png b/static/img/pwm-1.png
new file mode 100644
index 0000000..d58b9d2
Binary files /dev/null and b/static/img/pwm-1.png differ
diff --git a/static/img/rpi-pinout.jpg b/static/img/rpi-pinout.jpg
new file mode 100644
index 0000000..dbff511
Binary files /dev/null and b/static/img/rpi-pinout.jpg differ
diff --git a/static/img/static-thrust-1.png b/static/img/static-thrust-1.png
new file mode 100644
index 0000000..7bccf21
Binary files /dev/null and b/static/img/static-thrust-1.png differ
diff --git a/static/img/xt60-board.jpg b/static/img/xt60-board.jpg
new file mode 100644
index 0000000..bfb92d8
Binary files /dev/null and b/static/img/xt60-board.jpg differ
diff --git a/static/pages/Build-an-open-source-drone-with-a-Raspberry-Pi-and-Platypush.md b/static/pages/Build-an-open-source-drone-with-a-Raspberry-Pi-and-Platypush.md
new file mode 100644
index 0000000..a0f5dbe
--- /dev/null
+++ b/static/pages/Build-an-open-source-drone-with-a-Raspberry-Pi-and-Platypush.md
@@ -0,0 +1,1563 @@
+[//]: # (title: Design and build a drone from scratch)
+[//]: # (description: How to use a Raspberry Pi, Platypush and some cheap electronics to build your own drone.)
+[//]: # (image: /img/drone-schema.png)
+[//]: # (author: Fabio Manganiello )
+[//]: # (published: 2021-08-23)
+
+Drones are increasingly popular and affordable nowadays, and they have become a popular toy for kids and adults
+of all ages. If all you need is a flying camera to take your holiday selfies then an off-the-shelf solution may
+be good enough. If instead you want something a bit more customized, or you just enjoy the pleasure of building
+things from scratch to understand how they work (and few things give more satisfaction than seeing something you have
+built lift off the ground), then you are in the right place.
+
+This article is about designing and building a drone completely from scratch and is divided in four parts.
+
+The first part covers the physics of lift, both under an aerodynamic (how to pick the right motors and blades and
+how to use them to generate lift) and an electric perspective (how lift power and time-of-flight relate to battery
+capacity and discharge rate and how to optimize your design).
+
+The second part covers the hardware side, how to get the frame in place, which electronic components to pick, how to
+wire them together, how to place them on the body of the drone and how PWM modulation works.
+
+The third part covers the software side, specifically how to code a flight controller on a Raspberry Pi as set of
+Python scripts that run on top of [Platypush](https://platypush.tech).
+
+The fourth part shows how to put all things together, calibrate and fly your new drone.
+
+## Disclaimer
+
+A disclaimer is owed before we start.
+
+Drones are devices that use rigid propellers that spin hundreds or thousands of times in a minute. That's at least ten
+times faster than your regular table fan. Needless to say, if those things touch your fingers you can get very, very
+hurt. Moreover, these propellers are usually powered by high-power lithium batteries that spit out lots of current to
+ensure that they can run so fast. If any of the connections between your propellers and the battery are loose, you may
+get some bad sparks as soon as you try and fly your drone - and nothing nice can happen if those sparks touch the
+battery.
+
+So some precautions are due before you assemble and start anything:
+
+1. **NEVER**, ever run your drone with the blades mounted unless you have completed the calibration phase, and you
+ are **REALLY** sure that you can quickly switch everything off in case your drone leaps towards your face.
+
+2. As a corollary of the point above: **ALWAYS** make sure that you have a kill switch (either hardware or software)
+ to physically cut the power from the motors before your drone damages objects or living beings.
+
+3. Second corollary: when you are in the initial calibration phase of your drone, or you are checking that all the
+ connections are fine and that the motors spin in the right direction, **ALWAYS** do it without the blades mounted.
+
+4. **ALWAYS** make sure that the drone has enough room around before starting the motors, and that no wires nor other
+ mechanical obstacles are on the path of the propellers when they spin.
+
+5. **ALWAYS** pay attention when you work with large LiPo batteries. They are amazing and can store a lot of juice, but
+ they are also ready to go on fire on the first mistake. Make sure that none of the moving parts is ever touching the
+ battery - even a small puncture can cause the battery to smoke, as the oxygen from the air comes in contact with the
+ electrolytes of the battery. **NEVER** overcharge them and never over-discharge them - refer to the manual for the
+ advised voltage range. **NEVER** short-circuit them.
+
+6. **ALWAYS** make sure that all the connections between the battery and the motors (including those to the power board,
+ to the ESCs and to the motors) are stable and well soldered. When you install electronics on things that can move so
+ fast, even a small loose connection can be a recipe for disaster. Also, make sure that all the wires and electrolyte
+ capacitors are installed with the right polarity.
+
+7. Consider installing physical protections for your propellers.
+
+8. If you are planning to fly the drone outdoor, remember to check your local rules, and always behave responsibly:
+ after all, you are flying DIY vehicles in public areas.
+
+![Drone warning](../img/drone-warning.png)
+
+With these recommendations out of the way, let's start getting our hands dirty with a bit of theory about drones and
+lift.
+
+## The physics of a drone
+
+### The physics of lift
+
+This section will include some theory on how to get drones (and things in general) to lift and fly. I would advise to
+do these calculations *before* you start assembling or even order components for your drone: flying objects rely on
+delicate physical balances. If you get any of the variables wrong (e.g. you assemble a drone that is too heavy for what
+the propellers can lift, or you install a battery that can't provide enough current for lift-off) then you'll only
+notice it when the drone is completely assembled, and it'll take time, money and effort to disassemble the drone,
+redesign it and reorder components in order to address these issues (I have learned this the hard way). Time to dust
+off some physics books!
+
+A quadcopter flies by displacing air through the quick rotations of its propellers. The blades of a propeller share a
+similar structure with wings, and both are designed to move air at a higher speed above them than the air below. A wing
+is designed to "cut" through the air at high speeds, causing the air above it to move as fast as the wing but in the
+opposite direction (3rd Newton's law a.k.a. _action-and-reaction_). This movement ends up creating a pocket of air with
+lower pressure and higher speed on the top of the blade. This volume of air is forced to flow downwards along the
+structure, and it meets the bottom flow of air at the tip of the blade. This movement causes the air at the bottom of
+the blade to apply an upward pressure towards the bottom of the propeller that is higher than the downward pressure
+applied to the top. The imbalance between the pressure of the two columns of air (above and below the blade) is what
+causes the attached structure to lift.
+
+![Drone lift diagram](../img/drone-lift.gif)
+
+The first fundamental definition when it comes to drone flight is that of **static thrust** \(T_0\). The static thrust
+is informally defined as the force that the propellers need to provide to the vehicle to maintain it in _stationary_
+conditions. When the drone is _hovering_, this is basically the force that the propellers need to apply to the vehicle
+in order to balance its weight and keep it in position. Calculating this force is fundamental to design any vehicle that
+can fly, albeit it's not a sufficient condition: to actually get a vehicle to lift off you first need to apply a force
+\(T > T_0\), so you should usually design a drone in such a way that its propellers can provide a thrust at least 25%
+higher than its static thrust.
+
+![Static thrust force diagram](../img/static-thrust-1.png)
+
+Intuitively, the thrust provided by a propeller must be somehow proportional to the velocity of the air it displaces.
+The higher the speed of the air it moves, the lower its pressure, the higher the gradient of pressure between the air
+above and below, the higher the lift it can provide. A formalization of this intuition is provided by the
+[Bernoulli's principle](https://en.wikipedia.org/wiki/Bernoulli%27s_principle), which states that air pressure _p_
+and velocity _v_ at any point in space are connected by the following relation:
+
+$$
+\frac{v^2}{2} + \frac{p}{\rho} = const
+$$
+
+where \(\rho\) is the density of the medium (in this case air).
+
+If we now define \(p_0\) as the pressure of air below the blade, \(p_1\) the pressure above, \(v_0\) as the speed
+of air before it hits the blade and \(v_1 = v_0 + \Delta v\) as the speed of air after it is accelerated by the blade,
+then we must have:
+
+$$
+p_0 + \frac{1}{2} \rho v_0^2 = p_1 + \frac{1}{2} \rho v_1^2
+$$
+
+The difference in air pressure that generates the lift can therefore be written as:
+
+$$
+\Delta p = p_1 - p_0 = \frac{1}{2} \rho (v_1^2 - v_0^2)
+$$
+
+We can picture how air speed and pressure change when they are accelerated by a propeller with diameter _d_ (and
+how the width of the column of air changes as well) through this diagram:
+
+![Diagram of the air flowing through a propeller](../img/propeller-1.png)
+
+We also know that pressure equals force divided by area, therefore the thrust must equal the difference of pressure
+times the area of the propeller _A_:
+
+$$
+T = A \cdot \Delta p = \frac{1}{2} \rho A (v_1^2 - v_0^2)
+$$
+
+The area of a propeller moving at very high speed can be approximated as the area of a circular disc whose diameter
+equals the diameter of the propellers:
+
+$$
+A = \pi \Big( \frac{d}{2} \Big)^2 = \frac{\pi}{4} d^2
+$$
+
+Therefore we can rewrite the thrust as:
+
+$$
+T = \frac{\pi}{8} \rho d^2 (v_1^2 - v_0^2)
+$$
+
+If we replace \(v_1\) with \(v_0 + \Delta v\) we get:
+
+$$
+T = \frac{\pi}{4} \rho d^2 \Big(v_0 + \frac{\Delta v}{2}\Big) \Delta v
+$$
+
+We can assume that \(v_0 = 0\), i.e. the velocity of air far from the propellers is zero (this may not be a good
+approximation when you operate the drone in a windy or highly turbulent environment though). With this assumption, the
+formula can be simplified to:
+
+$$
+T = \frac{\pi}{8} \rho d^2 \Delta v^2
+$$
+
+This equation provides two very valuable pieces of information when it comes to designing our drone:
+
+1. *The thrust is proportional to the square of the diameter of the propellers*. If you double the diameter of the
+ propellers then the thrust increases by 4. If you triple it then it increases by 9, and so on. However, larger
+ propellers tend to have larger mass, the motors have to apply a greater force to spin them, and they generate greater
+ air turbulence, resulting in drones that are harder to control.
+
+2. *The thrust is proportional to the square of the variation of speed of the air around the propellers*. Propellers
+ that can displace more air can generate more thrust. The more obvious way to increase the speed of air is to increase
+ the speed of the propellers by applying a higher current to the motors. Another way is to increase the number of
+ blades on the propellers (more blades will cause more air to move, which in turn causes a greater difference in
+ velocity), but a higher number of blades tends to also increase air turbulence. Usually a two blade configuration is
+ used for most of the commercial drones, while a three blade configuration is often used on racing drones or
+ high-power vehicles. Also keep in mind that the efficiency of a real propeller goes down when \(\Delta v\) becomes
+ too high (that's because a higher gradient of velocity means a higher turbulence when the air flowing on top of the
+ wing meets the air below at the edge of the blade), so you may want to strike a balance between thrust generated
+ by air displacement and propeller efficiency.
+
+Albeit useful, this equation isn't really the most used when it comes to quadcopter design. That's because it's tricky
+to build a model that takes into account the velocity of air displaced by each single propeller. Instead, most of the
+drones rely on electric batteries, and we have better information about battery power, drawn current and voltage than we
+have about volumes of displaced air or gradients of pressure. So it's convenient to transform velocity into power _P_,
+remembering that:
+
+$$
+P = \frac{\Delta E}{\Delta t} = \frac{F \cdot \Delta s}{\Delta t} = F \cdot v
+$$
+
+A common convention is to set _v_ as the median between the velocity of air before (\(v_0 = 0\)) and after
+(\(v_1 = v_0 + \Delta v\)) hitting the blade:
+
+$$
+v = \frac{v_0 + v_1}{2} = \frac{\Delta v}{2}
+$$
+
+We can then rewrite the equation of power with respect to propeller thrust as:
+
+$$
+P = \frac{1}{2} T \Delta v
+$$
+
+In a real-case scenario, however, not all the energy drawn from the battery is converted into kinetic energy that spins
+the propellers. Motors have their own electric efficiency \(\eta_{el}\), which depends on the losses caused by electric
+energy transformed to heat during the rotation, and propellers also have their own mechanical efficiency \(\eta_
+{prop}\), mostly caused by the energy dissipated by the drag of the rotors against the air. A good brushless motor has
+an electric efficiency around 90%, while a good propeller has a mechanical efficiency between 80-85%. Therefore, the
+equation of the actual power that goes into lifting the drone should be rewritten as:
+
+$$
+P_{real} = \eta_{el} \cdot \eta_{prop} \cdot P = \frac{1}{2} \eta_{el} \eta_{prop} T \Delta v
+$$
+
+Solving for \(\Delta v\):
+
+$$
+\Delta v = \frac{2 \eta_{el} \eta_{prop} P}{T}
+$$
+
+Now that we have found a way to express the variation of air velocity in function of the power provided to the
+propellers, we can rewrite the equation of thrust calculated previously as:
+
+$$
+T = \frac{\pi}{8} \rho d^2 \Delta v^2 \\
+ = \sqrt[3]{\frac{\pi}{2} \rho d^2 \eta_{el}^2 \eta_{prop}^2 P^2}
+$$
+
+Finally, remembering that \(F = ma\), let's divide both the terms in the equation above by the gravity acceleration
+_g_ to get a value for thrust expressed in kg:
+
+$$
+m = \frac{1}{g} \sqrt[3]{\frac{\pi}{2} \rho d^2 \eta_{el}^2 \eta_{prop}^2 P^2}
+$$
+
+This is a very useful equation that can be used to design our drone. If we set _m_ equal to the mass of the drone,
+then we can solve for _P_ and calculate how much power we need to provide to the motors in order to generate a lift that
+equals the mass of the drone - in other words, this is the power required to achieve static thrust.
+
+$$
+P = \frac{1}{d \eta_{el} \eta_{prop}} \sqrt{\frac{2 m^3 g^3}{\pi \rho}}
+$$
+
+We can easily wrap the above formula into a small Python function:
+
+```python
+def balance_power(mass, propel_diam, motor_eff, propel_eff):
+ """
+ Calculate how much power is required to provide a thrust to a
+ drone that matches its weight, given a certain configuration
+ of propellers and motors.
+
+ :param mass: Mass (in kg)
+ :param propel_diam: Propellers diameter (in meters)
+ :param motor_eff: Motor efficiency (between 0 and 1)
+ :param propel_eff: Propellers efficiency (between 0 and 1)
+ :return: The power required to generate a lift that balances the weight
+ of the vehicle, in Watts
+ """
+ import math
+
+ # Air density at sea level and room temperature is about 1.225 kg/m^3
+ density = 1.225
+ # Gravity acceleration = 9.8 m/s^2
+ g = 9.8
+
+ return (
+ (1/(propel_diam * motor_eff * propel_eff)) *
+ math.sqrt((2 * math.pow(mass,3) * math.pow(g,3))/(math.pi * density))
+ )
+```
+
+For example, if you have a drone with the following characteristics:
+
+- _mass_: 500 grams
+- _propellers diameter_: 5 inches (= 0.127 meters)
+- _motors efficiency_: 90%
+- _propellers diameter_: 80%
+
+We can infer that it takes about 85W of power to generate a lift that balances the weight:
+
+```python
+>>> balance_power(mass=0.5, propel_diam=0.127, motor_eff=0.9, propel_eff=0.8)
+85.51256229077079
+```
+
+### The physics of electric vehicles
+
+The last formula brings us directly to the next topic: once we have figured the dynamics and the aerodynamics, and how
+much work is required to lift our object, it's time to translate those constraints into electric constraints and size
+battery, speed controllers and motors accordingly. Importantly, we should also design the system to be able to provide
+more power than the power required to simply balance its weight - thrust needs to be greater than weight if we want the
+drone to go up.
+
+You may probably need a powerful LiPo battery if you want to provide your motors with enough power to generate
+lift for a drone that will sport a Raspberry Pi, a camera, a bunch of sensors, and who knows how much more stuff you are
+planning to add :) high-power LiPo batteries usually come in packages like this:
+
+![Picture of a LiPo battery](../img/lipo-1.jpg)
+
+High-power LiPo batteries usually come with two types of connectors: XT60 (yellow adapter in the picture above)
+or T-Plug (original dark red connector connected to the battery in the picture above). Always make sure that the
+connector of your battery matches the connector of your power distribution board (adapters are available and cheap, but
+they add a bit of weight and take extra space on the drone).
+
+A LiPo battery usually comes with an indication of its total capacity _Q_, usually expressed in mAh, and its
+voltage, expressed in volts (keep in mind, however, that the advertised voltage goes down as the battery discharges,
+and that also impacts the output power).
+
+You also have an indication of the **discharge rate**, usually indicated by a number suffixed by _C_. This number
+is used to calculate the maximum current \(I_{max}\) that the battery can provide without resulting in damage to the
+battery itself - this number is the most fundamental cap to the maximum power that a battery can provide. The maximum
+current is calculated as the total capacity expressed in amperes divided by one hour and multiplied by the _C_ number.
+For example, in the case of the battery pictured above, with an advertised _50C_ value and _5200 mAh_ capacity, we have:
+
+$$
+I_{max} = 5.2 \cdot 50 = 260 A
+$$
+
+Now that we have the tools to measure the capacity, voltage and maximum current supported by the battery, we can
+substitute \(P = IV\) in the previous equation of power and, assuming that the voltage is about constant, we can
+calculate how much current the motors will absorb to generate a certain lift:
+
+$$
+I = \frac{P}{V} = \frac{1}{Vd \eta_{el} \eta_{prop}} \sqrt{\frac{2 m^3 g^3}{\pi \rho}}
+$$
+
+For example, if the minimum power required to provide stationary thrust is 85W, and the battery works with a tension of
+11.1V, then the average current that will be absorbed by the motors while hovering is about 7.6A.
+
+It is _very_ important that this current is lower than \(I_{max}\) - ideally it should be a fraction of \(I_{max}\),
+or at least half of it to give some headroom during current peaks, especially during lift off, in order to prevent
+permanent damage to the battery, so choose a battery with an appropriate discharge rate for the physical characteristics
+of your vehicle.
+
+Finally, we can calculate how long the battery will be able to provide a certain current. Knowing that
+\(I = \frac{\Delta Q}{\Delta t}\), we can express time as a function of battery capacity and required power as:
+
+$$
+\Delta t = \frac{Q}{I} = \frac{QV}{IV} = \frac{E}{P}
+$$
+
+Where _E_ is the total energy stored in the battery and _P_ is the power we want to provide to the load.
+
+Suppose that a 5000 mAh, 11.V battery is used on our previous 500 grams drone. Then in an ideal case the battery can
+provide a power of 85W (8A * 11.1V) for:
+
+$$
+\Delta t = \frac{5A * 3600\mbox{ sec} * 11.1V}{85W} = 39\mbox{ min}
+$$
+
+In reality this value is usually lower (usually at least a half or a third of it, depending on the minimum current
+required by the motors to spin) with a non-ideal battery, because below a certain charge left the battery won't be able
+to provide the same nominal values of voltage and current, while the formula above assumes that the battery can provide
+the same power until the last bit of charge left. However, you can empirically estimate the amount of charge left in the
+battery when it starts to struggle to provide the motors with enough torque (you can hear the motors slowing down when
+this happens), replace _E_ in the equation above with \(\Delta E = E_{max} - E_ {min}\), and you can get a more
+realistic estimate of the maximum flight time.
+
+### Theory in practice
+
+The math that governs the physics of lift and electric power may take a while to sink in, but we can briefly sum up the
+key takeaways from the formulas above when it comes to designing your drone:
+
+- The power you need to provide to lift your drone is inversely proportional to the diameter of the blades (larger
+ blades mean more air moved by the propellers, therefore more thrust, therefore less power required). However, larger
+ blades also add up to the total mass, and they also generate greater turbulence, making the vehicle harder to control.
+
+- The power you need to provide to lift your drone is proportional to the 3/2 power of its mass (a bit more than a
+ linear dependency, a bit less than a squared dependency). So beware of entering the vicious cycle where greater mass
+ requires bigger battery and bigger propellers, which in turn add mass, which in turn requires greater power, and so
+ on.
+
+- The discharge rate (_C_) of a battery is an important factor that you should take into account before selecting a
+ battery. Make sure that the battery can provide enough current to achieve static balance given the physical
+ characteristics of your drone.
+
+- The maximum time of flight for a drone (roughly calculated as the time that a battery can provide enough lift to
+ counteract its weight) in an ideal scenario equals the total energy stored in the battery divided by the power
+ required to achieve static balance.
+
+- In reality, this quantity assumes that the battery can provide the same power until the last drop of juice left, which
+ is usually not the case for real batteries. For a more accurate number, you should estimate how much charge is left in
+ your battery when the motors start to slow down, and calculate the total time in function of that \(\Delta E\)
+ difference.
+
+- Larger batteries can provide longer flight time, but they also add up to the total mass, so you may want to find an
+ appropriate trade-off for your case.
+
+Time to get our hands dirty with the real thing now!
+
+## The hardware of a drone
+
+### Components of a quadcopter
+
+- **Flight controller**: This is the "brain" of the drone. It receives commands from an input source and sends signals
+ down the line to control the motors. This is usually the most expensive component of a drone if you want to buy an
+ off-the-shelf circuit. We will be using a Raspberry Pi Zero as a flight controller in this article. That makes the
+ final cost of the drone considerably lower, but it also requires us to write the code for receiving remote commands
+ and for sending the correct signals downstream to the motors.
+
+- **Propellers**: These are technically the only moving parts of the vehicle. We have already covered previously how
+ their diameter affects the generated thrust, the required power and the level of air turbulence. Another important
+ consideration is about the number of blades. 2-blades propellers often result in vehicles that are easier to control,
+ even though they may not provide the same acceleration. 3-blades configuration are a bit harder to control, but they
+ usually provide greater acceleration and are preferred for larger drones or racing configurations. The material of
+ the propellers also plays a role, both in terms of robustness and added weight. Carbon fibers propellers are usually
+ preferred in professional applications because of their lower weight and greater robustness, but they come at a higher
+ cost. Plastic propellers are arguably the most common in amateur configurations. Remember: if you are still testing
+ and calibrating your drone, then go for cheap plastic propellers (they will get damaged for sure after a few bumps!),
+ and only use more expensive propellers once you have calibrated the vehicle and mastered the control.
+ Protective shields for propellers are usually also a *VERY* good idea.
+
+- **Brushless motors**: Smaller drones may sometimes opt for brushed motors, but these usually have lower efficiency
+ (75-80% vs. 85-90% for brushless motors) and tend to have a shorter life. However, brushless motors are usually more
+ expensive. The principle of a brushless motor is relatively simple: it consists of two parts, a _rotor_ (the rotating
+ part), and a _stator_ (the stationary part at the center of the motor). Both the stator and the rotor usually include
+ multiple permanent or coiled magnets. An alternate electric current applied to the stator generates a magnetic field,
+ and such magnetic field causes a misalignment between the magnetic fields of the stator and the rotor, continuously
+ attracting or repelling the coils in order to adjust the misalignment, and therefore resulting in a spinning movement.
+
+![Brushless motor structure](../img/brushless-motor-1.png)
+
+You can find many brushless motors for quadcopters online. There are a few things to keep an eye on when you select your
+motors. First, the motor size, expressed as diameter and height of the stator (e.g. 2207 means that the stator is 22mm
+wide and 7mm tall): bigger motors result in higher torque but they also absorb more power. Then, the _kV_ of the motor,
+which expresses the ideal number of rotations for each volt applied. The _thrust-to-weight ratio_ expresses how much
+thrust the motor can generate for each unit of weight - the higher, the better, 2:1 is an acceptable minimum value, 4:1
+is considered a middle sweet spot, high ratios such as 8:1 or 13:1 can theoretically be achieved by high-performance
+motors, but after a certain number of rotations per second spinning a motor any faster is considered inefficient.
+Finally, you may want to look at the advised number of LiPo cells for the motor. Since high-speed motors drain
+more power, they usually require batteries with a higher number of cells, as a higher number of cells translates in a
+higher voltage.
+
+- **Electronic Speed Controllers** (**ESCs**), one for each motor. Brushless motors work thanks to a varying current
+ that generates a rotating magnetic field, but you usually need a component between your controller and the motors
+ to transform the desired motor speed into an alternate current configuration to be applied to the motors. ESCs are
+ small circuits that do exactly this job. A factor to take into account when choosing an ESC is its current
+ specification (in Amperes). That expresses the maximum current that the ESC can deliver to the motors. Make sure that
+ it is higher than the current absorption you have estimated for your drone and lower than the current defined by the
+ discharge rate of your battery.
+
+- A **PWM servo controller**: Most of the ESCs out there communicate over _pulse width modulation_ (_PWM_). This is a
+ quite efficient way to transmit analog signals using the duration (width) of a digital signal. PWM basically allows
+ you to send analog signals using only one digital PIN. We will explore its internals a bit more in depth in the
+ coding section. Unfortunately, even though the Raspberry Pi theoretically has 4 PWM PINs, each of the pairs shares a
+ PWM resource (GPIO 12 and GPIO 18 share a PWM channel while GPIO 13 and GPIO 19 share the other channel). This means
+ that you can actually send a maximum of two distinct PWM signals at the same time, while our quadcopter obviously
+ needs four of them. To solve the problem, you can use a PWM servo controller to extend the PWM capabilities of the
+ Raspberry Pi - a popular choice is the [Adafruit 16-channel PWM driver](https://www.adafruit.com/product/815), which,
+ as the name suggests, can control up to 16 independent PWM channels at the same time, and you can even connect up to
+ 62 of these boards to control up to 992 channels, just in case you are planning to design a spaceship.
+
+- A pair of **batteries**. I said _pair_ because my advice is to use two separate power sources: a high-power LiPo
+ battery to power the motors, and a small LiPo battery to power the Raspberry Pi and the electronics (a Raspberry Pi
+ Zero that only runs the drone code shouldn't take much power). Even if a high-power LiPo can theoretically provide
+ enough power both for the motors and for the electronics, in reality the motors can suck up a lot of juice when they
+ spin fast, and if the current drops below a certain threshold the Raspberry Pi will just reboot (and you don't want
+ that to happen while your drone is hovering midair). You may also need a LiPo-to-USB power converter like the
+ [PowerBoost](https://shop.pimoroni.com/products/powerboost-1000-charger-rechargeable-5v-lipo-usb-boost-1a-1000c),
+ whose job is both to provide a USB output from a LiPo source and to stabilize the output voltage and current draw.
+ Keep in mind the previous consideration when you pick the battery to power the motors (weight, charge, discharge rate,
+ number of cells and output voltage).
+
+- A **power distribution board**, like [this one](https://www.amazon.de/-/en/gp/product/B071NXZLBM/). First, LiPo
+ batteries for quadcopters have specific connectors (usually XT60 or T-Plug) and you need some kind of adapter to
+ actually connect load to them. Second, these boards are a compact solution to distribute the power of a battery to
+ multiple loads - the board linked above can split the power of a LiPo battery to up to 6 loads.
+
+- A **controller**: Most of the commercial quadcopters have radio controllers, but that would add up both to the cost
+ and to the complexity of the circuitry used for the drone. For simplicity, in this article I'll illustrate how to set
+ up any device that exposes a joystick interface on Linux as your controller, with a particular focus on a Bluetooth
+ joystick (Bluetooth can only operate up to 10 meters, but since the Raspberry Pi has a built-in Bluetooth chip it
+ doesn't require USB dongles nor extra RC circuits).
+
+- A **drone frame**: You can find many drone frames online for quite affordable prices, or you can easily download a 3D
+ model and print it yourself. Personally I have used
+ [this model](https://cults3d.com/en/3d-model/various/drone-fpv-280) for my prototype, but
+ [this](https://cults3d.com/en/3d-model/game/chassis-drone-quadcopter) and
+ [this](https://cults3d.com/en/3d-model/gadget/drone-qav-250-fpv) are also popular (and more versatile) form factors.
+
+- A **camera** to take your amazing pictures and videos from above. We'll use a Raspberry Pi Camera in this tutorial,
+ since it already comes with a RPi native interface, and it doesn't require extra USB dongles and cables.
+
+- (_Optional_) an **accelerometer**: if you want to bring your drone to the next level then you can also add an
+ accelerometer to the Raspberry Pi. The accelerometer measures the gravity acceleration along the three axes. You can
+ easily add some logic to stabilize a drone if the input data from the accelerometer shows that it's leaning too much
+ in one direction.
+
+- (_Optional_) a **distance sensor**: ultrasound, laser or lidar sensors are very useful to study the environment around
+ the drone. You can place one at the bottom of the drone to detect when the drone has touched the ground, or some
+ sensors around the body to detect and avoid possible obstacles.
+
+## Putting things together
+
+You should now have a clear idea both of how to get a vehicle to lift and which components you need in order to achieve
+it. Let's put together the ingredients covered in the previous section to show a possible schema for the connections of
+your components.
+
+![Drone schema](../img/drone-schema.png)
+
+A couple of recommendations before jumping in a step-by-step breakdown of the diagram above:
+
+- Once you have all the components required for your drone, and before starting connecting, soldering and gluing things
+ together, get a scale, put all the components on it and check the total weight. This is the perfect moment to run the
+ calculations shown previously to estimate thrust, power and flight duration of your drone, now that the total mass is
+ known.
+
+- Play a bit with the placement of your components on the drone frame before you start connecting them together. Given
+ the shape of your frame, study what's the best placement of all the components - Raspberry Pi, PWM driver, camera,
+ power board, batteries, adapters, etc. Make a drawing of their placement, if it helps you remember a particular
+ configuration. Also, study how each component would connect with the others given their placement - the last thing you
+ want is an unmanageable ball of wires going all over the place. Space is limited on the body of a drone, and an
+ optimal initial arrangement of all the components puts your project in a good place.
+
+- Also, make sure that the weight is more or less balanced. It doesn't have to be 100% balanced on all the four arms, a
+ few grams of difference are still ok, the thrust can be adjusted accordingly during the calibration phase. However, a
+ too heavy imbalance can only be fixed by spinning some propellers at very high speeds, resulting in a higher current
+ drain, shorter duration of the flight, earlier damage to the motors and a harder to control drone.
+
+With these recommendations out of the way, let's dive into assembling our drone.
+
+### Frame
+
+Start by mounting the frame. Depending on the frame you bought or printed, this can be a task with a varying level of
+challenge. When you screw the parts together, remember that plastic screws are lighter than metallic ones. Avoid using
+hot glue to keep things together: you can use hot glue to stick other components to the frame or hold wires together,
+but make sure that the frame is sturdy and strong, and that it holds together even without glue. Your drone may fall
+a lot, and the last thing you want is the drone's body coming completely apart after a small fall.
+
+Once the frame is installed, motors are usually the next logical step. **ONLY** mount the motors right now, **NOT** the
+propellers. Propellers should be installed only when you have managed to calibrate and remotely control the motors, and
+you are sure that the drone won't flip backwards (or, worst, in your face) once you start giving power to the motors.
+
+### Motors and propellers
+
+If you have paid of attention to the previous diagram, you may have noticed that the motors spin in different
+directions. More precisely, the motors on the opposite diagonals spin in the same direction, and those on the same side
+spin on opposite directions. The most popular configuration is the following:
+
+- _Front-left motor_: clockwise spin.
+- _Front-right motor_: anti-clockwise spin.
+- _Back-left motor_: anti-clockwise spin.
+- _Back-right motor_: clockwise spin.
+
+The reason for this configuration can be found in Newton's 3rd-law, a.k.a. "action and reaction".
+
+If a helicopter only had the top propeller and not the smaller propeller on the back, it simply won't fly. Its body
+will just spin in the opposite direction as the rotation of its propeller. It's the propeller on the back (and the
+length of the arm that connects it to the body) that balances the spinning movement and provides direction to the
+vehicle.
+
+Similarly, if all the motors of a drone spun in the same direction then the drone will simply spin in the opposite
+direction as their rotation. This is because the motors apply a _torque_ to the body of the drone, and the body of
+the drone responds to this force with a forces that pushes it in the opposite direction. If instead you use an X-shaped
+configuration, then the rotational components of the four torques cancel each other if they rotate at the same speed,
+and all you have left is the lift. However, you can still adjust the torques during the flight to temporarily produce a
+rotation (or _yaw_) of the drone around its _z_ axis.
+
+Most of the sets of motors for quadcopters already come in pairs that rotate in different directions. The direction of
+the rotation is usually reported as a small arrow on the side of the motor's body.
+
+Once you have identified the correct spin direction of the motors, install the motors on the body of the drone.
+
+### Propellers direction, wind direction and lift
+
+Before proceeding with the other components, we should probably spend two words on the direction of the propellers
+(again, **DON'T** install them yet, but just keep these considerations in mind when you are ready to install them).
+
+Propellers for quadcopters usually come in two pairs with wings facing in slightly different directions. If you look at
+the sections of a set of propellers, you'll notice that they come with two different profiles: they either have the high
+edge on the left side and the bottom edge on the right side, or the other way around.
+
+We previously mentioned that lift is generated by creating a pocket of fast-moving, low-pressure air on top of the wing
+opposed to slow-moving, high-pressure air on the bottom. The high-pressure air below the wing ends up applying an
+upward facing force that causes the vehicle to lift.
+
+![Propeller rotation vs. air direction](../img/propeller-schema.png)
+
+If you look at the section of a propeller and picture it against the diagram above, then it's quite simple to figure
+out which propellers need to be attached to which motors. The propellers should "cut" through the air through the side
+that faces upwards. Because of Newton's 3rd law, air will start moving in the direction opposite to the spin direction
+of the motor. This causes the air above the blade to "jump" up, flow downwards along the blade, and eventually apply a
+higher pressure to the air below it. Therefore, a propeller with the "high" edge on the left of its section needs to be
+mounted on a clockwise spinning motor, and a propeller with the "high" edge on the right should be mounted on a
+counter-clockwise spinning motor. If you invert the direction, then no matter how fast you spin the blades, your drone
+won't move by an inch. That's because, under such configuration, the upper face of each blade would move the air up,
+therefore the moving air would apply no lift force to the structure of the propeller, and all you have built is a
+powerful fan to keep you cool in summer.
+
+### Setting up the remaining hardware
+
+Once the motors are set up, it's time to glue the ESCs to the frame of the drone and connect the motors to them.
+
+An ESC usually has three wires or soldering pads on one side and five wires on the other. The side with three
+wires/soldering pads should face the motor and be connected to the three wires coming out of the motor. These
+wires work as a current tri-phase system with each phase shifted by 120 degrees compared to the others. The shift in the
+output currents is what generates the alternate current that generates the rotating electromagnetic field that causes
+the motor to spin. It shouldn't matter in which order you solder these wires to the motor.
+
+![Example of an ESC](../img/esc-1.jpg)
+
+On the other side you will usually find a red wire and a black wire, respectively the reference VCC and GND, which need
+to be connected to the battery through the power distribution board.
+
+Finally, you usually find three additional wires on the same side, usually bundled together in a plastic connector and
+marked by black, red and white colors. These wires need to be plugged to the PWM circuit board, the red and black
+cables must be respectively be plugged to the VCC and GND PINs of the board and the white one to the PWM signal PIN.
+
+After you have installed all the four ESCs and connected them to the motors, it's time to install the power supply
+board and connect the ESCs to it.
+
+![Power supply board](../img/xt60-board.jpg)
+
+Most of the boards for quadcopters have two pairs of power supply connectors for the ESCs respectively on the left and
+right side. Solder the red wire of each ESC to a _+_ connector and each black wire to the _-_ connector.
+
+After you have mounted the power supply board and soldered the ESC power connectors to it, it's time to move to the
+PWM servo board that will control the PWM signals sent to the motors.
+
+![Adafruit 16-channel PWM board](../img/adafruit-16-pwm.jpg)
+
+There may be quite a bit of PINs to solder if you are using the Adafruit 16-channel board. You have to at least solder
+the following:
+
+- At least one header of PINs on either the left or right side of the board. These PINs are used to communicate with
+ the Raspberry Pi over I2C interface, so the only connections you really need are VCC (to the Raspberry Pi 3.3V or 5V
+ PIN), GND (to any of the Raspberry Pi ground PINs), SDA (serial data, to the Raspberry Pi I2C SDA PIN, usually GPIO2)
+ and SCL (serial clock, to the Raspberry Pi I2C SCL PIN, usually GPIO3).
+
+- Four triples of PINs (one per ESC) on the PWM connectors on the bottom side. You will connect the wires from the
+ ESC to these PINs - white wire to PWM, red wire to V+ and black wire to GND.
+
+- Two PINs or a screwed adapter on the V+/GND connectors on the top of the board. You may want to connect the power
+ supply board 12V/GND connectors here, since most of the ESC devices usually operate around a 9-16V voltage.
+ **NEVER** connect the V+ line to the Raspberry Pi in any way! 3.3V is supposed to be the maximum voltage of any
+ circuitry connected to the Raspberry Pi. If any higher voltage is present, then the current may start flowing on
+ wrong paths and you may end up damaging your Raspberry Pi for good. So connect the V+ to the power supply board,
+ but make sure that no other V+ PIN is connected to the Raspberry Pi - the only voltage connection to the Raspberry Pi
+ must be through VCC, which should always be in the 3.3-5V range.
+
+- It's also advised to solder an electrolytic capacitor on the top-left area of the board marked by _C2_. The
+ electrolytic capacitor provides a voltage buffer to the connected components in case of sudden current spikes or
+ drops.
+
+Once the PWM circuitry is set up, you can move to gluing or screwing the Raspberry Pi Zero. Connect the I2C interface
+on the side of the Adafruit board to the Raspberry Pi. If we consider the following pinout diagram:
+
+![Raspberry Pi pinout](../img/rpi-pinout.jpg)
+
+Then you'll need the following connection:
+
+- PWM board VCC PIN: connect to PIN 1, 2, 4 or 17 (the Adafruit 16-channel PWM board has an operative voltage range of
+ 3-6V).
+
+- PWM board GND PIN: connect to PIN 6, 9, 14, 20, 25, 30, 34 or 39.
+
+- PWM board SDA PIN: connect to PIN 3 (GPIO 2/I2C SDA).
+
+- PWM board SCL PIN: connect to PIN 5 (GPIO 3/I2C SCL).
+
+If you want, you can connect a camera now - simply plug the ribbon cable into the slot on the right side of the board
+if you are using a Raspberry Pi compatible camera.
+
+Once the Raspberry Pi is mounted and connected to the rest of the circuitry, you can finally proceed with the
+installation of the batteries. Mount the large LiPo battery on the frame and connect its XT60 or T-Plug cable to the
+power supply board. If you got all the connections right, you should see both the PWM board and the ESCs power up.
+
+Proceed with the installation of the smaller LiPo battery for the Raspberry Pi. You can use any available LiPo battery
+shield or adapter available for the Raspberry Pi, there are many options available to power the Raspberry Pi from a LiPo
+either over a USB adapter or through a shield/hat.
+
+Once everything is connected, it's time to move to the software side of the project.
+
+## Prepare the Raspberry Pi
+
+First, you'll need to flash a Linux image to an SD card. Any distro should work, but this article will mostly focus on
+Raspbian/Raspberry Pi OS because it makes the CircuitPython setup much easier.
+
+[Download a Raspberry Pi OS image](https://www.raspberrypi.org/%20downloads/). I personally used a headless version
+(i.e. with no X server nor desktop environment) because you don't really need to plug an HDMI cable, a mouse and a
+keyboard that often into a drone and use it like a computer, but if that's one of the use cases that you have in mind
+feel free to get a full desktop image. Use any of the supported methods to flash the downloaded image to the SD card,
+depending on the host operating system. For instance, the quickest way from Linux would be to simply use `dd`
+(**ALWAYS** make sure that the `/dev` file specified actually points to your SD card, or you may risk wiping your own
+filesystem):
+
+```shell
+$ sudo dd if=./raspberry-pi-os-VERSION.img of=/dev/mmcblk0 bs=8M conv=fsync status=progress
+```
+
+Wait a bit (depending on the size of the SD card) and once the copy is done unmount the device and place it into the SD
+card slot on the Raspberry Pi. Before booting the device, connect it to a monitor over an HDMI cable and connect a
+wireless/wired keyboard - this may be the only time that you need to actually need to connect the Raspberry to a screen.
+Boot the device and wait for the partition resize process to complete. You should eventually be prompted to a login
+screen - use the default credentials, _pi - raspberry_, to log in.
+
+We may now want to set up Wi-Fi connection, I2C and camera interfaces and SSH access for remote control. This can be
+easily done on Raspberry Pi OS through the `raspi-config` utility:
+
+- Type `sudo raspi-config`.
+
+- Select `System Options` -> `Wireless LAN` and enter the details of your Wi-Fi network.
+
+- Go back, select `Interface Options` -> `SSH` and enable SSH access.
+
+- It's also a good idea to set up auto-login with the `pi` user, so a user session with all the required scripts and
+ services will start automatically once the device is booted. This can be done from `System Options` ->
+ `Boot / Auto Login` -> `Console/Desktop Autologin`.
+
+- The Adafruit PWM servo communicates over I2C interface, therefore make sure it's enabled on the Raspberry Pi
+ (`Interface Options` -> `I2C`).
+
+- If you are using a Raspberry Pi camera, you can enable it from here (`Interface Options` -> `Camera`).
+
+- Select `Finish` and reboot.
+
+- On reboot type `ifconfig` or `ip addr` to verify that you are connected to the Wi-Fi network and note down the
+ assigned IP address (you may also want to add a static MAC rule on your router to make sure that it stays the same
+ across reconnections).
+
+Once your device is online, you can unplug the HDMI connection and any mouse or keyboard and restart the Raspberry Pi.
+After a few seconds you should be able to ping its IP address, and you can SSH into it from your desktop/laptop through
+`ssh pi@rpi-ip`.
+
+Once you have your remote shell to the drone, update it and make sure that the basic Python dependencies are available:
+
+```shell
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt install python3 python3-pip
+```
+
+You should now proceed with installing the CircuitPython environment. The
+PCA9685 chip used by the Adafruit 16-channel PWM board is only compatible with CircuitPython (a minimal installation
+of the Python interpreter dedicated to IoT and low-power devices), which isn't installed on the Raspberry Pi by default.
+
+Official instructions are
+[provided on the Adafruit website](https://learn.adafruit.com/16-channel-pwm-servo-driver?view=all#python-circuitpython).
+The quickest way if you are running Raspbian/Raspberry Pi OS is probably to install the Adafruit Python shell, download
+and run the `blinka` script:
+
+```shell
+$ sudo pip3 install --upgrade adafruit-python-shell
+$ wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py
+$ sudo python3 raspi-blinka.py
+```
+
+Wait a bit for the script to install all the required dependencies, then reboot the device. If everything was
+successful, upon reboot you should see a new direct-access block device under `/dev/i2c-1`. You can now install the
+CircuitPython driver for the PCA9685 chipset:
+
+```shell
+$ sudo pip3 install --upgrade adafruit-circuitpython-pca9685
+```
+
+## Set up the controller
+
+Time to move to the controller for our drone. As mentioned earlier, my choice for prototyping is a Bluetooth controller,
+since the Raspberry Pi Zero already comes with a built-in Bluetooth adapter and connecting it doesn't require any extra
+wires nor dongles. However, Bluetooth poses a limit on the maximum distance between the drone and the controller, since
+Bluetooth signals quickly degrade after about 10 meters, and on even shorter distances if there are some obstacles
+between the sender and the receiver. My advice is therefore to pick Bluetooth in the initial design and prototyping
+phases, or if you plan to fly the drone mostly indoor or on short distances. If instead you plan to fly your drone more
+than 10 meters away from you, then you may want to opt for a proper radio controller like
+[this](https://www.amazon.com/Radiolink-Transmitter-Controller-Multicopters-Helicopter/dp/B07FPF2HQR/). However, a
+proper radio set up is usually expensive, and you'll also have to install and configure a radio receiver on the
+Raspberry Pi as well. Moreover, if you are planning to fly your drone outdoor while viewing its camera feed then you
+may also want to install a 3G/4G SIM module on the drone, as well as a GPS sensor in order to locate it. These are
+eventually the most expensive parts that impact the final price of high-range drones, and they are going to be expensive
+even if you go for the DIY way. So my advice is to make a first prototype using the Bluetooth controller on short range,
+and once you are confident about your creature then you can make the investment and buy 3G/4G, GPS and RC hardware for
+a proper remote control and interface.
+
+Sticking to the Bluetooth controller case, first turn it on and put it in pairing mode (instructions differ from device
+to device, consult the manual of the controller). Once the controller is in pairing mode, start the Bluetooth
+administration service on the Raspberry Pi and connect the controller:
+
+```text
+$ sudo bluetoothctl
+[bluetooth]# scan on
+...
+[NEW] Device 00:11:22:33:44:55 My Joystick
+...
+[bluetooth]# scan off
+[bluetooth]# pair 00:11:22:33:44:55
+[bluetooth]# connect 00:11:22:33:44:55
+[bluetooth]# trust 00:11:22:33:44:55
+[bluetooth]# exit
+```
+
+If the connection was successful you should see a new joystick device under `/dev/input/js0`. You can test that all
+the buttons are correctly detected through the `jstest` utility:
+
+```shell
+$ jstest /dev/input/js0
+```
+
+If you can't access the device through the `pi` user then check its permissions and groups through
+`ls -l /dev/input/js0`. If it has no read privileges for non-group users then simply add the `pi` user to the
+associated group (usually `input` on Raspberry Pi OS) and reboot the device.
+
+## Connect the pieces together
+
+Now that all the hardware is in place and connected, we need to program the "brain" of the drone - the software
+component that emulates the flight controller, reads commands from the joystick and forwards them to the motors as
+PWM signals.
+
+We'll use [Platypush](https://platypush.tech) to connect the pieces together, since it comes both with a plugin for the
+[PCA9685 chipset](https://docs.platypush.tech/platypush/plugins/pwm.pca9685.html) and
+[a backend](https://docs.platypush.tech/platypush/backend/joystick.linux.html) to read events from joysticks and joypads
+on Linux, as well as several plugins and backends to stream camera feeds.
+
+Install Platypush on the Raspberry Pi Zero together with the web server, PCA9685 and PiCamera dependencies:
+
+```shell
+$ sudo pip3 install --upgrade 'platypush[http,picamera,pca9685]'
+```
+
+Then create a simple configuration file under `~/.config/platypush/config.yaml`:
+
+```yaml
+backend.http:
+ # Listen port
+ port: 8008
+
+backend.joystick.linux:
+ device: /dev/input/js0
+
+camera.pi:
+ horizontal_flip: False
+ vertical_flip: False
+ resolution:
+ - 800
+ - 600
+
+pwm.pca9685:
+ # PWM main frequency, in Hz
+ frequency: 500
+
+ # Default PWM channels to control.
+ # In this case, we have connected the ESCs
+ # to the first four channels on the board.
+ channels:
+ - 0
+ - 1
+ - 2
+ - 3
+
+# Start streaming the camera feed on TCP port 5000
+# when the application starts
+event.hook.OnApplicationStarted:
+ if:
+ type: platypush.message.event.application.ApplicationStartedEvent
+ then:
+ - action: camera.pi.start_streaming
+ args:
+ listen_port: 5000
+```
+
+All the sections of the file should be relatively self-explanatory, but the PCA9685 configuration requires a deeper dive
+in how PWM modulation works under the hood to be properly grasped - we'll dive into it very soon.
+
+In the meantime, you should already be able to start the application through the `platypush` command
+(use the `pi` user). If everything was installed and configured properly, after a while you should see the camera
+sensor turn on - you can view the camera feed either through the
+[RPi Camera Viewer](https://play.google.com/store/apps/details?id=ca.frozen.rpicameraviewer&hl=en_US&gl=US) app for
+Android, or through VLC:
+
+```shell
+$ vlc tcp/h264://rpi-ip:5000
+```
+
+If you turn on and pair your Bluetooth controller you should also be able to see some events in the application log
+when you press some controls on the device:
+
+```
+...
+INFO|platypush|Received event: {"type": "event", "args": {"type": "platypush.message.event.joystick.JoystickConnectedEvent", "device": "/dev/input/js0", "name": "PG-SW021", "axes": ["x", "y", "z", "rz", "gas", "brake", "hat0x", "hat0y", "x", "y", "z", "rz", "gas", "brake", "hat0x", "hat0y", "x", "y", "z", "rz", "gas", "brake", "hat0x", "hat0y", "x", "y", "z", "rz", "gas", "brake", "hat0x", "hat0y", "x", "y", "z", "rz", "gas", "brake", "hat0x", "hat0y"], "buttons": ["a", "b", "c", "x", "y", "z", "tl", "tr", "tl2", "tr2", "select", "start", "mode", "thumbl", "thumbr", "a", "b", "c", "x", "y", "z", "tl", "tr", "tl2", "tr2", "select", "start", "mode", "thumbl", "thumbr", "a", "b", "c", "x", "y", "z", "tl", "tr", "tl2", "tr2", "select", "start", "mode", "thumbl", "thumbr", "a", "b", "c", "x", "y", "z", "tl", "tr", "tl2", "tr2", "select", "start", "mode", "thumbl", "thumbr", "a", "b", "c", "x", "y", "z", "tl", "tr", "tl2", "tr2", "select", "start", "mode", "thumbl", "thumbr"]}}
+...
+INFO|platypush|Received event: {"type": "event", "args": {"type": "platypush.message.event.joystick.JoystickAxisEvent", "device": "/dev/input/js0", "axis": "hat0x", "value": -1.0}}
+INFO|platypush|Received event: {"type": "event", "args": {"type": "platypush.message.event.joystick.JoystickAxisEvent", "device": "/dev/input/js0", "axis": "hat0x", "value": 0.0}}
+...
+```
+
+If you see these events it means that the joystick was successfully detected. We'll come back to these events in a bit
+to see how to connect them to drone actions. It is now a good idea to configure Platypush as a startup service for the
+`pi` user, so whenever a new user session starts the application will also be started. Get its absolute path:
+
+```
+$ which platypush
+```
+
+Then create a new systemd service file as `~/.config/systemd/user/platypush.service` with the following content:
+
+```
+[Unit]
+Description=Platypush service
+After=network.target bluetooth.target
+
+[Service]
+ExecStart=/path/to/platypush
+Restart=always
+RestartSec=5
+
+[Install]
+WantedBy=default.target
+```
+
+Then reload the systemd daemon, start and enable the service:
+
+```
+$ systemctl --user daemon-reload
+$ systemctl --user start platypush.service
+$ systemctl --user enable platypush.service
+```
+
+It's now time to dive into the PWM internals to understand how to _program_ the controls for the motors through the
+Raspberry Pi.
+
+## Pulse-Width Modulation (PWM) explained
+
+PWM is one of the most common ways of driving electric motors because it makes it easy to transmit analog values using
+only one digital signal that can either be high or low, and it optimizes power consumption by applying a voltage to the
+load only when a change to the output power is required. The _duration_ of the high part of the signal expresses the
+value that you want to transmit, therefore the longer the duration of the high part of a signal, the higher the voltage
+or current that will be transmitted to the load. Conceptually, PWM is somehow similar to popular radio modulation
+technologies such as AM or FM, but instead of modulating information through the amplitude or the frequency of the
+signal, it modulates it through the duration of a digital high signal.
+
+For example, the figure below shows how to transmit a sinusoidal signal _B_ through a PWM train of pulses _V_. The
+duration of each pulse expresses how much the output signal should go up (if the voltage is positive) or down (if the
+voltage is negative) compared to the previous output value. The duration of a high pulse is also called _duty cycle_.
+
+![PWM signal example](../img/pwm-1.png)
+
+The most important metric to take into account when designing PWM systems is the base frequency of the modulation
+process - in other words, how often the signal should be sampled. When driving motors that usually have a minimum and
+maximum power, you may also want to specify the minimum and maximum _duty cycle_, i.e. the minimum and maximum duration
+of a high pulse that will be respectively be mapped to the minimum and maximum output power. It is very important to
+pick these values in such a way that the minimum value is greater than the sampling period (which is simply the inverse
+of the sampling frequency). To be more specific, according to
+[Nyquist-Shannon's sampling theorem](https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem), the
+minimum frequency used to deliver actual information must be at least twice the sampling frequency. If that's not the
+case then some pulses will not be detected, because the duration of the pulse may be shorter than the sampling period.
+
+Most of the ESC controllers on the market have their own supported PWM configurations, and some advanced controllers may
+even allow more complex controls over PWM - for example for controlling sounds, lights or supporting multiple motor
+control paradigms. Therefore, the user manual of your ESC (or the chipset used by your ESC) is usually the best place
+to start before writing the code that actually controls the motors.
+
+In my case I have used some ESC devices with a
+[BLHeli_32 ARM chipset](https://github.com/bitdump/BLHeli/tree/master/BLHeli_32%20ARM), a quite popular option for
+mid-to-high tier quadcopters - and also a very versatile one, since it's basically a small programmable 32-bit ARM
+microcontroller. The
+[user manual](https://render.githubusercontent.com/view/pdf?color_mode=light&commit=746220a3da8b36a37b9388fec624bc50299ce974&enc_url=68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f62697464756d702f424c48656c692f373436323230613364613862333661333762393338386665633632346263353032393963653937342f424c48656c695f333225323041524d2f424c48656c695f33322532306d616e75616c25323041524d25323052657633322e782e706466&nwo=bitdump%2FBLHeli&path=BLHeli_32+ARM%2FBLHeli_32+manual+ARM+Rev32.x.pdf&repository_id=3813420&repository_type=Repository#c0f6bc3e-8870-4047-8084-2b0cafc3cfbd)
+mentions support for a regular pulse width input between 1-2ms. We can pick 2ms as our sampling period, and
+\(1/2ms = 500 Hz\) will therefore be our sampling frequency - configured in the Platypush `pwm.pca9685` plugin through
+the `frequency` attribute. The manual also mentions support for other PWM configurations (such as OneShot125, OneShot42,
+Multshot and Dshot) that usually rely on shorter pulses, but for sake of simplicity (and compatibility with other ESC
+devices) we can stick to the regular 2ms sampling period.
+
+Also, the driver of the PCA9685 controller automatically takes care of picking the right duty cycle range given
+the frequency, and it maps the duty cycle to 16-bit integers between 0 and 65535. A duty cycle of zero means 0% of the
+maximum power, a duty cycle of 0xffff means 100% of the power, a duty cycle of 0x7fff means 50% of the power. The
+Platypush wrapper for the PCA9685 uses `min_duty_cycle=0` and `max_duty_cycle=0xffff` unless configured otherwise, but
+this is probably not what you want for your drone. You usually want a higher minimum duty cycle because under a certain
+threshold the power will be insufficient to even get the motors to spin. And, in most of the cases, you don't want to
+have a maximum duty cycle that is 100% of the maximum power. If you have 35A ESC, that will draw 35A from the battery
+and, most importantly, it will probably turn your drone into an uncontrollable killer machine. In a general use case you
+may want to identify the minimum duty cycle associated to a minimal spin of the motors and pick that as the minimum duty
+cycle, and identify the duty cycle associated to a lift-off at the desired speed and pick that as the maximum duty
+cycle. We'll see how to use Platypush to calibrate this range through the joystick in a bit.
+
+Finally, most of the ESC devices have an **arming** sequence. Its main purpose is to prevent unexpected currents or
+accidental controls from suddenly spinning the motors when it's not desired/required. Therefore, before flying the drone
+you need to actually send a special PWM sequence that _arms_ the ESC controllers, and sometimes you may need to send
+another sequence to disarm them (this is usually achieved by bringing the power back to zero, and most of the ESC would
+automatically disarm after a while if no further pulses are sent).
+
+You should consult the user manual of your ESC to check the supported arming and disarming sequences. For examples,
+the BLHeli_32 user manual describes the following procedure:
+
+![BLHeli_32 arming sequence](../img/esc-pwm.png)
+
+What this diagram says is that:
+
+- The ESC should beep three times when it's powered on.
+
+- Once the ESC is powered on, the arming sequence can be initiated by gradually increasing the voltage on the PWM
+ input from 0 to approximately 50% the maximum duty cycle, and then bringing the voltage down back to zero.
+
+- When the input voltage hits the first low voltage threshold, the ESC emits a first low beep. When it goes back to
+ zero, the ESC emits a high beep.
+
+- If you hear both the beeps it means that the arming sequence was correct, and you can start stepping up the voltage
+ to actually send power to the motors.
+
+- If you only hear the low beep, it means that the arming sequence hasn't been successful. It usually means that the
+ pulse train was either too long or too short - its duration should be a multiple of the sampling period, but if it's
+ too much higher then the arming sequence will time out.
+
+- The deactivation sequence simply consists in bringing the duty cycle to zero and de-initializing the PCA9685 driver.
+
+Now that we have learned the theory of what PWM is, how to use it for signal transmission protocols and how to arm and
+deactivate an ESC, it's time to get our hands dirty with some code.
+
+## Coding the flight controller
+
+Also, be aware: this is the part where you actually spin and test the motors. Once again, **do it without the
+propellers**, and double check again that all the connections to the motors are properly set up and that the ESCs are
+powered on before you try and send any signal.
+
+Create a new script under `~/.config/platypush/scripts/drone.py`. This script will contain the automation to control
+your drone through the joypad. We first need to code a function to arm the four ESC devices. If you are using a
+BLHeli_32-based ESC (or any other ESC device with a double-ramp arming sequence) then you may want to code a function
+that uses the `pwm.pca9685` plugin to write a zero to the PWM channel, wait a bit, write a value about half of the
+maximum duty cycle, wait a bit, and write another zero. And we also need a function that resets the motors by bringing
+the voltage to zero and resetting the PCA9685 driver:
+
+```python
+import logging
+import time
+
+from platypush.context import get_plugin
+
+logger = logging.getLogger(__name__)
+
+def arm_motors():
+ logger.info('Arming drone ESCs')
+ pwm = get_plugin('pwm.pca9685')
+ pwm.write(0)
+ time.sleep(0.5)
+ pwm.write(0x7fff)
+ time.sleep(0.5)
+ pwm.write(0)
+ time.sleep(0.5)
+ logger.info('Drone ESCs ready')
+
+def reset_motors():
+ logger.info('Resetting drone ESCs')
+ pwm = get_plugin('pwm.pca9685')
+ pwm.write(0)
+ pwm.reset()
+ pwm.deinit()
+ logger.info('Drone ESCs reset')
+```
+
+The right time between the writes largely depends on the arming sequence supported by the ESC and it may be a bit of a
+trial-and-error process. Many ESCs will emit a particular sequence of beeps or flash their LED in a particular way upon
+successful arming sequence. Before you add more code or hook these events to joystick events, open a Python CLI from
+the `~/.config/platypush` directory and give your logic a test:
+
+```python
+>>> from scripts.drone import arm_motors, reset_motors
+>>> arm_motors()
+# Arming drone ESCs
+# Drone ESCs ready
+>>> reset_motors()
+# Resetting drone ESCs
+# Drone ESCs reset
+```
+
+In this case we are not specifying the list of PWM channels to write: if a single value is specified on the `.write()`
+method then the same value will be written to all the channels configured on the plugin, and we previously configured
+the `pwm.pca9685` plugin to use the first four channels by default.
+
+Once you have managed to arm and disable the ESCs, let's associate these functions to joypad events. For instance, we
+can prepare the motors when the joypad connects to the drone and disable them when the joypad disconnects. This can
+be easily done in the `drone.py` script by leveraging the Platypush
+[`JoystickConnectedEvent`](https://docs.platypush.tech/platypush/events/joystick.html#platypush.message.event.joystick.JoystickConnectedEvent)
+and [`JoystickDisconnectedEvent`](https://docs.platypush.tech/platypush/events/joystick.html#platypush.message.event.joystick.JoystickDisconnectedEvent):
+
+```python
+from platypush.event.hook import hook
+from platypush.message.event.joystick import JoystickConnectedEvent, JoystickDisconnectedEvent
+
+@hook(JoystickConnectedEvent)
+def on_joystick_connected(**_):
+ arm_motors()
+
+@hook(JoystickDisconnectedEvent)
+def on_joystick_disconnected(**_):
+ reset_motors()
+```
+
+Now restart the service on the drone:
+
+```shell
+$ systemctl --user restart platypush
+```
+
+Then start/pair/connect the joypad. Upon connection, you should see a `JoystickConnectedEvent` on the logs and the ESCs
+should automatically get armed. Now it's a good idea to set up a joystick button to trigger the `.reset_motors()`
+and `.arm_motors()` methods. This will allow us to programmatically arm/disable the motors without having to
+connect/disconnect the joypad. Press the joystick keys that you want to associate to the arm and reset actions while the
+application is running and check the associated key code in the logs. For example, if you pick the _Select_ and _Start_
+buttons on a PlayStation or XBox-like joypad:
+
+```text
+Received event: {"type": "event", "args": {"type": "platypush.message.event.joystick.JoystickButtonPressedEvent",
+"device": "/dev/input/js0", "button": "select"}}
+Received event: {"type": "event", "args": {"type": "platypush.message.event.joystick.JoystickButtonPressedEvent",
+"device": "/dev/input/js0", "button": "start"}}
+```
+
+So create an event hook in your `drone.py` script that associates the `.arm_motors()` and `.reset_motors()` methods to
+these buttons:
+
+```python
+from platypush.event.hook import hook
+from platypush.message.event.joystick import JoystickButtonPressedEvent
+
+@hook(JoystickButtonPressedEvent, button='start')
+def on_start_button_pressed(**_):
+ arm_motors()
+
+@hook(JoystickButtonPressedEvent, button='select')
+def on_select_button_pressed(**_):
+ reset_motors()
+```
+
+Now pick two joystick buttons that you want to use to bring the motor power respectively up and down and log the current
+duty cycle, and add event hooks for them. For example, during calibration I usually use the L2/R2 buttons on the back of
+the joystick to respectively bring the power down or up:
+
+```python
+import logging
+
+from platypush.context import get_plugin
+from platypush.event.hook import hook
+from platypush.message.event.joystick import JoystickButtonPressedEvent
+
+logger = logging.getLogger(__name__)
+# This defines how much we should take the motor power up or down on each step.
+# Feel free to experiment different values for different levels of control.
+step = 25
+
+def power_down():
+ pwm = get_plugin('pwm.pca9685')
+ channels = {
+ i: value-step
+ for i, value in pwm.get_channels().output.items()
+ }
+
+ logger.info(f'Power down. Channel values: {channels}')
+ pwm.write(channels=channels)
+
+def power_up():
+ pwm = get_plugin('pwm.pca9685')
+ channels = {
+ i: value+step
+ for i, value in pwm.get_channels().output.items()
+ }
+
+ logger.info('Power up. Channel values: {channels}')
+ pwm.write(channels=channels)
+
+@hook(JoystickButtonPressedEvent, button='tl2')
+def on_power_down_button_pressed(**_):
+ power_down()
+
+@hook(JoystickButtonPressedEvent, button='tr2')
+def on_power_up_button_pressed(**_):
+ power_up()
+```
+
+Now restart the service and arm the ESCs. Once armed, press R2 repeatedly to bring up the power provided to the motors.
+At the beginning the motors might not move at all, but once passed a certain current threshold they will start spinning
+lightly. Take note of the power configuration required to achieve a minimum level of spin on the motors, it should now
+be reported on the application logs. This is going to be the `min_duty_cycle` on the `pwm.pca9685` plugin configuration,
+since any lower currents won't cause any change to the speed of the motors. Some motors may also have an upper boundary
+for the maximum current that could be lower than the maximum current that can be delivered by the ESCs. If that's the
+case, then the motors will usually automatically stop. If you notice such behaviour while powering up the motors, then
+configure the associated duty cycle as `max_duty_cycle`. For example, this configuration seems to work quite well for my
+combination of motors and ESCs:
+
+```yaml
+pwm.pca9685:
+ frequency: 500
+ min_duty_cycle: 2000
+ max_duty_cycle: 4000
+ channels:
+ - 0
+ - 1
+ - 2
+ - 3
+```
+
+Now that we have some commands configured to arm and reset the motors, and some logic to increase or decrease the power,
+we can proceed to calibrating the drone.
+
+## Drone calibration
+
+This is the moment of truth of the project: you can now set up the propellers on the motors and verify that your
+creature can move (follow the previous instructions about the direction and the order of the blades).
+
+However, before calling your friends to show off your new drone, you may consider calibrating the motors - and this
+may be a quite time-intensive process.
+
+In theory, sending the same current to two motors should cause the same torque on the body of the drone. However, many
+factors (such as small production differences between individual motors, distribution of the weight on the frame or
+different level of friction between the stator and the rotor) usually cause the motors of a drone to behave slightly
+differently. If the torques of the motors are not perfectly balanced, then the drone will either spin around its body or
+flip in the direction opposite to the one with the strongest torque. Therefore, you may want to add to your `drone.py`
+script a mapping of the current offsets for each motor. A smaller value means that less current will be provided to
+that motor compared to the others, a greater value is the other way around. For now let's initialize this table with
+zeros:
+
+```python
+class Channel:
+ """
+ Motor position to PWM channel index.
+ """
+ TOP_LEFT = 0
+ TOP_RIGHT = 1
+ BOTTOM_LEFT = 2
+ BOTTOM_RIGHT = 3
+
+offsets = {
+ Channel.TOP_LEFT: 0,
+ Channel.TOP_RIGHT: 0,
+ Channel.BOTTOM_LEFT: 0,
+ Channel.BOTTOM_RIGHT: 0,
+}
+```
+
+Now let's wrap the `.arm_motors()` method into a `.warmup()` method that does the following:
+
+1. Arms the ESCs.
+
+2. Increases the current sent to the motors by gradually increasing the duty cycle to each of the ESCs until
+ `min_duty_cycle + offset[i]`. This will get the motors to rotate at low speed.
+
+3. The joystick `start` button will call this `.warmup()` method instead of `.arm_motors()`.
+
+The logic will look like this:
+
+```python
+import logging
+
+from platypush.context import get_plugin
+from platypush.event.hook import hook
+from platypush.message.event.joystick import JoystickButtonPressedEvent
+
+logger = logging.getLogger(__name__)
+
+def warmup():
+ arm_motors()
+ logger.info('Starting the propellers!')
+ pwm = get_plugin('pwm.pca9685')
+
+ # Start from 50% of min_duty_cycle
+ channels = {
+ channel: int(0.5 * pwm.min_duty_cycle) + offset
+ for channel, offset in offsets.items()
+ }
+
+ pwm.write(channels=channels)
+
+ # Bring the power up to min_duty_cycle
+ channels = {
+ channel: pwm.min_duty_cycle + offset
+ for channel, offset in offsets.items()
+ }
+
+ pwm.write(channels=channels, step=15, step_duration=0.03)
+
+@hook(JoystickButtonPressedEvent, button='start')
+def on_start_button_pressed(**_):
+ warmup()
+```
+
+Now it's time to remind a few precautions:
+
+- Make sure that there's enough room around the drone and that it's sitting on a large surface.
+
+- Make sure that no obstacles or people are present in a radius of at least 2-3 meters from the drone.
+
+- Remember that once the motors have the propellers attached and you start loading a lot of current into them, whatever
+ you attached to them will start moving. But you haven't calibrated the motors yet, so you _don't know_ yet in which
+ direction the drone will move. Behave accordingly.
+
+With your drone sitting in a safe spot, it's time to restart Platypush, trigger the `.warmup()` function and start
+powering juice to the motors. At some point you'll hopefully start seeing the drone hover and slightly move in some
+direction. This is the moment where you should pay most of the attention, for two reasons:
+
+1. You know have an uncalibrated hovering device with blades that spin hundreds of times a second, and you
+ know the rules: as soon as anything goes wrong, _hit that `.reset_motors()` button_!
+
+2. It's very likely that the drone won't simply move up straight from the floor at this stage, because some motors may
+ generate a lower or higher torque than others.
+
+3. Unless you have been very lucky on the point above, the first time you try and fly the drone it'll move in some
+ random direction. Therefore don't provide too much power to the motors - you don't want to fly it just yet, you
+ just want to let it hover a bit to see how the forces are balanced.
+
+Regarding the second point, pay attention to how the drone moves in order to understand how to calibrate it:
+
+1. If it spins clockwise, then the torque from the anti-clockwise motors is stronger than the torque from the motors
+ that spin clockwise. Slightly decrease the offsets of `TOP_RIGHT` and/or `BOTTOM_LEFT`. The opposite applies if the
+ drone spins clockwise.
+
+2. If it spins clockwise/anti-clockwise, but the rotation axis is closer to a motor instead of the center of the drone,
+ then the motor closer to the rotation axis generates a lower torque: increase its offset value.
+
+3. If it tilts towards the left, then the torque generated by the motors on the right side is stronger: reduce the
+ offsets values of `TOP_RIGHT` and `BOTTOM_RIGHT`. The opposite applies if it tilts on the left side.
+
+4. If it tilts towards the front, then the torque generated by the motors on the back is stronger: reduce the
+ offsets values of `BOTTOM_LEFT` and `BOTTOM_RIGHT`. The opposite applies if it tilts towards the back.
+
+Observe how the balance of the forces affects the movement of the drone, adjust the offset values accordingly, restart
+the service. Repeat this procedure until you get the drone to lift it more or less on a straight line once a sufficient
+level of power has been reached.
+
+Once you have found the combination of power offsets that causes your drone to lift along a straight line, reduce the
+power pumped to the motors until the drone stabilizes into a hovering position. Take note of the values being sent to
+the ESCs when static hovering is achieved, they will be reported on the application logs as `Power up/down.
+Channel values: ` lines. These will be the values needed to achieve static thrust. Save them in your
+`drone.py` script, we'll use them later as a reference to control the movement of the drone while it's flying:
+
+```python
+static_thrusts = {
+ Channel.TOP_LEFT: ...,
+ Channel.TOP_RIGHT: ...,
+ Channel.BOTTOM_LEFT: ...,
+ Channel.BOTTOM_RIGHT: ...
+}
+```
+
+Now that the most difficult part of the project (getting the drone off the ground on a straight line) is done, it's time
+to move to programming the logic to actually move your drone while it's in the air.
+
+## The mechanics of flight control
+
+Once it's hovering above the ground, you can picture a drone as a body that can move in four possible ways. It can
+go up/down depending on the power supplied to the motors, or it can rotate around its two horizontal axes (_x_ and _y_)
+or around the vertical axis (_z_).
+
+1. The movement of a drone up or down along the vertical axis is called **throttle**.
+
+2. The rotation of a drone around the _x_ axis is called **roll**.
+
+3. The rotation of a drone around the _y_ axis is called **pitch**.
+
+4. The rotation of a drone around the _z_ axis is called **yaw**.
+
+![Pitch, roll and yaw schematics (credits: https://www.researchgate.net/publication/329392693_Autonomous_Person_Detection_and_Tracking_Framework_Using_Unmanned_Aerial_Vehicles_UAVs)](../img/pitch-roll-yaw-1.png)
+
+To regulate each of these movements from a stationary position:
+
+1. _Throttle_: simply increase/decrease by the same offset the power provided to each of the motors. Higher offsets
+ result in the drone moving up, lower values result in the drone moving down.
+
+2. _Roll_: you can use this movement to move the drone laterally without rotating its front-facing side. Slightly
+ increase the power supplied the motors on the opposite side of the desired direction and slightly decrease the
+ power supplied to the motors on the other side. So moving the drone to the left is achieved by increasing the power
+ to the `TOP_RIGHT` and `BOTTOM_RIGHT` motors and decreasing the power to the `TOP_LEFT` and `BOTTOM_LEFT` motors,
+ and the other way around if you want to move the drone to the right.
+
+3. _Pitch_: you can use this movement to move the drone forward or backwards. Again, slightly increase the power
+ supplied the motors on the opposite side of the desired direction and slightly decrease the power supplied to the
+ motors on the other side. So moving the drone forward is achieved by increasing the power to the `BOTTOM_LEFT`
+ and `BOTTOM_RIGHT` motors and decreasing the power to the `TOP_LEFT` and `TOP_RIGHT` motors, and the other way around
+ if you want to move the drone backwards.
+
+4. _Yaw_: you can use this movement to rotate the drone around the vertical axis without changing its inclination. This
+ is particularly used when you want to take 360 degrees pictures or videos of the area around the drone. This movement
+ is achieved by increasing the power supplied to the motors that rotate in the opposite direction as the desired
+ direction, and decreasing the power supplied to the other two motors. So rotating the drone clockwise is achieved by
+ increasing the power to the `TOP_RIGHT` and `BOTTOM_LEFT` motors and decreasing the power to the `TOP_LEFT` and
+ `BOTTOM_RIGHT` motors, and the other way around if you want to rotate the drone anti-clockwise.
+
+## Controlling the movement through the joystick
+
+Now that we have an understanding of how to get the drone to move in any direction, it's time to translate our
+understanding into code by mapping these movements to joystick controls. Most of the controllers available for the
+commercial drones use this configuration:
+
+![Common mapping between joystick controls and drone movements (credits: https://smaccmpilot.org/hardware/rc-controller.html)](../img/pitch-roll-yaw-2.png)
+
+I'll assume that your joystick has analog axis controllers, since it makes things more consistent with the common
+conventions use by the drones on the market.
+
+First, let's write a `.move()` method to move the drone in each of the desired directions. For simplicity, we'll assume
+that the drone can do one movement at the time (e.g. it can't climb and yaw, or roll and pitch, at the same time), but
+the code can easily be extended with more complex logic:
+
+```python
+from enum import IntEnum
+from logging import getLogger
+from threading import RLock
+
+from platypush.context import get_plugin
+
+class Movement(IntEnum):
+ THROTTLE = 0
+ PITCH = 1
+ ROLL = 2
+ YAW = 3
+
+class Channel:
+ TOP_LEFT = 0
+ TOP_RIGHT = 1
+ BOTTOM_LEFT = 2
+ BOTTOM_RIGHT = 3
+
+static_thrusts = {
+ Channel.TOP_LEFT: ...,
+ Channel.TOP_RIGHT: ...,
+ Channel.BOTTOM_LEFT: ...,
+ Channel.BOTTOM_RIGHT: ...,
+}
+
+movement_lock = RLock()
+logger = getLogger(__name__)
+# Base step offset for "smoothening" the PWM changes
+step = 25
+# Duration of each step pulse during the transients
+step_duration = 0.03
+
+def normalize_thrust(channel: int, percent: float) -> int:
+ """
+ Converts a percent change to one of the channels into a
+ duty cycle integer that can be sent to the PWM controller.
+ The base value for all the percentage changes is always the
+ static thrust value for a given channel.
+ """
+ pwm = get_plugin('pwm.pca9685')
+ static_thrust = static_thrusts[channel]
+ percent = max(-1., min(1., percent))
+ thrust = static_thrust * (1 + percent)
+ return int(
+ max(pwm.min_duty_cycle, min(thrust, pwm.max_duty_cycle))
+ )
+
+
+def move(movement: Movement, percent: float):
+ """
+ :param movement: Direction for the movement.
+ :param percent: Percentage of change compared to the
+ static_thrusts configuration, must be between -1.0 and 1.0.
+ """
+ channel_values = static_thrusts.copy()
+
+ if movement == Movement.THROTTLE:
+ channel_values = {
+ channel: normalize_thrust(channel, percent=percent)
+ for channel in channel_values.keys()
+ }
+ elif movement == Movement.ROLL:
+ channel_values = {
+ channel: normalize_thrust(
+ channel, percent=percent/2
+ if channel in [Channel.TOP_LEFT, Channel.BOTTOM_LEFT]
+ else -percent/2
+ )
+ for channel in channel_values.keys()
+ }
+ elif movement == Movement.PITCH:
+ channel_values = {
+ channel: normalize_thrust(
+ channel, percent=percent/2
+ if channel in [Channel.BOTTOM_LEFT, Channel.BOTTOM_RIGHT]
+ else -percent/2
+ )
+ for channel in channel_values.keys()
+ }
+ elif movement == Movement.YAW:
+ channel_values = {
+ channel: normalize_thrust(
+ channel, percent=percent/2
+ if channel in [Channel.TOP_RIGHT, Channel.BOTTOM_LEFT]
+ else -percent/2
+ )
+ for channel in channel_values.keys()
+ }
+
+ # Ensure that only one thread at the time can control
+ # the current motion of the drone
+ with movement_lock:
+ logger.info(f'Moving drone: movement={movement.name}, percent={percent}')
+ pwm = get_plugin('pwm.pca9685')
+ pwm.write(channels=channel_values, step=step, step_duration=step_duration)
+```
+
+Now that we have a function that can easily translate the desired percentage of change along a certain direction into
+the appropriate PWM values for the motor channels, let's connect them to our joystick controls. With Platypush running
+and the controller connected, move the analog controls on your joystick and check the lines logged by the application.
+You should see events like this:
+
+```
+Received event: {
+ "type": "event",
+ "args": {
+ "type": "platypush.message.event.joystick.JoystickAxisEvent",
+ "device": "/dev/input/js0",
+ "axis": "x",
+ "value": 0.5
+ }
+}
+```
+
+A joystick's analog controls are usually mapped to four axes: the left control's horizontal and vertical axes are
+respectively mapped to _x_ and _y_, and the right control's horizontal and vertical axes are respectively mapped to
+_z_ and _rz_. From the previous drone controller schema, we know that the _x_ axis should be associated to the yaw
+movement, the _y_ axis is associated to the throttle (up/down) movement, the _z_ axis is associated to the roll movement
+and the _rz_ axis is associated to the pitch movement. Platypush already outputs events with values between -1 and 1,
+where 0 represents the middle point (analog control in "rest" position), -1 represents the control being at the leftmost
+position (or bottom if it's a vertical axis) and 1 represents the control being at the rightmost position (or top if
+it's a vertical axis). With these considerations in mind, we can easily write an event hook that translates a joystick
+event into a drone action:
+
+```python
+from platypush.event.hook import hook
+from platypush.message.event.joystick import JoystickAxisEvent
+
+@hook(JoystickAxisEvent)
+def on_joystick_axis_event(event: JoystickAxisEvent, **_):
+ movement = None
+ if event.axis == 'x':
+ movement = Movement.YAW
+ elif event.axis == 'y':
+ movement = Movement.THROTTLE
+ elif event.axis == 'z':
+ movement = Movement.ROLL
+ elif event.axis == 'rz':
+ movement = Movement.PITCH
+
+ if movement:
+ move(movement, percent=event.value)
+```
+
+Now restart Platypush, make sure that the controller is paired, lift off and start moving your drone around. If you have
+made it so far, congratulations on building and flying drone from scratch!
+
+# Conclusions
+
+By the end of this article you have learned how to design, build and fly your drone from scratch, but many improvements
+can be applied to the base model we've put together here. Some ideas:
+
+- Integrate an accelerometer to detect whether the drone "leans" too much in a direction other than the expected one,
+ and automatically adjust the thrusts to keep the drone stable. Platypush provides built-in support for some types of
+ accelerometers connected over I2C or SPI.
+- Move from a Bluetooth or IR joystick to an RC (radio-controlled) one, so you can control your drone over longer
+ ranges. If the RC controller is mapped as a standard joystick on Linux then all the code we've written for the
+ controller shouldn't require any changes.
+- Add a GPS module (Platypush provides support for any GPS device supported by `gpsd`) and a 3G/4G module to get the
+ location of the drone and communicate with it even when it's not connected to your Wi-Fi network.
+- Make a command-line script or a small UI application to control the drone from your computer or phone. The application
+ can still call the `.move()` method we have defined previously to control the vehicle.
+
+Good hacking!