go.mod 模块名为 xiaomihome,将所有内部 import 从 "miot" 更新为 "xiaomihome/miot",涉及所有测试文件和示例文件。新增 README.md 项目 介绍文档(架构概览、模块说明、移植进度等)。新增 ARCH_PLAN.md 架构 设计文档。
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
// 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"
|
|
|
|
"xiaomihome/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)
|
|
}
|