# melanpan.nl > Software Defined ADHD
contact [at] melanpan.nl -------------------------------------------------------------------------------- title: "melanpan.nl" url: https://melanpan.nl/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "About" url: https://melanpan.nl/about/index.md date: "2026-04-16" -------------------------------------------------------------------------------- Hi, I'm Melan (🍈)! A Dutch Programmer, weeb, homelab gremlin, and chaotic member of [NURDspace](https://nurdspace.nl). I spend my time on NixOS and Linux, AI, self-hosted services, ham radio, electronics, and Japanese animation in all sorts of forms. This is where I write about whatever hyperfocus has consumed me this week. The domain name is a mashup of メロンパン (*melonpan*) and my nickname Melan, which itself comes from Medicine Melancholy from Touhou many red moons ago. --- Avatar by https://x.com/ariastrella/status/2040572624913248421 (Moria Luluka from meitantei precure!) This site is powered by Hugo and the m10c theme. -------------------------------------------------------------------------------- title: "Posts" url: https://melanpan.nl/posts/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Hacking A Marine Dish" url: https://melanpan.nl/posts/hacking-a-marine-dish/index.md date: "2026-04-16" description: How I hacked a secondhand marine satellite dish to work at a hackerspace, reverse engineered the control protocol, and pretended to be a yacht. -------------------------------------------------------------------------------- Back in 2024 I got inspired by the YouTuber [saveitforparts](https://www.youtube.com/saveitforparts) and started looking into automatic satellite dishes. The ones sold here in the Netherlands are typically compact offset dishes on a motorised base, like a Megasat or Oyster, not quite the same as the Winegard units saveitforparts had access to in the US. (I still will probably end up getting one of those, though.) But while browsing Marktplaats I came across something arguably more interesting: a marine dish. An **Intellian i4**, going for around €200 (with a current new price of about €4000). I bought it immediately and became the proud owner of a piece of boat tech. Unlike most automatic dishes used on e.g. campers, the marine dish needs a higher level of precision. This means that all the motors in the unit are beefy stepper motors. The unit also features a quad LNB, and a fun spinny reflector that I will talk about later. Most of the brains of the dish itself (aside from the signal acquisition logic) are located in an indoor unit called the ACU (Antenna Control Unit). With the high price-tag this comes with Ethernet, WiFi and even RS-232! {{< gallery >}} {{< img src="Intellian_i4_Satellite_dish.jpg" caption="its radome." >}} {{< img src="intellian_i4_quad_lnb.jpg" caption="Quad LNBs" >}} {{< img src="intellian_i4_acu.jpg" caption="The ACU with a tuner sitting on top of it." >}} {{< img src="intellian_i4_acu_inside.jpg" caption="Inside the ACU." >}} {{< /gallery >}} ## But how does it work? A boat is constantly moving, rolling, pitching and yawing. But the satellite itself sits 36,000 km away in **geostationary orbit**. Meaning it stays fixed in the sky relative to the ground. The dish has to stay locked onto it regardless of what the boat is doing. And how does it accomplish that? Well, it needs to know its current position using GPS (it has one built-in), which direction it's pointing (heading, provided by the boat via [NMEA](https://en.wikipedia.org/wiki/NMEA_0183)) and where the satellite is. ## But wait.... we aren't a boat? As cool as it would have been to have NURDspace @ Sea, it's not something we can just do. So what CAN we do? Well, we **pretend** to be a boat! Since NMEA is a text-based serial protocol using [RS-422](https://en.wikipedia.org/wiki/RS-422) (Differential), it's fairly trivial to implement. All that is needed is a TTL to RS-422 module and an MCU to pretend we are a €130,000 yacht! All we need to do is send ``` $HCHDG,0.0,0.0,E,0.0,E*checksum ``` to the antenna control unit, so that it can do its math correctly! But what does it mean? Well, each NMEA sentence starts with a `$` followed by a two-letter talker ID (for example, `GP` for GPS and `HC` for compass), then a three-letter sentence type, comma-separated fields, and finally a `*` followed by a two-character hex checksum. The checksum is just an XOR of all the bytes between `$` and `*`. {{< img src="intellian_i4_boat.jpg" width="40%" caption="An RP2040 with an RS-422 emulating a €130,000 yacht." >}} ## But can I control it? This was the million euro question for me after buying it. Million euro because, well, that's roughly what you'd need to actually own a yacht. When I first got it, I didn't even have the software to control the thing nor could I find it online anywhere at that time. I also had no access to the Intellian Partner portal. So what did I do? Simple, I mailed Intellian asking them for the software. Which they happily gave me! {{< img src="intellian_i4_aptus.png" width="40%" caption="Aptus Controller" >}} So now I could control the dish with this software. But could I control it without using it? And thus I started sniffing the communication between Aptus and the ACU. It turned out the communication was a fairly simple text protocol ending with a checksum. ``` {XX param1 param2 ... paramN}C ``` Even more useful, the Aptus software is written in C#, meaning I could very easily decompile the code and look at it. And thus I had all the information I needed. Eventually, roughly two years later, I asked an LLM to write up a [protocol description](ACU_Protocol_Documentation.md.txt). So how does the checksum work? Simple, for each character in the message it calculates `num = (num + char_value - 32) % 95` and finally returns it as an ASCII character based on `num + 32`: ```C# public static char UIF_CheckSum(StringBuilder sb, int length) { int num = 0; for (int index = 0; index < length; ++index) num = (num + sb[index] - 32) % 95; return (char)(num + 32); } ``` And a Python version ```python def uif_checksum(message: str) -> str: num = 0 for char in message: num = (num + ord(char) - 32) % 95 return chr(num + 32) ``` So how would I tell it where to point at? Also very simple! All you need to tell the ACU is GO followed by the azimuth and elevation. ``` {GO 16250 2000} ``` But wait... why are these numbers so large? Well, rather than using floats, which behave unpredictably across different compilers and architectures, everything is stored as integers with an implicit two decimal places. This means 16250 == 16250 is always unambiguously true, whereas float comparisons can silently fail. Floating point computations are also slow when you don't have an FPU (or the FPU itself is also slow). For marine and mission-critical systems, that predictability matters a lot! ## The spinny thing? Let's talk a little bit about the design of the dish. Most TV dishes you'll see on rooftops use an **off-axis** (or offset) reflector design which means that the LNB sticks out to the side rather than sitting in the middle of the dish. This is done to avoid the LNB casting a shadow on the reflector and wasting signal. Our dish however uses a prime focus design, where the LNB sits right in the centre. Less common for consumer dishes, but it makes for a much more compact and symmetric pedestal mount, important when your dish needs to spin around on a boat. The dish is only 40 cm in size (and the bigger the dish, the more gain) but using a design like this makes the dish still plenty powerful. What also helps is that the dish features a motorised **sub-reflector** sitting between the dish surface and the LNB. This is part of Intellian's **Dynamic Beam Tilting (DBT)** technology. Rather than constantly running the stepper motors to correct for every wave and wobble, the sub-reflector continuously adjusts to keep the beam locked onto the satellite. The main motors only handle the big corrections, keeping the whole thing quiet and responsive. That's what our spinny thing does! What an interesting bit of tech this dish is. {{< gallery >}} {{< img src="intellian_i4_subreflector.jpg" caption="The subreflector" >}} {{< img src="intellian_i4_subreflector_motor.jpg" caption="And its motor" >}} {{< /gallery >}} ## So what could I do with it? Well, I first wrote a little script that sat in between the ACU and [Gpredict](https://oz9aec.dk/gpredict/) making the dish appear as a generic rotator to it. Allowing it to track satellites that aren't in the geostationary orbit. I didn't however play with this much, yet. Mainly because the feed on the dish is only for the [Ku-band](https://en.wikipedia.org/wiki/Ku_band) and I am not yet aware of any non-geostationary satellites using this band. After moving the dish itself to NURDspace and yeeting it onto the roof, we were also given some satellite stuff from a friendly fellow hacker. Amongst the 75 ohm cabling was also a Digibit R1, which next to a Dreambox DM800 served as a tuner for us to try tuning into some of the television transponders. The first lock was a nice feeling, we tuned to Astra 1P located at `19.2°E` showing us the Chinese channel CGTN HD! {{< gallery >}} {{< img src="intellian_i4_mpv.jpg" caption="MPV displaying a korean channel" >}} {{< img src="intellian_i4_roof.jpg" caption="The dish chilling on the roof at NURDspace" >}} {{< /gallery >}} Heck yeah, I can watch satellite TV at home, beamed straight at the space and served up via wireguard. Technology is amazing! Of course, I had other plans but those are worthy of their own posts! And finally, a video of the dish doing its happy little test dance {{< video src="intellian_dish_dance.mp4" >}} -------------------------------------------------------------------------------- title: "Using AI to detect garbage containers" url: https://melanpan.nl/posts/klikoai/index.md date: "2026-04-16" description: How NURDspace built KlikoAI: a vision AI system using Mistral3 to detect whether our garbage bins are inside or outside. -------------------------------------------------------------------------------- At **NURDspace**, we have a very specific problem: nobody remembers to put the bins out. Or bring them back in. There are two garbage containers, a blue one for paper and yellow for general waste. On collection days someone has to wheel them outside, and back in after pickup. Simple enough in theory. We had Home Assistant firing automations based on the municipal garbage calendar: yelling on IRC every few hours, spamming Signal and Telegram, and if the space was open, triggering the TTS system to yell at people in person. Passive-aggressive, but effective enough. We also had ultrasonic distance sensors on the containers. An ESPHome binary sensor would check whether the distance reading was within a calibrated range and report back to HA. The problem: the sensors were flaky, and if anyone bumped a container it'd spam IRC with movement alerts. This got especially unbearable once renovations started. Something had to change. {{< img src="KlikoAI_klikos.jpg" alt="Klikos" width="20%" caption="Klikos in their natural habitat." >}} ## The Attempts ### BLE Beacons My first idea: cheap BLE beacons from AliExpress, paired with an ESP32. The plan was to estimate distance from RSSI. A container outside would read weaker than one inside. Simple enough. Mounting was the problem. The beacon baseplate got screwed directly into the container *(sorry Multipacity!)*, with the PCB housing clipped on top. We lost the first beacon when the garbage truck emptied the bin. The second one fell off before we even got that far. ### YOLO Object Detection So I figured: why not run YOLO on the frames? Easier said than done, it turned out. I found a [garbage container dataset on Roboflow](https://universe.roboflow.com/finance-insitut/garbage-container-detection-sam7i), trained a custom YOLO model on my workstation (RTX 5090, so at least training was fast), and deployed it. The results were... not great. Our camera is mounted in a suboptimal position too high, not pointed directly at the containers but down at them. I briefly considered putting my own dataset together, but I found that it was more troublesome than worth it to find images of Dutch containers and the right angles. {{< gallery >}} {{< img src="klikoai_yolo_before_training.jpg" alt="Klikos" width="100%" caption="YOLO thinking we put our frikadellen in here." >}} {{< img src="klikoai_yolo_after_training.jpg" alt="Klikos" width="100%" caption="YOLO after training on containers" >}} {{< /gallery >}} The angle was just bad enough that the bounding boxes were nearly useless. Sometimes it'd see one container and miss the other entirely, sometimes barely clip either of them. Nothing reliable. I did consider a workaround: since YOLO gives you a bounding box, you could crop just the top portion of the detected object to isolate the lid and check the dominant colour. But the complexity was spiralling fast and the underlying detection was still shaky, so results would've been shaky too. ### Why not ask a VLM? Instead of trying to localise the bins geometrically, why not just ask a vision language model what it sees? VLMs are surprisingly good at this kind of fuzzy "*tell me about the colours in this image*" task. Far better than fighting with bounding boxes and colour histograms. KlikoAI now uses `mistral3:14b` via **llama-swap** with llama.cpp as the backend. It runs on `gpu.vm.nurd.space`, a VM at the space running NixOS with an Nvidia RTX 3080. The prompt is very simple: ``` Look carefully at this image. I can see garbage containers. Tell me which colored LIDS are visible: 1. Is there a YELLOW or cream colored lid? 2. Is there a BLUE or dark blue colored lid? Reply ONLY with valid JSON using double quotes: {"yellow": true, "blue": true} ``` Detection isn't 100% perfect though. Occasionally it still misses a container. But it's a massive improvement over everything we tried before. The JSON output parses cleanly and the false positive rate is low enough to actually trust. Inference takes around 40 seconds, but that includes loading and unloading the model dynamically via llama-swap since it's a shared GPU machine. The actual vision inference is much faster than it sounds. {{< gallery >}} {{< img src="klikoai_camerasees.png" alt="Klikos" width="100%" caption="What the camera sees" >}} {{< img src="klikoAI_lights_and_camera.jpg" alt="Klikos" width="100%" caption="What we see" >}} {{< /gallery >}} ### Illuminating The bins live in a fairly dark corner of the space. Poor lighting → bad photos → confused model. So I added a couple of bright LED strips controlled by an ESP8266 and an N-channel MOSFET. When KlikoAI triggers an inference run, it turns the lights on via Home Assistant, snaps the photo, then turns them back off. Side effect: the lights occasionally scare the hell out of people walking past the space at night. I've added a strobe mode for gabber parties. These things happen. {{< gallery >}} {{< img src="klikoAI_showcasing_lights.jpg" alt="Klikos" width="100%" caption="The lights on at max brightness." >}} {{< img src="klikoAI_lights_controller.jpg" alt="Klikos" width="100%" caption="The controller box housing the mosfet and a ESP8266." >}} {{< /gallery >}} ### Automation KlikoAI publishes its state as an MQTT sensor to Home Assistant: - `Paper: Visible` / `Paper: Not Visible` - `Waste: Visible` / `Waste: Not Visible` Home Assistant uses these to gate the garbage notification: **PUT THE BINS OUTSIDE** keeps firing until the relevant bin is not visible, i.e., it's been taken out. The automation shuts up the moment someone actually does the thing. For the time being there's no automation yet to yell at people to bring the bin back inside, but getting them out is the more pressing problem. That's next on the list. KlikoAI also integrates with NURDbot, our IRC bot, via two commands: - `!klikos` — display current container states - `!klikoupdate` — trigger a manual inference run immediately ``` <@Melan> !klikoupdate <@nurdbot> [Klikos] Running kliko inference task... <@nurdbot> [Klikos] Paper: Visible Waste: Visible | Last Update: 2026-04-14 19:52:04 | Avg time: 41.48 secs ``` ### Schedule KlikoAI uses the Home Assistant calendar integration to know when collection days are. On normal days it scans every 6 hours, enough to catch if someone forgot to bring a bin back or moved it. On collection days it switches to a much more frequent schedule to catch the "bin is now outside" state quickly and kill the reminder notifications. There's also the idea to add logic to detect significant frame changes (in case something moves in front of the camera, or the lights change, triggering an unscheduled scan). ### What's Next - Coding/testing the frame-change detection. - Test how the performance/error rate detection is with newer models like Gemma4 or Qwen3.5. - Yeet bin back indoors messages. NURDspace Project page: https://nurdspace.nl/KlikoAI -------------------------------------------------------------------------------- title: "Pirating a TTS voice" url: https://melanpan.nl/posts/femke/index.md date: "2026-04-15" description: How I trained a custom Dutch TTS voice using Piper, a Windows VM, VNC automation, and some creative dataset gathering. -------------------------------------------------------------------------------- One of the favourite text-to-speech voices at NURDspace is Femke, a Dutch-Flemish sounding voice by **Acapella Group**. She gets used for announcements, the `!say` IRC command, yelling at people to put the bins out, that kind of thing. For a long time we were pulling audio from an Android API, which worked well enough that I even started training my own model based on it. Then the API got shut down. This is the story of how I pirated Femke. Twice. ## What is Piper? [Piper](https://github.com/rhasspy/piper) is a fast local neural network TTS system from the rhasspy project. It can synthesise a short sentence in milliseconds on a CPU, which makes it perfect for home automation and IRC bots. It also has a reasonable training pipeline and a bunch of pretrained checkpoints to fine-tune from, which is exactly what I needed. {{< note >}}This was all done before development moved to https://github.com/OHF-Voice/piper1-gpl{{< /note >}} ## Version 1: The Android API Acapella Group used to expose their voices through an Android API endpoint. [WillFromAfarDownloader](https://github.com/weespin/WillFromAfarDownloader) made it straightforward to pull audio from this; feed it text, get Femke wavs back. ### The dataset For the text I used the Dutch [CC-100 corpus](https://data.statmt.org/cc-100/), which unpacks to a 30GB text file of Dutch sentences. I wrote a filtering script to pull out lines under 160 characters containing words like "*computer*" and "*hack*" and of course, a handful of Dutch swear words, because of course people are going to use Femke to swear. This brought it down to a manageable 62MB. I then outputted everything in LJSpeech format (a CSV with filename and transcription), splitting on full stops to keep sentences short, and filtering out anything under 10 characters. I stopped at 1500 samples. In hindsight I should have added more sanity checks, some URLs and non-Dutch strings slipped through into the metadata. Oh well! ### Training Training ran on an RTX 3090 under WSL. I had to drop to 16-bit precision because 32-bit was consuming more than 24GB of VRAM. After about 70 epochs it started sounding like coherent Dutch. It worked, but the quality left something to be desired. Then Acapella Group shut down the API endpoint. Back to square one. ## Version 2: Mind Express and a VM Contraption With the Android API gone, finding a new source of Femke audio took me a while. Acapella Group SDKs were a dead end. Eventually the answer turned out to be [Mind Express](https://www.jabbla.com/en/mind-express/), a communication aid for people with speech impairments. Their 30-day trial includes Acapella Group voices, including all the Dutch ones. Perfect! Getting audio *out* of it in bulk was the fun part. After attempting to reverse-engineer the Acapella Group DLLs I came up with the following solution. ### The setup I spun up a Windows 10 VM on Proxmox, gave it a virtual soundcard and a passwordless VNC server: ``` args: -vnc 0.0.0.0:77 audio0: device=intel-hda,driver=none ``` Inside the VM, [Voicemeter Banana](https://vb-audio.com/Voicemeter/banana.htm) creates a virtual audio device that Python can record from. Control of Mind Express itself went through `vncdotool`, because `pyautogui` flat out refused to send inputs to it. I then wrote a small Flask webserver running inside the VM that, on receiving a POST request with text: 1. Types the text into Mind Express via VNC 2. Moves the mouse to click Play 3. Starts recording from the virtual audio device simultaneously 4. Stops recording after 3 seconds of silence 5. Trims with ffmpeg 6. Returns the wav file to the caller So from the outside it's just a simple API, POST some text, get a wav back. Not fast, since the audio is generated in real time, but fast enough. ### The dataset I used the same CC-100 corpus and filtering approach as v1. I let the generator run until I had 6111 samples and called it there. For reference, Piper's own [recording studio](https://github.com/rhasspy/piper-recording-studio) uses 1150 prompts for Dutch, and the Nathalie model appears to be trained on around 1130. So 6111 is comfortably more than enough! ### Training This time instead of training from scratch, I fine-tuned from Piper's existing Dutch checkpoint: [Nathalie medium](https://huggingface.co/datasets/rhasspy/piper-checkpoints/tree/main/nl/nl_BE/nathalie/medium) following their now existing [training documentation](https://github.com/rhasspy/piper/blob/master/TRAINING.md). With a batch size of 42 at 32-bit precision, training ran for 6000 steps on the RTX 3090 and finished in about 4 hours. The difference compared to v1 is night and day. Version 1: {{< audio src="femke_150.wav" >}} Version 2: {{< audio src="femke_v2.wav" >}} ## What's Next Now that the pipeline is working, there are a few directions to take it: - Training more voices: not just Acapella Group, but members contributing their own voice for a personalised `!say` command - Training on well-known Dutch public figures (gather audio, transcribe with Whisper, fine-tune) - Starting an in-space AI band with Femke and Daan doing Dutch cover songs The NURDspace wiki has more technical detail on both versions: [Training Femke (Voice)](https://nurdspace.nl/Training_Femke_(Voice)) -------------------------------------------------------------------------------- title: "Categories" url: https://melanpan.nl/categories/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Tags" url: https://melanpan.nl/tags/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Ai" url: https://melanpan.nl/tags/ai/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Computer-Vision" url: https://melanpan.nl/tags/computer-vision/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Hardware" url: https://melanpan.nl/tags/hardware/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Home-Automation" url: https://melanpan.nl/tags/home-automation/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Machine-Learning" url: https://melanpan.nl/tags/machine-learning/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Nurdspace" url: https://melanpan.nl/tags/nurdspace/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Piper" url: https://melanpan.nl/tags/piper/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Reverse-Engineering" url: https://melanpan.nl/tags/reverse-engineering/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Satellite" url: https://melanpan.nl/tags/satellite/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Sdr" url: https://melanpan.nl/tags/sdr/index.md -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- title: "Tts" url: https://melanpan.nl/tags/tts/index.md --------------------------------------------------------------------------------