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
+64 -51
View File
@@ -6,7 +6,6 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"log"
"math"
"math/rand"
"strings"
@@ -14,6 +13,8 @@ import (
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"xiaomihome/logger"
)
// ============================================================================
@@ -21,14 +22,14 @@ import (
// ============================================================================
const (
mipsQoS = 2
mipsReconnectIntervalMin = 10.0 // seconds
mipsReconnectIntervalMax = 600.0 // seconds
mipsSubPatch = 300
mipsSubInterval = 1 // second
mipsMQTTInterval = 1 // second
uint32max = 0xFFFFFFFF
MipsRequestTimeoutDefault = 10000 // milliseconds
mipsQoS = 2
mipsReconnectIntervalMin = 10.0 // seconds
mipsReconnectIntervalMax = 600.0 // seconds
mipsSubPatch = 300
mipsSubInterval = 1 // second
mipsMQTTInterval = 1 // second
uint32max = 0xFFFFFFFF
MipsRequestTimeoutDefault = 10000 // milliseconds
)
// ============================================================================
@@ -217,16 +218,16 @@ type MipsBroadcast struct {
// mipsClient is the concrete MIPS client using paho MQTT.
type mipsClient struct {
mu sync.Mutex
logger *log.Logger
clientID string
host string
port int
username string
password string
caFile string
certFile string
keyFile string
mu sync.Mutex
lgr logger.Logger
clientID string
host string
port int
username string
password string
caFile string
certFile string
keyFile string
// MQTT connection
mqttConn MQTTClient // interface to paho MQTT
@@ -244,9 +245,9 @@ type mipsClient struct {
subscriptions map[string]*MipsBroadcast
// Requests
requestMu sync.Mutex
requests map[string]*MipsRequest // mid as string -> request
seedID int
requestMu sync.Mutex
requests map[string]*MipsRequest // mid as string -> request
seedID int
// State subscribers
stateSubMu sync.Mutex
@@ -276,26 +277,31 @@ type MQTTClient interface {
// newMipsClient creates a new mipsClient (internal).
func newMipsClient(clientID, host string, port int, username, password, caFile, certFile, keyFile string) *mipsClient {
return &mipsClient{
clientID: clientID,
host: host,
port: port,
username: username,
password: password,
caFile: caFile,
certFile: certFile,
keyFile: keyFile,
seedID: rand.Intn(uint32max),
pendingSubs: make(map[string]int),
subscriptions: make(map[string]*MipsBroadcast),
requests: make(map[string]*MipsRequest),
stateSubs: make(map[string]func(key string, connected bool)),
c := &mipsClient{
clientID: clientID,
host: host,
port: port,
username: username,
password: password,
caFile: caFile,
certFile: certFile,
keyFile: keyFile,
lgr: logger.Default(),
seedID: rand.Intn(uint32max),
pendingSubs: make(map[string]int),
subscriptions: make(map[string]*MipsBroadcast),
requests: make(map[string]*MipsRequest),
stateSubs: make(map[string]func(key string, connected bool)),
reconnectActive: true,
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
return c
}
// SetLogger sets a custom logger for mipsClient.
func (c *mipsClient) SetLogger(l logger.Logger) { c.lgr = l }
// connect starts the MQTT connection.
func (c *mipsClient) connect(mqttClient MQTTClient) error {
c.mu.Lock()
@@ -363,7 +369,7 @@ func (c *mipsClient) subscribe(topic string, handler func(topic string, payload
defer c.subMu.Unlock()
if c.subscriptions[topic] != nil {
log.Printf("[mips] re-register subscription: %s", topic)
c.lgr.Debugf("[mips] re-register subscription: %s", topic)
}
c.subscriptions[topic] = &MipsBroadcast{
Topic: topic,
@@ -388,7 +394,7 @@ func (c *mipsClient) unsubscribe(topic string) error {
if c.mqttConn != nil && c.mqttConn.IsConnected() {
if err := c.mqttConn.Unsubscribe(topic); err != nil {
log.Printf("[mips] unsubscribe error %s: %v", topic, err)
c.lgr.Debugf("[mips] unsubscribe error %s: %v", topic, err)
}
}
return nil
@@ -530,7 +536,7 @@ func (c *mipsClient) flushPendingSubs() {
}
if count > 3 {
delete(c.pendingSubs, topic)
log.Printf("[mips] retry sub exceeded: %s", topic)
c.lgr.Debugf("[mips] retry sub exceeded: %s", topic)
continue
}
subbed++
@@ -539,10 +545,10 @@ func (c *mipsClient) flushPendingSubs() {
c.onMQTTMessage(t, p)
}); err == nil {
delete(c.pendingSubs, topic)
log.Printf("[mips] sub success: %s", topic)
c.lgr.Debugf("[mips] sub success: %s", topic)
} else {
c.pendingSubs[topic] = count + 1
log.Printf("[mips] retry sub %d: %s %v", count, topic, err)
c.lgr.Debugf("[mips] retry sub %d: %s %v", count, topic, err)
}
}
@@ -647,13 +653,13 @@ func (c *mipsClient) tryReconnect(immediate bool) {
c.reconnectInterval = math.Min(c.reconnectInterval*2, mipsReconnectIntervalMax)
}
interval = time.Duration(c.reconnectInterval) * time.Second
log.Printf("[mips] reconnect after %v", interval)
c.lgr.Debugf("[mips] reconnect after %v", interval)
}
c.reconnectTimer = time.AfterFunc(interval, func() {
if c.mqttConn != nil {
if err := c.mqttConn.Connect(); err != nil {
log.Printf("[mips] reconnect failed: %v", err)
c.lgr.Debugf("[mips] reconnect failed: %v", err)
c.tryReconnect(false)
}
}
@@ -937,6 +943,8 @@ type pahoMQTTClient struct {
certFile string
keyFile string
lgr logger.Logger
onConnect func()
onDisconnect func()
onMessage func(topic string, payload []byte)
@@ -946,7 +954,7 @@ type pahoMQTTClient struct {
}
func newPahoMQTTClient(clientID, host string, port int, username, password, caFile, certFile, keyFile string) *pahoMQTTClient {
return &pahoMQTTClient{
c := &pahoMQTTClient{
clientID: clientID,
host: host,
port: port,
@@ -955,9 +963,14 @@ func newPahoMQTTClient(clientID, host string, port int, username, password, caFi
caFile: caFile,
certFile: certFile,
keyFile: keyFile,
lgr: logger.Default(),
}
return c
}
// SetLogger sets a custom logger for pahoMQTTClient.
func (p *pahoMQTTClient) SetLogger(l logger.Logger) { p.lgr = l }
func (p *pahoMQTTClient) Connect() error {
opts := mqtt.NewClientOptions()
opts.AddBroker(fmt.Sprintf("ssl://%s:%d", p.host, p.port))
@@ -969,7 +982,7 @@ func (p *pahoMQTTClient) Connect() error {
opts.SetCleanSession(true)
opts.OnConnect = func(c mqtt.Client) {
log.Printf("[mips] MQTT connected to %s:%d", p.host, p.port)
p.lgr.Debugf("[mips] MQTT connected to %s:%d", p.host, p.port)
p.connected = true
if p.onConnect != nil {
p.onConnect()
@@ -977,7 +990,7 @@ func (p *pahoMQTTClient) Connect() error {
}
opts.OnConnectionLost = func(c mqtt.Client, err error) {
log.Printf("[mips] MQTT connection lost: %v", err)
p.lgr.Debugf("[mips] MQTT connection lost: %v", err)
p.connected = false
if p.onDisconnect != nil {
p.onDisconnect()
@@ -985,7 +998,7 @@ func (p *pahoMQTTClient) Connect() error {
}
opts.DefaultPublishHandler = func(c mqtt.Client, msg mqtt.Message) {
log.Printf("[mips] MQTT message: %s (%d bytes)", msg.Topic(), len(msg.Payload()))
p.lgr.Debugf("[mips] MQTT message: %s (%d bytes) data:%s", msg.Topic(), len(msg.Payload()), string(msg.Payload()))
if p.onMessage != nil {
p.onMessage(msg.Topic(), msg.Payload())
}
@@ -997,7 +1010,7 @@ func (p *pahoMQTTClient) Connect() error {
return token.Error()
}
log.Printf("[mips] MQTT connected to %s:%d as %s", p.host, p.port, p.clientID)
p.lgr.Debugf("[mips] MQTT connected to %s:%d as %s", p.host, p.port, p.clientID)
return nil
}
@@ -1038,7 +1051,7 @@ func (p *pahoMQTTClient) Unsubscribe(topic string) error {
if token.Wait() && token.Error() != nil {
return token.Error()
}
log.Printf("[mips] unsub success: %s", topic)
p.lgr.Debugf("[mips] unsub success: %s", topic)
return nil
}