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:
+16
-1
@@ -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"
|
||||
|
||||
@@ -15,6 +15,7 @@ type Probes struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user