Files
AskaEth 2593765add docs(iot): 存量英文注释中文化(仅注释,代码零字节改动)
- 5 个文件:123 行英文注释 → 99 行中文注释(中文更紧凑,内容无删减)。
- 机械校验(git diff -U0 去掉 // 行与空行)输出为空;gofmt -l 干净(改动前亦干净)。
- 验收:go build/vet/test ./internal/iot/... 全绿。
2026-10-04 16:58:00 +08:00

322 lines
8.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package iot
import (
"context"
"fmt"
"log/slog"
"sort"
"strconv"
"sync"
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
)
// mockProvider 是内置的确定性 Provider,八台模拟设备覆盖全部受支持的设备类型。
// 它施加 control_rules(capability 约束),界定每种设备类型的支持范围。
type mockProvider struct {
mu sync.RWMutex
log *slog.Logger
devices map[string]*Device
rules map[string]typeRules
}
// typeRules 保存单一设备类型的 control_rules:受支持的动作集合,以及每个动作
// 如何改动设备状态。
type typeRules struct {
supported map[string]applyFunc
}
// applyFunc 依据动作修改 d.State;参数非法时返回错误。
type applyFunc func(d *Device, params map[string]any) error
const mockProviderName = "mock"
// Name 返回 Provider 标识。
func (m *mockProvider) Name() string { return mockProviderName }
func newMockProvider(log *slog.Logger) (*mockProvider, error) {
m := &mockProvider{
log: logger.WithSource(discardLogger(log), "iot/mock"),
devices: make(map[string]*Device, len(mockDevices())),
rules: buildRules(),
}
for _, d := range mockDevices() {
m.devices[d.ID] = d
}
return m, nil
}
// List 返回全部设备,按 ID 排序以保证输出确定。
func (m *mockProvider) List(ctx context.Context) ([]Device, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
m.mu.RLock()
defer m.mu.RUnlock()
out := make([]Device, 0, len(m.devices))
for _, d := range m.devices {
out = append(out, d.Clone())
}
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
return out, nil
}
// Status 返回指定设备的副本。
func (m *mockProvider) Status(ctx context.Context, deviceID string) (*Device, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
m.mu.RLock()
defer m.mu.RUnlock()
d, ok := m.devices[deviceID]
if !ok {
return nil, fmt.Errorf("iot: status %q: %w", deviceID, ErrDeviceNotFound)
}
cp := d.Clone()
return &cp, nil
}
// Control 对指定设备施加 action,并强制该类型的 control_rules。
func (m *mockProvider) Control(ctx context.Context, deviceID, action string, params map[string]any) (*Device, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
m.mu.Lock()
defer m.mu.Unlock()
d, ok := m.devices[deviceID]
if !ok {
return nil, fmt.Errorf("iot: control %q: %w", deviceID, ErrDeviceNotFound)
}
if !d.Online {
return nil, fmt.Errorf("iot: control %q: %w", deviceID, ErrOffline)
}
tr, ok := m.rules[d.Type]
if !ok {
return nil, fmt.Errorf("iot: control %q: %w", deviceID, ErrUnsupported)
}
apply, ok := tr.supported[action]
if !ok {
return nil, fmt.Errorf("iot: control %q action %q: %w", deviceID, action, ErrUnsupported)
}
if err := apply(d, params); err != nil {
return nil, err
}
m.log.Debug("iot mock device controlled", "device", deviceID, "action", action)
cp := d.Clone()
return &cp, nil
}
// buildRules 返回全部受支持设备类型的 control_rules。
func buildRules() map[string]typeRules {
return map[string]typeRules{
"light": lightRules(),
"ac": acRules(),
"curtain": curtainRules(),
"sensor": sensorRules(),
"lock": lockRules(),
}
}
// lightRules 返回灯类设备的 control_rules。
func lightRules() typeRules {
return typeRules{supported: map[string]applyFunc{
"on": func(d *Device, _ map[string]any) error {
d.State["on"] = true
if brightness(d) == 0 {
d.State["brightness"] = 100
}
return nil
},
"off": func(d *Device, _ map[string]any) error {
d.State["on"] = false
return nil
},
"toggle": func(d *Device, _ map[string]any) error {
d.State["on"] = !boolOf(d.State["on"])
return nil
},
"set_brightness": func(d *Device, p map[string]any) error {
b, err := intParam(p, "brightness")
if err != nil {
return err
}
d.State["brightness"] = clamp(b, 0, 100)
d.State["on"] = b > 0
return nil
},
}}
}
// acRules 返回空调设备的 control_rules。
func acRules() typeRules {
return typeRules{supported: map[string]applyFunc{
"on": func(d *Device, _ map[string]any) error {
d.State["on"] = true
return nil
},
"off": func(d *Device, _ map[string]any) error {
d.State["on"] = false
return nil
},
"set_temperature": func(d *Device, p map[string]any) error {
t, err := intParam(p, "temperature")
if err != nil {
return err
}
d.State["temperature"] = clamp(t, 16, 30)
return nil
},
"set_mode": func(d *Device, p map[string]any) error {
mode, err := strParam(p, "mode")
if err != nil {
return err
}
switch mode {
case "cool", "heat", "fan", "dry":
d.State["mode"] = mode
return nil
default:
return fmt.Errorf("iot: control mode %q: %w", mode, ErrUnsupported)
}
},
}}
}
// curtainRules 返回窗帘设备的 control_rules。
func curtainRules() typeRules {
return typeRules{supported: map[string]applyFunc{
"open": func(d *Device, _ map[string]any) error {
d.State["position"] = 100
return nil
},
"close": func(d *Device, _ map[string]any) error {
d.State["position"] = 0
return nil
},
"stop": func(d *Device, _ map[string]any) error { return nil },
"set_position": func(d *Device, p map[string]any) error {
pos, err := intParam(p, "position")
if err != nil {
return err
}
d.State["position"] = clamp(pos, 0, 100)
return nil
},
}}
}
// sensorRules 返回传感器设备的 control_rules。传感器只读:不支持任何动作,
// 因此任何 Control 尝试都会返回 ErrUnsupported。
func sensorRules() typeRules {
return typeRules{supported: map[string]applyFunc{}}
}
// lockRules 返回门锁设备的 control_rules。
func lockRules() typeRules {
return typeRules{supported: map[string]applyFunc{
"lock": func(d *Device, _ map[string]any) error {
d.State["locked"] = true
return nil
},
"unlock": func(d *Device, _ map[string]any) error {
d.State["locked"] = false
return nil
},
"toggle": func(d *Device, _ map[string]any) error {
d.State["locked"] = !boolOf(d.State["locked"])
return nil
},
}}
}
// mockDevices 返回八台预置的模拟设备,覆盖全部五种类型;ac.bedroom 预置为离线,
// 用于检验 Online 字段与离线控制保护逻辑。
func mockDevices() []*Device {
return []*Device{
{ID: "ac.bedroom", Name: "卧室空调", Type: "ac", Online: false, State: map[string]any{"on": false, "temperature": 26, "mode": "cool"}},
{ID: "ac.living", Name: "客厅空调", Type: "ac", Online: true, State: map[string]any{"on": false, "temperature": 26, "mode": "cool"}},
{ID: "curtain.living", Name: "客厅窗帘", Type: "curtain", Online: true, State: map[string]any{"position": 0}},
{ID: "light.bedroom", Name: "卧室灯", Type: "light", Online: true, State: map[string]any{"on": false, "brightness": 0}},
{ID: "light.living", Name: "客厅灯", Type: "light", Online: true, State: map[string]any{"on": false, "brightness": 0}},
{ID: "lock.back", Name: "后门锁", Type: "lock", Online: true, State: map[string]any{"locked": true}},
{ID: "lock.front", Name: "前门锁", Type: "lock", Online: true, State: map[string]any{"locked": true}},
{ID: "sensor.env", Name: "环境传感器", Type: "sensor", Online: true, State: map[string]any{"temperature": 24.5, "humidity": 60.0}},
}
}
func brightness(d *Device) int { return intOf(d.State["brightness"]) }
func intOf(v any) int {
f, ok := toFloat64(v)
if !ok {
return 0
}
return int(f)
}
func boolOf(v any) bool {
b, _ := v.(bool)
return b
}
func clamp(n, lo, hi int) int {
if n < lo {
return lo
}
if n > hi {
return hi
}
return n
}
// intParam 读取整型动作参数,可接受 int/int64/float64/string 等多种形态,
// 使 JSON 解码得到的参数与字面量参数行为一致。
func intParam(p map[string]any, key string) (int, error) {
v, ok := p[key]
if !ok {
return 0, fmt.Errorf("iot: missing %q parameter: %w", key, ErrInvalidParam)
}
f, ok := toFloat64(v)
if !ok {
return 0, fmt.Errorf("iot: %q must be a number: %w", key, ErrInvalidParam)
}
return int(f), nil
}
func strParam(p map[string]any, key string) (string, error) {
v, ok := p[key]
if !ok {
return "", fmt.Errorf("iot: missing %q parameter: %w", key, ErrInvalidParam)
}
s, ok := v.(string)
if !ok {
return "", fmt.Errorf("iot: %q must be a string: %w", key, ErrInvalidParam)
}
return s, nil
}
func toFloat64(v any) (float64, bool) {
switch n := v.(type) {
case float64:
return n, true
case float32:
return float64(n), true
case int:
return float64(n), true
case int8:
return float64(n), true
case int16:
return float64(n), true
case int32:
return float64(n), true
case int64:
return float64(n), true
case string:
f, err := strconv.ParseFloat(n, 64)
if err == nil {
return f, true
}
}
return 0, false
}