Files
Cyrene_v2/internal/iot/device.go
T
Aska 9b207a31e8
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(gateway): 地基③ 打通实时信号 producer→前端WS + loop 真流式 + 上行路由补全
打通前端 ws.ts 里那批死消费者事件(thinking/mood/tool_used/subagent_event/
task_output/device_update/reminder/backthink),并给 loop 接真 token 流式。

- wire.go:loop 建时传 OnStream(streamChunk 闭包把 delta 发 bus ws.stream_chunk);
  gateway.Deps 注入 Scheduler/Tasks/Streaming;iot 接共享总线 iot.WithBus(a.bus)。
- ws_bridge.go:hub 额外订阅 iot.>/task.>/automation.>/reminder.>/cyrene.agent.*.event,
  normalizeEvent 归一为 ws.*(device_update/task_output/task_done/automation_result/
  reminder/subagent_event);不订阅 cyrene.agent.> 以免 ask/reply 内部事件进 relay。
- ws_route.go:voice_input→ws.voice_input;subagent_ask→Scheduler.AuditContext(真实
  上下文审计,替换 pending stub);task_output_query→Manager.TaskOutput(真实输出,
  替换空 {})。
- server.go:新增 SubagentAuditor/TaskOutputStore 接口 + Deps.Scheduler/Tasks/Streaming。
- orchestration/auditcontext.go:Scheduler.AuditContext(full/tail/keyword)。
- task/manager.go:Manager.TaskOutput(id)。
- backthink:record() 补发 ws.backthink 事件。
- json tag 修正(device_update/reminder/task output 结构体加小写 tag + DeviceUpdate
  补 message),使前端分支能真正渲染。

实机验证:WS 握手 101、task manager ready、iot manager initialized(WithBus 生效)。
go build/vet/test 全绿;TestHubBridgeDeviceUpdate/TestHubBridgeSubagentProgress 覆盖
映射。dev 推至后续版本。
2026-09-06 15:53:48 +08:00

96 lines
4.2 KiB
Go

// Package iot provides the IoT device source abstraction for Cyrene v2.
//
// It models smart-home devices (light, AC, curtain, sensor, lock) as typed
// devices managed by pluggable providers. A Provider is a device source; the
// Manager selects a provider from configuration, exposes List/Control/Status,
// and publishes device state transitions as events on the in-process bus so
// other subsystems (frontend, memory, push) observe changes without polling
// (DevDocs/02-development-constraints.md "IoT 设备状态 bus 事件驱动").
//
// Milestone M5 delivers the pluggable provider skeleton plus a mock provider
// with eight simulated devices and the control_rules that bound what each device
// type supports. Real provider integrations (MQTT, Home Assistant) plug in later
// through the same Provider interface.
package iot
import "errors"
// DeviceUpdateSubject is the bus subject on which device state transitions are
// published. Subscribers use this subject (or "iot.>") to observe device
// changes.
const DeviceUpdateSubject = "iot.device_update"
// Sentinel errors returned by the iot subsystem. Use errors.Is to test for them.
var (
// ErrDeviceNotFound is returned when a device ID is unknown to every
// registered provider.
ErrDeviceNotFound = errors.New("iot: device not found")
// ErrUnsupported is returned when an action is not supported for a device's
// type (the device type's capability constraint), or when an action
// parameter value is outside the type's rule set.
ErrUnsupported = errors.New("iot: unsupported action")
// ErrOffline is returned when a Control call targets an offline device.
ErrOffline = errors.New("iot: device offline")
// ErrInvalidParam is returned when an action parameter is missing or has the
// wrong type.
ErrInvalidParam = errors.New("iot: invalid parameter")
// ErrUnknownProvider is returned when configuration names an unknown
// provider.
ErrUnknownProvider = errors.New("iot: unknown provider")
// ErrProviderNotRegistered is returned when a device operation is attempted
// but no provider is registered (for example the "none" provider).
ErrProviderNotRegistered = errors.New("iot: no provider registered")
// ErrInvalidArgument is returned when an argument is nil or empty.
ErrInvalidArgument = errors.New("iot: invalid argument")
// ErrDuplicate is returned when a provider with the same name is registered
// twice.
ErrDuplicate = errors.New("iot: provider already registered")
)
// Device is a single smart-home device exposed by a Provider.
type Device struct {
// ID is the globally unique device identifier used to target Control and
// Status calls.
ID string `json:"id"`
// Name is the human-readable device label.
Name string `json:"name"`
// Type is the device kind, for example "light", "ac", "curtain", "sensor" or
// "lock". The type determines the control_rules a Provider enforces.
Type string `json:"type"`
// State holds the device's current key/value state; the keys depend on the
// device Type.
State map[string]any `json:"state"`
// Online reports whether the device is currently reachable.
Online bool `json:"online"`
}
// Clone returns a copy of d with an independent State map, so callers can mutate
// the returned device without affecting the provider's internal state. Values in
// State are copied by reference; the mock provider stores only primitives.
func (d Device) Clone() Device {
cp := d
if d.State != nil {
cp.State = make(map[string]any, len(d.State))
for k, v := range d.State {
cp.State[k] = v
}
}
return cp
}
// DeviceUpdate is the payload published to DeviceUpdateSubject when a device
// state transitions, typically as the result of a Control call.
type DeviceUpdate struct {
// Provider is the name of the provider that owns the device.
Provider string `json:"provider"`
// Device is the post-transition device snapshot.
Device Device `json:"device"`
// Action is the control action that produced the transition.
Action string `json:"action"`
// Params are the optional control parameters.
Params map[string]any `json:"params,omitempty"`
// Message is a human-readable summary of the transition, surfaced to the
// frontend device_update feed.
Message string `json:"message,omitempty"`
}