feat: 初始化小米 IoT (MIoT) 智能家居 Go 库

- 实现 MIoT 客户端核心功能(MQTT 连接、设备管理、属性读写)
- 支持云端 API 调用与局域网设备发现(mDNS)
- 集成国际化(i18n)多语言支持
- 添加 MIoT 设备规约解析器(spec_parser)
- 包含单元测试与使用示例
This commit is contained in:
2026-06-28 22:05:49 +08:00
parent ac442812b4
commit 3a44cb9e6a
118 changed files with 31630 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
// E2 — Cloud device management example.
// Demonstrates: get user info → home list → device list → read/write prop → do action.
package main
import (
"fmt"
"os"
"miot"
"miot/examples/config"
)
func main() {
// 1. Load config
cfg, err := config.LoadConfig(config.DefaultPath())
if err != nil {
fmt.Printf("Load config error: %v\n", err)
return
}
if cfg.AuthInfo.AccessToken == "" {
fmt.Println("请先运行 E1 cloud_oauth 获取 token")
os.Exit(1)
}
// 2. Create HTTP client
http, err := miot.NewMIoTHttpClient(cfg.CloudServer, miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)
if err != nil {
fmt.Printf("Create HTTP client error: %v\n", err)
return
}
defer http.Close()
// 3. Get user info
user, err := http.GetUserInfo()
if err != nil {
fmt.Printf("GetUserInfo error: %v\n", err)
} else {
fmt.Println("=== 用户信息 ===")
printMap(user)
}
// 4. Get home list
homes, err := http.GetHomeInfos()
if err != nil {
fmt.Printf("GetHomeInfos error: %v\n", err)
return
}
fmt.Println("\n=== 家庭列表 ===")
if uid, ok := homes["uid"].(string); ok {
fmt.Printf("UID: %s\n", uid)
}
if homeList, ok := homes["home_list"].(map[string]interface{}); ok {
for hid, hi := range homeList {
if h, ok := hi.(map[string]interface{}); ok {
fmt.Printf(" Home: %s, Name: %v\n", hid, h["home_name"])
}
}
}
// 5. Get device list
devices, err := http.GetDevices(nil)
if err != nil {
fmt.Printf("GetDevices error: %v\n", err)
return
}
devMap, _ := devices["devices"].(map[string]map[string]interface{})
if len(devMap) == 0 {
fmt.Println("没有找到设备")
return
}
fmt.Println("\n=== 设备列表 ===")
var firstDID string
for did, d := range devMap {
fmt.Printf(" DID: %s, Name: %v, Model: %v, Online: %v\n",
did, d["name"], d["model"], d["online"])
if firstDID == "" {
firstDID = did
}
}
// 6. Read a property (use hardcoded siid=2, piid=1 — power for many devices)
siid := 2
piid := 1
fmt.Printf("\n=== 读取属性 (did=%s, siid=%d, piid=%d) ===\n", firstDID, siid, piid)
prop, err := http.GetProp(firstDID, siid, piid)
if err != nil {
fmt.Printf("GetProp error: %v\n", err)
} else {
fmt.Printf("属性值: %v\n", prop["value"])
}
// 7. Set a property (toggle power: 0→1)
fmt.Printf("\n=== 设置属性 (did=%s, siid=%d, piid=%d, value=false) ===\n", firstDID, siid, piid)
setResult, err := http.SetProp([]miot.PropParam{
{DID: firstDID, Siid: siid, Piid: piid, Value: false},
})
if err != nil {
fmt.Printf("SetProp error: %v\n", err)
} else {
for _, r := range setResult {
fmt.Printf(" DID: %s, Code: %d, Value: %v\n", r.DID, r.Code, r.Value)
}
}
// 8. Do an action (toggle: siid=2, aiid=1)
aiid := 1
fmt.Printf("\n=== 执行动作 (did=%s, siid=%d, aiid=%d) ===\n", firstDID, siid, aiid)
actionResult, err := http.DoAction(firstDID, siid, aiid, []interface{}{
map[string]interface{}{"value": true},
})
if err != nil {
fmt.Printf("DoAction error: %v\n", err)
} else {
fmt.Printf("动作结果: %v\n", actionResult)
}
}
func printMap(m map[string]interface{}) {
for k, v := range m {
fmt.Printf(" %s: %v\n", k, v)
}
}
+131
View File
@@ -0,0 +1,131 @@
// E1 — OAuth authorization flow example.
// Demonstrates: generate auth URL → get code → exchange for token → refresh token.
package main
import (
"encoding/json"
"fmt"
"miot"
"miot/examples/config"
)
func main() {
// 1. Load config (or use defaults)
cfg, err := config.LoadConfig(config.DefaultPath())
if err != nil {
fmt.Printf("Load config error: %v\n", err)
return
}
// Ensure UUID is set (may be empty from old-format config)
if cfg.UUID == "" {
cfg.UUID = config.NewUUID()
}
// 2. Create OAuth client (Go constructor appends "ha." internally)
oauth := miot.NewMIoTOauthClient(
miot.OAUTH2_CLIENT_ID,
cfg.RedirectURL,
cfg.CloudServer,
cfg.UUID,
)
// 3. Generate authorization URL
authURL := oauth.GenAuthURL("", "", nil, false)
fmt.Println("=== OAuth 授权 ===")
fmt.Println("请打开以下URL完成授权:")
fmt.Println(authURL)
// 4. Read authorization code from stdin
fmt.Print("\n输入回调URL中的 code: ")
var code string
fmt.Scanln(&code)
if code == "" {
fmt.Println("未输入 code,退出")
return
}
// 5. Exchange code for access token
result, err := oauth.GetAccessToken(code)
if err != nil {
fmt.Printf("获取 token 失败: %v\n", err)
return
}
// 6. Save tokens to config (aligns with Python auth_info structure)
cfg.AuthInfo = config.AuthInfo{
AccessToken: safeStr(result, "access_token"),
RefreshToken: safeStr(result, "refresh_token"),
MacKey: safeStr(result, "mac_key"),
ExpiresIn: safeInt(result, "expires_in"),
ExpiresTS: config.CalcExpiresTS(safeInt(result, "expires_in")),
}
// 7. Get UID from home list (uid is in GetHomeInfos response, not token response)
http, err := miot.NewMIoTHttpClient(cfg.CloudServer, miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)
if err == nil {
defer http.Close()
if homes, e := http.GetHomeInfos(); e == nil {
cfg.UID = safeStr(homes, "uid")
fmt.Printf("UID: %s\n", cfg.UID)
}
}
if err := config.SaveConfig(config.DefaultPath(), cfg); err != nil {
fmt.Printf("保存 config 失败: %v\n", err)
} else {
fmt.Println("\n=== Token 已保存 ===")
t := cfg.AuthInfo.AccessToken
fmt.Printf("access_token: %s...\n", t[:min(20, len(t))])
t = cfg.AuthInfo.RefreshToken
fmt.Printf("refresh_token: %s...\n", t[:min(20, len(t))])
fmt.Printf("uid: %s\n", cfg.UID)
fmt.Printf("uuid: %s\n", cfg.UUID)
}
// 8. Demonstrate token refresh
fmt.Println("\n=== 刷新 Token ===")
newResult, err := oauth.RefreshAccessToken(cfg.AuthInfo.RefreshToken)
if err != nil {
fmt.Printf("刷新 token 失败: %v\n", err)
return
}
cfg.AuthInfo.AccessToken = safeStr(newResult, "access_token")
cfg.AuthInfo.RefreshToken = safeStr(newResult, "refresh_token")
cfg.AuthInfo.ExpiresTS = config.CalcExpiresTS(safeInt(newResult, "expires_in"))
config.SaveConfig(config.DefaultPath(), cfg)
fmt.Println("Token 已刷新并保存")
}
func safeStr(m map[string]interface{}, key string) string {
if v, ok := m[key]; ok {
if s, ok := v.(string); ok {
return s
}
switch n := v.(type) {
case float64:
return fmt.Sprintf("%.0f", n)
case json.Number:
return n.String()
default:
return fmt.Sprintf("%v", v)
}
}
return ""
}
func safeInt(m map[string]interface{}, key string) int {
if v, ok := m[key]; ok {
switch n := v.(type) {
case float64:
return int(n)
case int:
return n
case json.Number:
i, _ := n.Int64()
return int(i)
}
}
return 0
}
+17
View File
@@ -0,0 +1,17 @@
{
"auth_info": {
"access_token": "V3_bmQhfIrkI-AxA2Tic4l7-t6bDURwgsZnwqTGLFGwWpk94q_8eUqkx0GONdP31teHuKekfTH9SzYnFQk1aShA6_t6peafUG11897cV8wPgPnUh1aww_PvLaRLagRGyNMjj01gqWp3_jf10D4wLctL8IPI8ZkGEA_n9HgKyxH-aMKuJf9BLbicmogQHBru2bVZ",
"refresh_token": "R3_dRDvElS486JEgbmmTOuhzLvGFMM89Chalyb9Q8p-S88EGwzC8SFdMfbNGTLrmggkJ-EPkJrNywQBWMpLGcve1MqknnhPA6S1df0R9V5xzoNC_sQoEYH4ltR_j3ymj9LSlaHJZSsC_bTfEKmY5nlQbmSTwdR0pIraQVS92IOwIUpmO4hiFOBc01RcndZNCwfpkmltHclDYj7rgUenzRMY2g",
"mac_key": "cLY53QhwiAtvlIGeZ6_Q_FgHyaQ",
"expires_in": 259200,
"expires_ts": 1782811831
},
"uid": "2897121707",
"cloud_server": "cn",
"uuid": "0684e27d49b7a234ff1a4d82f8fca234",
"redirect_url": "http://homeassistant.local:8123",
"language": "en",
"ctrl_mode": "auto",
"storage_path": "",
"nick_name": "Xiaomi"
}
+147
View File
@@ -0,0 +1,147 @@
// Package config provides shared configuration for all MIoT examples.
// Aligns with Python miot_client.py: entry_data + miot_storage.py: auth_info.
package config
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"os"
"time"
"miot"
)
// AuthInfo mirrors the OAuth token stored in Python miot_config/{uid}_{cloud_server}.dict.
// Python saves this via update_user_config -> {auth_info: {...}}.
type AuthInfo struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
MacKey string `json:"mac_key"`
ExpiresIn int `json:"expires_in"`
ExpiresTS int64 `json:"expires_ts"`
}
// Config mirrors Python entry_data (HA config_entry.data) + auth_info from storage.
// Python splits these: entry_data is in HA config, auth_info is in miot_config/*.dict.
// For standalone examples, we combine both into one file.
type Config struct {
// === auth_info (from OAuth flow, saved by E1) ===
AuthInfo AuthInfo `json:"auth_info"`
// === entry_data required fields (Python MIoTClient.__init__ validates these) ===
UID string `json:"uid"` // Xiaomi account user ID
CloudServer string `json:"cloud_server"` // Region: cn/de/i2/ru/sg/us
UUID string `json:"uuid"` // MQTT client ID seed (Python: SHA256 of HA instance UUID)
RedirectURL string `json:"redirect_url"` // OAuth callback URL
// === entry_data optional fields ===
Language string `json:"language"` // integration_language: en/zh-Hans/de etc.
CtrlMode string `json:"ctrl_mode"` // auto/cloud/lan
StoragePath string `json:"storage_path"` // data directory for miot_storage
NickName string `json:"nick_name"` // display name in Xiaomi Home
// internal
configPath string // path to this config file
}
// EntryData returns the map[string]interface{} representation for NewMIoTClient.
// Aligns with Python entry_data dict passed to MIoTClient.__init__.
func (c *Config) EntryData() map[string]interface{} {
return map[string]interface{}{
"access_token": c.AuthInfo.AccessToken,
}
}
// GetCtrlMode converts CtrlMode string to Go CtrlMode type.
func (c *Config) GetCtrlMode() miot.CtrlMode {
switch c.CtrlMode {
case "gateway":
return miot.CtrlModeGateway
case "lan":
return miot.CtrlModeLAN
default:
return miot.CtrlModeCloud
}
}
// LoadConfig reads a JSON config file at path.
func LoadConfig(path string) (*Config, error) {
if path == "" {
return SampleConfig(), nil
}
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return SampleConfig(), nil
}
return nil, err
}
cfg := &Config{}
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
cfg.configPath = path
return cfg, nil
}
// SaveConfig writes the config as JSON to path.
func SaveConfig(path string, cfg *Config) error {
cfg.configPath = path
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
// DefaultPath returns the shared config file path (examples/config.json).
func DefaultPath() string {
return "../config.json"
}
// SampleConfig returns a Config with defaults, matching Python const.py defaults.
func SampleConfig() *Config {
return &Config{
CloudServer: miot.DEFAULT_CLOUD_SERVER,
RedirectURL: miot.OAUTH_REDIRECT_URL,
Language: miot.DEFAULT_INTEGRATION_LANGUAGE,
CtrlMode: miot.DEFAULT_CTRL_MODE,
NickName: miot.DEFAULT_NICK_NAME,
UUID: newUUID(),
}
}
// newUUID generates a random UUID string (Python uses SHA256 of HA instance UUID).
func NewUUID() string {
b := make([]byte, 16)
rand.Read(b)
return hex.EncodeToString(b)
}
func newUUID() string {
return NewUUID()
}
// TokenValid returns true if the access token exists and hasn't expired.
// Python: checks expires_ts > time.time().
func (a *AuthInfo) TokenValid() bool {
if a.AccessToken == "" {
return false
}
if a.ExpiresTS > 0 {
return time.Now().Unix() < a.ExpiresTS
}
return true
}
// TOKEN_EXPIRES_TS_RATIO is the safety margin applied to token expiry (Python: 0.7).
const TOKEN_EXPIRES_TS_RATIO = 0.7
// CalcExpiresTS calculates expires_ts from expires_in (seconds from now, with ratio).
// Python: int(time.time() + expires_in * TOKEN_EXPIRES_TS_RATIO).
func CalcExpiresTS(expiresIn int) int64 {
return time.Now().Unix() + int64(float64(expiresIn)*TOKEN_EXPIRES_TS_RATIO)
}
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIBazCCAQ+gAwIBAgIEA/UKYDAMBggqhkjOPQQDAgUAMCIxEzARBgNVBAoTCk1p
amlhIFJvb3QxCzAJBgNVBAYTAkNOMCAXDTE2MTEyMzAxMzk0NVoYDzIwNjYxMTEx
MDEzOTQ1WjAiMRMwEQYDVQQKEwpNaWppYSBSb290MQswCQYDVQQGEwJDTjBZMBMG
ByqGSM49AgEGCCqGSM49AwEHA0IABL71iwLa4//4VBqgRI+6xE23xpovqPCxtv96
2VHbZij61/Ag6jmi7oZ/3Xg/3C+whglcwoUEE6KALGJ9vccV9PmjLzAtMAwGA1Ud
EwQFMAMBAf8wHQYDVR0OBBYEFJa3onw5sblmM6n40QmyAGDI5sURMAwGCCqGSM49
BAMCBQADSAAwRQIgchciK9h6tZmfrP8Ka6KziQ4Lv3hKfrHtAZXMHPda4IYCIQCG
az93ggFcbrG9u2wixjx1HKW4DUA5NXZG0wWQTpJTbQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBjzCCATWgAwIBAgIBATAKBggqhkjOPQQDAjAiMRMwEQYDVQQKEwpNaWppYSBS
b290MQswCQYDVQQGEwJDTjAgFw0yMjA2MDkxNDE0MThaGA8yMDcyMDUyNzE0MTQx
OFowLDELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFE1JT1QgQ0VOVFJBTCBHQVRFV0FZ
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdYrzbnp/0x/cZLZnuEDXTFf8mhj4
CVpZPwgj9e9Ve5r3K7zvu8Jjj7JF1JjQYvEC6yhp1SzBgglnK4L8xQzdiqNQME4w
HQYDVR0OBBYEFCf9+YBU7pXDs6K6CAQPRhlGJ+cuMB8GA1UdIwQYMBaAFJa3onw5
sblmM6n40QmyAGDI5sURMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIh
AKUv+c8v98vypkGMTzMwckGjjVqTef8xodsy6PhcSCq+AiA/n9mDs62hAo5zXyJy
Bs1s7mqXPf1XgieoxIvs1MqyiA==
-----END CERTIFICATE-----
File diff suppressed because one or more lines are too long
+125
View File
@@ -0,0 +1,125 @@
// E9 — Full MIoT client integration example.
// Demonstrates: create client → set storage/http/mips → init → refresh devices → subscribe → monitor.
package main
import (
"fmt"
"os"
"os/signal"
"time"
"miot"
"miot/examples/config"
)
func main() {
// 1. Load config
cfg, err := config.LoadConfig(config.DefaultPath())
if err != nil {
fmt.Printf("Load config error: %v\n", err)
return
}
if cfg.AuthInfo.AccessToken == "" {
fmt.Println("请先运行 E1 cloud_oauth 获取 token")
os.Exit(1)
}
// 2. Create entry data (Python: entry_data dict)
entryData := cfg.EntryData()
// 3. Create the full client
client := miot.NewMIoTClient("demo", entryData, cfg.UID, cfg.CloudServer, cfg.GetCtrlMode())
// 4. Set up storage
storage := miot.NewMIoTStorage("./data")
client.SetStorage(storage)
fmt.Println("Storage 已配置")
// 5. Set up HTTP client
http, err := miot.NewMIoTHttpClient(cfg.CloudServer, miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)
if err != nil {
fmt.Printf("Create HTTP client error: %v\n", err)
return
}
client.SetHTTPClient(http)
fmt.Println("HTTP Client 已配置")
// 6. Set up MIPS cloud client
mips := miot.NewMipsCloudClient(
"ssl://cn-ha.mqtt.io.mi.com:8883",
"ha."+cfg.UUID,
miot.OAUTH2_CLIENT_ID,
cfg.AuthInfo.AccessToken,
)
client.SetMipsCloud(mips)
fmt.Println("MIPS Cloud 已配置")
// 7. Initialize client
if err := client.Init(); err != nil {
fmt.Printf("Init error: %v\n", err)
return
}
fmt.Println("Client 已初始化")
// 8. Start background services
network := miot.NewMIoTNetwork(nil, nil, 30*time.Second)
network.Init()
defer network.Deinit()
svc := miot.NewMipsService()
svc.Init()
defer svc.Deinit()
lan, err := miot.NewMIoTLan(nil, network, svc, true, 0)
if err != nil {
fmt.Printf("LAN init warning: %v\n", err)
} else {
defer lan.Deinit()
fmt.Println("LAN 控制器已启动")
}
// 9. Connect MIPS
fmt.Println("正在连接 MIPS...")
if err := mips.Connect(); err != nil {
fmt.Printf("MIPS connect warning: %v\n", err)
} else {
defer mips.Disconnect()
fmt.Println("MIPS 已连接")
}
// 10. Refresh device list
fmt.Println("\n刷新设备列表...")
if err := client.RefreshDevices(); err != nil {
fmt.Printf("RefreshDevices warning: %v\n", err)
} else {
devices := client.DeviceList()
fmt.Printf("设备数量: %d\n", len(devices))
for did, info := range devices {
fmt.Printf(" %s: %v (online=%v)\n", did, info["name"], info["online"])
}
// Subscribe to first device's state
for did := range devices {
client.SubDeviceState(did, func(did string, state miot.MIoTDeviceState, ctx interface{}) {
fmt.Printf("[设备状态] DID=%s, State=%s\n", did, state)
})
// Subscribe to all properties for the first device
client.SubProp(did, func(params map[string]interface{}, ctx interface{}) {
fmt.Printf("[属性] did=%v, siid=%v, piid=%v, value=%v\n",
params["did"], params["siid"], params["piid"], params["value"])
}, 0, 0)
break // only first device for demo
}
}
// 11. Wait for Ctrl+C
fmt.Println("\n等待 Ctrl+C 退出...")
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
<-sigCh
// 12. Cleanup
fmt.Println("\n正在清理...")
client.Deinit()
fmt.Println("示例结束")
}
+65
View File
@@ -0,0 +1,65 @@
// E4 — LAN device control example.
// Demonstrates: init network → create LAN controller → register device → wait for online → subscribe.
package main
import (
"fmt"
"time"
"miot"
)
func main() {
// 1. Initialize network monitor
network := miot.NewMIoTNetwork(nil, nil, 30*time.Second)
network.Init()
defer network.Deinit()
// 3. Create MIPS service (required by MIoTLan)
svc := miot.NewMipsService()
svc.Init()
defer svc.Deinit()
// 4. Create LAN controller
lan, err := miot.NewMIoTLan(nil, network, svc, true, 0)
if err != nil {
fmt.Printf("Create LAN error: %v\n", err)
return
}
defer lan.Deinit()
// 5. Register devices with hardcoded values
// Replace with your actual device DID and token (32 hex chars)
devices := map[string]map[string]interface{}{
"12345678": {
"did": "12345678",
"token": "00000000000000000000000000000000",
},
}
lan.UpdateDevices(devices)
fmt.Println("已注册设备,等待上线...")
// 6. Subscribe to device state
lan.SubDeviceState("demo", func(did string, state map[string]interface{}, ctx interface{}) {
fmt.Printf("[设备状态] DID=%s, State=%v\n", did, state)
}, nil)
// 7. Subscribe to property changes (all properties)
did := "12345678"
lan.SubProp(did, func(params map[string]interface{}, ctx interface{}) {
fmt.Printf("[属性变化] DID=%s, siid=%v, piid=%v, value=%v\n",
params["did"], params["siid"], params["piid"], params["value"])
}, 0, 0, nil)
// 8. Wait for device to come online
fmt.Println("等待设备上线 (30秒)...")
time.Sleep(30 * time.Second)
// Print current online devices
devList := lan.GetDevList()
fmt.Println("\n=== 在线设备 ===")
for did, state := range devList {
fmt.Printf(" DID: %s, State: %v\n", did, state)
}
fmt.Println("示例结束")
}
+54
View File
@@ -0,0 +1,54 @@
// E5 — mDNS service discovery example.
// Demonstrates: create MipsService → init → discover _miot-central._tcp.local. → print results.
package main
import (
"fmt"
"time"
"miot"
)
func main() {
// 1. Create MIPS service browser
svc := miot.NewMipsService()
// 2. Initialize mDNS browser
svc.Init()
defer svc.Deinit()
fmt.Println("mDNS 服务浏览器已启动")
// 3. Subscribe to service changes
svc.SubServiceChange("demo", "*", func(groupID string, state miot.MipsServiceState, data map[string]interface{}) {
fmt.Printf("\n[服务变化] groupID=%s, state=%s\n", groupID, state)
for k, v := range data {
fmt.Printf(" %s: %v\n", k, v)
}
})
// 4. Wait a moment for initial discovery
fmt.Println("正在搜索 MIoT 中央网关 (5秒)...")
time.Sleep(5 * time.Second)
// 5. Get and print all discovered services
services := svc.GetServices("")
fmt.Println("\n=== 已发现的 MIoT 服务 ===")
if len(services) == 0 {
fmt.Println(" 未发现任何服务 (可能需要等待更长时间)")
} else {
for groupID, s := range services {
fmt.Printf(" GroupID: %s\n", groupID)
fmt.Printf(" Name: %s\n", s.Name)
fmt.Printf(" Addresses: %v\n", s.Addresses)
fmt.Printf(" Port: %d\n", s.Port)
fmt.Printf(" DID: %s\n", s.DID)
fmt.Printf(" Server: %s\n", s.Server)
fmt.Println()
}
}
// 6. Keep browser running for more discoveries
fmt.Println("继续监听服务变化 (30秒)...")
time.Sleep(30 * time.Second)
fmt.Println("示例结束")
}
+71
View File
@@ -0,0 +1,71 @@
// E3 — MIPS cloud subscription example.
// Demonstrates: connect to MQTT cloud broker → subscribe to device prop/event → wait for messages.
package main
import (
"fmt"
"os"
"time"
"miot"
"miot/examples/config"
)
func main() {
// 1. Load config
cfg, err := config.LoadConfig(config.DefaultPath())
if err != nil {
fmt.Printf("Load config error: %v\n", err)
return
}
if cfg.AuthInfo.AccessToken == "" {
fmt.Println("请先运行 E1 cloud_oauth 获取 token")
os.Exit(1)
}
// Need a device DID — hardcode or get from config
did := "2053225728" // use UID as placeholder, user should replace
fmt.Printf("使用 DID: %s (如无效请修改)\n", did)
// 2. Create MIPS cloud client
mips := miot.NewMipsCloudClient(
"ssl://cn-ha.mqtt.io.mi.com:8883",
"ha."+cfg.UUID,
miot.OAUTH2_CLIENT_ID,
cfg.AuthInfo.AccessToken,
)
fmt.Println("正在连接 MIPS Cloud...")
if err := mips.Connect(); err != nil {
fmt.Printf("MIPS Connect error: %v\n", err)
return
}
defer mips.Disconnect()
fmt.Println("MIPS Cloud 已连接")
// 4. Subscribe to property changes (wildcard: all properties)
propTopic := fmt.Sprintf("device/%s/up/properties_changed/#", did)
fmt.Printf("订阅属性: %s\n", propTopic)
_ = mips.Subscribe(propTopic, func(topic string, payload []byte) {
fmt.Printf("[属性变化] topic=%s, payload=%s\n", topic, string(payload))
})
// 5. Subscribe to events
eventTopic := fmt.Sprintf("device/%s/up/event_occured/#", did)
fmt.Printf("订阅事件: %s\n", eventTopic)
_ = mips.Subscribe(eventTopic, func(topic string, payload []byte) {
fmt.Printf("[事件] topic=%s, payload=%s\n", topic, string(payload))
})
// 6. Subscribe to device state
stateTopic := fmt.Sprintf("device/%s/state/#", did)
fmt.Printf("订阅状态: %s\n", stateTopic)
_ = mips.Subscribe(stateTopic, func(topic string, payload []byte) {
fmt.Printf("[状态变化] topic=%s, payload=%s\n", topic, string(payload))
})
// 7. Wait for messages
fmt.Println("\n等待设备消息 (60秒)...")
time.Sleep(60 * time.Second)
fmt.Println("示例结束")
}
+53
View File
@@ -0,0 +1,53 @@
// E6 — Network status monitor example.
// Demonstrates: init network → print status/interface info → subscribe to changes.
package main
import (
"fmt"
"time"
"miot"
)
func main() {
// 1. Create network monitor (use defaults)
network := miot.NewMIoTNetwork(nil, nil, 30*time.Second)
network.Init()
defer network.Deinit()
// 2. Print current network status
fmt.Println("=== 网络状态 ===")
if network.GetNetworkStatus() {
fmt.Println("状态: 已连接")
} else {
fmt.Println("状态: 未连接")
}
// 3. Print each network interface
fmt.Println("\n=== 网络接口 ===")
for name, info := range network.GetNetworkInfo() {
fmt.Printf(" 接口: %s\n", name)
fmt.Printf(" IP: %s\n", info.IP)
fmt.Printf(" Netmask: %s\n", info.Netmask)
fmt.Printf(" NetSeg: %s\n", info.NetSeg)
}
// 4. Subscribe to network status changes
network.SubNetworkStatus("demo", func(status bool) {
if status {
fmt.Println("[网络状态] 已连接")
} else {
fmt.Println("[网络状态] 已断开")
}
})
// 5. Subscribe to interface changes
network.SubNetworkInfo("demo", func(status miot.InterfaceStatus, info *miot.NetworkInfo) {
fmt.Printf("[接口变化] %s: %s (%s/%s)\n", status, info.Name, info.IP, info.Netmask)
})
// 6. Wait for network events
fmt.Println("\n监听网络变化 (30秒)...")
time.Sleep(30 * time.Second)
fmt.Println("示例结束")
}
File diff suppressed because one or more lines are too long
+107
View File
@@ -0,0 +1,107 @@
// E8 — MIoT device spec parser example.
// Demonstrates: parse a device URN → iterate services/properties/actions → query value lists.
package main
import (
"fmt"
"miot"
)
func main() {
// 1. Create storage (needed for spec caching)
storage := miot.NewMIoTStorage("./data")
// 2. Create spec parser
parser := miot.NewMIoTSpecParser("zh-Hans", storage, "specs")
fmt.Println("Spec 解析器已创建")
// 3. Parse a sample device URN
urn := "urn:miot-spec-v2:device:light:0000A001:yeelink-light1:1"
fmt.Printf("\n解析 URN: %s\n", urn)
instance, err := parser.Parse(urn, "")
if err != nil {
fmt.Printf("Parse error: %v\n", err)
fmt.Println("\n提示: 请先在 ./specs/ 目录下放置对应的 JSON 规格文件")
return
}
// 4. Print instance info
fmt.Printf("\n=== 设备规格 ===\n")
fmt.Printf("URN: %s\n", instance.URN)
fmt.Printf("Type: %s\n", instance.Type)
fmt.Printf("Description: %s\n", instance.DescriptionTrans)
fmt.Printf("Services: %d\n", len(instance.Services))
// 5. Iterate each service
for _, svc := range instance.Services {
fmt.Printf("\n--- Service: %s ---\n", svc.DescriptionTrans)
fmt.Printf(" Type: %s\n", svc.Type)
fmt.Printf(" IID: %d\n", svc.IID)
fmt.Printf(" Description: %s\n", svc.Description)
// Properties
if len(svc.Properties) > 0 {
fmt.Printf(" Properties (%d):\n", len(svc.Properties))
for _, prop := range svc.Properties {
fmt.Printf(" [%d.%d] %s\n", prop.SIID, prop.PIID, prop.DescriptionTrans)
fmt.Printf(" Format: %s, Access: %v", prop.Format, prop.Access)
if prop.Writable() {
fmt.Printf(" [可写]")
}
if prop.Readable() {
fmt.Printf(" [可读]")
}
fmt.Println()
if prop.ValueRange != nil {
fmt.Printf(" Range: [%v, %v], Step: %v\n",
prop.ValueRange.Min, prop.ValueRange.Max, prop.ValueRange.Step)
}
if len(prop.ValueList) > 0 {
fmt.Printf(" ValueList (%d items):\n", len(prop.ValueList))
names := prop.ValueList.Names()
for i, item := range prop.ValueList {
fmt.Printf(" - %s: %v (%s)\n", names[i], item.Value, item.Desc)
}
// Demonstrate GetValueByDescription
if firstDesc := prop.ValueList[0].Desc; firstDesc != "" {
v := prop.ValueList.GetValueByDescription(firstDesc)
fmt.Printf(" GetValueByDescription(%q) = %v\n", firstDesc, v)
}
}
}
}
// Actions
if len(svc.Actions) > 0 {
fmt.Printf(" Actions (%d):\n", len(svc.Actions))
for _, act := range svc.Actions {
fmt.Printf(" [%d.%d] %s\n", act.SIID, act.AIID, act.DescriptionTrans)
if len(act.In) > 0 {
fmt.Printf(" In: %v\n", act.In)
}
if len(act.Out) > 0 {
fmt.Printf(" Out: %v\n", act.Out)
}
}
}
}
// 6. Demonstrate ValuePrecision / ValueFormat
if len(instance.Services) > 0 && len(instance.Services[0].Properties) > 0 {
prop := instance.Services[0].Properties[0]
fmt.Printf("\n=== 值转换演示 (property: %s) ===\n", prop.DescriptionTrans)
raw := prop.ValueFormat("1")
fmt.Printf(" ValueFormat(\"1\") = %v (type: %T)\n", raw, raw)
prec := prop.ValuePrecision(3.14159)
fmt.Printf(" ValuePrecision(3.14159) = %v\n", prec)
expr := prop.EvalExpr(100.0)
fmt.Printf(" EvalExpr(100.0) = %v\n", expr)
}
fmt.Println("\n示例结束")
}
+92
View File
@@ -0,0 +1,92 @@
// E7 — Storage and certificate management example.
// Demonstrates: save/load data → user config → certificate operations → manufacturer lookup.
package main
import (
"fmt"
"miot"
"miot/examples/config"
)
func main() {
// 1. Load config
cfg, err := config.LoadConfig(config.DefaultPath())
if err != nil {
fmt.Printf("Load config error: %v\n", err)
return
}
// 2. Create storage
storage := miot.NewMIoTStorage("./data")
fmt.Println("Storage 已创建 (root=./data)")
// 3. Save and load data
fmt.Println("\n=== 读写操作 ===")
if err := storage.Save("test", "hello", "world"); err != nil {
fmt.Printf("Save error: %v\n", err)
} else {
fmt.Println("Save: test/hello → world (OK)")
}
val, err := storage.Load("test", "hello", "str")
if err != nil {
fmt.Printf("Load error: %v\n", err)
} else {
fmt.Printf("Load: test/hello = %v\n", val)
}
// 4. User config operations
fmt.Println("\n=== 用户配置 ===")
configData := map[string]interface{}{"language": "zh-Hans", "theme": "dark"}
if uid := cfg.UID; uid != "" {
err := storage.UpdateUserConfig(uid, cfg.CloudServer, configData, false)
if err != nil {
fmt.Printf("UpdateUserConfig error: %v\n", err)
} else {
fmt.Println("UpdateUserConfig: OK")
}
loaded := storage.LoadUserConfig(uid, cfg.CloudServer, nil)
fmt.Printf("LoadUserConfig: %v\n", loaded)
} else {
fmt.Println("跳过 (UID 为空)")
}
// 5. Certificate operations
fmt.Println("\n=== 证书操作 ===")
cert, err := miot.NewMIoTCert(storage, cfg.UID, cfg.CloudServer)
if err != nil {
fmt.Printf("NewMIoTCert error: %v\n", err)
} else {
ok, err := cert.VerifyCACert()
if err != nil {
fmt.Printf("VerifyCACert error: %v\n", err)
} else if ok {
fmt.Println("CA 证书验证: 通过")
} else {
fmt.Println("CA 证书验证: 失败 (hash 不匹配)")
}
fmt.Printf("CA 文件: %s\n", cert.CAFile())
fmt.Printf("Key 文件: %s\n", cert.KeyFile())
fmt.Printf("Cert 文件: %s\n", cert.CertFile())
}
// 6. Device manufacturer lookup
fmt.Println("\n=== 厂商信息 ===")
mfg := miot.NewDeviceManufacturer(storage)
mfg.Init()
manufacturers := []string{"xiaomi", "yeelink", "lumi", "chuangmi"}
for _, m := range manufacturers {
name := mfg.GetName(m)
if name != m {
fmt.Printf(" %s → %s\n", m, name)
} else {
fmt.Printf(" %s → (未缓存)\n", m)
}
}
fmt.Println("\n示例结束")
}