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
+118
View File
@@ -0,0 +1,118 @@
// Package miot provides MIoT core client for Xiaomi Home devices.
// This file is a direct port of py-miot/common.py utility functions.
package miot
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"os"
"path/filepath"
"regexp"
"strings"
"gopkg.in/yaml.v3"
)
// ============================================================================
// RootPath (aligned with Python MIOT_ROOT_PATH)
// ============================================================================
// RootPath is the root directory of the miot package.
// It mirrors Python's MIOT_ROOT_PATH = path.dirname(path.abspath(__file__)).
// Callers should set this before using GenAbsolutePath.
var RootPath string
func init() {
// Attempt to discover the project root from the working directory.
// Callers may override this after init.
if wd, err := os.Getwd(); err == nil {
RootPath = wd
}
}
// ============================================================================
// Utility functions (aligned with py-miot/common.py)
// ============================================================================
// GenAbsolutePath generates an absolute path relative to RootPath.
// Aligns with Python: gen_absolute_path(relative_path).
func GenAbsolutePath(relativePath string) string {
return filepath.Join(RootPath, relativePath)
}
// CalcGroupID calculates a group ID from a user ID and home ID.
// Uses SHA1 with a "central_service" salt, returning the first 16 hex chars.
// Aligns with Python: calc_group_id(uid, home_id).
func CalcGroupID(uid, homeID string) string {
src := fmt.Sprintf("%scentral_service%s", uid, homeID)
h := sha1.Sum([]byte(src))
return hex.EncodeToString(h[:])[:16]
}
// LoadJSONFile loads and parses a JSON file.
// Aligns with Python: load_json_file(json_file).
func LoadJSONFile(filePath string) (map[string]interface{}, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("read JSON file %s: %w", filePath, err)
}
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
return nil, fmt.Errorf("parse JSON file %s: %w", filePath, err)
}
return result, nil
}
// LoadYAMLFile loads and parses a YAML file.
// Aligns with Python: load_yaml_file(yaml_file).
func LoadYAMLFile(filePath string) (map[string]interface{}, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("read YAML file %s: %w", filePath, err)
}
var result map[string]interface{}
if err := yaml.Unmarshal(data, &result); err != nil {
return nil, fmt.Errorf("parse YAML file %s: %w", filePath, err)
}
return result, nil
}
// RandomizeInt randomizes an integer within the given ratio range.
// Formula: int(value * (1 - ratio + random() * 2 * ratio))
// Aligns with Python: randomize_int(value, ratio).
func RandomizeInt(value int, ratio float64) int {
return int(float64(value) * (1 - ratio + rand.Float64()*2*ratio))
}
// RandomizeFloat randomizes a float64 within the given ratio range.
// Formula: value * (1 - ratio + random() * 2 * ratio)
// Aligns with Python: randomize_float(value, ratio).
func RandomizeFloat(value float64, ratio float64) float64 {
return value * (1 - ratio + rand.Float64()*2*ratio)
}
// ============================================================================
// Slugify (aligned with py-miot/common.py slugify_name / slugify_did)
// ============================================================================
// nonAlphaNum matches sequences of non-alphanumeric characters.
var nonAlphaNum = regexp.MustCompile(`[^a-z0-9]+`)
// SlugifyName converts a name to a lowercase slug with underscore separators.
// Aligns with Python: slugify_name(name, separator='_').
func SlugifyName(name string) string {
s := strings.ToLower(name)
s = nonAlphaNum.ReplaceAllString(s, "_")
s = strings.Trim(s, "_")
return s
}
// SlugifyDID creates a slug from a cloud server and device ID.
// Pattern: f"{cloud_server}_{did}" then slugify.
// Aligns with Python: slugify_did(cloud_server, did).
func SlugifyDID(cloudServer, did string) string {
return SlugifyName(cloudServer + "_" + did)
}