Add Xiaomi miIO probe detector

Detect stock Xiaomi/Mijia devices via miIO hello packet on UDP:54321.
Response magic 0x2131 uniquely identifies miIO devices.

Detector priority: ONVIF > HomeKit > Xiaomi > standard.
This commit is contained in:
eduard256
2026-04-17 20:35:02 +00:00
parent b3e3e8ab1a
commit ccb100fcd0
3 changed files with 88 additions and 7 deletions
+16 -1
View File
@@ -51,6 +51,14 @@ func Init() {
return ""
})
// Xiaomi detector (miIO hello on UDP:54321)
detectors = append(detectors, func(r *probe.Response) string {
if r.Probes.Xiaomi != nil {
return "xiaomi"
}
return ""
})
api.HandleFunc("api/probe", apiProbe)
}
@@ -129,12 +137,19 @@ func runProbe(parent context.Context, ip string) *probe.Response {
resp.Probes.ONVIF = r
mu.Unlock()
})
run(func() {
r, _ := probe.ProbeXiaomi(fastCtx, ip)
mu.Lock()
resp.Probes.Xiaomi = r
mu.Unlock()
})
wg.Wait()
// determine reachable
resp.Reachable = (resp.Probes.Ports != nil && len(resp.Probes.Ports.Open) > 0) ||
resp.Probes.MDNS != nil
resp.Probes.MDNS != nil ||
resp.Probes.Xiaomi != nil
// determine type
resp.Type = "standard"
+12 -6
View File
@@ -9,12 +9,13 @@ type Response struct {
}
type Probes struct {
Ports *PortsResult `json:"ports"`
DNS *DNSResult `json:"dns"`
ARP *ARPResult `json:"arp"`
MDNS *MDNSResult `json:"mdns"`
HTTP *HTTPResult `json:"http"`
ONVIF *ONVIFResult `json:"onvif"`
Ports *PortsResult `json:"ports"`
DNS *DNSResult `json:"dns"`
ARP *ARPResult `json:"arp"`
MDNS *MDNSResult `json:"mdns"`
HTTP *HTTPResult `json:"http"`
ONVIF *ONVIFResult `json:"onvif"`
Xiaomi *XiaomiResult `json:"xiaomi"`
}
type PortsResult struct {
@@ -51,3 +52,8 @@ type ONVIFResult struct {
Name string `json:"name,omitempty"`
Hardware string `json:"hardware,omitempty"`
}
type XiaomiResult struct {
DeviceID uint32 `json:"device_id"`
Stamp uint32 `json:"stamp"`
}
+60
View File
@@ -0,0 +1,60 @@
package probe
import (
"context"
"encoding/binary"
"net"
"time"
)
// miIO hello packet -- 32 bytes. Stock Xiaomi/Mijia devices listen on
// UDP:54321 and reply with the same magic 0x2131 + their device_id + stamp.
// Newer firmwares always return 0xFF in the token field, regardless of
// pairing status -- real token is only available via Mi Cloud API.
var xiaomiHello = []byte{
0x21, 0x31, 0x00, 0x20,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff,
}
// ProbeXiaomi sends miIO hello to ip:54321 and checks the reply magic.
// Returns nil, nil if the device is not a Xiaomi miIO device.
func ProbeXiaomi(ctx context.Context, ip string) (*XiaomiResult, error) {
conn, err := net.ListenPacket("udp4", ":0")
if err != nil {
return nil, err
}
defer conn.Close()
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(100 * time.Millisecond)
}
_ = conn.SetDeadline(deadline)
addr := &net.UDPAddr{IP: net.ParseIP(ip), Port: 54321}
if _, err = conn.WriteTo(xiaomiHello, addr); err != nil {
return nil, err
}
buf := make([]byte, 64)
n, _, err := conn.ReadFrom(buf)
if err != nil || n < 32 {
return nil, nil
}
// magic must be 0x2131 -- unique miIO header
if buf[0] != 0x21 || buf[1] != 0x31 {
return nil, nil
}
return &XiaomiResult{
DeviceID: binary.BigEndian.Uint32(buf[8:12]),
Stamp: binary.BigEndian.Uint32(buf[12:16]),
}, nil
}