# mijia-go-api `mijia-go-api` 是一个纯 Go 米家 API 库,支持二维码登录与 token 刷新、家庭/设备/场景/耗材查询、属性读写、action 执行、统计查询,以及基于 MIoT 设备描述的高级 `Device` 操作。 本项目只提供 Go 库,不提供 CLI、MCP、skills 或 HAR decrypt 功能。 ## 安装 ```bash go get git.misaka.ren/m1saka/mijia-go-api ``` ## 登录 `NewClient` 的认证路径传空字符串时,默认使用 `~/.config/mijia-api/auth.json`。`Login` 会优先尝试刷新已有 token;需要重新登录时,会在终端输出二维码供米家 App 扫描。 ```go package main import ( "context" "log" mijia "git.misaka.ren/m1saka/mijia-go-api" ) func main() { client, err := mijia.NewClient("") if err != nil { log.Fatal(err) } if _, err := client.Login(context.Background()); err != nil { log.Fatal(err) } auth := client.AuthData() log.Printf("已登录 userId=%s", auth.UserID) } ``` `auth.json` 包含 `serviceToken`、`passToken`、`ssecurity` 等敏感认证数据。库写入该文件时使用 `0600` 权限;请保持此权限,并且不要将该文件提交到版本库。 Web/GUI 应用可通过 `WithQRWriter` 接收登录输出,并在阻塞的 `Login` 等待期间实时展示给用户: ```go import ( "bufio" "context" "io" mijia "git.misaka.ren/m1saka/mijia-go-api" ) func streamLogin(ctx context.Context, renderLoginLine func(string)) error { reader, writer := io.Pipe() defer reader.Close() client, err := mijia.NewClient("", mijia.WithQRWriter(writer)) if err != nil { writer.Close() return err } loginDone := make(chan error, 1) go func() { _, err := client.Login(ctx) writer.CloseWithError(err) loginDone <- err }() scanner := bufio.NewScanner(reader) for scanner.Scan() { renderLoginLine(scanner.Text()) } loginErr := <-loginDone if loginErr != nil { return loginErr } return scanner.Err() } ``` `Login` 会阻塞等待扫码,因此必须同步消费 Writer 输出。Writer 可能由另一个 goroutine 写入,不能无同步地并发读写 `bytes.Buffer`。Writer 接收的内容包含登录二维码 URL,属于敏感登录信息;调用方不得将其写入日志、监控事件或其他持久化记录。 ## 底层 API 以下示例展示设备、属性和 action 的直接调用。`GetDevices` 的 `homeID` 传空字符串时查询所有家庭。 ```go devices, err := client.GetDevices(ctx, "") if err != nil { return err } properties, err := client.GetProperties(ctx, []mijia.PropertyRequest{ {DID: devices[0].DID, SIID: 2, PIID: 1}, }) if err != nil { return err } _, err = client.SetProperties(ctx, []mijia.PropertySetRequest{ {DID: devices[0].DID, SIID: 2, PIID: 1, Value: true}, }) if err != nil { return err } _, err = client.RunActions(ctx, []mijia.ActionRequest{ {DID: devices[0].DID, SIID: 2, AIID: 1}, }) ``` 家庭、场景、耗材和统计分别使用 `GetHomes`、`GetScenes`、`RunScene`、`GetConsumables` 和 `GetStatistics`。 ## 高级 Device `NewDevice` 可通过 `DeviceSelector.DID` 或 `DeviceSelector.Name` 选择设备;名称匹配到多个设备时会返回 `MultipleDevicesFoundError`。通过文件认证的 `NewClient` 默认把设备描述缓存到认证文件所在目录;`NewClientWithAuthData` 默认不启用缓存,调用方需要在创建 `Device` 时传入 `WithDeviceCacheDir` 才会缓存。每次成功的 `Get`、`Set`、`RunAction` 后默认等待 `500ms`。 ```go device, err := mijia.NewDevice(ctx, client, mijia.DeviceSelector{DID: "设备 DID"}) // 也可以按名称选择:mijia.DeviceSelector{Name: "客厅灯"} if err != nil { return err } value, err := device.Get(ctx, "on") if err != nil { return err } if err := device.Set(ctx, "on", true); err != nil { return err } _, err = device.RunAction(ctx, "toggle", nil) _ = value ``` 执行 action 前可通过 `device.Actions()["toggle"].Inputs` 检查可信 MIoT 描述中的参数类型、范围和值列表。 可通过实际导出的 `DeviceOption` 调整行为: ```go device, err := mijia.NewDevice( ctx, client, mijia.DeviceSelector{Name: "客厅灯"}, mijia.WithDeviceDelay(0), // 使用 NewClientWithAuthData 时,显式指定目录才能缓存设备描述。 mijia.WithDeviceCacheDir("./miot-cache"), mijia.WithDeviceHTTPClient(http.DefaultClient), ) ``` ## 超时与错误 所有网络和设备操作都接收 `context.Context`,建议设置超时: ```go ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() devices, err := client.GetDevices(ctx, "") ``` API 错误和高级设备错误可使用 `errors.As` 判断: ```go var apiErr *mijia.APIError var deviceErr *mijia.DeviceGetError switch { case errors.As(err, &apiErr): log.Printf("API 错误: code=%d message=%s", apiErr.Code, apiErr.Message) case errors.As(err, &deviceErr): log.Printf("设备读取错误: device=%s property=%s code=%d", deviceErr.DeviceName, deviceErr.Name, deviceErr.Code) } ``` Token 刷新失败且必须重新扫码授权时,可使用 `errors.Is` 稳定判断,同时仍可通过 `errors.As` 获取 `LoginError` 详情: ```go if errors.Is(err, mijia.ErrReauthenticationRequired) { log.Print("认证已失效,请重新扫码授权") } ``` 写属性和执行 action 时,对应错误类型为 `DeviceSetError` 和 `DeviceActionError`;设备选择还可能返回 `DeviceNotFoundError` 或 `MultipleDevicesFoundError`。