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