// Package iotmock implements a simulated IoT device provider for development and testing. // It provides 8 virtual smart home devices with realistic behavior including // sensor fluctuation and operation history tracking. package iotmock import ( "context" "fmt" "math/rand" "sync" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/plugin" ) // Plugin implements the IoTProvider interface with 8 simulated devices. type Plugin struct { devices map[string]*plugin.Device subs []chan plugin.DeviceEvent mu sync.RWMutex stopCh chan struct{} stopped bool } // New creates a new iot-mock plugin with 8 pre-configured devices. func New() *Plugin { return &Plugin{ devices: map[string]*plugin.Device{ "light-livingroom": { ID: "light-livingroom", Name: "客厅灯", Type: plugin.DeviceLight, State: map[string]any{"power": false, "brightness": 80, "color": "warm_white"}, Online: true, }, "light-bedroom": { ID: "light-bedroom", Name: "卧室灯", Type: plugin.DeviceLight, State: map[string]any{"power": false, "brightness": 60, "color": "warm_white"}, Online: true, }, "ac-livingroom": { ID: "ac-livingroom", Name: "客厅空调", Type: plugin.DeviceAC, State: map[string]any{"power": false, "temperature": 26, "mode": "cool"}, Online: true, }, "ac-bedroom": { ID: "ac-bedroom", Name: "卧室空调", Type: plugin.DeviceAC, State: map[string]any{"power": false, "temperature": 25, "mode": "cool"}, Online: true, }, "curtain-livingroom": { ID: "curtain-livingroom", Name: "客厅窗帘", Type: plugin.DeviceCurtain, State: map[string]any{"power": false, "position": 0}, Online: true, }, "sensor-temperature": { ID: "sensor-temperature", Name: "温度传感器", Type: plugin.DeviceSensor, State: map[string]any{"temperature": 25.0, "unit": "celsius"}, Online: true, }, "sensor-humidity": { ID: "sensor-humidity", Name: "湿度传感器", Type: plugin.DeviceSensor, State: map[string]any{"humidity": 55.0, "unit": "percent"}, Online: true, }, "lock-door": { ID: "lock-door", Name: "智能门锁", Type: plugin.DeviceLock, State: map[string]any{"locked": true, "battery": 85}, Online: true, }, }, subs: make([]chan plugin.DeviceEvent, 0), stopCh: make(chan struct{}), } } // --- Plugin interface --- func (p *Plugin) Name() string { return "iot-mock" } func (p *Plugin) Version() string { return "1.0.0" } func (p *Plugin) Description() string { return "模拟8个智能家居设备,仅供开发测试使用" } func (p *Plugin) Enabled() bool { return true } func (p *Plugin) Init(_ context.Context, _ plugin.PluginConfig) error { // Start sensor fluctuation goroutine go p.fluctuateSensors() return nil } func (p *Plugin) Close() error { p.mu.Lock() defer p.mu.Unlock() if p.stopped { return nil } p.stopped = true close(p.stopCh) for _, ch := range p.subs { close(ch) } p.subs = nil return nil } // --- IoTProvider interface --- func (p *Plugin) ListDevices(_ context.Context) ([]plugin.Device, error) { p.mu.RLock() defer p.mu.RUnlock() result := make([]plugin.Device, 0, len(p.devices)) for _, d := range p.devices { // Return a copy to prevent external mutation clone := *d clone.State = make(map[string]any, len(d.State)) for k, v := range d.State { clone.State[k] = v } result = append(result, clone) } return result, nil } func (p *Plugin) GetDevice(_ context.Context, id string) (*plugin.Device, error) { p.mu.RLock() defer p.mu.RUnlock() d, ok := p.devices[id] if !ok { return nil, fmt.Errorf("device %s not found", id) } clone := *d clone.State = make(map[string]any, len(d.State)) for k, v := range d.State { clone.State[k] = v } return &clone, nil } func (p *Plugin) SetDevice(_ context.Context, id, field string, value any) error { p.mu.Lock() defer p.mu.Unlock() d, ok := p.devices[id] if !ok { return fmt.Errorf("device %s not found", id) } oldValue := d.State[field] d.State[field] = value event := plugin.DeviceEvent{ DeviceID: id, Field: field, OldValue: oldValue, NewValue: value, Timestamp: time.Now().Unix(), } d.History = append(d.History, event) // Keep only last 20 history events if len(d.History) > 20 { d.History = d.History[len(d.History)-20:] } // Broadcast to subscribers p.broadcast(event) return nil } func (p *Plugin) ToggleDevice(_ context.Context, id string) error { p.mu.RLock() d, ok := p.devices[id] p.mu.RUnlock() if !ok { return fmt.Errorf("device %s not found", id) } currentPower, _ := d.State["power"].(bool) return p.SetDevice(context.Background(), id, "power", !currentPower) } func (p *Plugin) SubscribeChanges(_ context.Context) (<-chan plugin.DeviceEvent, error) { p.mu.Lock() defer p.mu.Unlock() ch := make(chan plugin.DeviceEvent, 32) p.subs = append(p.subs, ch) return ch, nil } // --- Internal --- func (p *Plugin) broadcast(event plugin.DeviceEvent) { for _, ch := range p.subs { select { case ch <- event: default: // Subscriber is too slow, drop event } } } // fluctuateSensors simulates temperature and humidity changes every 30 seconds. func (p *Plugin) fluctuateSensors() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-p.stopCh: return case <-ticker.C: // Fluctuate temperature sensor: ±0.2°C p.fluctuateDeviceField("sensor-temperature", "temperature", 0.2) // Fluctuate humidity sensor: ±1% p.fluctuateDeviceField("sensor-humidity", "humidity", 1.0) } } } func (p *Plugin) fluctuateDeviceField(deviceID, field string, maxDelta float64) { p.mu.Lock() d, ok := p.devices[deviceID] if !ok { p.mu.Unlock() return } current, ok := d.State[field].(float64) if !ok { p.mu.Unlock() return } delta := (rand.Float64()*2 - 1) * maxDelta newVal := current + delta // Round to 1 decimal place newVal = float64(int(newVal*10)) / 10 oldVal := d.State[field] d.State[field] = newVal p.mu.Unlock() p.mu.RLock() event := plugin.DeviceEvent{ DeviceID: deviceID, Field: field, OldValue: oldVal, NewValue: newVal, Timestamp: time.Now().Unix(), } p.mu.RUnlock() p.broadcast(event) }