从设备孤岛到智能协同:SagooIoT场景联动实战解析
做物联网这几年,有个感受越来越强烈:单台设备的智能化已经不是难题,真正难的是让一群设备协同起来。
你可能会说,不就是"温度超过30度就开空调"吗?一个if-else不就搞定了?
听起来很简单。但当你的系统里同时跑着几十个这样的规则,有些依赖设备状态、有些需要定时触发、有些要等前一个动作执行完才能继续------你会发现,事情远没有那么简单。
这篇文章,我想聊聊SagooIoT里的场景联动模块------为什么我们需要它,它是怎么设计的,以及在实际项目中踩过的坑。
一、场景联动到底解决什么问题?
先从一个真实场景说起。
之前在某个智慧楼宇项目里,需求是这样的:当会议室红外传感器检测到有人进入,需要自动打开灯光和空调;如果室外光照度足够,则只开空调不开灯;会议结束后15分钟无人,自动关闭所有设备;同时还要把使用数据上报到能源管理系统。
如果用传统方式写代码实现,大概是这样:
// 伪代码:传统方式处理联动逻辑
func onSensorTrigger(roomID string, event SensorEvent) {
if event.Type == "人进入" {
light := getLightStatus(roomID)
outdoorLight := getOutdoorLightLevel()
if outdoorLight < threshold {
turnOnLight(roomID)
}
turnOnAC(roomID, 26)
startOccupancyTimer(roomID, 15*time.Minute)
}
// ... 还有各种边界情况
}
问题出在哪?需求一变,就得改代码、重新编译、重新部署。 而且这样的逻辑散落在各个服务里,时间长了谁也理不清楚。
场景联动要解决的核心问题就一个:把"设备之间怎么配合"这个决策权,从代码里拿出来,交给配置。 让运维人员、项目交付人员能够直接在平台上编排设备行为,而不是每次都找开发改代码。
二、SagooIoT场景联动的设计思路
SagooIoT的场景联动模块在设计上遵循几个原则:
2.1 三种触发方式,覆盖主要场景
手动触发:适合需要人工确认的场景。比如"一键巡检"------运维人员点击按钮,系统依次检查所有关键设备状态并生成报告。
设备触发:这是最常见的模式。设备上报数据后,根据预设条件判断是否需要执行联动动作。比如温度传感器值超过阈值 → 触发告警 + 启动备用制冷设备。
定时触发:基于cron表达式的时间调度。比如每天凌晨2点自动备份设备配置,每天早上8点自动巡检。
这三种模式可以组合使用------你可以设置一个"定时触发 + 条件判断 + 设备动作"的复合场景。
2.2 串行与并行,灵活编排
这个设计决策花了不少心思。在实际场景中,有些动作必须按顺序执行,有些则可以并行。
举个例子:设备初始化流程。先下发配置参数(串行),然后同时启动数据上报和状态监控(并行)。如果配置下发失败,后面的动作都不应该执行。
SagooIoT的场景联动支持在规则配置中指定执行模式:
场景:设备批量初始化
├── 步骤1:下发基础配置 ← 串行(必须先完成)
├── 步骤2:启动数据上报 ← 并行
├── 步骤3:启动状态监控 ← 并行
└── 步骤4:注册到监控中心 ← 串行(等步骤2、3都完成后执行)
这种编排能力让复杂的设备管理流程变得可配置、可追溯。
2.3 不止设备到设备,还有设备到业务
很多物联网平台只做"设备到设备"的联动,但实际项目中经常需要"设备到业务系统"的联动。
比如:某个工业设备的关键参数异常 → 不只是告警,还要在工单系统里自动创建维修工单,同时发送企业微信通知给负责人。
SagooIoT在这方面做了扩展:联动动作不限于设备操作,还可以是HTTP回调、消息队列推送、数据库写入等。这让场景联动真正连接了OT和IT。
三、核心实现剖析
来看看场景联动模块的关键代码结构。SagooIoT的场景联动核心定义了几个关键模型:
3.1 场景模型
// 场景定义
type SceneInfo struct {
Id int64 `json:"id"`
Name string `json:"name"` // 场景名称
TriggerType int `json:"triggerType"` // 1手动 2设备触发 3定时
Status int `json:"status"` // 1启用 0禁用
Conditions string `json:"conditions"` // 触发条件(JSON)
Actions string `json:"actions"` // 执行动作(JSON)
Mode string `json:"mode"` // serial/parallel
Cron string `json:"cron"` // 定时表达式
Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
}
3.2 条件引擎
条件判断是整个联动流程的核心。SagooIoT采用了灵活的表达式引擎,支持多种操作符:
// 条件示例:温度>35 且 湿度<30
{
"logic": "and",
"conditions": [
{
"deviceId": "sensor_temp_01",
"property": "temperature",
"operator": "gt",
"value": 35
},
{
"deviceId": "sensor_humidity_01",
"property": "humidity",
"operator": "lt",
"value": 30
}
]
}
支持的运算符包括:大于、小于、等于、不等于、介于、包含、正则匹配等,基本覆盖了物联网场景下常见的判断需求。
3.3 动作执行器
动作执行采用策略模式设计,每种动作类型有独立的执行器:
// 动作执行器接口
type ActionExecutor interface {
Execute(ctx context.Context, action ActionConfig) error
Type() string
}
// 设备控制执行器
type DeviceActionExecutor struct {
deviceService DeviceService
}
func (e *DeviceActionExecutor) Execute(ctx context.Context, action ActionConfig) error {
return e.deviceService.SendCommand(ctx, action.DeviceId, action.Command)
}
// HTTP回调执行器
type HttpActionExecutor struct {
httpClient *http.Client
}
func (e *HttpActionExecutor) Execute(ctx context.Context, action ActionConfig) error {
// 向外部系统发送HTTP请求
return e.sendRequest(ctx, action.Url, action.Payload)
}
这种设计让新增动作类型变得非常简单------实现接口、注册到执行器工厂即可。插件化的执行器体系也意味着你可以通过SagooIoT的插件系统,用任意语言编写自定义的动作执行器。
3.4 串并行调度
执行模式的控制是串联起整个流程的关键。简化后的调度逻辑:
func (s *SceneEngine) executeActions(ctx context.Context, scene *SceneInfo) error {
actions := s.parseActions(scene.Actions)
if scene.Mode == "serial" {
// 串行:一个接一个执行,失败即停止
for _, action := range actions {
if err := s.executeAction(ctx, action); err != nil {
log.Errorf(ctx, "action failed: %v", err)
return err
}
}
} else if scene.Mode == "parallel" {
// 并行:使用errgroup并发执行
g, ctx := errgroup.WithContext(ctx)
for _, action := range actions {
action := action
g.Go(func() error {
return s.executeAction(ctx, action)
})
}
return g.Wait()
}
return nil
}
对于更复杂的场景,SagooIoT支持混合模式------先串行执行一组初始化动作,成功后再并行执行后续动作。这在设备批量初始化、固件升级等场景中非常实用。
四、真实场景实战
说再多不如看几个实际落地的场景。下面这几个都是从真实项目中提炼出来的。
场景一:智慧农业大棚自动控制
需求:大棚内温度超过32度且光照强度大于50000lux时,自动开启遮阳网和通风系统;当CO2浓度低于400ppm时,自动开启CO2补充设备;所有操作记录同步到数据大屏。
配置:
-
触发类型:设备触发(温度传感器 + 光照传感器 + CO2传感器)
-
条件逻辑:组合条件,各条件独立判断
-
执行模式:并行(遮阳网、通风、CO2补充互不依赖)
-
联动动作:设备控制(继电器开关)+ 数据上报
这个场景的巧妙之处在于,不同的环境参数触发不同的设备动作,但彼此之间不需要严格的先后顺序。并行执行减少了响应延迟,在农业场景中,几分钟的延迟可能就是作物受损的差别。
场景二:工业园区能耗预警
需求:每天早上9点、下午3点定时巡检所有重点能耗设备;如果某设备实时功率超过额定值的120%,立即告警并降低该设备所在区域的其他非关键设备功率。
配置:
-
触发类型:定时(cron: 0 9,15 * * *)+ 设备触发(功率阈值)
-
条件逻辑:多级条件(巡检 + 阈值判断 + 关联设备查询)
-
执行模式:串行(先巡检 → 判断 → 再执行降功率)
这个场景展示了定时触发和设备触发的组合使用,以及如何利用串行模式确保操作的安全性------必须确认功率确实超标之后,才执行降功率操作。
场景三:设备全生命周期管理
需求:新设备首次上线 → 自动注册、下发初始配置、开启数据上报;设备离线超过30分钟 → 发送短信告警给运维;设备固件有新版本 → 自动标记待升级,在凌晨低负载时段批量升级。
-
触发类型:设备触发(上线事件、离线事件、版本上报)
-
执行模式:混合(注册流程串行,告警和通知并行)
-
联动动作:设备配置 + 短信通知 + OTA升级任务创建
这个场景把场景联动和SagooIoT的其他几个核心模块(设备管理、远程配置、OTA升级)串联了起来,真正体现了平台一体化的价值。
五、开发中踩过的几个坑
5.1 循环触发
这是场景联动里最经典的问题。场景A触发场景B,场景B又触发场景A,形成死循环。
SagooIoT的处理方式是:为每次联动执行分配一个traceId,在执行动作之前检查该traceId是否已经触发过目标场景。如果检测到循环,直接中断并记录告警日志。
// 循环检测
func (s *SceneEngine) checkLoop(ctx context.Context, traceId string, sceneId int64) bool {
key := fmt.Sprintf("scene_loop:%s:%d", traceId, sceneId)
exists, _ := s.redis.Exists(ctx, key).Result()
if exists > 0 {
log.Warnf(ctx, "scene loop detected: sceneId=%d, traceId=%s", sceneId, traceId)
return true
}
s.redis.SetEX(ctx, key, "1", 5*time.Minute)
return false
}
5.2 执行超时
联动动作可能涉及网络请求、设备通信等耗时操作。如果不设超时,一个设备响应慢可能拖垮整个场景。建议每个动作单独设置超时时间,并做好超时后的补偿机制。
5.3 条件的时序问题
设备数据上报有时间差。比如"温度>35度 AND 湿度<30%"这个条件------温度先达到了但湿度数据还是5分钟前的旧数据。对于这种情况,建议在条件配置中指定数据的有效时间窗口,超过窗口的数据不参与判断。
六、和规则引擎的区别
经常有人问我,场景联动和规则引擎有什么区别?功能上确实有交集,但定位不同。
规则引擎更偏向数据处理:接收数据 → 清洗转换 → 路由分发 → 持久化存储。它的核心是数据流。
场景联动更偏向设备协同:事件触发 → 条件判断 → 动作编排 → 结果反馈。它的核心是行为流。
两者在SagooIoT里是互补关系。规则引擎负责把原始设备数据处理成结构化、有价值的信息,场景联动基于这些信息驱动设备做出响应。一个管数据,一个管行为。
七、总结
场景联动看似简单,实则是物联网平台"智能化"程度的关键标尺。一个平台能不能快速响应业务变化,能不能让非开发人员配置自动化流程,在很大程度上取决于场景联动模块的设计。
SagooIoT的场景联动经过多个项目的打磨,在灵活性和可靠性之间找到了一个还不错的平衡点:
-
三种触发方式覆盖了绝大多数场景
-
串并行混合编排支持复杂的执行流程
-
设备到设备的直连 + 设备到业务的桥接
-
插件化的执行器架构,扩展成本低
说到底,物联网的价值不在于连接了多少设备,而在于这些设备能协同起来创造什么。场景联动,就是让设备从"能连上"走向"能干活"的那一步。
SagooIoT(沙果物联网系统)是一个企业级开源物联网平台,基于Go语言开发,支持多协议设备接入、物模型管理、规则引擎、可视化大屏、流媒体服务等能力。项目地址: https://github.com/sagoo-cloud/sagooiot