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 ""
|
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)
|
api.HandleFunc("api/probe", apiProbe)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,12 +137,19 @@ func runProbe(parent context.Context, ip string) *probe.Response {
|
|||||||
resp.Probes.ONVIF = r
|
resp.Probes.ONVIF = r
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
})
|
})
|
||||||
|
run(func() {
|
||||||
|
r, _ := probe.ProbeXiaomi(fastCtx, ip)
|
||||||
|
mu.Lock()
|
||||||
|
resp.Probes.Xiaomi = r
|
||||||
|
mu.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// determine reachable
|
// determine reachable
|
||||||
resp.Reachable = (resp.Probes.Ports != nil && len(resp.Probes.Ports.Open) > 0) ||
|
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
|
// determine type
|
||||||
resp.Type = "standard"
|
resp.Type = "standard"
|
||||||
|
|||||||
+12
-6
@@ -9,12 +9,13 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Probes struct {
|
type Probes struct {
|
||||||
Ports *PortsResult `json:"ports"`
|
Ports *PortsResult `json:"ports"`
|
||||||
DNS *DNSResult `json:"dns"`
|
DNS *DNSResult `json:"dns"`
|
||||||
ARP *ARPResult `json:"arp"`
|
ARP *ARPResult `json:"arp"`
|
||||||
MDNS *MDNSResult `json:"mdns"`
|
MDNS *MDNSResult `json:"mdns"`
|
||||||
HTTP *HTTPResult `json:"http"`
|
HTTP *HTTPResult `json:"http"`
|
||||||
ONVIF *ONVIFResult `json:"onvif"`
|
ONVIF *ONVIFResult `json:"onvif"`
|
||||||
|
Xiaomi *XiaomiResult `json:"xiaomi"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortsResult struct {
|
type PortsResult struct {
|
||||||
@@ -51,3 +52,8 @@ type ONVIFResult struct {
|
|||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Hardware string `json:"hardware,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