Skip to content

业务逻辑开发手册

基本概念

服务

服务是业务逻辑的配置单元。 开发页面点击发布后服务开始运行,在服务列表页可以手动停止或重新开启运行中的服务。 服务的配置有基本配置、变量、节点。 服务的基本配置(服务名、描述)在服务列表页修改。 服务的变量、节点在开发页面修改。

类型系统

逻辑开发系统使用的类型系统是JSON的超集,节点间传递和使用的值都遵从此类型系统。 值有以下类型:

  • null:与JSON相同
  • boolean:与JSON相同
  • number:与JSON相同
  • string:与JSON相同
  • array:与JSON相同
  • map:与JSON的object相同
  • time:时间类型,不能从JSON中转换得到,转换为JSON时为字符串

变量

在业务逻辑开发页面上侧的操作栏中找到变量配置按钮,点击之后会出现变量配置页面,每个应用对应存在着不同的变量配置。添加完变量后可以在对应的服务中使用该变量。 image.png

添加变量 点击添加变量按钮出现侧边栏 image.png

配置项说明
变量名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过50个字符。
变量类型设置变量的作用域
  • 全局:此变量所有的服务都能可以访问到
  • 局部:此变量只有当前的服务可以访问到 | | 数据类型 | 设置该变量的类型
  • **Num (数值型):**输入数字,可以包含小数点
  • **String (字符型):**输入字符串数据
  • **Boolean (布尔型):**设置为true或者false
  • **Time (时间型):**可选择具体时间点
  • **JSON (结构体):**可设置JSON格式的数据 | | 默认值 | 可选:
  • 设置该变量的默认值 | | 描述 | 可选:
  • 对该变量的描述
  • 字符长度最长为200 |

节点

节点是服务的功能单元。 在开发页面中,节点之间以相互串联的形式组成服务的运行流程。 HTTP触发设备触发定时触发为输入节点,每个服务都必须有一个输入节点,且只能有一个输出节点。 当输入节点是HTTP触发时,可以使用HTTP返回配置触发服务时HTTP请求的返回,否则HTTP返回不可用。

节点输出

有些节点会将其计算、运行的结果输出,供其他节点使用。

实例

每当服务的输入节点所设定的时间发生时,称服务被触发。每次服务触发时都会产生一个触发实例。 触发实例按照节点所配置的流程进行操作和运算,当执行到某个节点后找不到下一个节点时,实例运行结束。 如果触发实例产生时,当前服务下还有其他实例未结束,则服务的多个实例可以同时存在。 在实例运行时,如果产生错误(例如引用了不存在的输出、数值计算类型不匹配),实例会直接中止运行。 为了防止逻辑错误导致服务陷入死循环,当实例运行超过1000个节点后也会直接中止运行。

功能介绍

按照不同节点的功能,我们将节点分成了 6 个分组

输入节点

定时触发

在规定的时间内触发整个服务的运行。 定时触发节点用于设置时间,使服务在指定时间执行。常用于定时推送消息、定时执行任务、定时提醒、定时触发设备等场景。每个业务服务仅支持使用一个触发类型的节点。

节点配置

在业务逻辑编辑页面的** 输入列表 中,拖拽定时触发**节点到中间画布进行节点配置,如图所示 image.pngimage.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
触发配置触发配置是一个数组,可以设置多个触发规则,当任意一项触发规则满足时,即可触发流程,该选项目前最多支持十个条件
触发模式
  • **单次触发 **(只触发一次):选择具体触发时间, 精确到分钟。
  • 间隔固定分钟数触发:按分钟间隔进行规则出发,需要设置具体的时间间隔分钟数,范围 1~59分钟。
  • 间隔固定小时数触发:按小时时间间隔进行规则触发。需设定具体的时间间隔小时数,范围:1-23小时。
  • 每天固定时间触发一次:每天的固定时间进行触发。需设定具体的触发时间点,可精确到秒。
  • **每周固定时间触发:**每周的固定时间进行触发。需选择周内触发日期(工作日、周末或周内某天)和具体的触发时间点,可精确到秒。
  • **每月固定时间触发:**每月的固定时间进行触发。需选择月内具体日期(或选择最后一天)和具体触发时间点,可精确到秒。
  • 选择生效时间。
  • 选择结束时间。不设置该内容为永久生效。 | | 执行时间 | 需要流程触发的时间
  • 单次触发模式时,该选项为具体的日期时间( yyyy-mm-dd hh:mm:ss )
  • 以天为间隔触发时,该选项为具体的时间( hh:mm:ss )
  • 以周为间隔触发时,该选项为具体的星期加**时间 **(星期几 hh:mm:ss)
  • 以月为间隔触发时,该选项为具体的号数加**时间 **(几号 hh:mm:ss) | | 循环间隔 | 循环的间隔时间
  • 以分钟为间隔触发时,该选项为**分钟数值 **(1~59)
  • 以小时为间隔触发时,该选项为**小时数值 **(1~24) | | 生效时间 | 必填:当模式不为单次触发时显示,循环开启的时间 ( yyyy-mm-dd hh:mm:ss ) | | 结束时间 | 可选:当模式不为单次触发时显示,循环结束的时间 ( yyyy-mm-dd hh:mm:ss ) |
节点输出

节点的输出为触发时间。

触发模式的一些细节
  1. 间隔固定分钟数、间隔固定小时数触发时,生效时间不仅用于限定触发时间,还用于决定触发时间具体的时、分、秒。例如生效时间是某天0点,间隔2小时触发,则实际的触发时间是0点、2点、4点……对于其他触发模式,则需要手动指定具体的时、分、秒,生效时间只用于限定触发时间。
  2. 每月固定时间触发时,如果选择29、30、31日,但某月没有29、30、31日,则当月不触发
触发丢失

如果服务器停机维护,本应在停机期间定时触发的服务,在服务器重启后不会补齐

触发延迟

受服务器负载影响,实际触发时间可能会比预定时间略有延迟,延迟期间额外的触发计划不会补齐。例如预定某日00:00:00触发,实际触发时间可能是00:00:05,即使00:00:01还有一次预定触发,则00:00:05也触发一次。

设备触发

每当设备上报信息时,触发服务的运行。 设备触发节点是将设备上报的属性(读写型)、事件数据或状态变更作为服务的输入,触发服务后续的业务逻辑。设备触发节点支持通过虚拟设备上报属性或事件触发服务,帮助您自定义设备信息响应的服务流。

节点配置

在业务逻辑开发编辑页面的 输入列表 中,拖拽 **设备触发节点 **到中间画布并进行配置,如图所示。 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
设备类型选择需要的设备所属的类型
设备可选:
根据所属的设备类型选择设备,注意如果不选择设备的时候,当该类型分组下任意一个设备触发时都会触发此节点
设备点位可选:
触发流程的点位 (注意:设备点位与状态之间至少需要选择一项)
状态可选:
触发流程的状态 (注意:设备点位与状态之间至少需要选择一项)
节点输出

节点输出为map,字段如下:

字段名类型说明
deviceTypeIDstring触发设备的设备类型ID
deviceIDstring触发设备的设备ID
timetime触发事件
statestring状态触发时,触发设备最新上报的状态
pointIDstring点位触发时,触发设备最新上报的点位ID
valueunknown点位触发时,触发设备最新上报的点位值

节点可以同时配置关心的点位和状态,但实例每次只能由点位或状态二者之一触发。点位触发时,节点输出包含pointIDvalue字段;状态触发时,节点输出包含state字段。也即pointID+value字段和state字段不会同时出现。如果同时配置了点位和状态,可以用“条件判断”或“路径选择”节点,比较相关字段是否为null来判断当前实例的触发类型。

HTTP 触发

通过 HTTP 请求触发规则。 HTTP触发节点是创建API服务的开始节点,通过该节点可配置API的请求参数和SDK调用时的Action。每个API有且仅有一个HTTP触发节点,中间逻辑节点可根据业务需要选择其他功能节点,但必须以HTTP返回节点作为终止节点。

节点配置

在业务逻辑开发编辑页面的 输入列表 中,拖拽HTTP触发节点到中间画布并进行配置,如图所示。 image.png

配置项说明
节点名称支持中文汉字、英文字母、数字和下划线(_)。长度不超过30个字符。
认证方式
  • 是否需要做账号鉴权的选项
  • :不使用鉴权
  • **bearer:**使用 token 进行鉴权
  • **basic:**使用用户名与密码的方式进行鉴权 | | token | 当认证方式为 bearer 时必须填写 | | 用户名 | 当认证方式为 basic 时必须填写 | | 密码 | 当认证方式为 basic 时必须填写 |
节点输出

节点的输出即是触发请求的请求体。其类型依赖于触发请求的Content-Type请求头。 如果Content-Typetext/plain,则请求体整体被解析为字符串,再作为节点输出。 如果Content-Typeapplication/json,则请求体整体被解析为JSON,再作为节点输出。 如果请求头不含Content-Type,请求体被忽略,节点输出为null。 除此之外的Content-Type都会导致HTTP响应406 Not Acceptable。

请求认证

节点可以设置Basic或Bearer认证:

  • Basic:触发请求应有请求头Authorization: Basic {token},其中{token}应为节点指定的用户名、密码,中间以冒号拼接后,再用base64编码的字符串。例如用户名为hello,密码为world,则{token}应替换为hello:world的base64编码,即aGVsbG86d29ybGQ=
  • Bearer:触发请求应有请求头Authorization: Bearer {token},其中{token}应为节点指定的token

未设置认证时,请求不需要Authorization请求头,如有则被忽略。 认证未通过会导致HTTP响应401 Unauthorized。

触发请求

向平台发送如下HTTP请求即可触发:

POST /logic/api/v1/service/{service_id}/trigger

其中{service_id}替换为服务的ID。

触发成功时,触发请求的响应码一般由HTTP响应节点指定。以下响应码有可能不是由HTTP返回节点指定的:

响应码原因
201 Created①实例已完成,但没有终止于HTTP返回节点
②实例在一定的时间内没有终止
③实例由于运行节点数超过限制而被中止
400 Bad Request无法按照指定的Content-Type解析请求体
401 Unauthorized节点开启了认证,但Authorization不存在或与没有通过认证
404 Not Found指定的服务不存在,或服务没有HTTP触发节点
406 Not AcceptableContent-Type不是text/plain或application/json
413 Request Entity Too Large请求体过大
500 Internal Server Error服务器内部错误
502 Bad Gateway服务器内部错误
503 Service Unavailable服务器内部错误
504 Gateway Timeout服务器内部错误

MQTT订阅

MQTT 订阅到的消息将以 JSON 格式解析作为节点输出。 MQTT 订阅节点所使用的连接与 MQTT 发布节点相互独立。 MQTT 订阅节点使用30秒心跳,短线重连。如果初始连接失败超过10次(不包括断线重连),则当前服务不再重试连接,必须重新发布服务才会重新开始连接。

输出节点

HTTP返回

配置 HTTP 请求返回的数据,返回码等内容。 HTTP返回节点可配置为业务服务的结束节点。使用HTTP请求节点配置HTTP接口时,可以使用HTTP返回节点作为结束节点,来配置HTTP请求的返回值。 HTTP返回节点必须搭配HTTP请求节点使用,如果服务的输入节点不是HTTP请求,则不能使用HTTP返回节点。

节点配置

在业务逻辑编辑页面的节点中,选择对应功能节点配置业务流,HTTP返回节点配置页面如下图所示。 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
返回码HTTP返回的状态码,值区间为 [100, 600]
输出设置API返回参数为:
  • 不同数据类型的固定值。
  • 来自节点的值。
    • 上一个节点输出的payload的子属性。
    • 某个前置节点的输出对象payload或其子属性。
    • API的请求参数。
  • 变量值 (已添加的变量):添加变量的详细内容请参见添加变量。 |

功能节点

条件判断

根据选择的数据进行 if-else 判断以分配后续路径。 条件判断节点根据设定的条件对输入值进行判断,再根据判断结果执行不同的路径。条件判断结果产生两个路径:满足条件的路径和不满足条件的路径。

节点配置

在业务逻辑编辑页面的节点列表中,选择** 功能列表** 下的 条件判断 节点,条件判断节点配置页面如下图所示。 image.pngimage.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
条件配置单击 **添加条件 **添加判断条件,您可以为当前节点添加多个条件。
  • 用于比较的数据源,可设置为:
    • 常量:静态数据。可选数据类型:
      • 数值型:输入数字,可以包含小数点。
      • 布尔值:设置为true或者false。
      • 字符串:输入字符串数据。
      • 时间型:可选择具体时间点。
      • JSON:可以用名称或值对的方式来表达复杂的数据格式,需要采用JSON格式书写。对象可以包含多个名称或值对。例如{ "firstName":"John" , "lastName":"Doe" }
    • 来自节点:设置节点的值。
      • 上一节点(payload):需结合上一个节点的输出数据格式。可以手动输入上一个节点的变量名称,则调用该变量对应的值;如果不填写变量,则返回上个节点的默认值或全部返回值。
      • 本节点之前的任一节点的某个参数。
    • 变量:设置为已添加的变量。有关变量配置的详细内容,请参见变量配置
  • 比较方式:大于、大于等于、小于、小于等于、等于、不等于、为空、非空。 | | | 支持调整条件的前后顺序,配置条件之间的满足关系。
  • AND:条件都满足时,判断为true;否则,判断为false。
  • OR:满足任意一个条件时,判断为true;当所有条件均不满足时,判断为false。

有多个条件时,从上至下依次进行布尔运算,得出最终的运算结果为true则执行满足条件的分支,为false则执行不满足条件的分支。 例如:条件1为true,条件2为false,条件3为true,依次设置条件关系为ANDOR,则true&& false || true的运算结果为true,即执行满足条件的分支。 image.png |

路径选择

路径选择节点可以根据设定的规则,对数据源进行判定,从而执行不同路径逻辑。当输入值满足路径1的条件时,执行路径1;不满足时,继续判断路径2的条件;当所有条件都不满足时执行路径else的条件。

使用场景

如果需要对前一节点的内容输入值做出判断,并根据判断结果执行不同的逻辑,则可以使用路径选择节点。典型使用场景如下所示。image.pngimage.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
输入路径选择的条件均需与输入的数据源进行对比。
  • 常量:静态数据。可选数据类型: - 数值型:输入数字,可以包含小数点。 - 布尔值:设置为true或者false。 - 字符串:输入字符串数据。 - 时间型:可选择具体时间点。 - JSON:可以用名称或值对的方式来表达复杂的数据格式,需要采用JSON格式书写。对象可以包含多个名称或值对。例如{ "firstName":"John" , "lastName":"Doe" }
    • 来自节点:设置节点的值。
      • 上一节点(payload):需结合上一个节点的输出数据格式。可以手动输入上一个节点的变量名称,则调用该变量对应的值;如果不填写变量,则返回上个节点的默认值或全部返回值。
      • 本节点之前的任一节点的某个参数。
    • 变量:设置为已添加的变量。有关变量配置的详细内容,请参见变量配置。 | | 路径配置 |
  • 节点上默认没有任何路径,当创建了一个路径之后自动产生 else 路径,路径内选项存在比较选项
  • 比较条件: ><>=<===!=null(为空)、!null(非空)、truefalse
  • 比较数据源:
    • 当 **比较条件 **为 null、!null、true、false 时不需要选择被比较的数据源
    • 比较条件 为其他情况需要选择被比较的数据源,格式与输入节点相同
  • 其他路径 (else):当所有条件不满足时执行此条路径 |

条件判断

条件判断节点根据设定的条件对输入值进行判断,再根据判断结果执行不同的路径。条件判断结果产生两个路径:满足条件的路径和不满足条件的路径。

节点配置

在业务逻辑编辑页面的节点列表中,选择** 功能列表** 下的 条件判断 节点,条件判断节点配置页面如下图所示。 image.pngimage.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
条件配置单击 **添加条件 **添加判断条件,您可以为当前节点添加多个条件。
  • 用于比较的数据源,可设置为:
    • 常量:静态数据。可选数据类型:
      • 数值型:输入数字,可以包含小数点。
      • 布尔值:设置为true或者false。
      • 字符串:输入字符串数据。
      • 时间型:可选择具体时间点。
      • JSON:可以用名称或值对的方式来表达复杂的数据格式,需要采用JSON格式书写。对象可以包含多个名称或值对。例如{ "firstName":"John" , "lastName":"Doe" }
    • 来自节点:设置节点的值。
      • 上一节点(payload):需结合上一个节点的输出数据格式。可以手动输入上一个节点的变量名称,则调用该变量对应的值;如果不填写变量,则返回上个节点的默认值或全部返回值。
      • 本节点之前的任一节点的某个参数。
    • 变量:设置为已添加的变量。有关变量配置的详细内容,请参见变量配置
  • 比较方式:大于、大于等于、小于、小于等于、等于、不等于、为空、非空。 | | | 支持调整条件的前后顺序,配置条件之间的满足关系。
  • AND:条件都满足时,判断为true;否则,判断为false。
  • OR:满足任意一个条件时,判断为true;当所有条件均不满足时,判断为false。

有多个条件时,从上至下依次进行布尔运算,得出最终的运算结果为true则执行满足条件的分支,为false则执行不满足条件的分支。 例如:条件1为true,条件2为false,条件3为true,依次设置条件关系为ANDOR,则true&& false || true的运算结果为true,即执行满足条件的分支。 image.png |

JS脚本

JS 脚本支持 ECMAScript 5.1 语法规则,不支持引入第三方库。

节点输出

如果脚本执行成功,脚本中最后一个表达式的值将成为节点的输出。 JavaScript类型和输出类型的对应关系:

JavaScript类型(typeof)输出类型
undefinednull
symbolstring
stringstring
numbernumber
bigintnumber
functionstring,值为"[Function $name]"
booleanboolean
object
  1. 如果值为null,输出null
  2. 如果是特定实例,按对应类型输出
  3. 否则,输出map,键为其getOwnPropertyNames |

特定实例如下:

object实例类型(instanceof)输出类型
Numbernumber
Booleanboolean
Stringstring
Datetime
Arrayarray
Mapmap
Setmap,值皆为null
Errorstring,值为toString()返回的字符串
全局变量

payload

typescript
const payload: unknown

全局变量payload为上一节点的输出。上一节点无输出时,payloadnull

query

typescript
const query: unknown

全局变量query为输入节点的输出。

node

typescript
const node: { [nodeID: string]: unknown }

全局变量node为一个object,其键为已运行节点的ID,值为对应节点的输出。对应节点无输出时,值为null。节点未运行时,node上不存在节点的键。

javascript
// 节点 node_25f6e4 的输出为 { "rows": [{ "id": 1 }] }
console.log(node.node_25f6e4.rows[0].id); // 引用节点 node_25f6e4 的输出并打印到日志

env

typescript
const env: { [variable: string]: unknown }

全局变量env为一个Proxy,以变量名(包括局部变量和全局变量)为键可以查询变量的值,变量的值可以在代码中修改。

javascript
console.log(env.variable1); // 查询变量 variable1 的值并打印到日志
env.variable1 = 1; // 将变量 variable1 的值设为 1

kv

typescript
const kv: { [key: string]: unknown }

全局变量kv为一个Proxy,用于访问键值储存。用法和env类似,并可以删除特定键。

javascript
console.log(kv.running); // 查询键 running 的值并打印到日志
kv.running = 1; // 将键 running 的值设为 1
delete kv.running; // 删除键 running

console

typescript
const console: {
  log: (...args: any[]) => void;
  debug: (...args: any[]) => void;
  info: (...args: any[]) => void;
  warn: (...args: any[]) => void;
  error: (...args: any[]) => void;
}

全局变量console为一个object,其方法debuginfo(等同于log)、warnerror可以用于向日志打印消息。上述方法会将每个参数转为字符串,以空格为间隔拼接。 注意:这个对象与 Web APInode.js 不兼容device

typescript
const device: { [deviceID: string]: Device }

type Device = {
  point: DevicePointAPI;
  prop: DevicePropertyAPI;
}

interface DevicePointAPI {
  get(this: DevicePointAPI, pointID: string): unknown;
  get(this: DevicePointAPI, ...pointIDs: string[]): { [pointID: string]: unknown };
  set(this: DevicePointAPI, pointID: string, value: any): void;
  set(this: DevicePointAPI, Map<string, any> | { [pointID: string]: any }): void;
}

interface DevicePropertyAPI {
  get(this: DevicePropertyAPI, propertyID: string): unknown;
  get(this: DevicePropertyAPI, ...propertyIDs: string[]): { [propertyID: string]: unknown };
}

全局变量device为一个Proxy,以设备ID为键可以获取到对应设备的Device对象。获取Device对象时,不会检查设备是否存在。 Device对象为绑定到某台具体设备的一个object。其属性point为同一设备的DevicePointAPI对象,prop为同一设备的DevicePropertyAPI对象。 DevicePointAPI对象为绑定到某台具体设备的一个object,用于访问其点位。方法get传入点位ID用于查询一个点位值,传入多个点位ID时返回值为object形式。方法set传入点位ID和点位值时用于下发点位值,传入一个object时将以其键为点位ID、值为点位值一次性下发多个点位值,也可以传入一个MapDevicePropertyAPI对象和DevicePointAPI对象类似,用于访问设备的属性。目前只能查询而不能修改。

javascript
// 查询设备 SD1 点位 V1 的值
console.log(device.SD1.point.get('V1')); // => 42.0
// 查询设备 SD1 点位 V1、V2 的值
console.log(device.SD1.point.get('V1', 'V2')); // => { "V1": 42.0, "V2": 11.5 }
// 下发设备 SD1 点位 V1 的值
device.SD1.point.set('V1', 60);
// 下发设备 SD1 点位 V1、V2 的值
device.SD1.point.set({ V1: 60, V2: 45 });
// 查询设备 SD1 属性 label 的值
console.log(device.SD1.prop.get('label')); // => "golden"
// 查询设备 SD1 属性 label、state 的值
console.log(device.SD1.prop.get('label', 'state')); // => { "label": "golden", "state": "ready" }

Python脚本

Starlark 语言是Python语言的一门方言。本系统中使用的 Python 脚本基于 Starlark 实现。

脚本结构

如果脚本中定义了一个名为main的函数,则执行脚本后会调用一次此函数(不带任何参数),其返回值转换为为节点的输出。

python
def main():
    # ......

如果脚本中没有定义main的函数,则执行脚本后节点没有输出。

类型关系
Starlark类型(type)输出类型
NoneTypenull
stringstring
int、floatnumber
functionstring,值为"[function $name]"
builtin_function_or_methodstring,值为"[function $name]"
dictmap,仅包含string类型键的值
setmap,值皆为null
list、tuplearray
modulestring,值为"[module $name]"
输入类型Starlark类型(type)
nullNoneType
stringstring
number如果包含小数部分则为float,否则为int
mapdict
arraylist
timeint,值为对应的毫秒级UNIX时间戳
全局变量

payload 全局变量payload为上一节点的输出。上一节点无输出时,payloadNonequery 全局变量query为输入节点的输出。 node 全局变量node为一个dict,其键为已运行节点的ID,值为对应节点的输出。对应节点无输出时,值为None。节点未运行时,node上不存在节点的键。

python
# 节点 node_25f6e4 的输出为 { "rows": [{ "id": 1 }] }
log.debug(node["node_25f6e4"]["rows"][0]["id"]); # 引用节点 node_25f6e4 的输出并打印到日志
内置模块和函数

env

  • env.get查询变量(包括局部变量和全局变量)
  • env.set修改变量
python
log.debug(env.get("variable1")) # 查询变量 variable1 的值并打印到日志
env.set("variable1, 1) # 将变量 variable1 的值设为 1

kv

  • kv.get查询键值储存
  • kv.set修改键值储存
  • kv.delete删除键
python
log.debug(kv.get("running")) # 查询键 running 的值并打印到日志
kv.set("running", 1) # 将键 running 的值设为 1
kv.delete("running") # 删除键 running

log

  • log.debug
  • log.info
  • log.warn
  • log.error

打印日志。 device

  • device.get查询点位值。参数为设备标识、点位标识(可以多个)。查询多个点位时返回dict。
  • device.set下发点位值。下发单个点时,参数为设备标识、点位标识、值;下发多个点时,参数为设备标识、点位标识和值的dict。
  • device.prop查询属性值,与查询点位值类似
python
# 查询设备 SD1 点位 V1 的值
log.debug(device.get('SD1', 'V1')) # => 42.0
# 查询设备 SD1 点位 V1、V2 的值
log.debug(device.get('SD1', 'V1', 'V2')) # => { "V1": 42.0, "V2": 11.5 }
# 下发设备 SD1 点位 V1 的值
device.set('SD1', 'V1', 60)
# 下发设备 SD1 点位 V1、V2 的值
device.set('SD1', { "V1": 60, "V2": 45 })
# 查询设备 SD1 属性 label 的值
log.debug(device.prop('SD1', 'label')) # => "golden"
# 查询设备 SD1 属性 label、state 的值
log.debug(device.prop('SD1', 'label', 'state')) # => { "label": "golden", "state": "ready" }

数值计算 对来自设备,API等的数据进行数值计算并输出结果 设备操作 对设备进行操作

数值计算

使用数值计算节点,您无需写代码就能实现简单的逻辑运算,例如多个设备属性值相运算、多个设备属性值之间取最大值、最小值、平均值等简单的逻辑运算操作。

节点配置

在业务逻辑编辑页面的** 功能列表** 下,拖拽数值计算节点到中间画布中,进行配置。 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
输入设置可设置为常量(静态数值)、来自节点、变量(已添加的变量)。
**注意:**数值计算节点的数值类型只能是数值型数据,如果选择的数据类型不为数值类型可能会引发错误。
运算方法选择数据源输入值与参数值的计算方法。目前支持的计算能力包含:相加、相减、相乘、相除、最大值、最小值、平均值。
添加参数设置与数据源输入值进行计算的参数值。可以添加多个参数。

设备操作

设备操作节点,主要用于向设备进行查询或下发操作。

节点配置

在业务逻辑编辑页面的** 功能列表** 下,拖拽设备操作节点到中间画布中,进行配置。 image.png

配置项描述
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
设备类型可选:
  • 所需要操作的设备的所属类型 | | 设备 | 所需要操作的设备 | | 操作类型 |
  • 属性查询:对设备上存在的属性结果进行数据查询
  • 点位查询:针对设备上存在的点位进行数据查询
  • 点位下发:针对设备进行数据下发 | | 多选 | 当操作类型为属性查询或点位查询时出现,用于控制 设备属性 与 **设备点位 **是否为多选 | | 设备属性 |
  • 操作类型为属性查询时需要
  • 需要查询的设备属性,当多选为真时该选项为多选 | | 设备点位 |
  • 操作类型为点位查询时需要
  • 需要查询的设备点位,当多选为真时该选项为多选 | | 下发数据 |
  • 操作类型为点位下发时需要
  • 需要设置下发点位
  • 需要设置想要下发数据,详情见下方 **数据选择 **字段 | | 数据选择 |
  • 用于设置需要下发的数据源,可设置为:
    • 常量:静态数据。可选数据类型:
      • 数值型:输入数字,可以包含小数点。
      • 布尔值:设置为true或者false。
      • 字符串:输入字符串数据。
      • 时间型:可选择具体时间点。
      • JSON:可以用名称或值对的方式来表达复杂的数据格式,需要采用JSON格式书写。对象可以包含多个名称或值对。例如{ "firstName":"John" , "lastName":"Doe" }
    • 来自节点:设置节点的值。
      • 上一节点(payload):需结合上一个节点的输出数据格式。可以手动输入上一个节点的变量名称,则调用该变量对应的值;如果不填写变量,则返回上个节点的默认值或全部返回值。
      • 本节点之前的任一节点的某个参数。
    • 变量:设置为已添加的变量。有关变量配置的详细内容,请参见变量配置。 |
节点输出

操作类型为点位查询时,节点包含输出。 单点位查询时,输出为点位值。 多点位查询时,输出为以点位ID为键、点位值为值的map。

操作类型为属性查询时,节点包含输出。 单属性查询时,输出为属性值。 多属性查询时,输出为以属性ID为键、属性值为值的map。

操作类型为点位下发时,节点无输出。

消息节点

短信

MQTT发布

连接池

如果同一业务逻辑下两个MQTT发布节点的数据库地址、端口、用户名、心跳都相同,则它们共用同一条连接。 自连接成功后,只要此连接被使用(包括被建立连接的实例使用,也包括被后续其他实例使用),则连接被保留,即使实例结束也继续保留。如果持续5分钟没有使用,则连接被主动关闭。

消息机器人(暂未提供)

调用 Webhook 向钉钉机器人推送消息。 调用 Webhook 向飞书机器人推送消息。 调用 Webhook 向企业微信机器人推送消息。

API调用节点

自定义API

支持调用外部的API,返回 body 并完整传递给下一节点。

节点配置

在业务逻辑编辑页面的 API调用 > **自定义API **中,选择对应功能节点配置业务流,该节点配置如下。 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
请求方式自定义API的请求方法支持GET、POST、PUT、DELETE、HEAD、OPTIONS、PATCH、TRACE。
API地址需要调用的API的地址。此项为**“模板字符串”**。
请求头以JSON对象格式书写的请求头,其键位请求头名称,值位请求头值的**“模板字符串”**
Body类型(未发布)
  • form-data
  • x-www-form-urlencoded
  • json
  • raw | | 参数编写 | 请求体,此项为**“模板字符串”** | | JSONPath | (未发布)如果不为空,响应体将解析为JSON,按照此路径指定的值成为节点输出 |
模板JSON

模板JSON是“模板字符串”的一种特殊形式。 JSON中object的键被视为模板字符串进行替换。 JSON中object的值,如果是字符串,则被视为模板字符串进行替换。特别地,当此模板字符串中只有一个path时,path对应的值将按对应类型替换到JSON。 例如payload{ x: 3 }

  • { "x": "${payload.x}" }替换后为{ "x": 3 },object 值的部分被替换,且替换为数值。
  • { "${payload.x}": "${payload.x}" }变量替换后为{ "3": 3 },object 键和值的部分被替换,但只有值的部分被替换为数值,键的部分仍为字符串。
  • { "x": "value = ${payload.x}" }变量替换后为{ "x": "value = 3" },object 值的部分被替换,但由于path的两侧还有其他字符,替换后仍为字符串形式。
  • { "x": "${payload.x}${payload.x}" }变量替换后为{ "x": "33" },object 值的部分被替换,但由于存在多个path,因此替换后仍为字符串形式。
节点输出

节点没有输出。 (未发布)如果JSONPath为空,节点没有输出;如果JSONPath不为空,响应体解析为JSON后,按照JSONPath指定的值为节点输出。

数据节点

变量设置

变量设置节点可修改已添加变量值。使用该节点,设置变量值作为当前服务的中间逻辑功能,并将设置后的值作为节点的输出。 节点配置 在业务逻辑编辑页面的 数据 下,拖拽变量设置节点到中间画布中。 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
变量设置添加待修改的变量。

image.png注意: 修改变量只能在同数据类型之间进行修改

数据库操作

使用数据库操作节点对数据库进行 增删改查 等操作。

节点配置

在业务逻辑编辑页面的节点中,选择 **数据库操作 **节点 image.png

配置项说明
节点名称设置节点名称。支持中文、英文字母、数字和下划线(_),长度不超过30个字符
数据库类型目前只支持 MySQL 与 PostgreSQL 两种类型的数据库
用户名数据库的用户名
密码数据库的密码(可选)
连接地址填入实例的数据库外网地址
数据库名需要连接的数据库名称
端口号需要连接的数据库端口
  • MySQL 默认为 3306
  • PostgreSQL 默认为 5432 | | 操作类型 | 需要对数据库进行的操作
  • 查询:查询数据库中的数据
  • 插入:在数据库中插入数据
  • 更新:更新数据库中的数据
  • 删除:删除数据库中的数据 | | 参数 | 根据不同的操作类型,需要我们使用不同的模板写法 |
节点输出

此节点包含输出。输出形式根据操作的不同而不同,详见后文。

连接池

如果同一业务逻辑下两个数据库节点的数据库类型、地址、端口、数据库名、用户名、schema都相同,则它们共用同一条数据库连接。 自数据连接成功后,只要此数据库连接被使用(包括被建立连接的实例使用,也包括被后续其他实例使用),则数据库连接被保留,即使实例结束也继续保留。如果持续5分钟没有使用,则数据库连接被主动关闭。

基础:SqlValue
typescript
type SqlValue =
  | number
  | boolean
  | null
  | Template

SqlValue表示参与运算的值。有以下几种形式:

  • number类型的常量
  • boolean类型的常量
  • null
  • 一个**“模板字符串”**

对于模板字符串,其根据模板本身的格式,有以下几种情况:

  • 字符串常量:如果模板字符串中不包含变量替换,则被视为字符串常量参与运算。如"hello"
  • 模板字符串:如果模板字符串中包含不止一个变量替换,或者除了变量替换之外还有其他字符(包括空格),则在进行变量替换后,以字符串形式参与运算。如"x is ${x}"
  • 模板值:如果模板字符串中有且仅有一个替换,且变量替换的两边没有其他字符(包括空格),则在进行变量替换后,按照替换结果的类型参与运算。如"${ payload.x }"

对于最后一种模板值的情况,如果替换结果为numberbooleannullstringtime类型的值,则使用此值参与运算;如果替换结果为arraymap类型的值,则会再次转换为string类型再参与运算。

基础:Where
typescript
type Where =
  | SqlValue
  | WhereSimpleForm
  | WhereComplexForm

type WhereSimpleForm = { [column: string]: WhereSimpleOperator }

type WhereSimpleOperator =
  | SqlValue
  | { "$eq": SqlValue }
  | { "$neq": SqlValue }
  | { "$gt": SqlValue }
  | { "$gte": SqlValue }
  | { "$lt": SqlValue }
  | { "$lte": SqlValue }
  | { "$like": SqlValue }

type WhereComplexForm =
  | Where[]
  | { "$expr": SqlValue }
  | { "$column": string }
  | { "$and": Where[] }
  | { "$or": Where[] }
  | { "$not": Where }
  | { "$eq": [Where, Where] }
  | { "$neq": [Where, Where] }
  | { "$gt": [Where, Where] }
  | { "$gte": [Where, Where] }
  | { "$lt": [Where, Where] }
  | { "$lte": [Where, Where] }
  | { "$like": [Where, Where] }

Where用于表示SelectUpdateDelete所使用的限定条件。

Where是可选的,如果省略,则生成的语句中没有WHERE

json
{
  "table": "users",
  "row": ["id"]
}
sql
SELECT id FROM users;

Where 设为一个 SqlValueWhere可以直接设为一个SqlValue。 如果这个SqlValuenull,则Where本身被忽略。否则,Where的值将会被数据库隐式转换为布尔值使用,包括模板值替换之后得到的null(被数据库识别为false)。 需要直接使用SqlValue作为Where场景比较少见。一种使用场景是,如果数据库开启了严格检查,不带WHEREUPDATEDELETE语句会报错:

json
{
  "table": "users",
  "where": true
}
sql
DELETE FROM users WHERE true;

Where 的简单键值形式Where的简单键值形式是一个JSON对象,其键为列名,如果值为SqlValue则表示相等:

json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "age": "${ payload.age }"
  }
}
sql
-- payload : {
--   "age": 35,
-- }
SELECT id FROM users WHERE age = 35;

多个键会按照字母表顺序排列后用AND链接。如果有逻辑短路的需求,需要用复杂形式严格指定顺序。

json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "is_manager": true,
    "age": "${ payload.age }"
  }
}
sql
-- payload : {
--   "age": 35,
-- }
SELECT id FROM users WHERE age = 35 AND is_manager = true;

简单键值形式的值可以是一个键为"$eq""$neq""$gt""$gte""$lt""$lte""$like"之一特殊值,值为SqlValue的JSON对象,分别表示等于、不等于、大于、大于等于、小于、小于等于、LIKE

json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "age": {"$gte": 60}
  }
}
sql
SELECT id FROM users WHERE age >= 60;

这种值必须只有一对键值,否则不保证哪一种特殊键被识别。

json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "age": {"$gte": 60, "$lt": 80}
  }
}
sql
SELECT id FROM users WHERE age >= 60;
-- 也有可能是:
-- SELECT id FROM users WHERE age < 80;

简单键值形式的列名不能以美元符号开头,如果有此需求,需要用复杂形式表示。 如果简单键值形式的键值数量为0,则WHERE本身被省略。

Where 的复杂形式 Where的复杂形式是一个JSON对象,其键为美元符号开头的特殊键。 复杂形式只能有一对键值,否则不保证哪一种特殊键被识别。 特殊键如下:

  • "$expr":值应为一个SqlValue,表示一个常量值
  • "$column":值应为一个字符串常量,表示列名
  • "$and""$or":值应为一个嵌套的Where的列表(包括SqlValue、简单键值形式、复杂形式),列表长度不限,表示ANDOR。如果列表长度为0,则不生成SQL表达式。
  • "$not":值应为一个嵌套的Where,表示NOT
  • "$eq""$neq""$gt""$gte""$lt""$lte""$like":值应为一个嵌套的Where的列表(包括SqlValue、简单键值形式、复杂形式),且列表长度必须为2,分别表示等于、不等于、大于、大于等于、小于、小于等于、LIKE
json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "$or": [
      {"is_manager": true},
      {"age": {"$gte": 60}},
      {"$neq": [{"$column": "locked"}, false]}
    ]
  }
}
sql
SELECT id FROM users
WHERE
  is_manager = true OR
  age >= 60 OR
  users."locked" <> false;

这个例子中可以看到,WHERE为使用了特殊键"$or"的复杂形式,前两个嵌套的Where是简单形式,并且没有按字母表顺序重新排序,最后第三个嵌套的Where是复杂形式。

特别地,复杂形式如果是"$and"的话,可以简写为一个列表。 运算符优先级 使用复杂形式的时要注意,需要用嵌套关系来表达运算符优先级关系。

sql
SELECT id FROM users
WHERE
  is_manager = true OR
  age >= 60 AND
  locked = true;

在这个例子中,因为AND的优先级比OR高,所以使用括号显式表达的语句是这样的:

sql
SELECT id FROM users
WHERE
  is_manager = true OR
  (age >= 60 AND locked = true);

代码如下:

json
{
  "table": "users",
  "row": ["id"],
  "where": {
    "$or": [
      {"is_manager": true},
      [
        {"age": {"$gte": 60}},
        {"locked": true}
      ]
    ]
  }
}

如果需要先结合OR两侧的条件,应当显式进行结合:

sql
SELECT id FROM users
WHERE
  (is_manager = true OR age >= 60) AND
  locked = true;
json
{
  "table": "users",
  "row": ["id"],
  "where": [
    {
      "$or": [
        {"is_manager": true},
        {"age": {"$gte": 60}}
      ]
    },
    {"locked": true}
  ]
}

这个例子同时展示了将"$and"简写为列表。

Select
typescript
type Select = {
  'table': string;
  'row': '*' | SelectColumn[];
  'where'?: Where;
  'limit'?: number;
  'offset'?: number;
  'order'?: OrderBy[];
  'group'?: string[];
}

type SelectColumn =
  | Selectable
  | { [alias: string]: Selectable }

type Selectable =
  | string
  | { "$column": string }
  | { "$count": string | null }
  | { "$max": string }
  | { "$min": string }

type OrderBy =
  | string
  | { "$asc": string }
  | { "$desc": string }
json
{
  "table": "users",
  "row": [{"user_id": "id"}, "name"],
  "where": {
    "name": "admin",
    "locked": false
  }
}
sql
SELECT id AS user_id, name FROM users WHERE name = 'admin' AND locked = false;

Select的输出为列表,列表元素为查询结果的行。

json
[
  {"user_id": "0001", "name": "john"},
  {"user_id": "0002", "name": "ruby"}
]

**SELECT * **

将row设为字符串"*"可以实现SELECT *

json
{
  "table": "users",
  "row": "*",
  "where": {
    "name": "admin",
    "locked": false
  }
}
sql
SELECT * FROM users WHERE name = 'admin' AND locked = false;

简单聚合 Select支持几种简单聚合,为以下几种特殊键的JSON对象,值为列名:

  • "$count"
  • "$max"
  • "$min"

特别地,{ "$count": null }表示COUNT(*)。 注意聚合不能直接作为row,而需要作为其元素。

json
{
  "table": "users",
  "row": [{ "$count": null }],
  "where": {
    "name": "admin",
    "locked": false
  }
}
sql
SELECT COUNT(*) FROM users WHERE name = 'admin' AND locked = false;

聚合语义校验 使用聚合时,非聚合的列必须在group中使用。

json
{
  "table": "users",
  "row": [{ "$count": null }, "age"]
}

此例中使用了聚合,但非聚合列age没有在group中使用。 修正方法是将非聚合列age添加到group中。

json
{
  "table": "users",
  "row": [{ "$count": null }, "age"],
  "group": ["age"]
}

另一种修正方法是将非聚合列age改为聚合列。

json
{
  "table": "users",
  "row": [{ "$count": null }, { "$max": "age" }]
}

另外,如果group不为空,即使row"*"或者row中没有聚合列,当前操作也视为使用了聚合。

json
{
  "table": "users",
  "row": ["age"],
  "group": ["is_manager"]
}

聚合语义在发布时进行校验,并非在运行时由数据库进行校验。

排序 order为排序列的列表,可以用特殊键"$asc""$desc"的JSON对象显式表示升序或降序:

json
{
  "table": "users",
  "row": "*",
  "order": ["age", { "$desc": "name" }, { "$asc": "id" }]
}
sql
SELECT * FROM users ORDER BY age, name DESC, id ASC;
Insert
typescript
type Insert = {
  'table': string;
  'rows': InsertRow[];
}

type InsertRow = { [column: string]: SqlValue }
json
{
  "table": "users",
  "rows": [
    {"name": "my name is ${ payload.john }", "age": 35},
    {"name": "my name is ${ payload.ruby }", "age": 40}
  ]
}
sql
-- payload : {
--   "john": "John Ted",
--   "ruby": "Ruby Gem",
-- }
INSERT INTO users (name, age) VALUES (
   ('my name is John Ted', 35),
   ('my name is Ruby Gem', 40)
);

生成的语句中,列名取所有输入行中列名的并集,如果某输入行缺少对应列,以NULL填充:

json
{
  "table": "users",
  "rows": [
    {"id": 1},
    {"id": 2, "name": "Wink"}
  ]
}
sql
INSERT INTO users (id, name) VALUES (
   (1, NULL),
   (2, 'Wink')
);

Insert的输出为map,值rowsAffected为插入的行数。

json
{
  "rowsAffected": 2
}
Update
typescript
type Update = {
  'table': string;
  'row': InsertRow;
  'where'?: Where;
}
json
{
  "table": "users",
  "row": {"locked": true},
  "where": {
    "age": {"$gte": 60}
  }
}
sql
UPDATE users SET locked = true WHERE age >= 60;

Update的输出为map,值rowsAffected为影响的行数。

json
{
  "rowsAffected": 1
}

注意:根据数据库的不同此处返回结果可能会不同。有的数据库将WHERE匹配到的行数作为影响的行数,而有的数据库则会对比更新的值和原值,值发生改变的行才算作影响的行。

Delete
typescript
type Delete = {
  'table': string;
  'where'?: Where;
}
json
{
  "table": "users",
  "where": {
    "age": {"$gte": "${vpt}"}
  }
}
sql
DELETE FROM users WHERE age >= 60;

Delete的输出为map,值rowsAffected为影响的行数。

json
{
  "rowsAffected": 1
}

键值对操作

键值对操作节点封装了KV存储服务API。使用该节点以键值对形式进行数据的写入、获取或删除操作。

节点配置

在业务逻辑编辑页面的节点中,选择 **键值对操作 **节点 image.png

配置项说明
节点名称设置节点名称。支持中文汉字、英文字母、数字和下划线(_),长度不超过30个字符。
操作类型可选:
  • KV存储获取
  • KV存储写入
  • KV存储删除 | | 键(key) | 设置要操作的键值对的键。可设置为固定值、上一节点(payload)、本节点之前的任一节点的某个参数或变量。 | | 值(value) | 当操作类型选择为KV存储写入时出现的参数。设置要写入的值。可设置为固定值、上一节点(payload)、本节点之前的任一节点的某个参数或变量。 |
节点输出

操作为读取时,节点输出为读取的值。 操作为写入、删除时,节点无输出。

键值储存和全局变量的区别

局部变量的作用域为当前实例,每次触发时初始化,实例之间互不影响。 全局变量的作用域为整个项目,进行持久化。没有实例修改其值时,项目下所有实例读取到的都是变量的默认值;当某实例修改了全局变量的值时,同一项目下所有其他实例都可以读取到设置的值。 键值储存的作用域为当前服务,进行持久化。没有实例修改其值时,服务下所有实例读取到的都是变量的默认值;当某实例修改了全局变量的值时,同一服务下所有其他实例都可以读取到设置的值。但同一项目下其他服务的实例无法读取当前服务的键值储存,换言之其他服务下可以使用相同键,服务之间互不影响。

模板字符串

在模板中,对于大括号${}括起的 path,按此 path 访问变量得到的值将以字符串形式替换到模板中。大括号和 path 之间可以有空格。 如果模板中包含美元符号本身,需要写两次以转义$$。 如果美元符号的下一个符号不是${,也表示美元符号本身。 在渲染到字符串时,字符串类型的值不会带有引号,如要保留引号,可以在 path 后加 | raw。 在模板中,payloadquerynode三个变量被用于引用上一节点的输出、输入节点的数据和任意节点的输出,因此尽量不要用这三个名字定义变量。payloadquery可以整体引用,但node不能整体引用。payloadquerynode可以后接**“访问路径”,除此之外只能引用到变量本身,不能接“访问路径”**。 例如:变量x"false"payload{"args": ["ok"]},模板:

arg0 = ${ payload.args[0] }
quoted arg0 = ${ payload.args[0] | raw }
x = ${x}
quoted x = ${ x | raw }
dolor: $
explicit escaped dolor: $$

渲染到字符串时将得到:

arg0 = ok
quoted arg0 = "ok"
x = false
quoted x = "false"
dolor: $
explicit escaped dolor: $

访问路径

在访问payload时,可以使用路径进行深层访问。 路径由以下三种形式自由组合而成

  1. 下标访问:当要访问的值是array时,可用方括号括起的十进制整数访问,例如[0][1]。 方括号和数字之间可以有空格。
  2. 名称访问:当要访问的值是map时,可以用点号加标识符访问,例如.a.b。 标识符的规则是字母或下划线开头,后接零或多个字母、数字、下划线。 当整个路径是以名称访问开头时,第一个点号可以省略。
  3. 带引号的名称访问:名称访问的另一种形式,如果名称不符合标识符的规则,可以用方括号括起一个单引号或双引号括起的属性名,在引号中,可以用反斜线转义特殊字符。 例如["a"]['b'] 方括号和引号之间可以有空格。

反斜线能转义的特殊字符如下:

  • \n
  • \r
  • \t
  • \\:反斜线本身
  • \':单引号本身,在双引号字符串中单引号可以不转义
  • \":双引号本身,在单引号字符串中双引号可以不专一
  • \u{0000}:一个Unicode字符,其中0000替换成Unicode值

如果下标访问的不是array,或者名称访问的不是 map,访问会出错。 如果下方访问时,下标溢出array长度,访问会出错。

例如,如果payload是{"a": {"b": ["ok"]}},以下几种路径都能得到"ok"

  • a.b[0]
  • .a.b[0]
  • ["a"].b[0]
  • ["a"]['b'][0]

特别地,空路径可以访问到整个payload本身。

path = [[first_segment] {, segment}];

first_segment = index_segment | attr_segment | first_attr_segment;
segment = index_segment | attr_segment;

index_segment = "[" index "]";
attr_segment = ".", unquoted_attr | "[" quoted_attr "]";
first_attr_segment = unquoted_attr;

index = INTEGER;
unquoted_attr = IDENTIFIER;
quoted_attr = SINGLE_QUOTED_STRING | DOUBLE_QUOTED_STRING;