# xiaomi 客户端重构方案 > 状态:Proposed | 版本:v3.0 | 日期:2026-06-29 | 参照:miot/examples/full_client --- ## 一、Config 字段说明 | 字段 | 含义 | 来源 | |------|------|------| | `ClientID` | OAuth Client ID,默认 `"2882303761520251711"`(小米官方 HA 客户端) | 小米开放平台,可留空走默认值 | | `ClientSecret` | OAuth Client Secret,用于 token 交换 | 小米开放平台申请 | | `AccessToken` | OAuth 授权后获得的 token,云端 API 鉴权 | OAuth 流程获取(AuthURL → ExchangeCode) | --- ## 二、目标形态 ```go // 零 miot import,1 个对象 client, err := xiaomi.NewClient(ctx, xiaomi.Config{ ClientID: "", // 空则默认 "2882303761520251711" ClientSecret: "your-secret", AccessToken: "your-access-token", }) if err != nil { log.Fatal(err) } defer client.Close() user, _ := client.GetUserInfo(ctx) ``` --- ## 三、参照源码 `miot/examples/full_client/main.go`(已验证可运行)的初始化流程: ```go // 1. entryData 只放 access_token entryData := cfg.EntryData() // → {"access_token": "xxx"} // 2. 创建 MIoTClient client := miot.NewMIoTClient("demo", entryData, cfg.UID, cfg.CloudServer, cfg.GetCtrlMode()) // 3. 设置组件(必须在 Init 之前) client.SetStorage(miot.NewMIoTStorage("./data")) client.SetHTTPClient(miot.NewMIoTHttpClient(cfg.CloudServer, miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)) client.SetMipsCloud(miot.NewMipsCloudClient("ssl://cn-ha.mqtt.io.mi.com:8883", "ha."+cfg.UUID, ...)) // 4. Init(一次性完成:OAuth 客户端、证书、HTTP、MIPS 云、mDNS、LAN) client.Init() // 5. 业务操作 client.RefreshDevices() client.DeviceList() client.SubDeviceState(...) client.SubProp(...) // 6. 清理 client.Deinit() ``` **关键要点**: - entryData 只需 `access_token` 一个字段 - `SetStorage` / `SetHTTPClient` / `SetMipsCloud` 必须在 Init 之前调用 - Init 内部会跳过已设置的组件(`if c.http == nil { ... }`) - 不需要两次 Init,一次足够 --- ## 四、逐文件修改 ### 4.1 新建 `xiaomi/config.go` ```go package xiaomi // Config 客户端初始化配置 type Config struct { // ClientID OAuth Client ID,空则默认 "2882303761520251711"(小米 HA 客户端) ClientID string // ClientSecret OAuth Client Secret ClientSecret string // AccessToken OAuth 授权后获取的 access token AccessToken string } ``` ### 4.2 重写 `xiaomi/client.go` **删除**: ```go func NewClient(inner *miot.MIoTClient, httpClient *miot.MIoTHttpClient) *Client func (c *Client) Inner() *miot.MIoTClient func (c *Client) HTTPClient() *miot.MIoTHttpClient ``` **新增**(完全对齐 full_client 示例的初始化流程): ```go import "context" // NewClient 创建并初始化客户端。 // // 内部流程(对齐 miot/examples/full_client): // 1. 构建 entryData(仅 access_token) // 2. 创建 MIoTClient // 3. SetStorage → SetHTTPClient → SetMipsCloud(必须在 Init 前) // 4. Init(一次性完成所有子组件初始化) func NewClient(ctx context.Context, cfg Config) (*Client, error) { clientID := cfg.ClientID if clientID == "" { clientID = miot.OAUTH2_CLIENT_ID // 默认小米 HA 客户端 } // 构造 entryData(仅 access_token,对齐 full_client 的 EntryData()) entryData := map[string]interface{}{ "access_token": cfg.AccessToken, } // 创建 MIoTClient(uid 空、CloudServer=cn、CtrlMode=auto) miotClient := miot.NewMIoTClient("xiaomi_sdk", entryData, "", "cn", miot.CtrlModeAuto) // 设置存储(对齐 full_client line 36-37) miotClient.SetStorage(miot.NewMIoTStorage("./data")) // 设置 HTTP 客户端(对齐 full_client line 41-46) httpClient, err := miot.NewMIoTHttpClient("cn", clientID, cfg.AccessToken) if err != nil { return nil, fmt.Errorf("xiaomi: http: %w", err) } miotClient.SetHTTPClient(httpClient) // 设置 MIPS 云端客户端(对齐 full_client line 50-57) mipsCloud := miot.NewMipsCloudClient( "ssl://cn-ha.mqtt.io.mi.com:8883", "ha."+uuid.New().String(), clientID, cfg.AccessToken, ) miotClient.SetMipsCloud(mipsCloud) // 一次性 Init(对齐 full_client line 60) if err := miotClient.Init(); err != nil { return nil, fmt.Errorf("xiaomi: init: %w", err) } return &Client{ cfg: cfg, inner: miotClient, http: httpClient, devices: make(map[string]*miot.MIoTDevice), propSubs: make(map[string]string), eventSubs: make(map[string]string), }, nil } // Close 关闭客户端(对齐 full_client line 125: client.Deinit()) func (c *Client) Close() error { if c.inner != nil { return c.inner.Deinit() } return nil } ``` **Client struct 新增 cfg 字段**: ```go type Client struct { cfg Config // ← 新增 inner *miot.MIoTClient http *miot.MIoTHttpClient deviceMu sync.RWMutex devices map[string]*miot.MIoTDevice propSubs map[string]string eventSubs map[string]string mu sync.RWMutex } ``` ### 4.3 新建 `xiaomi/oauth.go` ```go package xiaomi import "context" // AuthURL 生成小米 OAuth 授权页面 URL func AuthURL(cfg Config) string // ExchangeCode 用 authorization code 交换 access token func ExchangeCode(ctx context.Context, cfg Config, code string) (*TokenResult, error) // TokenResult OAuth token 交换结果 type TokenResult struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` UID string `json:"uid"` } ``` ### 4.4 重写 `xiaomi/examples/main.go` ```go package main import ( "context" "log" "time" "xiaomihome/xiaomi" "xiaomihome/xiaomi/examples/config" ) func main() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cfg, _ := config.LoadFromEnv() client, err := xiaomi.NewClient(ctx, xiaomi.Config{ ClientSecret: cfg.ClientSecret, AccessToken: cfg.AccessToken, }) if err != nil { log.Fatal(err) } defer client.Close() user, _ := client.GetUserInfo(ctx) log.Printf("用户: %s (UID: %s)", user.NickName, user.UID) } ``` --- ## 五、不动的文件(15 个) `properties.go` `actions.go` `subscribe.go` `user.go` `homes.go` `devices.go` `types.go` `errors.go` `convert.go` + `devices/` 下全部 9 个文件 --- ## 六、附带清理 ### 6.1 `xiaomi/examples/config/config.go` — 去掉无用的 CountryCode / CloudServer ```go // 修改前 type Config struct { CountryCode string // ← 无用 CloudServer string // ← 硬编码 "cn",无需暴露 ClientID string ClientSecret string AccessToken string } func (c *Config) LoadFromEnv() { cfg.CountryCode = getEnv("XIAOMI_COUNTRY_CODE", "86") // ← 删 cfg.CloudServer = getEnv("XIAOMI_CLOUD_SERVER", "cn") // ← 删 } // 修改后 type Config struct { ClientID string ClientSecret string AccessToken string } ``` ### 6.2 `miot/miot_client.go` — `NewMIoTClient` 中去掉无用的 `country_code` 已验证 Go 代码和 Python 代码中均无任何地方读取 entryData 的 `country_code`,一并移除。 --- ## 七、实施步骤 1. 新建 `xiaomi/config.go` 2. 重写 `xiaomi/client.go`(旧 NewClient 改名内部保留) 3. 新建 `xiaomi/oauth.go` 4. 清理 `xiaomi/examples/config/config.go`(去 CountryCode / CloudServer) 5. 重写 `xiaomi/examples/main.go` 6. 清理 `miot/miot_client.go` 中无用的 `country_code` 7. 更新 `xiaomi/migration/ARCH_PLAN.md` 4.2 节 8. `go vet ./...` 编译验证