Some time in mid-December, I was wondering if I should buy a Flipper Zero device. The official website describes it as follows:
Flipper Zero is a portable multi-tool for pentesters and geeks in a toy-like body. It loves hacking digital stuff, such as radio protocols, access control systems, hardware, and more. It’s fully open-source and customizable, so you can extend it in whatever way you like.
I’ll refer to F0 instead of Flipper Zero in the text below.
F0 allows you to play around with technologies such as BlueTooth, USB, infrared, NFC and much more. It offers a neat software development platform so you can easily output graphics and communicate with peripherals. It also has a cute dolphin pet that levels up as you explore. Its software is very interesting and I found myself wondering whether I could port the code to another microcontroller (MCU).
ESP32 has been my favourite MCU for the past few years. Most variants of the hardware come with a multi-core 240 MHz CPU, WiFi, bluetooth, 520kB of RAM (expandable to 8MB), several megabytes of storage and a bunch of wired connectivity options. It’s roughly the size of a postage stamp (18 x 25.5 x 3.1 mm) and it costs about 2 EUR before adding a USB port and a battery to it. For those who are interested: Flipper uses an STM32WB55 MCU.
My porting efforts didn’t start out smoothly, but one thing led to another and 3 completely separate iterations later, I’ve got something that looks usable. The result is an application platform called Tactility.
Originally, I intended to port the F0 project partially to ESP32 with as little deviation from the original as possible. I first started with copying a ton of code and removing the bits that weren’t needed (yet) or that were blatantly incompatible. This was not a successful approach: once everything compiled, it was too hard to debug memory corruption issues.
In a second attempt to make a Flipper-like platform, I tried to make everything from scratch. It didn’t take long for me to realise that it was way too much work and I found myself copying in more and more bits from the Flipper project.
In my third and final attempt, I did something in-between: I started building up the platform with mostly existing F0 code. The main difference was that I was importing smaller bits and verifying them before moving on to the next feature. I also wasn’t afraid to implement certain things radically different if it was beneficial to the project. This worked better and I soon had a functional prototype. It featured an application launcher and the ability to start and quit applications:
Let’s dive into the software development journey…
It didn’t take long to build a few simple apps like “Hello world” and one that showed memory information. I also had a ‘desktop’ app at this point, but this wasn’t very challenging as it just lists the installed applications and launches them when you click on them.
When I set out to make a Wi-Fi service and related app, I didn’t know just how much it would change the entire platform! A good Wi-Fi app does a lot more things than you’d expect. Not only is it multi-threaded, you also need to store settings. And then there’s the increased complexity in UI and user experience.
As I was making this feature, it resulted in a complete change of the platform. I started with a simple screen to toggle Wi-Fi on/off. So far so good. Then I needed an additional screen so the user can enter credentials to connect to a network. This almost doubled the amount of code and suddenly the app had to manage “which screen should I show - and when?”.
I decided to split the Wi-Fi app into two separate apps: one for the Wi-Fi overview and one for connecting to a new network. To implement this, I now had to support:
Similar to Android and Flipper Zero, I created a ledger that defines a user app. It looks like this:
static void app_show(App app, lv_obj_t* parent) {
lv_obj_t* label = lv_label_create(parent);
lv_label_set_text(label, "Hello, world!");
}
const AppManifest hello_world_app = {
.id = "helloworld",
.name = "Hello World",
.icon = NULL,
.type = AppTypeUser,
.on_start = NULL,
.on_stop = NULL,
.on_show = &app_show,
.on_hide = NULL
};
This is a functional app. It shows the text label on the screen and nothing more. It almost looks like object-oriented programming. In fact, I might create a C++ wrapper in the future.
Background services work in a similar manner.
The main difference is that they don’t define on_show
, on_hide
, an icon
or a human-readable name
.
When you have apps starting other apps, you can consider it as a stack, which you aproach as LIFO or “Last In, First Out”: The last app that is put on the stack is the one that is shown to the user. It is also the first one to be closed before you can get back to the app that was launched before it.
Starting an app and closing goes like this:
When app A starts app B, then the views of app A would be destroyed as the app is hidden, but app A itself would not be destroyed. When app B is closed and destroyed, the views of app A would be re-created and shown again.
Once I was able to launch apps onto a stack, I wanted to be able to launch them with certain parameters. Flipper Zero originally just passed a string, but I wasn’t a fan of that: command line arguments are terrible to process. Instead, I opted to use “bundles” like on Android. A bundle is a sort of flexible dictionary that maps strings onto a specific set of types. It’s a simplified in-memory database (key-value store) with a flexible type system, while still using explicit typing.
This is how bundles work on Tactility:
// Make a bundle and store some data in it
Bundle bundle = tt_bundle_alloc();
tt_bundle_put_bool(bundle, "is_enabled", true);
tt_bundle_put_int(bundle, "count", 42);
tt_bundle_put_string(bundle, "label", "Hello, world!");
// Check if the key and value exist...
int value;
if (tt_bundle_opt_bool(bundle, "some_key", &value)) {
// ...
}
// This one crashes if the key doesn't exist:
bool is_enabled = tt_bundle_get_bool(bundle, "is_enabled");
Typing the Wi-Fi password on a touchscreen was becoming tedious. It was happening after every reboot. That’s why I needed to store the Wi-Fi credentials on the device. Once encrypting and decrypting was working, I still wasn’t done. The problem with encryption is that you need to store the secret that decodes the encrypted data. You can ask the user every time for a password to decrypt, but then you’ve got yourself the same problem as when entering the Wi-Fi password manually: that’s tedious!
The most simple way to approach this, is to create a reasonably safe random set of numbers as the key, and store that key somewhere safe. This approach is not safe if an attacker has physical access to your device: they can connect via USB and fetch all the data from the flash memory (all the partitions on disk). This holds the encryption key and the encrypted data.
This was pretty bad, but the solution was fairly obvious: enable boot protection and flash encryption. The problem with these features is that they are not easy to set up. Most hobby developers won’t bother. So, instead, I opted to show a warning when a credential is stored on an unprotected ESP32.
It didn’t sit right with me. I knew most hobby devs won’t bother with setting up these security features. I knew there wasn’t much I could do to make it safe, but I found a way to make it better: If I can use some kind of secret information that isn’t readable from flash, but also isn’t easily available via the USB interface, perhaps I can mix this into the cryptographic key?
That’s where the eFuses come in: An eFuse is a programmatic fuse. You can blow the fuse once with software, and that’s it. Each fuse can be seen as a single bit of data, so if you have enough fuses, you can create a storage mechanism.
The ESP32’s eFuses already offer 6-8 bytes of random data. There is more data space, but it’s all set to 0 by default. With the hardware identifier, I could now create the private key as follows:
An attack wouldn’t be as simple as just copying the data over USB. An attacker that wants to remain unseen would have to:
It’s not a lot more secure this way, but it sure takes a whole lot more effort. I’ll still keep showing warnings when secure boot and flash encryption aren’t enabled though.
I’m still working on UI/UX improvements for the Wi-Fi app. The connection dialog doesn’t have a “busy” animation as it connects. At a later stage, I also want to have Wi-Fi auto-reconnect and have the option to have Wi-Fi enabled (and connecting) on boot.
Right now, I’m looking at writing some tests…
]]>For several years, I’ve been looking for a project where I could dump a bunch of creativity in. A project of my own, that would be challenging and rewarding. Preferrably a project that combines electronics and software. Handheld PCs always had a special place in my heart. Palm III was my first one, and a bit later I got my hands on a Sharp HC-4500. I was intrigued by the Yarh.io projects and early this year I considered buying a uConsole. The uConsole was supposed to be shipped in March, but currently it’s still pending. So with a bunch of ideas and motivation, I started my own handheld PC project: Decktility.
I wanted to challenge myself and push the boundaries of homebrew solutions. The Yarh.io Micro 2’s design was too crude for my taste. It was clearly limited by the hardware availability at the time of development. The Yarh.io documentation gave me a good understanding of what I was about to embark on. I set out to make device that looked more refined. I wanted the end result to be sufficiently light, and the battery life had to be at least a couple of hours.
At this point, I was already considering the BigTreeTech Pad 5 as a foundation, as this was the thinnest touchscreen and Pi combination that I could find. It seemed logical that I wasn’t going to make a foldable device. Making a foldable device would pose several problems:
15 mm
thickness on one side, while the side with the keyboard would be
at least about 20 mm
thick considering an 18 mm
18650 battery cell and a case. 35 mm
would not be
acceptable. I considered a regular flat lipo, but I didn’t like that they get spicy when they short-circuit.So the decision was made: it was going to be a package size similar to Yarh.io Mini 2 and uConsole.
Before starting the 3D design, I bought some basic components online. This allowed me to measure them, so I could get an idea of how much space each component would take up. It also enabled me to start building up a prototype:
Just like the wiring in the assembly guide, I started by building up the power delivery:
A USB battery management system (BMS) that can charge two 18650 lithium cells, paired with a
5 V
step down converter as the Pi, keyboard and fan would require 5 V
.
I used a USB-C PD tester to verify the power while charging.
Once that was working, I added some breadboard wires and connected the Pi:
I found out that that the BMS was getting quite hot while charging. It was stepping up the 5 V
from USB
to about 8.4 V
that the batteries require. Increasing the voltage is much less efficient than decreasing it,
so there is more heat involved. This implied that I would need to accomodate cooling for this scenario.
45 C
is not too bad in open air, but it wouldn’t be great in an enclosure, where the Pi would generate heat too.
In theory, having a battery, a switch and a Pi is going to result in a working product,
but what if the battery was running low? If the power switch would remain on, it would drain and damage the battery.
To solve this problem, I introduced an Arduino Nano. The Arduino would end up doing many things, but it started
with reading the voltage from the battery. To do this, I added two resistors with a high resistance value,
because they would always be connected to the battery and thus leak power. By selecting 2.2M R
and 3.9 MR
,
they would leak only 0.82 μA
(5 / (2200000 + 3900000)). That’s about 4.1 μW
, or in other words: it would take
203252 days
to drain a 20 Wh
battery. It’s not going to be problematic this way.
Now that we know when it’s safe to turn on the hardware, I had to add the ability to actually do so. I used a special kind of transistor - a power MOSFET (or “power FET”). The green PCB behind the cable mess holds the FET circuit:
Around this time, I started designing the CAD prototypes in OnShape. Many hours were spent measuring and drawing various components that would go into Decktility. I needed their 3D representation so I could do an integration test of sorts as I was creating the case:
In the early stages, I had to solve the dilemma of battery placement. Firstly, the battery had to be a counter-weight to the screen. It’s important that the device feels balanced in your hands. The screen and the battery are both quite heavy, so they cannot be on one side of the device.
I thought of 2 options for the battery: Either on the sides, as to create handles to grab the device,
or I near the bottom center of the case.
Having them on the sides would require two separate battery holders, and it would create a minimum height of about 7.5 cm
for the bottom half of the device. I really liked the idea of the handles, but the downsides resulted in chosing the alternative setup.
After working out the basic case design, it was time to print the first one. The first of many…
The massive FET board quickly catches the eye. The board in the picture is just to showcase how out-of-place it really is. I swapped it with a smaller one before continueing.
Swapping the FET board was harder than I thought. There are many pre-built modules available, but most of them are built with N-FETs and not P-FETs. The N-FET boards are cheaper and easier to build, and result in an electronic switch that switches the GND wire. I would later find out that I need a P-FET, because I need a common ground connected at all times for the Arduino logic and the Pi logic to work together (for I2C communication).
In the end, I couldn’t find a small enough FET board, so I started reverse-engineering the existing one in my very first KiCad project:
and then rebuilding it on an experiment board:
I might make a custom PCB in the future. That way, I can add some connectors for various components, making the project easier to assemble.
Before the custom FET board, I was able to get most of the basic components working before adding the Pi to the mix.
I would quickly find out that I didn’t have the correct wire thickness for the main power. I was using 20 AWG
, which I use
for drones that draw way more power. I would later replace them with 24 AWG
wires.
When I was working on the Arduino firmware, I had a problem: every time I would upload a new firmware, the Arduino would restart. This would make the electronic switch go off, and thus restart the Pi. To overcome this, I would wire a second Arduino to I2C, isntead of using the one in the device. At a later stage, I also added a JST-SH connector to be able to easily disconnect the Arduino inside the device.
Charging status LEDs would be a nice touch. But adding 1 or 2 LEDs to the case would require a relatively considerable effort. And then it hit me: I could use fiber optics! Fishing wire (or flexible bracelet wire) can guide the light from the existing LEDs on the BMS to the edge of the case. All I would have to do is glue the wire in place. An overnight shipping and a quick experiment later, the theory was proven:
After a lot of iterations, the hardware was finally finished:
At this point, a custom I2C device implementation was enabling the Pi and the Arduino to talk to eachother. The Pi could ask the Arduino about the charging status or the battery voltage, and the Arduino would report it back. I started investigating how to integrate it into a Linux desktop, so I read about dbus and upower. At first, the intention was to write a custom kernel driver, but then it hit me: What if I change my Arduino firmware so that it acts like an already supported Linux device? I did some research and settled on the LTC294x “Battery Fuel Gauges” implementation. It was one of the very few power-related Linux kernel drivers that were avaible in Raspberry Pi OS. The Arduino now acts like such a device, so it is fully supported in Linux.
I was stoked when I saw the battery icon appear for the first time:
Here’s a quick summarization of some noteworthy learnings that aren’t covered above:
As mentioned before, airflow was important. I had to cool the BMS board and also the Pi. Some holes for airflow would be insufficient. I ended up roughly aligning my components in the direct airflow of the fan. This ended up also as a bit of a constraint on the rest of the electronics design, as I wouldn’t be able to easily move the BMS board anymore.
Less fasteners(/parts) is better: The best fastener is the one you don’t need. If you create a groove and latch mechanism to save yourself some parts, it might be worth it! The cost for me was added complexity in the design.
Designing for … 3D printers? Or CNC milling? The plan was to initially make a 3D printed case and then later attempt an aluminum CNC-milled case. Designing for 3D printers is very different. It would require changes to make the cases millable on a 3 axis mill.
Electronics placement is difficult: Next to the airflow considerations, you also have to consider the amount of wiring needed to connect all the parts. I also wanted to make the build as small as I could. Finding the best trade-off is not an easy task. Then there is theory versus usability: Your SD card might fit in the slot, but is it easy to pull it out? Can you grip it with your nail?
3D printing - Chamfer is life: I used chamfers on overhangs as to not require support when 3D printing them. I also used chamfers on certain edges near case openings (e.g. ethernet and GPIO connectors), to slide objects into the case more easily.
3D printing - Manual painted-on supports: These are very handy if you have lots of overhangs, but only some of them need support. (used with SuperSlicer/PrusaSlicer)
3D printing - Parts can flex: If you make a battery tray, the pressure of the battery inside the case might budge it outward. This can result in your battery tray not being placeable when batteries are in it. More importantly: you should measure the flex and see if it’s bending outwards too much, because you don’t want a critical failure with lithium cells.
Use SSH to the fullest: I increased my dev cycle by using remote commands. I could execute my local Python remotely with ssh me@cyberdeck 'python' < powermanager.py
Raspberry Pi 4 has more than two I2C buses: When you fry your CM4 I2C bus, you can use other GPIO pins to create extra I2C ports.
Naming your project matters: While “Decktility” is probably not the best name out there, it is a good name because it’s easy to remember, and it’s a unique name so an online search will easily turn up the correct results. You can use ChatGPT to help you find a good name. (Decktility refers to ductility/utility, where ductility is a wink towards the welder part in my online persona)
If you want more details, check out the Decktility GitHub repository.
]]>This is what the finished print head looks like:
The feedstock of the printer is a mix of micrometer-sized particles (e.g. metals or ceramics) with a fuel source like IPA or naphtha. A monofilament wire is inside the fuel chamber. Heating the wire creates a hot gas, which is then pushed out of a nozzle, mixes with air and catches fire. Due to the heat, the metal melts and hits a surface (substrate) to which it attaches.
To build a strong print head, we’re using multiple layers made from PCBs. These layers are stacked in the order as they are laid out below:
I first prepared the inlet and outlet. I need to connect Viton tubing to the
print head, so I’m soldering small copper pipes (3 mm
diameter) to the PCB.
The picture shows a larger copper pipe, because I used the wrong size when taking pictures:
I started with adding solder to the pads:
Initially, the solder will likely not connect neatly all around the copper pipe. It might look like the picture below. I take some extra solder on my iron and then place the iron at the base of the copper pipe to fix this issue:
Another problem that might occur is when your solder smudges. I cleaned it off by wiping my (cleaned) soldering iron on it. It’s not problematic if there is some solder on the side, but I have to make sure it’s not sharp. That way, it won’t cut the Viton tubing later on:
When I used too much solder, the inlet became too narrow. I could’ve desoldered
it. Instead, I used a set of drills to carefully drill it out by hand.
I kept increasing the drill diameter by 0.1 mm
until the diameter was the right size:
Finally, I had a neat collar on the copper pipe:
The next step was to put the monofilament on one of the PCB layers. This monofilament is what heats up the fuel, which causes the fuel to eject through the nozzle. The filament gets sandwiched between the nozzle PCB and the PCB that holds the connector. I store the filament in a folded sheet of paper:
First I cut the filament to size. We make it as wide as possible, so the thickness of the glue won’t interfere with the electrical conductivity:
Combining them is tricky, because the filament won’t lay still on a flat surface. I used some CA glue on the edges to keep it into place. I learned later that glue is not the ideal way. In the future, I will tape it near the edges if the PCB with some kapton tape.
The nozzle PCB layer had holes that were too small in diameter. I widened them
by hand with some cheap drills, slowly increasing the diameter 0.1 mm
at a time:
When attaching the nozzle PCB to the connector PCB, I start with a single row of screws:
It’s important to tighten the screws evenly, so initially all screws are kept loose. I then tightened them carefully and as evenly as possible. When everything stops sliding around, I start measuring resistance before continueing to tighten further. The resistance measurements are repeated until the values are in an acceptable range.
I had to make sure that the nozzle plate wasn’t crooked, like in the picture below:
The prepared PCBs are ready to be combined. I took 8 pieces of M2 x 10mm
and
some M2
nuts to match. I also used medium-strength loctite:
At this point, I have a finished physical assembly:
]]>Please note that everything written below is written in the context of hobbyist CNC router hardware.
A CNC router (or mill) is not a complex machine. Its main job is to move a spindle (~ drill) along the X, Y and Z axis at a specific speed. In a hobby router, you generally use stepper motors, just like you would in a 3D printer - albeit perhaps more powerful ones.
These stepper motors require a lot of power, so they are controlled by drivers. A driver is a device that gets low voltage input signals and translates it into high voltage power signals for the stepper motors.
The drivers themselves are controllerd by a controller. The controller is the heart and brains of the CNC mill: It translates a “job” into signals that it sends to the drivers.
A CNC “job” is defined by “gcode” file. This contains all the commands for the machine to execute. A basic command could be something like “move the spindle 2mm along the X axis”.
There are few more bits and pieces that I’m not talking about here, but these are the main things a hobbyist CNC router is about.
The first hard part is to find a cabinet that fits your needs. Here were some of my considerations:
I settled on a cabinet from the German company BoxExpert
. I chose the 600 x 400 x 200 mm
size. It comes with a transparent door that allows me to see the status LEDs of the hardware inside, and it comes with a metal plate to mount hardware on. I paid about 82 EUR
including shipping, so that was reasonable.
Initially, the plan was to mount the hardware directly to the plate. This has one major issue: you can’t change the layout at a later time. Imagine having to start drilling holes into it while there’s already hardware mounting!
DIN-rails were the answer to this! Not only did they help with strengthen the steel mounting plate, they allow me to change the entire setup freely at any time. There are lots of helpful DIN-compatible components out there that helped me in making my build clean.
To make the cabinet layout, I used InkScape. A vector graphics editor like that (or Affinity Designer and many others) is ideal for me: I created a 600 x 400
pixel image, where each pixel represents a millimeter. The end result looks like this:
The yellow marked areas are space in the box that is unusable.
Some considerations included:
Although airflow isn’t super critical, it’s important to have at least some. We’re not generating a lot of heat inside the cabinet, but since it’s a water-proof cabinet, we want to create some airflow.
I wanted to have a rough idea of how different kinds of airflow would behave, so I used the free tier of SimScale to model some scenarios. Simulating airflow for the cabinet is overkill for a project like this, but it was fun to experiment with.
The first design has an intake fan on the bottom of the cabinet and an exit hole on the top-left. The downside of this designis that we can’t put the cabinet on a flat surface without obstructing the airflow:
The second design has an intake fan on the bottom-left and the outtake hole on the top-left:
The last design is the one I ended up chosing, but both were viable options.
The first real build step was mounting the DIN rails on the steel plate of the cabinet. I used a medium-strength thread lock for the nuts and bolts. I also ensured that the plate could be grounded by drilling an extra hole for grounding.
Once the rails were ready, it was time to try out the first components: The top row has all 4 stepper drivers, connected to the power supply below. I’m using Wago
clamps to tie it all together. The power cord is temporary:
There are a lot of DIN parts on Thingiverse. A lot of the DIN brackets that I used come from there. Some of them were custom. The controller and fan brackets are a remix of an existing ones on Thingiverse:
The CNC controller that I’m using as Grbl32bits board from Makerfr. I’ve mounted it together with its power supply and the fan controller in the below picture:
The next step was drilling holes in the cabinet to facilitate all the connectors:
In case you are wondering: yes, that is indeed my living room table being used as a workbench. My wife is awesome for putting up with it.
After everything is wired up, the cabinet is finished!
You can find my collection of 3D printable parts here.
]]>A software development platform, where the user is - by default - the sole owner of its data.
For offline applications, this is easy. Anything you store on your own devices remains in your ownership. Any offline application automatically passes this criterium.
For online applications, this becomes a different story. Online applications imply that a server is a mediator that processes and likely also stores some of your data. There are few exceptions, such as instant messengers that implement E2EE.
This made me wonder: What if we wrote more software on E2EE principles? What if a calendar or contacts service wouldn’t be able to read your calendar entries or contacts? And how do we move these concepts to more complex online applications? Sometimes the server simply must know some data. Even an E2EE messenger needs to have a basic understanding of the recipient that you want to communicate with. Or perhaps you want to expose calendar through CalDAV, or share an entry from your address book via email? We want the user to remain the sole owner of its data, but we should also give the freedom to open up that data.
In my thought experiment, all user data is encrypted before handing it over to an online service.
This service can do few things:
It acts as an online file system. The user’s applications can store their data here.
Applications can send encrypted data to other applications, within the user’s ownership.
For example: The user has a calendar application on their phone and on their desktop. When the user adds an address on their phone, the desktop needs to be notified to update its data.
For example: The user has a calendar application and whishes to share a calendar event with to a friend. This friend might be hosted on the same service, but he might also be hosting their own or use a third party to host it.
If(!) this setup could work for various types of applications, we don’t want individual applications to have access to our secrets like our private key or password. This cryptographic secret is still required to be able to encrypt the data. To fix this, we could introduce a “controller” application that runs on each device. Consider this controller similar to how you can log in with iCloud on your iPhone, giving all iCloud-enabled applications access to your iCloud data, but not your iCloud credentials or cryptographic secrets.
This controller would serve the following purpose:
I’d like to hear your thoughts on this thought experiment, so feel free to contact me through twitter/email via my main website.
]]>The best point to start with is the official documentation. In its current state (July 8, 2021) it tells us:
When a customer of Analytics requests IP address anonymization, Analytics anonymizes the address as soon as technically feasible. The IP anonymization feature in Analytics sets the last octet of IPv4 user IP addresses and the last 80 bits of IPv6 addresses to zeros in memory shortly after being sent to Google Analytics. The full IP address is never written to disk in this case.
(feel free to skip ahead)
Your internet connection is identifyable by something that is called an “IP address” - or in short: “IP”. Most people have an IP that looks somewhat like this: 101.102.103.104
. For some devices and internet providers, this might change over time, but for many it stays the same over the course of years. This means that an IP can be used to help identify a user across the internet.
When you browse the web or use applications, your computer talks with various other machines and they can locate each other through these IP addresses. Google Analytics is one of those entities that you can communicate with.
When a computer is talking to Google Analytics and asks it to enable IP anonymization, something happens to it before Google stores it: the last part of that number series is removed - replaced by a 0
. If your IP would be 101.102.103.104
, then it would look like this after Google’s anonymization process: 101.102.103.0
The removed number is always in the range of [0 - 255]
. This means that Google is only 256
guesses away from recovering your “anonymized” IP address! “Why is this a problem?” you might ask:
256
guesses aren’t a whole lot. It’s likely that Google stores other kinds of information along your IP. Data is the bread and butter that gets the analytics engine running after all. Let’s take a further look…
Firstly, when taking a look at the range of .0
to .255
, not all the related addresses are necessarily in use, which could reduce the sample set.
More likely, the program or website that is using analytics is sending other data through. A common data point is a “user agent”. The user agent is a short definition of what kind of software is contacting the server. It can look like this: Homebrew/2.5.0 (Macintosh; Intel Mac OS X 10.15.6) curl/7.64.1
. Depending on how unique your computer configuration is, this mere user agent is possibly unique enough to detect your computer in a group of at most 256
machines.
A user agent is just 1 data point. There are many more that one can think of to help define a user uniquely, and that are consistent across various applications or websites.
With sufficient data, Google has the ability to de-anonymize those IP addresses. With a known IP - especially combined with more data - one could easily cross-reference data from users across services.
We don’t know if Google does this, but we should be aware of the inherent risks of using Google Analytics. Both as a customer who runs Google Analytics with their software, and as an end-user who is browsing the web.
Even with the partial IP, Google can still make a rough estimate of your physical location. The documentation explains:
Geographic dimensions are later derived from anonymized IP addresses.
While technically, the anonymization technique of Google does anonymize the IP address, it does so very poorly. Only partial anonymization is happening. Calling it that gives a false impression.
If someone would post your mobile phone number with only the last 2 digits removed in a public space, it would be easy for a caller to try all 100 numbers and see which one is the one where you pick up. Would you consider such a phone number anonymous? I know I wouldn’t.
The average person on the web could pick one of the popular browser extensions to block advertisements and analytics, such as uBlock Origin and many more. Web developers could reconsider using Google Analytics. Google could use more clear language or it could simply not store partial IP addresses.
So what about IPv6? Luckily, Google did better here: when IP anonymization is applied, the last 80 bits
of data are removed.
This gives a massively larger pool of possible addresses when attempting to recover an IP.
However, since most internet users use IPv4, this is not very relevant.
]]>The intention of this article is to give a glimpse into various approaches and considerations. It’s likely not complete or perfect, but I hope it will be useful for developers that are somewhat new to networking for games.
Does your game logic require a central server to host your game? How much centralized control does your game need? And how much control are you willing to cease to the local machine, a third party or even one of your players? Answering these questions will lead to one of the main choices that you’ll make when designing a network stack for your game.
This is the first type of server, and probably the most common one. I bet that if you’d ask 10 gamers what a multi-player server roughly does, this is what most of them will describe.
It is often a machine (or part of a machine) that all the players (clients) connect to. It that manages all the data and everything that happens in the game. It keeps all the game state and ensures that all communication to the clients is happening properly.
The main benefit is that the server owner (most often the developer) is in full control of the game and everything that happens within it.
With this approach, there simply is no server that runs game logic. A common peer-to-peer approach in gaming is that of master/slave:
All the clients would vote for - and elect - a “session master”. This machine would basically act as a server. To ensure that this special client wouldn’t be cheating, the other clients can still observe it and vote to un-elect this machine if things don’t appear to be going in order.
Another problem is that this session master would possibly get increased traffic in certain kinds of scenarios, like that of a first person shooter game.
Peer-to-peer games might still have some kind of server in their toolbox. For example: when there is a need of a lobby mechanism, a friend list, analytics, etc.
Although this isn’t entirely an approach on its own, a multi-casting server can be a tool for a serverless network setup. In the case of a peer-to-peer approach, it can help with the heavy lifting on the session master: The session master can leverage this machine to multicast messages to all the other clients, without sacrificing too much of its own bandwidth.
Most games probably will solely rely on TCP/IP for its networking stack. This ensures that data arrives in order, or that it even arrives at all. Connections can still fail, of course, but at least the client knows it when data fails to send.
With UDP, this is different: Not only are you unsure about whether data (packets) arrive at their destination at all. There is also no guarantee about the order in which data is received, or even how many times a specific packet of data is received! The main reason to pick UDP would be due to its reduced latency. All that reliability of TCP costs extra bandwidth, because the recipient of the data must somehow acknowledge that data is received. UDP doesn’t have that kind of overhead.
To get best of both worlds, TCP and UDP can be combined. For example: You could send the audio data from a voice chat over UDP, while there is also an active control channel that manages some meta-data about the UDP link.
Another hybrid approach is reliable UDP, which uses UDP to create a reliable data connection. In this case you might increase your bandwidth and/or latency to resolve some of the reliability issues that a plain UDP protocol has. An example would be the acknowledgment of received packages. When a package is not acknowledged by the receiver, the sender can then retry sending it. Unlike with TCP/IP, a UDP protocol like this will retain full control over how this error-handling is done. With TPC/IP, such a failure to send would result in either a connection that stalls, or perhaps even disconnects! With a reliable UDP protocol, we can chose by ourselves which error scenarios are critical.
Bandwidth is less of a consideration these days than it was before. In the 90s and early 2000s, bandwidth was simply a hard limitation.
Games would often be designed to transfer at a rate of at most 56 kBps
, because otherwise it would affect the target market.
These days, most of us have broadband available. We’ll still have to deal with differences in latency, though. Especially when it
comes to mobile gaming, when the user is traveling with his device.
Bandwidth, however, is never free. More data might also negatively affect that precious latency, which can be crucial for game types that are fast-paced.
So how much data do you expect to send from each client? And how much does the server need to handle? Do you need to send a lot of updates in a second? Perhaps you have large amount of game entities that all move around? Will it require a lot of CPU capacity to process all that?
All these questions will influence the model of the data that you will come up with. For example: a real-time strategy game might have hundreds of game entities moving along your screen. It might not be feasible to send updates for all these entities to the server at a high speed rate.
Catching cheaters and hackers in your game is one of the more difficult tasks. Banning the wrong person can be damaging to your brand or product.
While guarding against malicious usage should be a consideration during the game design phase, your network stack will also affect how much control you’ll have over these scenarios: The more control you have over the network stack, the easier it will be to deal with cheaters and abuse.
Since anti-cheating is a form of security, we must consider the security principle of: Never trust the client-side application. In other words: You don’t know what a user does with your application, or whether he’s even using the application that you built. Or that it is used without modifications, or using it in the way that you thought up.
With that in mind, you can guess that a peer-to-peer approach is the riskiest when it comes to guarding your project. Since the clients are the server, the server might also be compromised. In this scenario, the clients might guard against a potentially malicious server. Perhaps they could even report potentially malicious servers… this reporting mechanic by itself could then be abused, by a bunch of malicious clients: these clients could join a game and vote another user onto a ban list of some sort. As you can see: it’s complicated!
When a player breaks the game rules, it’s often easily visible to the players (and the server). In these scenario’s, it’s relatively easy to guard against it. When cheaters mimic real-world network scenarios, it becomes much harder. For example: a player connected to a wired network in a peer-to-peer game might use a foot switch to control when the outgoing data can exit his network. Imagine this player is in a first person shooter game, and he’s about to go around the corner to see (and attack) an opponent. He disconnects the receiving end of the network cable with his foot, walks around the corner, and shoots his opponent. His opponent didn’t receive his position, so he doesn’t even know he’s lost yet! At first, this looks like regular lag, but it starts to become more suspicious when it happens more often…
In the end, your network stack design will depends on a lot of factors. While some games have more obvious approaches than others, I don’t think there is no cookie-cutter answer on how to tackle it. Instead of trying to give you such answer, here’s a summation of the main questions that will help you decide on your own stack:
That’s it! I hope this is useful to someone out there… If you’d like to send me feedback, you can find my contact info on my personal website.
Happy development!
]]>Materials used:
464 x 366 x 176 mm
internally, type “MAX 430S” on Amazon)14 AWG
for the power delivery spec that I wanted)40 mm
fan (can be more/less, depending on your ventilation requirements)24 V
and 15 A
)220 V
power outlet with switch and fuse (the same one as the Ender 3 Pro comes with)(note: this might be incomplete)
These are some of the tools that I remember using:
I also designed some parts:
These parts are optional, as you can also use pieces of wood or plexiglass to construct something similar.
The total cost as about EUR 250 with the ISDT chargers. Half of that amount was just the cost of the chargers, so buying a cheaper charger could save some money. I also bought some components that were optional (PDB, more fans, etc.) You could probably bring the total down to slightly below EUR 200 if you’re making some different choices here and there.
First, I bought a case. It was relatively cheap, and the only downside was that it is a bit heavy:
The next step was to start creating a bottom plate. I wanted to keep it detachable, so I bought some 3M Dual Lock. I added a bit too much in retrospect, so when I detach the plate, sometimes the sticky tape comes off rather than the Dual Lock:
On this bottom plate, I added the switching power supply. The main consideration here was to have it placed in such a way that the combined airflow of the power supply unit (PSU) was working well with the airflow of the chargers. Another consideration was how the placement would affect the center of gravity of the case, so it would be nice to carry.
I mounted the PSU on the wooden plate with standoffs, so that the airflow would be better. I also wanted to make sure that if the power supply ever got really hot (which I don’t expect), it wouldn’t be touching the wood:
Here it is placed in the case:
Similarly, I created a top plate. It rests nicely on the rim of the case, and due to the design of the case, the lid still closes perfectly.
I drew some edges on it, to visualize how much actual space was usable. That way, I wouldn’t be hitting the side of the case:
I made many sketches on paper before picking my preferred layout. I laid some of the components on the paper to see if it would work well. The considerations were: airflow, wire connections (amount of wire needed), space during charging (how close is everything to each other).
After that was done, I transferred my layout to the top plate:
And of course I verified it by adding the components on top:
At this point, I started prepping wires already. This would allow me to already connect the chargers and start charging. That way, I didn’t have to wait to the end of the project until I’d have a functioning charging setup!
As you can see, the rig was already functionally usable:
I wanted a single wire to go from the PSU to the rest of the electronics. This allows me to easily disconnect the electronics on the top plate from the PSU on the bottom for maintenance and testing. In theory, I could just solder all the XT60 connectors together, in parallel, and be done with it. I chose another path and bought a power distribution board (PDB) to make the job a bit prettier. This PDB also comes with a BEC, so I could easily add some LEDs to the case later, or add more electronics that require a lower voltage.
To this PDB, I added some extra BECs (step down converters) that will drive the fans. They are separate, as I need more current from it than the PDB will give me. The BECs allow me to regulate the voltage, effectively allowing me to change the speed of the fans.
I am adding XT60 and XT30 connectors, because I want the entire thing to be modular and easy to disassemble:
Originally, I had only 1 BEC. This provided insufficient power to start rotating all fans at the same time. I added a second BEC to solve this problem, but in the end it was still insufficient. The proper fix would be to have a setup where each fan gets its own switch. That way, they can all individually get their needed current spike to start spinning - but not at least not at the same time!
At this point I started cutting out the top plate:
Because I used triplex wood, and I’m not super experienced with it, the wood partially disintegrated when cutting into it. I used some 2 component epoxy filler to fix most of it:
To finish the top plate, I painted it black:
Also the fans were mounted:
Then the XT60 top-mounted connectors were added:
Because the chargers were shaped irregularly, and I needed to support the balance boards with something, I designed and printed out some parts. You can also buy some wood or plexiglass and cut out similar parts by hand. I used 2 component epoxy filler (yes, filler) to glue the charger brackets on:
Then I put the balance charging cables in place and glued in the supports for the balance board:
The lasts step was to connect everything:
And then the project was finished:
]]>RushFPV Mini Tank Stacks
that were rated for 30 A
and connected them to some DYS Samguk 2600 Kv
motors without propellers. This minimal load blew the fuse on the ESC, making the ESC unusable.
The result was that BetaFlight showed 0.01 V
when a lipo was connected.
A friendly RushFPV customer support representative stated that this stack wasn’t meant for 5"
quadcopters and motors with
such high Kv
values. In my opinion, 30 A
is 30 A
, regardless of motor size, so I’m still under the impression
that this stack just can’t deliver what it says on the specifications.
Thanks to the help of the RushFPV CSR, I was advised to bridge the fuse.
I was given some pictures as a guide, but decided to make a comprehensive photo write-up myself.
These are the tools that I used:
If you plan to fix your own ESC, you should understand that removing the fuse (and shorting/bridging it) means that there is nothing preventing damage to the ESC if too much current is pulled through it. It can lead to smoke and fire if you abuse the ESC beyond its limits without this safety net.
First, let’s take a look at the fuse itself. It’s the green SMD component on the ESC:
As mentioned in the list of tools, I used a fine chisel tip:
Use the clippers to splice the wire length-wise. Then cut a smaller piece off that is a bit smaller than the size of the fuse:
Have your soldering iron on high heat (I put mine on 425 C
) and add a little bit of solder.
This solder will help spread the heat on the SMD component:
Start holding the fuse with the tweezers. Don’t put a lot of force on it, but make sure you grab hold of it:
Bring the soldering iron to the fuse (don’t push hard) and gently pull the fuse off with the tweezers:
The ESC will now look like this:
Add some fresh solder to the fuse pads. I used the sharp end of the chisel tip to set down my iron in a stable manner, and then I fed a little bit of solder into it:
Now place the tiny piece of copper wire, with the flat side down, between the ESC fuse pads.
Before you put the soldering iron down, make sure that you have some grip on the wire, as it will start sliding a bit when the solder is flowing. To solder it, just put your iron down on the copper bit and let the heat of the copper conduct into the solder and melt the solder into the copper:
That’s it, you’ve now bridged the fuse!
You can test as follows:
2S
pack with 21700
cells.
This guide is also relevant for constructing with 18650
cells.
Materials needed:
18650
or 21700
cells (they must both be exactly the same cell!)Let’s first list the tools that I used:
Making a battery pack is dangerous. Ensure that you have a basic understanding electricity and lipo & li-ion battery tech. This guide might not be perfect, so proceed at your own risk. Using battery cells incorrectly may lead to fire and physical harm. Treat them with the respect that they deserve. The author is not responsible for any damage or harm that may happen from following the steps in this document.
First we start with two identical cells. These are Samsung INR21700-50E
cells:
You can find the specifications of these cells here.
Before we actually start, please note that all wires should be pre-tinned. This will make it much easier to combine the components:
Now put the cells together. They must touch each other. I used 2 coasters to help me with that. You can then use a tool to align them vertically:
Carefully apply hot glue on one side of the cells. Make sure your glue gun doesn’t touch the cells, so you don’t melt the plastic wrapper. Let it dry for 30-60 seconds and then do the other side too:
Since I don’t have a spot, I need to solder them regularly. Before we can add solder to the cells, we need to remove the oxidised layer from the cells. I do this by scraping carefully with a knife. But the cell flat on the table and start scraping at the center of the contact point.
Heat the soldering iron to 450 C
(842 F
). Less might also work, but this is the temperature that I used.
Put some solder on your iron, then put the soldering iron on the cell, then add a bunch more solder:
Let it cool and then clean off the resin:
You can test the strength of your weld by applying force with a knife on the edge of the solder. I had to hold my camera to take a picture, but you should hold the batteries with one hand, and then carefully apply a few kilograms of force with the other hand. Be careful to not cut yourself.
Repeat the knife scraping, soldering, cleaning and strength testing for all 4 contact points:
Cut a small piece of wire to length to connect 2 battery cells in the back:
Make a 2S
(3-pin) balance cable to length, or cut one from an existing balance extension cable:
Solder the center cable of the balance connector to the back of the battery:
Fasten the balance cable with some hot glue. This will make it easier to work with:
Measure and cut the remaining 2 wires of the balance cable. Make sure the red cable goes to the positive side of the cell, and the black cable goes to the negative side of the other cell:
Then solder the two balance cables onto the cells:
Now solder an XT30 connector to the same contacts:
We’re about to make some covers to protect the top and bottom of the battery pack. Take some double-sided tape, cut it to length. Then apply kapton tape (or electrical tape?) on one side.
Measure some shrink tube. It should stick out about 8-10mm on each end of the cells:
Hold the lipo by all of its wires and use the heat gun to carefully heat the shrink tube. Your pack is now finished:
Since we used Samsung INR21700-50E
cells, this battery pack is a 2S
pack with 5000 mAh
. Even though these are Li-Ion
cells, they are charged to 4.2 V
. The cut-off voltage is a mere 2.5 V
! You can charge at maximum 4900 mA
, but it’s advised to charge them slower. They can be discharged at 9800 mAh
continuously, or 14700 mA
pulse. (according to this page)
When using different cells than the ones above, make sure to look up the the specifications of these cells. The voltages and currents will very likely be different.
]]>