// 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) }