Wishful Coding

Didn't you ever wish your
computer understood you?

Your Router is Fine. You Do Not Need To Build Your Own Instead!

After I upgraded to 1000Mbps fiber and discovered my ISP has remote access to my router, I wanted a new router, and some friends conspired with Linus Tech Tips to convince me that your router sucks, build your own instead! This is my journey through building my own router and eventually ending up with an off the shelf TP-Link Omada setup.

The TL;DR is the only thing that actually sucks about your router is the WiFi coverage. Use the wired network and/or put some extra access points in strategic locations.

Literally any router can do 1000Mbps, which is enough for multiple 4K video streams. But chances are the router is stuffed away somewhere in a corner of the hallway. Of course WiFi on the other end of the house is going to suck. The solution is simple: place some extra access points in strategic locations throughout the house.


But let’s go back to the beginning. Since we’re both working from home, I had just upgraded our internet to the fastest fiber available in the area, 1000Mbps, and in the progress discovered I can change my WiFi password from the website of my ISP, meaning my ISP has remote access to my router. Not cool. And if I was going to revise the setup, I had a few other ideas

  • Better WiFi coverage upstairs
  • Power over Ethernet for the access points
  • Put IoT devices on their own VLAN
  • Get rid of the media converter and plug fiber directly into the router
  • Get rid of the Raspberry Pi home server
  • Prepare for an eventual 10GbE upgrade

And from there it went a bit like this. And like my Linux journey, there came a point where I just want stuff to work reliably rather than always having the new hotness.

This really is a true story, and she doesn't know I put it in my comic because her wifi hasn't worked for weeks.

The Hardware

The idea was that if I used a repurposed thin client combined with a PoE switch, that I could plug in an SFP+ card for a fiber transciever and an easy 10GbE upgrade path, transfer the Raspberry Pi’s duties to the thin client, and power the access points from the PoE switch.

The easy part was picking access points. I just selected some TP-Link Omada WiFi 6 access points. For the switch I initially selected a fairly simple PoE switch, but then realised I’d need a managed switch for my IoT VLAN, and then I figured why not an Omada managed switch so I could control everything from one place. But, foreshadowing, the Omada managed switch wasn’t that much cheaper than a full Omada PoE router with built-in controller.

At this point I was under the impression my fiber was GPON and not compatible with the available SFP trancievers, so 10GbE SFP+ cards would be for another day. Finding a network card was actually the hardest, because many of the older and cheaper NICs have a higher TDP than would be wise to put into such a small PC, and often use brands that don’t have great support in DPDK or FreeBSD. Eventually I found a two port Intel NIC.

For the thin client, I spent a bunch of time searching for ones that have PCIe slots, such as the HP t620 PLUS, Fujitsu FUTRO S920, and the final choice, Lenovo ThinkCentre M720 Tiny. The Fujitsu is a much cheaper option, but I had 10GbE in mind and also wanted to use it as a home server, so opted for a bit more powerful machine. Serve The Home was a great resource in this journey.

Here is where the setbacks started:

  • The PC turned out to have a 2.5” drive where the NIC should go, so I had to buy an NVMe SSD.
  • It actually doesn’t have a standard PCIe slot, so I had to buy a riser and use some double sided tape in lieu of a front plate/bracket.
  • The fan is so loud! Probably just worn out? So I had to find a replacement fan on eBay, which at the time of writing hasn’t arrived yet.

At this point I was having my doubts, but sunken cost fallacy kept me going. I also just like to play with tech toys. So once the riser arrived I proceeded to set it up as a router.

The software

At first I was planning to go with OpnSense, but then a friend told me that kernel mode routing is so slow, and all the cool kids use DPDK these days, so why not use DANOS or VPP? Who needs a web GUI anyway. [Week six]

So I installed Ubuntu Server and VPP, and then moved on to their VPP as a home gateway page.

What that page neglects to mention is how to get your kernel to give up the normal network driver so that DPDK can use it. It turns out that if you apt install dpdk it installs a service that automatically does this for you after you obtain the IDs of the NIC with lspci and add them to /etc/dpdk/interfaces:

pci	0000:01:00.0	vfio-pci
pci	0000:01:00.1	vfio-pci

Then I decided I wanted to use dnsmasq as both the DNS and DHCP server, which has the benefit that you can access your devices by their hostname. I settled on the following /etc/dnsmasq.conf which binds to the VPP bridge interface, ignores, resolv.conf, uses Google DNS, a .lan domain, adds DHCP hosts to the DNS, sets the gateway to VPP, and the DHCP server to itself.

# Set default gateway
# Set DNS servers to announce

When I was setting up VPP, the configuration files they provided were essentially broken, resulting in a long struggle to get NAT working. The documentation has since been updated, which is potentially a better reference than below config.


unix {
  log /var/log/vpp/vpp.log
  cli-listen /run/vpp/cli.sock
  startup-config /setup.gate
  gid vpp
  poll-sleep-usec 100
api-trace {
api-segment {
  gid vpp
socksvr {
dpdk {
	dev 0000:01:00.0
	dev 0000:01:00.1
plugins {
	plugin default { disable }
	plugin dpdk_plugin.so { enable }
	plugin nat_plugin.so { enable }
	plugin dhcp_plugin.so { enable }
	plugin ping_plugin.so { enable }


define HOSTNAME vpp1
define TRUNKHW GigabitEthernet1/0/0
define TRUNK   GigabitEthernet1/0/0.300
define VLAN 300

comment { Specific MAC address yields a constant IP address }
define TRUNK_MACADDR 90:e2:ba:47:df:ec
define BVI_MACADDR 90:e2:ba:47:df:ed

comment { inside subnet 192.168.<inside_subnet>.0/24 }

define INSIDE_PORT1 GigabitEthernet1/0/1

exec /setup.tmpl


show macro

set int mac address $(TRUNKHW) $(TRUNK_MACADDR)
set int state $(TRUNKHW) up
create sub-interfaces $(TRUNKHW) $(VLAN)
set int state $(TRUNK) up

set dhcp client intfc $(TRUNK) hostname $(HOSTNAME)

bvi create instance 0
set int mac address bvi0 $(BVI_MACADDR)
set int l2 bridge bvi0 1 bvi
set int ip address bvi0 192.168.$(INSIDE_SUBNET).1/24
set int state bvi0 up

set int l2 bridge $(INSIDE_PORT1) 1
set int state $(INSIDE_PORT1) up

comment { dhcp server and host-stack access }
create tap host-if-name lstack host-ip4-addr 192.168.$(INSIDE_SUBNET).2/24 host-ip4-gw 192.168.$(INSIDE_SUBNET).1
set int l2 bridge tap0 1
set int state tap0 up

nat44 forwarding enable
nat44 plugin enable sessions 63000
nat44 add interface address $(TRUNK)
set interface nat44 in bvi0 out $(TRUNK)

Note that without the poll-sleep-usec command, the CPU will be at 100% all the time, which is not very power efficient. But taking a high performance user space dataplane for low latency and then inserting a delay seems kinda pointless doensn’t it? But even with the delay, it was plenty fast in my testing, saturating the uplink without any obvious extra latency.

One significant difference in my config is that my ISP requires a VLAN of 300 on the WAN port, which was another struggle. The solution is that you have to set up a sub-interface like GigabitEthernet1/0/0.300 which will add the VLAN tag on outbound traffic and strip it on inbound trafffic. You then have to use this subinterface for all further setup.

Sinking the costs

While initially I tested VPP as a secondary router behind the ISP router, the VLAN struggle had to happen while my girfriend was without internet, another moment of realisation: I am now managing a sever exposed to the public internet, and if there is an issue we’re without internet access. Do I want that kind of responsibility for fun?

Is there any way I can justify this project? Time to do some testing. First I ran speedtest.net on the ISP router and the VPP router, and the results are pretty much indistinguishable. Around 8ms latency and 930Mbps up and down. But then my friend said the real test is packets per second, not just megabits, so I went to iperf3 and basically failed to run any test that would show a significant difference. That is, UDP tests with small packets would just kinda hang, and TCP tests would report similar numbers to Speedtest.

The other metric I looked at is wall power. Turns out the Raspi uses around 2W and the ISP router 18W, while the VPP router uses 20W. I slightly rounded the numbers for effect, but it’s not an obvious win. And that’s without the access point and switch this setup would need.

So in the end it’s not faster or more efficient, and a hecking lot more maintenance. Assuming the new fan would be sufficiently silent. And then my girlfriend said her Skype calls kept dropping upstairs, which is when I decided to quit messing around and buy a setup that just works.

The Omada Hardware

At this point I had kind of dismissed 10GbE as uneccessery, expensive, and power hungry. It also occured to me that if routers were actually bad, router companies would be put out of business by kids selling preconfigured pfSense thin clients. So I asked the owner of Routershop.nl for advice, and just bought whatever he recommended.

  • TP-Link Omada SDN TL-ER7212PC
  • TP-Link Omada SDN EAP653 Slim

The router is a fanless 3-in-1 Gigabit router, PoE switch, and Omada controller. It has 12 ports, of which 2 SFP cages, and a generous 110W PoE budget. The access point just a random Gigabit Wifi 6 device. More than sufficient for my needs for the forseeable future.

Around this time I also found a thread on my ISP’s forum where I learned that my fiber is actually AON, and which type of SFP module I need. I confirmed this information with customer support and the Routershop guy. The module he sold me slots right into the router and worked on the first try. Bye media converter!

I guess I’ll just keep the Raspberry Pi around, and equip it with a PoE+ hat for fun.

The Omada Software

Getting to the same point as the VPP router was a breeze: The setup wizard detected the access points and configured the main WiFi network. Then I just had to go to settings, wired network, internet, select the WAN port, and then under advanced settings put the Internet VLAN to 300. I first did this with the media converter, and once confirmed working, did a speed test, and switched the fiber from the media converter to the SFP module. Once again the test results were indistinguishable. Et voila, new network.

Omada controller

For science, I powered on the old router, to compare the reception upstairs in the furthest corner of the house. With my laptop on my lap I could not even see the old 5GHz network, but putting it down I captured the following image, with in purple the old WiFi, and red and gree respectively the downstairs and upstairs access points. Not only is it obvious why WiFi was so bad upstairs, it also shows how much better the dedicated access points are. The new downstairs AP comes through almost 20dB stronger than the old router. We could probably have made do with just the new downstairs AP.

wifi spectrum

While I’ve repeatedly found that any of the 3 routers can saturate a 1000Mbps link, WiFi is a different story. The new AP is actually 200Mbps faster than the old ISP router, while sitting about 3m away from each. Note that my laptop doesn’t have WiFi 6 so this is probably leaving speed on the table. But of course the wired network remains faster and more reliable.

The one feature missing from the Omada setup compared to VPP is that it doesn’t run a DNS server, so you can’t access devices by their hostname. But if I’m running the Raspberry Pi anyway I could run dnsmasq there if I wanted to. Or even Pihole while I’m at it.

I hear you like LAN so I put a VLAN in your LAN so you can VLAN while you LAN

Now that I have this easy to manage network, it’s time to tick off the last item on my wishlist: put all the IoT stuff on its own VLAN. I found this video quite helpful, even though my setup is a bit different.

I actually have two types of IoT devices. Cloud based ones that I begrudgingly allow internet acccess so that their app works. And local ones that talk to Home Assistant and have no business connecting to the internet.

The cloud one is simple, make a new WiFi network, and tick “guest mode”. This prevents it from connecting to any other device on the network. For the Home Assistant devices, I made a new LAN (purpose: interface, apparently), and gave it a nice VLAN ID and DHCP setup. Then I created another WiFi network, no guest mode, same VLAN ID.

Home Assistant itself actually runs on the main LAN so it can access the internet and it’s easy to target all the IoT devices. First I added a gateway rule from LAN to WAN that denies the IoT network to the IP group Any_IP. Then I added two EAP rules, one that allows the Iot network to a new IP group with just Home Assistant in it (which I assigned a static IP), and then a deny rule that denies the IoT network to the default LAN network.

Omada EAP ACL rules


You can make your own router, but there isn’t really a good reason to in my opinion. I could not find any measureable advantage. You can do it if you are looking for a new hobby, or if you have really specific requirements.

Even if you’re on a budget, the 50 Euro version of this build is just a single WiFi 5 access point, which would probably get you 90% of the way. You could repurpose an old PC if you don’t care about the energy bill and care a lot about your ISP not having remote access to your router I suppose. But with current energy prices the venn diagram of people who can’t afford a new router and care about their energy bill is almost a cricle.

I’m quite happy with the new setup, but in particular the access points are just much better than what’s built into the ISP router.

Pepijn de Vos

Lego 42121 Heavy-Duty Smartphone Tripod

Sometimes I film my projects, and sometimes I’d like to use my hands while filming. Usually this means propping up my phone on some random objects to point it roughly in the right direction. To improve this situation, I decided to build a Lego smartphone tripod from parts of the Lego 42121 Heavy-Duty Excavator. Here is a video, where I use an extra janky potato setup to film the tripod, sorry about the quality, better pictures below.

I started designing the part that clamps the phone, and the tilt mechanism. Everything from building the Lego set to making the tripod was basically an excuse to use these linear screw cylinders in my builds. The clamp is made of these rounded pieces to hold the phone, with a 5.5 axle with stop sandwiched between cross blocks to provide a stiff adjustable size. From there I just tried to find the most compact and straightforward way to attach the linear screw cylinder.

phone clamp and tilt mechanism

Next I wanted to be able to raise and lower the phone. At first I wanted to use scisor lift mechanism, but quickly realised it’d be too complicated. Instead I went for a car jack mechanism. The set comes with just the right amount of gears and liftarms. The only downside is that the gears have to be offset half a tooth so it’s not exactly straight. It also wasn’t very stable at first, but after adding the center bar, it’s acceptable. Just give it a bit of help when tryig to raise the jack while a phone is inserted.

jack bottom jack top

Then it was just a matter of connecting them together with the turntable. Here it is in the lowest and highest position.

low tall

To use my phone as a camera, I use DroidCam OBS

Pepijn de Vos

Design of a GPS patch antenna

At MCH I met up with a few maps people and worked on a GPS app for the badge. After that I decided I should make my own add-on with a GPS antenna, and that I wanted to design and print the antenna on a PCB. I succeeded, and here is the story.

GPS SAO in the window


I consulted several books and papers. A regular patch antenna is a pretty common and simple thing, but GPS is a circularly polarized signal, and that part is a bit more obscure.

  • Antenna Theory: Analysis and Design
  • Microstrip Antenna Design Handbook
  • Circularly Polarized Antennas

To start simple I decided to make a regular patch antenna first and then try to modify it for circular polarization. So I took some equations from the Belanis book and arrived at the following code to calculate the dimensions for a rectangular patch antenna for the L1 GPS frequency of 1574MHz:

from scipy.constants import c, mu_0, epsilon_0
import math

f0 = 1574e6
eps_r = 4.5
h = 1.6e-3

# CP uses this as initial guess for L too
W = c/(2*f0)*math.sqrt(2/(eps_r+1))
print("W:", W*1e3, "mm")

eps_eff = (eps_r+1)/2 + (eps_r-1)/2*(1+12*h/W)**-0.5
print("eps_eff:", eps_eff)

print("DL:", DL*1e3, "mm")

L = 1/(2*f0*math.sqrt(eps_eff*mu_0*epsilon_0))-2*DL
print("L:", L*1e3, "mm")

That it pretty much the extent of analysis I did. For the feedline I used the KiCAD PCB calculator to calculate the width of a 50Ω transmission line. To fine-tune everything I just did a lot of parameter sweeps in the simulator.

As far as designing the CP antenna goes, none of the books IMO strike a good balance of giving useful and understandable theory about CP. They kind of hand-wave about disturbances and throw some integrals at you. So in the end I just went with the truncated corner design and did some sweeps on how much to chop off.


At first I tried to simulate the antenna with OpenEMS, based on modifying their simple patch antenna tutorial. But I got really weird results and kind of gave up on that idea. I had seen Andrew Zonenberg do a lot of EM simulations in Sonnet, and they were kind enough to provide me with a trial license for this project, as well as amazing support. I really can’t recommend them enough. As Andrew says: OpenEMS might be good in the future, but right now if you value your time, Sonnet is the way to go.

My analysis suggested a patch size of around 45mm, so I tried to set up a sweep in Sonnet, but was having issues where different widths would give exactly the same results. I’m just going to quote the entire email I received from their support, who are total experts in their domain:

The reason why you are observing stepped behavior in your parameter sweep responses is because the variable step is smaller than the cell size and the metal is off grid. The variable L is swept from 44 to 46 mm, with a step of 0.1 mm: image1 The cell size is 1.27 x 1.27 mm: image1 Below is a screenshot of the geometry with the dimension parameters displayed: image1 The Sonnet EM solver analyzes the metal as it is snapped to the grid. This means the following:

  • When L is 44 mm, the metal snapped to the grid is 44.45 mm (35 cells).
  • When L is 45 mm, the metal snapped to the grid is 44.45 mm (35 cells).
  • When L is 46 mm, the metal snapped to the grid is 45.72 mm (36 cells).

The dependent parameter Y equals L/7.5 mm.

  • When L is 44 mm, Y = 5.866 mm. The snapped dimension would be 6.35 mm (5 cells).
  • When L is 45 mm, Y = 6.0 mm. The snapped dimension would be 6.35 mm (5 cells).
  • When L is 46 mm, Y = 6.133 mm. The snapped dimension would be 6.35 mm (5 cells).

With the specified parameter sweep of L of 44 to 46 mm, step of 0.1 mm, effectively the L dimension is 6.35 mm in all combinations. The key then is to work with a smaller cell size, to resolve the small dimensional changes. A smaller cell size will increase the memory requirement and analysis time, so there is a tradeoff. In the attached “feedviapatch_gk” model variation, I made a number of modifications:

  • Cell Size – Reduced cell size from 1.27 x 1.27 mm to 0.2 x 0.2 mm. This will resolve smaller dimension changes and match the patch dimensions better, which are whole numbers 45 x 45 mm
  • Via Port- The via size for the Via Port was 1.27 x 1.27 mm. I snapped this via to the new grid, reducing it to 1.2 x 1.2 mm. This should have minimal impact on the response.
  • Box Size - Wavelength with Er=1, at 1.575 GHz is 190 mm. For antenna models, we recommend approximately 2 wavelengths or more clear area around the antenna. Specified a box size of 800 x 800 um, which is a reasonable size. With a relatively large box size and relatively small cell size, this will lead to a large number of cells. In this model it is 4000 x 4000 number of cells, which is reasonable. As the number of cells reaches 20000 or more, the analysis time will increase substantially.
  • Upper air layer thickness – With an antenna model, with the box bottom cover used as a lower groundplane, the upper air layer thickness should be approximately 0.5 wavelength. Specified a value of 95 mm.
  • Patch position and orientation – Centered the patch and rotated it 90 degrees clockwise
  • Symmetry – Enabled symmetry. With the centered patch and new orientation, symmetry can be used to substantially reduce the memory requirement and analysis time.
  • Via Port position variable – Deleted the existing X and Y dimension parameters. Redefined a new X parameter. A new Y dimension parameter was not required as the Via Port remains centered.
  • Parameter sweep - Changed parameter sweep of L to 44 to 46 um, step 1 mm. All of the L values will be resolved and fall on the grid. The X values will be 5.866, 6, and 6.133 mm. Due to the 0.2 x 0.2 mm grid, these will be snapped to 5.8, 6, and 6.2 mm.

Below is a dB[S11] plot comparing the response at L values of 44, 45, and 46 mm: image1 For finer resolution, you could use a 0.1 x 0.1 mm cell size, but this will come at the expense of additional memory and analysis time. The change in the X variable value will drive the cell size. For example a 1 mm change in L, results in a 1/7.5 = 0.133 mm change in X.

For this model, the total analysis time on my desktop machine was 5 minutes, 37 seconds. The reported memory requirement was only 2 MB, because the Lite EM solver only counts one portion of the analysis. You can see the actual memory requirement in the logfile, which was 146 MB.

The attached model is a packed project archive (zon extension) with data. You can open it as you would a project file (son extension). Since it already includes data, you do not need to reanalyze and can plot the S-parameter and current density data.

Wow I did a learn. Amazing. After that I could easily run some sweeps and find an antenna size suitable for GPS reception. From here things happened kind of in parallel, but for the sake of the story let’s move on to simulating the CP antenna.

I had two more minor problems with this antenna. First is that it’s no longer symmetrical so I should turn off that setting again. The other is that circular polarization requires far-field analysis, and far-field analysis requires a full EM solve at the desired frequency. When you just do a normal sweep Sonnet tries to be smart and interpolate a bunch of things, so if you want to compare data from different parameters, you actually need to tell it to do a full EM solve at that frequency by adding a linear sweep and/or a single frequency.


Then you can set up and parameterize a truncated patch and do some sweeps:


Here is the S11 of my final antenna and some sweeps of the axial ratio of the far field.

s11 axial ratio


After the simulations it was finally time to make some antennas. I decided the best way to prototype these things was to use the CNC at Tkkrlab to mill a double sided PCB blank into an antenna and solder some SMA connectors to it. First I drew the antenna in KiCAD using SMD pads as the patch, there is even an option to make chamfered pads. I also added a stripline to callibrate the width.

kicad patch antenna design

Then I exported the PCB design to Gerber, loaded it into FlatCAM to convert it to a toolpath, and used bCNC to execute it. The process is a bit unintuitive and requires some experimentation, but nothing too crazy. I was able to just show up to Tkkrlab with my gerber files, create toolpaths, put on PPE, and watch my antenna being made. I used a 3mm flat bit to clear all the metal, and then a pointy bit to route the edges for exact dimensions and to get the SMA pads cut.

cncd antenna pcb


For testing the impedance of the antenna as well as the S11 I got myselve a LiteVNA. For the rectangular patch, I cut slots at the feedline to reduce the impedance, and for the truncated patch I was worried I’d have to construct some RF magic like a quarter wave transformer or hybrid but for some reason the impedance was actually kinda alright. Here is what the readout of the truncated andenna looks like:

litevna screenshot

If you’ll look back at the simulated S11 you’ll see it’s pretty close! Exciting stuff.


So of course the next step is to actually receive GPS signals with it. But how? I went for a two-pronged approach. I ordered an RTL-SDR, which I read can do GPS, and I ordered a PCB with my patch antenna and an u-blox MAX-M10S.

I never got to the SDR part, because the u-blox actually worked!!! It took a bit of fiddling to figure out how the I2C interface worked, but what you see in this picture is my GPS SAO PCB plugged into a MCH badge spewing NMEA messages to my laptop. I blurred the details, but it actually saw satelites and found my location!

GPS SAO in the window

All the stuff is on github although it’s more about the process than drawing a 45mm pad ;)

Pepijn de Vos