refactor: 重大架构升级 — HomeID/RoomID int64化 + 设备驱动重构 + 结构化日志

主要变更:

**类型系统重构**
- HomeInfo.ID/RoomInfo.ID/HomeID 从 string 改为 int64,json tag 使用 ,string
- GetHome/GetRooms/GetRoomDevices 接口同步修改
- 新增 int64FromStr 工具函数,更新 parseHomeInfo 解析逻辑

**新增 Speaker 设备**
- 新增 KindSpeaker 类型
- classify 映射覆盖 speaker/bluetooth-speaker/alarm-clock/media-player/playback/music

**设备驱动重构**
- AC 驱动彻底重写:从硬编码常量改为基于 SPEC ValueList 的动态 valMapper
- 所有设备(Fan/Humidifier/Light/OccupancySensor/Switch/TempHumiditySensor/Thermostat/Vacuum/WaterHeater/Cover)新增 GetState() 和 FetchState(ctx) 接口
- base.go 新增 specResolver.format() 方法

**MIoT 模块增强**
- MIoTClient 新增 homeIDs 字段,支持按家庭过滤设备
- RefreshDevices 支持 homeIDs 参数
- SpecStdLib 引入 logger.Logger,log.Printf 全部迁移为结构化日志

**其他**
- Config 新增 RedirectURL 和 HomeIDs 字段
- AuthInfo.ExpiresIn 类型从 int 改为 int32
- UUID 生成简化为 hex.EncodeToString
- 新增 github.com/sirupsen/logrus 依赖
- .gitignore 新增 examples/*/data/ 忽略规则
- 删除已提交的 config.json 敏感配置
This commit is contained in:
2026-07-13 10:40:20 +08:00
parent 8ffc9e4e44
commit b808e51b3e
68 changed files with 2506 additions and 688 deletions
+61 -53
View File
@@ -12,13 +12,14 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"log"
"math/big"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"xiaomihome/logger"
)
// ============================================================================
@@ -155,7 +156,7 @@ func newLanDevice(mgr *MIoTLan, did, tokenHex, ip string) (*LanDevice, error) {
d.updateKeepAlive(LanDeviceDead)
})
log.Printf("[lan] device added: did=%s", d.DID)
mgr.lgr.Infof("[lan] device added: did=%s", d.DID)
return d, nil
}
@@ -164,7 +165,7 @@ func (d *LanDevice) KeepAlive(ip, ifName string) {
d.IP = ip
if d.IfName != ifName {
d.IfName = ifName
log.Printf("[lan] device if_name changed: %s, did=%s", d.IfName, d.DID)
d.manager.lgr.Infof("[lan] device if_name changed: %s, did=%s", d.IfName, d.DID)
}
d.updateKeepAlive(LanDeviceFresh)
}
@@ -315,12 +316,12 @@ func (d *LanDevice) Subscribe() {
func (d *LanDevice) subscribeHandler(msg map[string]interface{}, subTS int) {
result, _ := msg["result"].(map[string]interface{})
if result == nil {
log.Printf("[lan] subscribe error: no result, did=%s, msg=%v", d.DID, msg)
d.manager.lgr.Infof("[lan] subscribe error: no result, did=%s, msg=%v", d.DID, msg)
return
}
code, _ := result["code"].(float64)
if code != 0 {
log.Printf("[lan] subscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg)
d.manager.lgr.Infof("[lan] subscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg)
return
}
d.subscribed = true
@@ -329,7 +330,7 @@ func (d *LanDevice) subscribeHandler(msg map[string]interface{}, subTS int) {
"online": d.online,
"push_available": d.subscribed,
})
log.Printf("[lan] subscribe success: if=%s, did=%s", d.IfName, d.DID)
d.manager.lgr.Infof("[lan] subscribe success: if=%s, did=%s", d.IfName, d.DID)
}
// Unsubscribe sends a miIO.unsub request to the device.
@@ -350,11 +351,11 @@ func (d *LanDevice) Unsubscribe() {
result, _ := msg["result"].(map[string]interface{})
if result != nil {
if code, _ := result["code"].(float64); code != 0 {
log.Printf("[lan] unsubscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg)
d.manager.lgr.Infof("[lan] unsubscribe error: code=%v, did=%s, msg=%v", code, d.DID, msg)
return
}
}
log.Printf("[lan] unsubscribe success: if=%s, did=%s", d.IfName, d.DID)
d.manager.lgr.Infof("[lan] unsubscribe success: if=%s, did=%s", d.IfName, d.DID)
}, nil, 5000)
d.subscribed = false
d.manager.broadcastDeviceState(d.DID, map[string]interface{}{
@@ -373,7 +374,7 @@ func (d *LanDevice) OnDelete() {
d.unstableTimer.Stop()
d.unstableTimer = nil
}
log.Printf("[lan] device deleted: did=%s", d.DID)
d.manager.lgr.Infof("[lan] device deleted: did=%s", d.DID)
}
// UpdateInfo updates the device token if it has changed.
@@ -395,12 +396,12 @@ func (d *LanDevice) UpdateInfo(info map[string]interface{}) {
aesIV := md5Sum(append(aesKey[:], d.Token...))
block, err := aes.NewCipher(aesKey[:])
if err != nil {
log.Printf("[lan] update token cipher error: %v, did=%s", err, d.DID)
d.manager.lgr.Infof("[lan] update token cipher error: %v, did=%s", err, d.DID)
return
}
d.cipher = block
d.aesIV = aesIV[:]
log.Printf("[lan] token updated: did=%s", d.DID)
d.manager.lgr.Infof("[lan] token updated: did=%s", d.DID)
}
// ============================================================================
@@ -412,7 +413,7 @@ func (d *LanDevice) updateKeepAlive(state LanDeviceState) {
d.state = state
if d.state != LanDeviceFresh {
log.Printf("[lan] device status: did=%s, state=%s", d.DID, d.state)
d.manager.lgr.Infof("[lan] device status: did=%s, state=%s", d.DID, d.state)
}
// Cancel existing timer
@@ -440,11 +441,11 @@ func (d *LanDevice) updateKeepAlive(state LanDeviceState) {
})
// Fast ping
if d.IfName == "" {
log.Printf("[lan] if_name not set for device: did=%s", d.DID)
d.manager.lgr.Infof("[lan] if_name not set for device: did=%s", d.DID)
return
}
if d.IP == "" {
log.Printf("[lan] ip not set for device: did=%s", d.DID)
d.manager.lgr.Infof("[lan] ip not set for device: did=%s", d.DID)
return
}
d.manager.ping(d.IfName, d.IP)
@@ -463,7 +464,7 @@ func (d *LanDevice) nextKATimeout() float64 {
}
func (d *LanDevice) changeOnline(online bool) {
log.Printf("[lan] change online: did=%s, online=%v", d.DID, online)
d.manager.lgr.Infof("[lan] change online: did=%s, online=%v", d.DID, online)
tsNow := int(time.Now().Unix())
d.onlineHistory = append(d.onlineHistory, onlineEvent{ts: tsNow, online: online})
if len(d.onlineHistory) > networkUnstableCntTh {
@@ -482,9 +483,9 @@ func (d *LanDevice) changeOnline(online bool) {
float64(tsNow-d.onlineHistory[0].ts) > networkUnstableTimeTh {
d.setOnline(true)
} else {
log.Printf("[lan] unstable device detected: did=%s", d.DID)
d.manager.lgr.Infof("[lan] unstable device detected: did=%s", d.DID)
d.unstableTimer = time.AfterFunc(time.Duration(networkUnstableResumeTh*float64(time.Second)), func() {
log.Printf("[lan] unstable resume threshold passed: did=%s", d.DID)
d.manager.lgr.Infof("[lan] unstable resume threshold passed: did=%s", d.DID)
d.setOnline(true)
})
}
@@ -540,6 +541,8 @@ type MIoTLan struct {
// Profile models (loaded from YAML, models that should NOT use LAN control)
profileModels map[string]bool
lgr logger.Logger
}
type pendingRequest struct {
@@ -601,8 +604,10 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService,
lanStateSubs: make(map[string]func(bool)),
lanCtrlVotes: make(map[string]bool),
profileModels: make(map[string]bool),
lgr: logger.Default(),
}
for _, ifName := range netIFs {
m.netIFs[ifName] = true
}
@@ -615,7 +620,7 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService,
// Auto-init if no central hub gateway exists and we have interfaces
if len(mipsService.GetServices("")) == 0 && len(netIFs) > 0 {
log.Println("[lan] no central hub gateway service, scheduling init")
m.lgr.Infof("[lan] no central hub gateway service, scheduling init")
go func() {
m.Init()
}()
@@ -624,6 +629,9 @@ func NewMIoTLan(netIFs []string, network *MIoTNetwork, mipsService *MipsService,
return m, nil
}
// SetLogger sets a custom logger for MIoTLan.
func (m *MIoTLan) SetLogger(l logger.Logger) { m.lgr = l }
// VirtualDID returns the virtual device ID used for LAN communication.
func (m *MIoTLan) VirtualDID() string {
return m.virtualDID
@@ -648,12 +656,12 @@ func (m *MIoTLan) Init() {
m.mu.Lock()
if m.initDone {
m.mu.Unlock()
log.Println("[lan] already initialized")
m.lgr.Infof("[lan] already initialized")
return
}
if len(m.netIFs) == 0 {
m.mu.Unlock()
log.Println("[lan] no net_ifs configured")
m.lgr.Infof("[lan] no net_ifs configured")
return
}
@@ -667,13 +675,13 @@ func (m *MIoTLan) Init() {
}
if !hasVote {
m.mu.Unlock()
log.Println("[lan] no vote for lan ctrl")
m.lgr.Infof("[lan] no vote for lan ctrl")
return
}
if len(m.mipsService.GetServices("")) > 0 {
m.mu.Unlock()
log.Println("[lan] central hub gateway service exists, skip LAN init")
m.lgr.Infof("[lan] central hub gateway service exists, skip LAN init")
return
}
@@ -683,7 +691,7 @@ func (m *MIoTLan) Init() {
}
if len(m.availableIFs) == 0 {
m.mu.Unlock()
log.Println("[lan] no available network interfaces")
m.lgr.Infof("[lan] no available network interfaces")
return
}
@@ -697,7 +705,7 @@ func (m *MIoTLan) Init() {
}
if !hasValid {
m.mu.Unlock()
log.Println("[lan] no valid net_ifs matching configured list")
m.lgr.Infof("[lan] no valid net_ifs matching configured list")
return
}
@@ -721,7 +729,7 @@ func (m *MIoTLan) Init() {
go h(true)
}
log.Printf("[lan] initialized: netIFs=%v, available=%v", m.netIFs, m.availableIFs)
m.lgr.Infof("[lan] initialized: netIFs=%v, available=%v", m.netIFs, m.availableIFs)
}
// Deinit stops the LAN controller.
@@ -732,7 +740,7 @@ func (m *MIoTLan) Deinit() {
m.mu.Lock()
if !m.initDone {
m.mu.Unlock()
log.Println("[lan] not initialized")
m.lgr.Infof("[lan] not initialized")
return
}
m.initDone = false
@@ -765,7 +773,7 @@ func (m *MIoTLan) Deinit() {
go h(false)
}
log.Println("[lan] deinitialized")
m.lgr.Infof("[lan] deinitialized")
}
// ============================================================================
@@ -773,7 +781,7 @@ func (m *MIoTLan) Deinit() {
// ============================================================================
func (m *MIoTLan) internalLoop() {
log.Println("[lan] internal loop started")
m.lgr.Infof("[lan] internal loop started")
m.initSockets()
// Start scan timer
@@ -786,7 +794,7 @@ func (m *MIoTLan) internalLoop() {
if cmd == nil {
// Stop signal
m.deinitInternal()
log.Println("[lan] internal loop stopped")
m.lgr.Infof("[lan] internal loop stopped")
return
}
cmd()
@@ -858,13 +866,13 @@ func (m *MIoTLan) createSocketLocked(ifName string) {
ni := m.network.GetNetworkInfo()
info, ok := ni[ifName]
if !ok || info.IP == "" {
log.Printf("[lan] no IP for interface: %s", ifName)
m.lgr.Infof("[lan] no IP for interface: %s", ifName)
return
}
conn, err := createBoundSocket(info.IP, m.localPort)
if err != nil {
log.Printf("[lan] create socket error: if=%s, err=%v", ifName, err)
m.lgr.Infof("[lan] create socket error: if=%s, err=%v", ifName, err)
return
}
@@ -877,7 +885,7 @@ func (m *MIoTLan) createSocketLocked(ifName string) {
// Start reader goroutine for this socket
go m.socketReader(ifName, conn)
log.Printf("[lan] socket created: if=%s, ip=%s, port=%d", ifName, info.IP, m.localPort)
m.lgr.Infof("[lan] socket created: if=%s, ip=%s, port=%d", ifName, info.IP, m.localPort)
}
func (m *MIoTLan) destroySocketLocked(ifName string) {
@@ -887,7 +895,7 @@ func (m *MIoTLan) destroySocketLocked(ifName string) {
}
delete(m.socks, ifName)
conn.Close()
log.Printf("[lan] socket destroyed: if=%s", ifName)
m.lgr.Infof("[lan] socket destroyed: if=%s", ifName)
}
// createBoundSocket creates a UDP socket bound to a specific IP address.
@@ -936,7 +944,7 @@ func (m *MIoTLan) send(ifName, targetIP string, data []byte) {
} else {
conn, ok := m.socks[ifName]
if !ok {
log.Printf("[lan] invalid socket: if=%s", ifName)
m.lgr.Infof("[lan] invalid socket: if=%s", ifName)
return
}
conn.WriteToUDP(data, &net.UDPAddr{IP: net.ParseIP(targetIP), Port: otPort})
@@ -1110,7 +1118,7 @@ func (m *MIoTLan) handleRawMessage(data []byte, dataLen int, ip, ifName string)
if dataLen > otProbeLen {
msg, err := dev.DecryptPacket(data[:dataLen])
if err != nil {
log.Printf("[lan] decrypt error: did=%s, err=%v", did, err)
m.lgr.Infof("[lan] decrypt error: did=%s, err=%v", did, err)
return
}
m.handleMessage(did, msg)
@@ -1120,7 +1128,7 @@ func (m *MIoTLan) handleRawMessage(data []byte, dataLen int, ip, ifName string)
func (m *MIoTLan) handleMessage(did string, msg map[string]interface{}) {
msgID, ok := msg["id"]
if !ok {
log.Printf("[lan] message without id: did=%s, msg=%v", did, msg)
m.lgr.Infof("[lan] message without id: did=%s, msg=%v", did, msg)
return
}
id, ok := toInt(msgID)
@@ -1161,7 +1169,7 @@ func (m *MIoTLan) handleMessage(did string, msg map[string]interface{}) {
return
}
log.Printf("[lan] message: did=%s, method=%s", did, method)
m.lgr.Infof("[lan] message: did=%s, method=%s", did, method)
switch method {
case "properties_changed":
@@ -1256,7 +1264,7 @@ func (m *MIoTLan) scanDevices() {
scanTime := m.nextScanTime()
m.scanTimer = time.AfterFunc(time.Duration(scanTime*float64(time.Second)), m.scanDevices)
log.Printf("[lan] next scan in %.0fs", scanTime)
m.lgr.Infof("[lan] next scan in %.0fs", scanTime)
}
func (m *MIoTLan) nextScanTime() float64 {
@@ -1272,7 +1280,7 @@ func (m *MIoTLan) nextScanTime() float64 {
// ============================================================================
func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo) {
log.Printf("[lan] network info change: status=%s, if=%s", status, info.Name)
m.lgr.Infof("[lan] network info change: status=%s, if=%s", status, info.Name)
m.mu.Lock()
// Rebuild available IFs
@@ -1296,7 +1304,7 @@ func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo)
}
if !valid {
m.mu.Unlock()
log.Println("[lan] no valid net_ifs after change")
m.lgr.Infof("[lan] no valid net_ifs after change")
go m.Deinit()
return
}
@@ -1332,12 +1340,12 @@ func (m *MIoTLan) onNetworkInfoChange(status InterfaceStatus, info *NetworkInfo)
// ============================================================================
func (m *MIoTLan) onMipsServiceChange(groupID string, state MipsServiceState, data map[string]interface{}) {
log.Printf("[lan] mips service change: groupID=%s, state=%s, data=%v", groupID, state, data)
m.lgr.Infof("[lan] mips service change: groupID=%s, state=%s, data=%v", groupID, state, data)
if len(m.mipsService.GetServices("")) > 0 {
log.Println("[lan] central hub gateway found, deinit LAN")
m.lgr.Infof("[lan] central hub gateway found, deinit LAN")
go m.Deinit()
} else {
log.Println("[lan] no central hub gateway, init LAN")
m.lgr.Infof("[lan] no central hub gateway, init LAN")
go m.Init()
}
}
@@ -1348,7 +1356,7 @@ func (m *MIoTLan) onMipsServiceChange(groupID string, state MipsServiceState, da
// VoteForLanCtrl votes for or against LAN control.
func (m *MIoTLan) VoteForLanCtrl(key string, vote bool) {
log.Printf("[lan] vote for lan ctrl: key=%s, vote=%v", key, vote)
m.lgr.Infof("[lan] vote for lan ctrl: key=%s, vote=%v", key, vote)
m.mu.Lock()
m.lanCtrlVotes[key] = vote
hasVote := false
@@ -1369,7 +1377,7 @@ func (m *MIoTLan) VoteForLanCtrl(key string, vote bool) {
// UpdateSubscribeOption enables or disables device subscriptions.
func (m *MIoTLan) UpdateSubscribeOption(enable bool) {
log.Printf("[lan] update subscribe option: %v", enable)
m.lgr.Infof("[lan] update subscribe option: %v", enable)
m.mu.Lock()
oldEnable := m.enableSub
m.enableSub = enable
@@ -1398,12 +1406,12 @@ func (m *MIoTLan) UpdateDevices(devices map[string]map[string]interface{}) {
for did, info := range devices {
if !isNumeric(did) {
log.Printf("[lan] invalid did (non-numeric): %s", did)
m.lgr.Infof("[lan] invalid did (non-numeric): %s", did)
continue
}
model, _ := info["model"].(string)
if model != "" && m.profileModels[model] {
log.Printf("[lan] model not supported for LAN ctrl: did=%s, model=%s", did, model)
m.lgr.Infof("[lan] model not supported for LAN ctrl: did=%s, model=%s", did, model)
continue
}
if existing, ok := m.devices[did]; ok {
@@ -1411,13 +1419,13 @@ func (m *MIoTLan) UpdateDevices(devices map[string]map[string]interface{}) {
} else {
tokenStr, _ := info["token"].(string)
if len(tokenStr) != 32 {
log.Printf("[lan] invalid token for device: did=%s", did)
m.lgr.Infof("[lan] invalid token for device: did=%s", did)
continue
}
ip, _ := info["ip"].(string)
dev, err := newLanDevice(m, did, tokenStr, ip)
if err != nil {
log.Printf("[lan] create device error: did=%s, err=%v", did, err)
m.lgr.Infof("[lan] create device error: did=%s, err=%v", did, err)
continue
}
m.devices[did] = dev
@@ -1483,7 +1491,7 @@ func (m *MIoTLan) SubProp(did string, handler func(map[string]interface{}, inter
}
m.cmdCh <- func() {
m.matcher.Sub(key, handler, ctx)
log.Printf("[lan] sub prop: key=%s", key)
m.lgr.Infof("[lan] sub prop: key=%s", key)
}
}
@@ -1495,7 +1503,7 @@ func (m *MIoTLan) UnsubProp(did string, siid, piid int) {
}
m.cmdCh <- func() {
m.matcher.Unsub(key)
log.Printf("[lan] unsub prop: key=%s", key)
m.lgr.Infof("[lan] unsub prop: key=%s", key)
}
}
@@ -1507,7 +1515,7 @@ func (m *MIoTLan) SubEvent(did string, handler func(map[string]interface{}, inte
}
m.cmdCh <- func() {
m.matcher.Sub(key, handler, ctx)
log.Printf("[lan] sub event: key=%s", key)
m.lgr.Infof("[lan] sub event: key=%s", key)
}
}
@@ -1519,7 +1527,7 @@ func (m *MIoTLan) UnsubEvent(did string, siid, eiid int) {
}
m.cmdCh <- func() {
m.matcher.Unsub(key)
log.Printf("[lan] unsub event: key=%s", key)
m.lgr.Infof("[lan] unsub event: key=%s", key)
}
}