The Idea Came First
Hi guys, I'm here again. After building Alfred here I wanted him to be able to control Juliana, my Xiaomi X20+ robot vacuum. I did not know how that was going to work and I did not have a clear path forward, but the goal was clear tbh. Ask Alfred something and then he can act on the available tools to him. So I started where most network curiosity starts. I ran an nmap scan on my LAN to see what Juliana was actually exposing to the network. All TCP ports were closed. But UDP port 54321 was open and listening. Bingo!.
If you have not read about Alfred yet, I wrote about how I built him here. This feature is a direct result of what I called the Floodgate Effect in that article. The moment Alfred works in one area of your life, you immediately want to connect everything else. Juliana was next on the list. Its weird that I have names for things in my house but yh that's me.
Speaking Juliana's Language
Having a port is not the same as having a conversation unfortunately. I needed to understand what protocol was running on that port and how to speak it. After some research I discovered that Xiaomi smart home devices communicate using MiIO, a proprietary protocol that runs over UDP port 54321. That was the first real breakthrough.
MiIO follows a three step flow.
- First you do a handshake. You send a 32 byte "hello" packet made entirely of 0xFF bytes and the device responds with its device ID and a timestamp called a stamp.
- Second you send a command. Every command is a 32 byte header combined with an AES-128-CBC encrypted JSON body. The encryption uses an MD5 derived key and IV generated from the device token. The header carries magic bytes, the packet length, the device ID, the stamp incremented by one, and an MD5 checksum.
- Third you receive a response in the exact same format and you decrypt it using the same token to get your JSON back.
The JSON itself follows a JSON-RPC style structure. A request looks like this:
{ "id": 1, "method": "get_properties", "params": [...] }
And a response comes back like this:
{ "id": 1, "result": [...] }
It is a clean protocol once you understand it, but getting to that understanding took some work.
The Roadblocks
Getting the Token
Understanding the protocol was one thing. Actually talking to Juliana required a device token, This is the way Xiaomi handles auth in communication with the device, and getting that token turned out to be its own challenge. My first instinct was to extract it programmatically through the Xiaomi cloud, but that path was immediately blocked by captcha. I had to find another way.
I ended up using the Xiaomi Cloud Tokens Extractor tool, which supports a QR code based login flow. That got me what I needed. The token, the device ID, and confirmation that Juliana was registered under the MiIO protocol. With those two values in hand, I could finally start sending real commands.
The MiOT Property System
Modern Xiaomi devices do not use simple named commands. They use MiOT, the Mi IoT specification, where every property and action is addressed by a service ID called siid and either a property ID called piid or an action ID called aiid. Reading the battery level means sending a get_properties request with siid 3 and piid 1. Setting the suction to strong means sending a set_properties request with siid 4, piid 4, and a value of 2. Starting a cleaning session means triggering an action with siid 2 and aiid 1. Every single thing the vacuum does maps to one of these numeric combinations.
Trial and Error All the Way Down
There is no official documentation that maps these IDs for the X20+. I had to probe them manually by iterating through siid values from 1 to 30 and piid values from 1 to 30 and cross referencing what came back against what I could see in the Xiaomi app. It was tedious work. Some of the latest firmware implementations returned values that did not line up with what you would logically expect, which made matching them to real behaviour even harder.
Cleaning History Lives in the Cloud
One limitation I hit fairly early was around maps and cleaning history. Both are stored in the Xiaomi cloud rather than on the device itself. The only thing you can reliably read from Juliana directly is the last cleaning session. I turned this into an advantage by tracking every session result myself and building a local history. That way Alfred always has full context about when Juliana last cleaned, how long it took, and how much area was covered, without depending on the cloud for any of it.
The Real Value
It Is Not About the Commands
Sending commands to Juliana via an API is not the interesting part. The Xiaomi app already does all of that. You can start a clean, dock the vacuum, set suction levels, and check the battery from your phone in seconds. Replicating that through code alone would not be worth writing about.
The interesting part is what happens when those commands become tools that an AI agent can reason about and invoke on your behalf.
Tools Alfred Can Use
Every capability I built around Juliana was wrapped into a tool that Alfred can call. There are 3 of them:
- executeVacuumStatus reads the current state of the device including battery level, cleaning mode, error codes, and consumable wear levels.
- executeVacuumCommand sends operational commands like start, stop, pause, resume, dock, and locate.
- executeVacuumHistory pulls from the locally tracked session log so Alfred can reason about when and where Juliana has cleaned.
What This Actually Looks Like
With those tools in place, my conversations with Alfred around the vacuum feel completely natural. I can ask things like:
Status and maintenance
- "What is Juliana's battery level?"
- "Does Juliana need any maintenance?"
- "How are Juliana's consumables holding up?"
Control
- "Start cleaning the living room"
- "Clean the master bedroom and the office"
- "Send Juliana home"
- "Find Juliana" (this makes her announce her location out loud)
History
- "When did Juliana last clean?"
- "How much has Juliana cleaned today?"
- "Show me Juliana's cleaning history"
Combined and conversational
- "How is everything at home?"
- "Start cleaning the guest bedroom and let me know when it is done"
- "Is Juliana's mop pad due for replacement?"
Alfred does not just relay commands. He reads the context, decides which tools to call, and responds with a full picture. That is the difference between a smart home app and an agent.
From a Single Open Port to a Talking Vacuum
What started as curiosity about controlling my robot vacuum and an open UDP port turned into a fully functional integration between Alfred and Juliana. Along the way there were real obstacles and each one had to be solved before the next step was even possible.
Getting the device token could not be automated due to captcha blocks on the Xiaomi cloud, so I used the Xiaomi Cloud Tokens Extractor with QR code login instead. With no official documentation for the property IDs on the X20+, I probed siid 1 through 30 and piid 1 through 30 manually and matched results against the Xiaomi app. UDP has no built in request response correlation so I built a command serialization queue that keeps exactly one command in flight at a time. Hello responses were mixing with command responses so I added an isHelloResponse() check to skip those 32 byte packets. Timeouts were killing subsequent commands so I reset the stamp to zero on timeout to force a fresh handshake. Sending too many properties at once caused failures so I batched them into groups of ten. The locate command was not triggering a beep until I found the right combination at siid 7, aiid 1, and piid 1 set to 1. Consumable values were coming back wrong because the correct properties were at siid 9, 10, 11, and 18 rather than siid 4 where I originally looked.
The one limitation I could not fully solve is map data. The X20+ stores its maps in the Xiaomi cloud and not on the device itself. Valetudo would solve this but that means a total OS flush of the robot which voids my warranty.
Every solution in this list is a direct result of building things the right way rather than the fast way. The command queue, the constants file, the local history store, all of it exists because the goal was never just to control a vacuum. The goal was to give Alfred enough context and capability to reason about the home the same way he reasons about a calendar or an inbox. Juliana is now part of that picture.

Top comments (0)