- 实现 MIoT 客户端核心功能(MQTT 连接、设备管理、属性读写) - 支持云端 API 调用与局域网设备发现(mDNS) - 集成国际化(i18n)多语言支持 - 添加 MIoT 设备规约解析器(spec_parser) - 包含单元测试与使用示例
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
// 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)
|
|
}
|