创建新节点

创建新节点

引言

扩展Node-RED的主要途径就是,向节点面板添加新的节点类型。
节点可以作为npm模块发布到公共npm存储库中,并添加到Node-RED流库中,使其对社群可用。
参考文档:https://nodered.org/docs/creating-nodes/

通用原则

在创建新节点时要遵循一些通用原则。
遵循这些原则可以帮助你创建出更可靠、更易于使用和维护的Node-RED节点。这些原则也是软件设计中的通用原则,有助于确保你的代码是清晰、高效且可维护的。
以下是针对这些原则的详细解释:

  1. 明确的目的
    节点应该有清晰、明确的用途。一个节点如果暴露了API的所有可能选项,可能会比一组每个都服务于单一目的的节点更难以使用。最佳的做法是保持每个节点的功能聚焦。
  2. 简单易用
    不论节点的底层功能如何复杂,它都应该对用户简单易用。应该隐藏复杂性,避免使用专业术语或特定领域的知识,除非这些术语对于理解节点的功能至关重要。
  3. 接受多种类型的消息属性
    消息属性可以是字符串、数字、布尔值、Buffer、对象、数组或null。一个高质量的节点应该能够妥善处理所有这些类型的输入,而不是仅限于某些特定类型。
  4. 发送一致性
    节点应该清楚地记录它们添加到消息中的属性,并且在行为上应该一致和可预测。这有助于用户理解节点的输出,并与其他节点协同工作。
  5. 适应流程中的不同位置
    节点应该能够灵活地放置在流程的开始、中间或结束位置,而不是仅适用于特定的位置。这增加了节点的通用性和可重用性。
  6. 捕获错误
    如果节点抛出未捕获的错误,Node-RED将停止整个流程,因为系统的状态不再可知。因此,节点应该尽可能地捕获错误,或者为它们进行的任何异步调用注册错误处理程序。这有助于保持流程的健壮性和稳定性。

创建第一个节点

Node-RED 中,当您部署(deploy)一个流(flow)时,相关的节点(nodes)会被创建。这些节点在流运行期间可以发送和接收消息,并在您部署下一个流时被删除(实际上,旧的节点会被新的节点替换)。
Node-RED 的节点通常由两个文件组成:

  • JavaScript 文件:这个文件定义了节点的功能。它包含了节点如何处理接收到的消息、如何将处理后的消息发送给下游节点以及任何与节点相关的逻辑。这个文件通常包含 RED.nodes.registerType 函数调用,用于在 Node-RED 运行时中注册节点类型。
  • HTML 文件:这个文件定义了节点的属性(properties)、编辑对话框(edit dialog)和帮助文本(help text)。节点的属性是用户可以在 Node-RED 编辑器中设置的参数,它们可以影响节点的行为。编辑对话框允许用户设置这些属性,并提供了一个可视化的界面来配置节点。帮助文本则提供了关于节点功能和用法的详细信息,可以在用户点击节点旁边的“?”图标时查看。

这两个文件通常位于 Node-RED 的 nodes 文件夹中,或者位于您安装的自定义节点包的文件夹中。当您部署流时,Node-RED 会读取这些文件,并根据其中的定义创建和配置节点。当您修改节点代码或属性定义并重新部署流时,旧的节点实例会被销毁,并根据新的定义创建新的节点实例。

package.json文件用于将其打包为一个npm模块。

创建一个简单的node

此示例将显示如何创建一个节点,将消息有效载荷转换为所有小写字符。
确保您的系统上安装了Node.js的当前LTS版本。在撰写本文章时,这是10.x。
创建一个目录,用于开发代码。在该目录中,创建以下文件:

  • package.json
  • lower-case.js
  • lower-case.html

package.json

这是Node.js模块用来描述其内容的标准文件。
要生成标准的package.json文件,可以使用命令npm init。它会向用户提问一系列问题,以帮助创建文件的初始内容,尽可能使用合理的默认值。出现提示时,将name设置为node-red-contrib-example-lower-case。
生成后,必须添加一个node-red节点,示例如下:

{
  "name": "node-red-contrib-example-lower-case",
  "version": "1.0.0",
  "description": "自定义node-red节点 小写转化",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "node-red" : {
        "nodes": {
            "lower-case": "lower-case.js"
        }
  }
}

这告诉运行时模块包含哪些节点文件。
有关如何打包节点的详细信息,包括发布节点之前应设置的命名和其他属性的要求,请参阅打包指南。
**注意:**请不要将此示例节点发布到npm!

lower-case.js

module.exports = function(RED) { // RED  可以对node-red 进行访问
    function LowerCaseNode(config) {
        RED.nodes.createNode(this,config); // 节点本身就会对调用该函数,包括节点输入的属性
        var node = this;
        node.on('input', function(msg) { // 对消息进行处理  消息到达节点时,事件侦听就会启动,进行消息转化
            msg.payload = msg.payload.toLowerCase();
            node.send(msg);  // 输出的消息   如果msg为空则不发任何消息  也可以进行多个发送,node.send([msg1,msg2])
        });
    }
    RED.nodes.registerType("lower-case",LowerCaseNode);
}
  1. 模块包装
    自定义节点被包装成一个Node.js模块。这意味着它遵循CommonJS规范,通过module.exports来暴露其功能和API。
  2. 初始化函数
    当Node-RED在启动时加载节点时,会调用模块导出的函数。这个函数通常接收一个参数RED,它是Node-RED运行时API的引用,允许节点与Node-RED环境进行交互。
  3. 定义节点类型
    在模块内部,定义了一个名为LowerCaseNode的函数(或其他任何您选择的名称)。每当在Node-RED流编辑器中创建一个该类型的新节点实例时,就会调用此函数。该函数接收一个对象,该对象包含在流编辑器中为该节点设置的特定属性。
  4. 节点初始化
    LowerCaseNode函数中,首先使用RED.nodes.createNode函数来初始化节点。这个函数提供了所有节点共享的基本功能和属性,例如节点的ID、类型、名称等。
  5. 实现节点功能
    接下来,您可以编写特定于节点的代码。在这个例子中,节点注册了一个监听器来监听输入事件。每当节点接收到消息时,就会调用这个监听器。在监听器内部,代码将消息的payload属性转换为小写,然后使用node.send函数将修改后的消息传递到流中的下一个节点。
  6. 注册节点
    最后,使用RED.nodes.registerType函数将LowerCaseNode函数注册到Node-RED运行时。您需要提供节点的名称(例如lower-case)以及其他配置选项(如类别、颜色等)。
  7. 处理外部依赖
    如果节点依赖于任何外部npm包,这些依赖项必须在package.json文件的dependencies部分中列出。这样,当其他人安装或使用您的节点时,npm将自动安装这些依赖项。
  8. 测试和部署
    在将节点部署到生产环境之前,您应该在本地环境中对其进行彻底测试。确保它在各种情况下都能正常工作,并且符合您的预期。
  9. 发布和分享(可选):
    如果您希望与他人分享您的节点,可以将其发布到npm仓库。这样,其他人就可以轻松地找到、安装和使用您的节点了。
    通过遵循这些步骤,您可以创建功能强大、易于使用和可重用的Node-RED自定义节点

有关节点的运行时部分的更多信息,请参阅下面章节JavaScript文件

lower-case.html

<script type="text/javascript">
    RED.nodes.registerType('lower-case',{  // 节点的类型必须和上面注册的类型匹配 RED.nodes.registerType
        category: 'function',   // 节点调色板的类别
        color: '#a6bbcf',       // 使用的背景颜色
        defaults: {           // 节点可编辑的属性
            name: {value:""}
        },
        inputs:1,         // 节点有多少输入  0 或者 1
        outputs:1,       // 节点有多少输出  0 或者更多
        icon: "file.png", // 要使用的图标
        label: function() { // 工作空间中要使用的标签
            return this.name||"lower-case";
        }
    });
</script>

<script type="text/x-red" data-template-name="lower-case">   <!--date-template-name   编辑模板,用户定义节点的编辑对话框内容,值对节点类型进行绑定 和上面的registerType 中类型进行匹配-->
    <div class="form-row">
        <label for="node-input-name"><i class="icon-tag"></i> Name</label> <!--node-input 后面的名字和defaults 中的名字相同-->
        <input type="text" id="node-input-name" placeholder="Name">  <!--id 值和for 中的值相同-->
    </div>
</script>

<script type="text/x-red" data-help-name="lower-case">   <!--对应的帮助文档-->
    <p>A simple node that converts the message payloads into all lower-case characters</p>   <!--提示信息-->
</script>

节点的HTML文件可能包含以下内容:

  1. 编辑模板:这部分HTML定义了用户在Node-RED编辑器中编辑节点实例时可以看到的界面。例如,对于一个名为lower-case的节点,您可能想要让用户能够编辑一个名为name的属性,以便在流中区分多个节点实例。
  2. 帮助文本:此部分HTML为节点提供帮助文本,当用户点击Node-RED编辑器中的“?”图标时会显示该文本。这可以用于解释节点的用途、如何配置它,以及任何其他相关信息。
  3. 节点定义(通常在JavaScript中):虽然HTML文件不直接包含节点的注册代码,但通常会在与之关联的JavaScript文件中找到这部分代码。JavaScript文件使用RED.nodes.registerType来注册节点类型,并指定HTML模板文件。
    有关节点的运行时部分的更多信息,请参阅下面章节HTML文件

在Node-RED里测试该节点

要在本地测试一个已经创建的Node-RED节点模块,你可以使用npm install <folder>命令将其安装到你的Node-RED运行环境中。这将允许你在本地目录中开发节点,并在开发过程中将其链接到本地的Node-RED安装。
在你的Node-RED用户目录下(通常是~/.node-red),你可以运行以下命令来安装本地目录中的节点模块:

npm install /path/to/your/local/node-module-folder

请确保将/path/to/your/local/node-module-folder替换为你的实际节点模块文件夹的路径。
如果你正在使用Node-RED的开发者版本(可能是从Git克隆的),或者你的Node-RED安装位于不同的位置,你可能需要修改安装命令以指向正确的Node-RED node_modules 文件夹。
例如,如果你的Node-RED安装位于/home/username/node-red,你可能需要这样做:
Mac OS or Linux系统

cd /home/username/node-red   
npm install /path/to/your/local/node-module-folder

Windows系统

cd C:\Users\my_name\.node_red
npm install C:\Users\my_name\Documents\GitHub\node-red-contrib-example-lower-case

或者,如果你正在使用npm的链接功能(npm link),你可以在你的节点模块目录中运行:

cd /path/to/your/local/node-module-folder   
npm link

然后在你的Node-RED目录中运行:

cd /home/username/node-red   
npm link your-node-module-name

这里的your-node-module-name是你的package.json文件中定义的name字段的值。
使用npm link可以创建一个全局链接,使得你的本地Node-RED安装能够访问你的本地节点模块,而无需实际将其安装到node_modules目录中。这对于开发期间频繁修改和测试节点模块非常有用。

单元测试

为了支持单元测试,可以使用一个名为 node-red-node-test-helpernpm 模块。这个测试助手是一个基于 Node-RED 运行时的框架,用于简化节点的测试。
使用这个框架,你可以创建测试流程,然后断言你的节点属性和输出是否按预期工作。要了解有关该辅助模块的更多信息,请参阅相关的README 文件

项目添加依赖
npm install node-red-node-test-helper node-red --save-dev
npm install mocha chai --save-dev

这将依赖模块添加到“package.json”文件中:

"devDependencies": {
    "chai": "^5.1.1",
    "mocha": "^10.4.0",
    "node-red": "^3.1.9",
    "node-red-node-test-helper": "^0.3.4"
  }
Adding test script to package.json
"scripts": {
    "test": "mocha \"test/**/*_spec.js\""
  }

这将允许您在命令行中使用npm test

Creating unit tests

lower-case 节点添加单元测试,你可以在你的节点模块包中添加一个名为 test 的文件夹,并在其中包含一个名为 _spec.js 的文件

test/lowser-case_spec.js
var helper = require("node-red-node-test-helper");
var lowerNode = require("../lower-case.js");

describe('lower-case Node', function () {

  afterEach(function () {
    helper.unload();
  });

  it('should be loaded', function (done) {
    var flow = [{ id: "n1", type: "lower-case", name: "test name" }];
    helper.load(lowerNode, flow, function () {
      var n1 = helper.getNode("n1");
      n1.should.have.property('name', 'test name');
      done();
    });
  });

  it('should make payload lower case', function (done) {
    var flow = [{ id: "n1", type: "lower-case", name: "test name",wires:[["n2"]] },
    { id: "n2", type: "helper" }];
    helper.load(lowerNode, flow, function () {
      var n2 = helper.getNode("n2");
      var n1 = helper.getNode("n1");
      n2.on("input", function (msg) {
        msg.should.have.property('payload', 'uppercase');
        done();
      });
      n1.receive({ payload: "UpperCase" });
    });
  });
});

这些测试用于检查节点是否正确加载到运行时中,并且它是否按预期将负载(payload)正确地转换为小写。
两个测试都使用 helper.load 方法将节点加载到运行时中,同时提供待测试的节点和一个测试流程。第一个测试检查运行时中的节点是否具有正确的名称属性。第二个测试使用一个辅助节点来检查节点的输出是否确实是小写。

Running your tests
npm test

JavaScript文件

Node.js 文件(在 Node-RED 的上下文中)定义了节点的运行时行为。
以下是你提到的各个点的简要解释

Node constructor

每个 Node.js 文件通常包含一个构造函数,它定义了节点的初始化行为。
在 Node-RED 中,节点是由构造函数函数定义的,该函数可以用于创建节点的新实例。这个构造函数函数在运行时注册,以便在流中部署对应类型的节点时被调用。
当 Node-RED 部署流时,它会为流中的每个节点实例调用相应的构造函数。这个函数接收一个对象,该对象包含在流编辑器中设置的属性。
在构造函数中,第一件要做的事情是调用 RED.nodes.createNode 函数来初始化所有节点共享的功能。这通常包括设置节点的 idtypename 等属性,以及设置一些事件监听器,如 'input' 事件监听器(用于接收消息)和 'close' 事件监听器(用于清理资源)。
在调用了 RED.nodes.createNode 之后,你就可以开始编写特定于该节点的代码了。这可能包括设置节点的内部状态、添加额外的属性、注册回调函数来处理输入消息、以及定义如何根据节点的配置和接收到的消息来发送输出消息等。
以下是一个简单的 Node-RED 节点构造函数的示例框架:

module.exports = function(RED) {  
    "use strict";  
  
    function MyNode(config) {  
        RED.nodes.createNode(this, config);  
  
        // 在这里添加你的节点特定代码  
        // 例如:  
        // this.on('input', function(msg, send, done) {  
        //     // 处理输入消息  
        //     // ...  
        //     send(msg); // 发送输出消息  
        //     done(); // 通知 Node-RED 输入消息已被处理  
        // });  
  
        // 如果需要,还可以添加 close 事件处理函数  
        // this.on('close', function(done) {  
        //     // 清理资源  
        //     // ...  
        //     done(); // 通知 Node-RED 清理已完成  
        // });  
    }  
  
    RED.nodes.registerType("my-node-type", MyNode);  
};

Receiving messages

在 Node-RED 中,节点通过在输入事件上注册监听器来接收来自流中上游节点的消息。随着 Node-RED 1.0 的发布,引入了一种新的监听器风格,它允许节点通知运行时它已经完成了消息的处理。这种新的监听器风格向监听器函数添加了两个新参数。
在 Node-RED 1.0 及更高版本中,输入事件监听器的典型签名如下:

this.on('input', function(msg, send, done) {  
    // 处理输入消息 msg  
    // ...  
  
    // 发送输出消息  
    send(msg);  
  
    // 当消息处理完成后,调用 done 回调函数  
    done();  
});

这里 msg 是从上游节点接收到的消息对象,send 是一个函数,用于将消息发送到下游节点,而 done 是一个回调函数,当消息处理完成后应该被调用。调用 done 通知 Node-RED 节点已经完成了对输入消息的处理,这对于支持流中并发消息处理的异步节点尤其重要。
然而,在 Node-RED 0.x 版本中,监听器的签名并不包含 senddone 参数,它可能只接受一个 msg 参数:

this.on('input', function(msg) {  
    // 处理输入消息 msg  
    // ...  
  
    // 在 0.x 版本中,你可能需要使用 node.send 来发送消息  
    node.send(msg);  
});

为了在 Node-RED 0.x 和 1.x 版本中都能使用你的节点,你需要编写一些兼容性的代码。这通常可以通过检查 senddone 参数是否存在来实现:

this.on('input', function(msg, send, done) {  
    var node = this;  
  
    // 检查 send 和 done 是否是函数  
    if (typeof send !== 'function') {  
        // 兼容 Node-RED 0.x  
        send = function(msgs) {  
            node.send(msgs);  
        };  
    }  
  
    if (typeof done !== 'function') {  
        // 兼容 Node-RED 0.x,在 0.x 中不需要调用 done  
        done = function() {};  
    }  
  
    // 处理输入消息 msg  
    // ...  
  
    // 发送输出消息  
    send(msg);  
  
    // 通知运行时消息已处理完成(仅在 Node-RED 1.x 中有效)  
    done();  
});

这样,你的节点就可以同时兼容 Node-RED 0.x 和 1.x 版本了。不过,随着时间的推移和 Node-RED 社区的演进,建议逐步淘汰对旧版本的兼容性支持,并鼓励用户升级到最新的稳定版本。

Handling errors

如果节点在处理消息时遇到错误,它应该将错误的详细信息传递给 done 函数(如果可用)。这样做会触发同一标签页上存在的任何 Catch 节点,从而允许用户构建流来处理错误。
然而,在 Node-RED 0.x 版本中,并没有 done 函数可用。在这种情况下,节点应该使用 node.error 方法来报告错误。
以下是一个处理这种情况的示例代码:

this.on('input', function(msg, send, done) {  
    var node = this;  
  
    // 检查 done 是否存在  
    if (typeof done !== 'function') {  
        // 兼容 Node-RED 0.x  
        done = function() {};  
    }  
  
    try {  
        // 尝试处理消息  
        // 这里可能会抛出异常  
        // ...  
  
        // 发送输出消息  
        send(msg);  
  
        // 通知运行时消息已处理完成  
        done();  
    } catch (err) {  
        // 捕获异常  
        if (done) {  
            // Node-RED 1.x如果有 done 函数,则传递错误  
            done(err);  
        } else {  
            // 兼容 Node-RED 0.x  
            // 使用 node.error 报告错误  
            node.error(err, msg);  
        }  
    }  
});

在这个例子中,我们使用了 try...catch 语句来捕获可能发生的任何异常。如果捕获到异常并且 done 函数存在(这通常意味着我们正在运行 Node-RED 1.x 或更高版本),我们将错误传递给 done 函数。如果 done 函数不存在(可能是在 Node-RED 0.x 中),我们则使用 node.error 方法来报告错误,并将原始消息 msg 作为第二个参数传递(尽管 node.error 通常只接受一个错误对象作为参数,但在某些情况下,传递原始消息可以帮助调试)。
注意:在 Node-RED 的实践中,node.error 方法通常只接受一个错误对象作为参数,但某些节点实现可能会选择传递额外的信息来帮助调试。然而,这不是 node.error 方法的官方用法,并且在不同版本的 Node-RED 中可能会有所不同。因此,在生产环境中使用时应该谨慎,并确保你的节点代码与你的目标 Node-RED 版本兼容。

Sending messages

  1. 一个节点如果位于流的起始位置并响应外部事件产生消息,它应该使用 Node 对象上的 send 方法来发送消息。
this.on('input', function(msg, send, done) {  
    // 注意:在这种情况下,'input' 事件可能不会被触发,因为节点是自驱动的  
  
    // 假设有一个外部事件触发,我们模拟它  
    msg.payload = "hi";  
  
    // 使用 Node 对象上的 send 方法发送消息  
    this.send(msg);  
  
    // 如果你的节点支持 Node-RED 1.x,并且你的操作是异步的,  
    // 你应该调用 done() 来通知运行时消息处理已完成  
    // done(); // 仅在异步操作完成后调用  
});  
  
// 但是,更常见的是,对于自驱动的节点,你可能会有不同的逻辑来触发发送消息  
// 例如,一个定时器、WebSocket 连接、HTTP 请求等
  1. 但如果你指的是节点在输入事件监听器内部,作为对接收到的消息的响应来发送消息,那么你应该使用传递给监听器函数的 send 回调函数。
this.on('input', function(msg, send, done) {  
    // 处理接收到的消息  
    // ...  
  	// send = send || function() { node.send.apply(node,arguments) }
    // 假设我们修改了消息并准备发送  
    msg.payload = "hi";  
  
    // 使用传递给监听器函数的 send 回调函数发送消息  
    send(msg);  
  
    // 如果你的操作是异步的,并且你的 Node-RED 版本支持 done 回调函数,  
    // 你应该在操作完成后调用它  
    // done(); // 仅在异步操作完成后调用  
});

第二种情况处理输入消息并发送输出消息,send 回调函数是专门为了这种用途而提供的,并且它应该被用来发送消息,而不是 Node 对象上的 send 方法。这是因为 send 回调函数可以正确处理流中并发消息的情况,而 Node 对象上的 send 方法主要用于自驱动节点的情况。
msgnull 时,通常不会发送任何消息。在 Node-RED 中,节点在接收到消息后发送响应时,通常会复用接收到的消息对象(如果适用),而不是创建一个全新的消息对象。

Multiple outputs(多个输出)

在 Node-RED 中,如果一个节点有多个输出端口,你可以通过传递一个消息数组给 node.send() 函数来发送多个消息。然而,这里的“对应”输出并不是指数组中的每个消息都会发送到特定的输出端口。实际上,Node-RED 会按照数组中的顺序将每个消息发送到下一个可用的输出端口。
例如,如果你有一个具有两个输出端口的节点,并且你传递了一个包含两个消息的数组给 node.send(),那么第一个消息会被发送到第一个输出端口,第二个消息会被发送到第二个输出端口。如果你尝试发送第三个消息,那么该消息将不会被发送,除非你的节点有更多的输出端口。

this.send([ msg1 , msg2 ]);

Multiple messages(一次发送多个消息)

this.send([ [msgA1 , msgA2 , msgA3] , msg2 ]);

在 Node-RED 中,通常你不能直接向一个特定的输出端口发送多个消息。每个 node.send() 调用都会将消息发送到下一个可用的输出端口。但是,你可以通过其他方法实现类似的功能。
一种常见的方法是使用 node.send() 发送一个包含多个消息的数组,并使用下游节点来处理这些消息。例如,你可以使用一个函数节点来迭代数组中的每个消息,并分别发送它们。但是,请注意,这些消息将仍然按照顺序被发送到下游节点的不同输入端口(如果有多个输入的话)。
如果你确实需要向同一个逻辑目标(可能是同一个下游节点)发送多个消息,你可能需要重新设计你的流程,或者使用其他节点(如 Join 节点)来合并这些消息。
总结来说,Node-RED 中的消息发送是基于输出端口的顺序的,而不是基于特定的输出端口。如果你需要向同一个目标发送多个消息,你可能需要使用其他方法来实现这一功能。

Closing the node

在 Node-RED 中,当你重新部署流程时,现有的节点实例通常会被删除,然后基于新的配置重新创建。如果某些节点在删除时需要执行清理操作,例如断开与远程系统的连接,它们应该监听 close 事件。

this.on('close', function() {
    // tidy up any state
});

当节点监听到 close 事件时,它应该执行任何必要的清理工作。如果这些清理工作是异步的(即它们需要一段时间才能完成),那么注册的监听器应该接受一个回调函数作为参数,该回调函数在清理工作全部完成后被调用。

this.on('close', function(done) {
    doSomethingWithACallback(function() {
        done();
    });
});

从 Node-RED 0.17 版本开始,如果您在自定义节点中注册的 close 事件监听器接受两个参数,那么第一个参数将是一个boolean标记,用于指示节点是因为被完全移除而关闭,还是因为正在被重启,或者节点已被禁用。如果节点已被禁用,它也将设置为true。

this.on('close', function(removed, done) {
    if (removed) {
        // This node has been disabled/deleted
    } else {
        // This node is being restarted
    }
    done();
});

Timeout behaviour

在 Node-RED 0.17 版本之前,运行时(runtime)会无限期地等待 done 函数被调用。如果某个节点未能调用 done,这会导致运行时挂起(hang)。
从 Node-RED 0.17 版本开始,如果某个节点在超过 15 秒后仍未调用 done 函数,则运行时将会超时该节点。此时,会记录一个错误,但运行时将继续运行。这个超时机制是为了防止由于某个节点的错误行为而导致的整个 Node-RED 流程挂起。
这意味着,如果你的自定义节点中的 close 事件处理函数包含异步操作,并且这些操作可能需要超过 15 秒的时间来完成,你应该确保在异步操作完成之前,使用某种方式(如设置超时或取消操作)来调用 done 函数,以避免节点被超时。如果无法避免超时,你应该确保在异步操作中处理任何潜在的错误,并在调用 done 时将错误传递给 Node-RED 的错误处理系统。

Logging events

在 Node-RED 中,如果节点需要向控制台输出日志,它可以使用几个不同的函数来做到这一点。这些函数不仅会在控制台上显示消息,而且警告(warn)和错误(error)消息还会被发送到流程编辑器的调试(debug)选项卡中。

this.log("Something happened");
this.warn("Something happened you should know about");
this.error("Oh no, something bad happened");

// Since Node-RED 0.17
this.trace("Log some internal detail not needed for normal operation");
this.debug("Log something more details for debugging the node's behaviour");
  1. node.log():
    用于记录一般信息性消息。这些消息通常只显示在 Node-RED 的控制台中,不会显示在流程编辑器的调试选项卡上。
  2. node.warn():
    用于记录警告消息。这些消息会同时显示在 Node-RED 的控制台和流程编辑器的调试选项卡上。警告通常用于指示潜在的问题,但不一定是错误。
  3. node.error():
    用于记录错误消息。这些消息也会同时显示在 Node-RED 的控制台和流程编辑器的调试选项卡上。与警告不同,错误通常指示发生了需要用户干预的问题。
  4. node.trace():
    (在某些 Node-RED 版本中可用)用于记录跟踪信息。这些信息可以帮助开发者调试节点,但通常不会在最终用户的环境中显示。
  5. node.status():
    这个函数不仅可以用于记录状态信息,还可以设置节点的状态图标。虽然它本身不直接发送消息到控制台或调试选项卡,但它可以通过设置状态图标和文本,向用户提供关于节点当前状态的反馈。

使用这些函数时,通常需要传递一个消息字符串作为参数。对于 node.error()node.warn(),你还可以传递一个可选的 msg 对象,该对象将包含与错误或警告相关的消息详细信息。

Setting status

当 Node-RED 中的一个节点正在运行时,它能够与编辑器 UI 共享状态信息。这是通过调用节点的 status 函数来实现的。status 函数允许你更新节点的状态,这将在 Node-RED 的编辑器 UI 中显示为颜色、形状和文本。

this.status({fill:"red",shape:"ring",text:"disconnected"});

status 函数通常接受一个对象作为参数,该对象可以包含以下属性:

  • fill: 设置状态图标的填充颜色。可以是一个颜色名称(如 “red”、“green”)、一个十六进制颜色代码(如 “#FF0000”)、一个 RGB 对象(如 {r:255, g:0, b:0}),或者一个包含颜色映射的数组(用于表示颜色渐变)。
  • shape: 设置状态图标的形状。可以是 “dot”(圆点)、“ring”(圆环)或 “fill”(实心)。
  • text: 在状态图标旁边显示的文本。

请注意,status 函数的调用是异步的,它不会阻塞节点的其他操作。你可以在任何时候调用 status 函数来更新节点的状态,例如在接收到消息时、在处理消息之前或之后,或者在执行某个长时间运行的操作时。

Custom node settings

在 Node-RED 中,一个节点可能希望在用户的 settings.js 文件中暴露配置选项。为了确保配置选项的一致性和可管理性,这些设置名称必须遵循特定的命名约定。以下是你提到的要求的详细说明和示例:

  1. 名称必须以相应的节点类型作为前缀
    这有助于避免设置名称的冲突,并确保设置与特定的节点类型相关联。
  2. 设置名称必须使用驼峰命名法(camel-case)
    驼峰命名法是一种命名约定,其中每个单词的首字母大写(除了第一个单词),并且没有下划线或连字符来分隔单词。例如,sampleNodeColour
  3. 节点不应该要求用户设置它——它应该有一个合理的默认值
    这提供了更好的用户体验,因为用户不必为每个设置都提供一个值。节点应该能够处理未设置的情况,并回退到一个默认值。

使用你给出的例子,如果节点类型 sample-node 想要暴露一个叫做 colour 的设置,那么设置名称应该是 sampleNodeColour。在 Node-RED 的运行时中,节点可以通过 RED.settings.sampleNodeColour 来引用这个设置。
settings.js 文件中,用户可以这样设置这个值(如果需要的话):

module.exports = {  
    // ... 其他设置 ...  
    sampleNodeColour: "blue" // 假设用户想要将颜色设置为蓝色  
};

在节点的代码中,你可以这样访问这个设置:

// 假设在 node 的某个方法中  
var colour = RED.settings.sampleNodeColour || "defaultColour"; // 如果未设置,则使用默认值

Exposing settings to the editor

从 Node-RED 0.17 开始,如果一个节点想要在编辑器中暴露某个设置的值,它需要在调用 registerType 方法时注册这个设置。这允许用户在节点的属性面板中直接配置这些设置。以下是关于如何注册和暴露设置的详细说明:

  1. 注册设置
    在注册节点类型时,通过 registerType 方法,节点可以指定其设置。这些设置包括一个 value 字段,用于指定设置的默认值,以及一个 exportable 字段,用于指示运行时将该设置暴露给编辑器。
  2. 命名要求
    设置名称的命名规则与前面提到的相同:它必须以节点类型作为前缀,并使用驼峰命名法。如果节点尝试注册不符合这些命名要求的设置,将会记录一个错误。
  3. 在编辑器中引用设置
    一旦设置被注册并暴露给编辑器,用户就可以在节点的属性面板中看到并修改这些设置。然而,在节点内部(无论是在运行时还是编辑器中),节点都可以通过 RED.settings.yourSettingName 来引用这些设置。

以下是一个简单的例子,展示如何在注册节点类型时暴露一个设置:

RED.nodes.registerType('sample-node', {  
    // ... 其他节点属性 ...  
  
    // 注册设置  
    settings: {  
        sampleNodeColour: {  
            value: "defaultColour", // 默认值  
            exportable: true // 暴露给编辑器  
        }  
    },  
  
    // ... 其他节点方法 ...  
  
    // 在节点内部引用设置  
    someMethod: function() {  
        var colour = RED.settings.sampleNodeColour || "defaultColour";  
        // 使用 colour 做一些事情...  
    }  
});

在这个例子中,我们创建了一个名为 sample-node 的节点类型,并在其设置中注册了一个名为 sampleNodeColour 的设置。我们为这个设置指定了一个默认值 "defaultColour",并设置了 exportabletrue,以便在编辑器的属性面板中暴露这个设置。然后,在节点的 someMethod 方法中,我们通过 RED.settings.sampleNodeColour 来引用这个设置。
如果节点尝试注册一个不符合命名要求的设置,Node-RED 将会在日志中记录一个错误。这是为了确保设置名称的一致性和可维护性。
命名要求通常包括:

  • 设置名称必须以节点类型作为前缀,例如 myNodeTypeSettingName
  • 设置名称应该使用驼峰命名法(camelCase),即每个单词的首字母大写,没有空格或下划线分隔。
  • 节点不应该强制要求用户设置它,而应该提供一个合理的默认值。

HTML文件

在Node-RED中 .html 文件通常包含了定义节点在编辑器中如何显示的三个主要部分,每个部分都包裹在它们自己的 <script> 标签中。以下是这三个部分的详细解释:

  1. 主节点定义:- 这部分在标准的 JavaScript <script> 标签中定义。
    • 它负责将节点注册到编辑器中,包括指定节点在调色板中的类别、可编辑的属性(默认值)以及使用的图标等。
    • 这部分通常使用 Node-RED 提供的 API 函数,如 RED.nodes.registerType,来注册节点类型。
  2. 编辑模板:- 这部分在 <script> 标签中定义,其 type 属性设置为 text/html
    • 它定义了节点编辑对话框的内容。
    • 通过设置 data-template-name 属性来指定该模板关联到哪个节点类型。
    • 这个模板通常包含 HTML、CSS 和可能的 JavaScript 代码,用于渲染和交互节点的编辑界面。
  3. 帮助文本:- 这部分也在 <script> 标签中定义,其 type 属性设置为 text/html
    • 它包含了在编辑器的信息侧边栏选项卡中显示的帮助文本。
    • 通过设置 data-help-name 属性来指定该帮助文本关联到哪个节点类型。
    • 这部分通常只包含纯文本或简单的 HTML 标记,用于向用户提供关于节点如何使用的说明和示例。

主节点定义

在Node-RED中,自定义节点必须通过调用RED.nodes.registerType函数来注册到编辑器中。这个函数接受两个参数:节点的类型(通常是一个唯一的字符串标识符)和节点的定义对象。

<script type="text/javascript">
    RED.nodes.registerType('node-type',{
        // node definition
    });
</script>

Node type(节点的类型)

在Node-RED中,节点类型是用于在整个编辑器中唯一标识节点的字符串。这个类型字符串必须与在相应.js文件中调用RED.nodes.registerType函数时所使用的值相匹配。
例如,如果你在.js文件中这样注册一个节点:

RED.nodes.registerType('my-custom-node', {  
    // ... 节点定义 ...  
});

那么在整个Node-RED的上下文中,包括在.html文件中的编辑模板和帮助文本,以及任何与该节点交互的代码中,你都会使用'my-custom-node'这个字符串来引用这个节点。
.html文件中,你会在<script>标签的data-template-namedata-help-name属性中使用相同的值来指定编辑模板和帮助文本与哪个节点类型相关联:

<!-- 编辑模板 -->  
<script type="text/html" data-template-name="my-custom-node">  
    <!-- 节点编辑模板内容 -->  
</script>  
  
<!-- 帮助文本 -->  
<script type="text/html" data-help-name="my-custom-node">  
    <!-- 节点帮助文本内容 -->  
</script>

确保在整个项目中一致地使用这个节点类型字符串是非常重要的,因为任何不匹配都可能导致节点无法正确注册、显示或工作。

Node definition(节点的定义对象)

节点定义涵盖了Node-RED中节点定义对象的主要属性。以下是对这些属性的简要概述:

  • category (string): 节点在调色板中显示的类别。
  • defaults (object): 节点的可编辑属性及其默认值。
  • credentials (object): 节点需要的凭据属性。这些属性通常用于存储敏感信息,如密码或API密钥,并且在部署到运行时环境之前会进行加密。
  • inputs (number): 节点拥有的输入端口数量,通常为0或1。
  • outputs (number): 节点拥有的输出端口数量。可以是0或更多。
  • color (string): 节点使用的背景颜色。
  • paletteLabel (string|function): 在调色板中使用的标签。如果是一个函数,它应该返回一个字符串。
  • label (string|function): 在工作区中使用的标签。如果是一个函数,它允许根据节点的当前状态动态生成标签。
  • labelStyle (string|function): 应用到标签的样式。如果是一个函数,它应该返回一个样式字符串。
  • inputLabels (string|function): 悬停在节点输入端口时显示的可选标签。
  • outputLabels (string|function): 悬停在节点输出端口时显示的可选标签。
  • icon (string): 节点使用的图标。通常是Font Awesome的图标类。
  • align (string): 图标和标签的对齐方式。
  • button (object): 添加到节点边缘的按钮的配置。
  • oneditprepare (function): 当编辑对话框正在构建时被调用。用于自定义编辑行为。
  • oneditsave (function): 当编辑对话框确认时被调用。用于处理节点属性的保存逻辑。
  • oneditcancel (function): 当编辑对话框取消时被调用。
  • oneditdelete (function): 当配置节点的编辑对话框中的删除按钮被按下时被调用。
  • oneditresize (function): 当编辑对话框大小调整时被调用。
  • onpaletteadd (function): 当节点类型被添加到调色板时被调用。
  • onpaletteremove (function): 当节点类型从调色板中删除时被调用。

在创建自定义节点时,这些属性允许开发者控制节点在Node-RED编辑器中的外观和行为。通过适当设置这些属性,开发者可以创建出功能丰富且用户友好的节点。

编辑模板

Node-RED中节点的编辑模板(edit template)描述了节点编辑对话框的内容。这个模板通常是一个HTML文件,用于定义编辑对话框的布局、样式以及交互行为。

<script type="text/html" data-template-name="node-type">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
    <div class="form-tips"><b>Tip:</b> This is here to help.</div>
</script>

更多信息请参阅章节 节点编辑模板

帮助文本

当选择一个节点时,其帮助文本将显示在信息选项卡中。这应该为节点的操作提供有意义的描述。它应该确定它在传出消息上设置的属性以及在传入消息上可以设置的属性。
将鼠标悬停在选项板中的节点上时,第一个<p>标记的内容将用作工具提示。

<script type="text/html" data-help-name="node-type">
   <p>Some useful help text to introduce the node.</p>
   <h3>Outputs</h3>
       <dl class="message-properties">
       <dt>payload
           <span class="property-type">string | buffer</span>
       </dt>
   <h3>Details</h3>
   <p>Some more information about the node.</p>
</script>

节点帮助的完整样式指南请参阅章节帮助样式指南

打包

在Node-RED中,节点可以打包为npm模块并发布到npm(Node Package Manager)仓库。这种方式不仅使得节点易于分发和共享,还允许节点开发者轻松管理节点的依赖项。

命名

Node-RED 的命名规范更新强调了使用作用域名称(scoped name)的重要性,这有助于更好地组织和管理 npm 上的 Node-RED 节点模块。以下是对新命名要求的概述:
作用域名称

  • 从 2022 年 1 月 31 日起,首次发布的模块应使用作用域名称。作用域名称以 @ 符号开头,后跟作用域名称(可以是用户名或组织名)和模块名。例如,@myScope/node-red-sample

节点名称

  • 使用作用域名称发布的节点在命名上没有进一步的要求。你可以使用 @myScope/node-red-sample 或更简短的 @myScope/sample。不过,在名称中包含 node-red 有助于将模块与 Node-RED 项目相关联。

分支(Forking)

  • 如果你正在为现有包提供修复而创建分支,你可以保持相同的名称但发布在你自己的作用域下。但请注意,如果原始维护者对你的贡献没有响应,分支通常应该是最后的选择。

目录结构

以下是节点包的典型目录结构:

├── LICENSE
├── README.md
├── package.json
├── examples
    │  ├── example-1.json
    │  └── example-2.json
└── sample
    ├── icons
    │  └── my-icon.svg
    ├── sample.html
    └── sample.js

对包中使用的目录结构没有严格的要求。如果一个包包含多个节点,它们可以都存在于同一目录中,也可以分别放置在自己的子目录中。examples文件夹必须位于包的根目录中。

在本地测试节点模块

要在本地测试节点模块,可以使用npm install<folder>命令。这允许您在本地目录中开发节点,并在开发过程中将其链接到本地node-red安装目录中。
在node red用户目录(通常为~/.node red)中,运行:

npm install <path to location of node module>

这将创建到目录的适当符号链接,以便节点RED在启动时发现该节点。对节点文件的任何更改都可以通过简单地重新启动node RED来获取。

package.json

除了通常的条目外,package.json文件还必须包含一个node-red属性,该属性列出了包含运行时要加载的节点的.js文件。
如果在一个文件中有多个节点,则只需列出该文件一次。
如果任何节点对其他npm模块具有依赖关系,则必须将它们包含在dependencies属性中。
为了帮助在npm存储库中发现节点,文件应在其keywords属性中包含node red。这将确保在按关键字搜索时显示包。
注意: 请不要添加“node red”关键字,除非您对节点稳定且工作正常感到满意,并且有足够的文档供其他人使用。

{
    "name"         : "@myScope/node-red-sample",
    "version"      : "0.0.1",
    "description"  : "A sample node for node-red",
    "dependencies": {
    },
    "keywords": [ "node-red" ],
    "node-red"     : {
        "nodes": {
            "sample": "sample/sample.js"
        }
    }
}

您应该通过version条目指定节点支持的Node RED版本。例如,以下表示节点需要node RED 2.0或更高版本。

"node-red"     : {
    "version": ">=2.0.0",
    "nodes": {
        "sample": "sample/sample.js"
    }
}

README.md

README.md文件应该描述节点的功能,并列出使其正常工作所需的任何先决条件。包含任何未包含在节点html文件的信息选项卡部分中的额外指令也可能很有用,甚至可能包含一个演示其使用的小示例流。
该文件应该使用GitHub风格的markdown进行标记。

LICENSE

请包括一个许可证文件,以便其他人知道他们可以和不能对您的代码做什么。

发布到npm

有很多关于将包发布到npm存储库的指南。此处提供了基本概述

添加到flows.nodered.org

从 2020 年 4 月起,Node-RED Flow Library 不再自动索引和更新 npm 上带有 node-red 关键字的节点。现在,为了将节点添加到库中,您需要手动提交请求。
以下是向 Node-RED Flow Library 添加新节点或更新现有节点的步骤:
添加新节点

  1. 确保您的节点包满足所有打包要求。
  2. 访问 Node-RED Flow Library(flows.nodered.org)。
  3. 在页面顶部点击 + 按钮,并选择 node 选项。这将带您到“添加节点”页面。
  4. 在“添加节点”页面上,您需要填写有关节点的信息,如名称、描述、npm 链接等。
  5. 提交您的请求。

更新现有节点

  1. 如果您要更新已经存在于 Flow Library 中的节点,您可以通过两种方式进行:- 像添加新节点一样重新提交该节点。
    • 登录到 Flow Library,找到该节点的页面,并点击“请求刷新”(request refresh)链接。这个链接只对登录用户可见。

请求帮助

  • 如果在提交后一段时间内您的节点没有出现在 Flow Library 中,您可以在 Node-RED 的论坛或 Slack 频道中寻求帮助。在那里,您可以获得来自社区成员或 Node-RED 团队的帮助。

请注意,向 Flow Library 提交节点需要一些时间进行验证和索引,因此请耐心等待。此外,确保您的 npm 包是公开的,以便 Flow Library 可以访问并索引它。

节点属性

在Node-RED中,节点的属性通常是通过其HTML定义中的defaults对象来定义的。这些属性在运行时创建节点的实例时会被传递给节点的构造函数。
假设你已经有一个节点的HTML定义,并且这个节点有一个叫做name的属性。现在,你想为这个节点添加一个叫做prefix的新属性。

  1. Add a new entry to the defaults object:
 defaults: {
     name: {value:""},
     prefix: {value:""}
 },

这个属性应该包含一个value字段,它指定了当新节点被拖动到工作区时该属性的默认值。
2.Add an entry to the edit template for the node

<div class="form-row">
     <label for="node-input-prefix"><i class="fa fa-tag"></i> Prefix</label>
     <input type="text" id="node-input-prefix">
 </div>

在这个模板中,你需要添加一个<input>元素来允许用户编辑新添加的属性。这个<input>元素的id应该设置为node-input-<propertyname>,其中<propertyname>是你想要编辑的属性的名称。
3. Use the property in the node
在Node-RED节点的JavaScript代码中,你可以在LowerCaseNode构造函数或任何其他方法中访问这个新属性。你可以通过this.prefix来访问它(假设你已经将config对象作为参数传递给了构造函数,并且已经调用了RED.nodes.createNode(this,config);)。

function LowerCaseNode(config) {
     RED.nodes.createNode(this,config);
     this.prefix = config.prefix;
     var node = this;
     this.on('input', function(msg) {
         msg.payload = node.prefix + msg.payload.toLowerCase();
         node.send(msg);
     });
 }

Property definitions(属性定义)

defaults 对象中的条目确实必须是对象,并且可以包含你提到的那些属性:valuerequiredvalidatetype

  • value:(任意类型),属性使用的默认值
  • required:(boolean)可选,属性是否为必需。如果设置为true,则如果属性的值为null或空字符串,则该属性将无效。
  • validate:(函数)可选,的一个函数,可用于验证属性的值。
  • type:(string)可选,当 type 属性被用于 defaults 对象的某个属性时,它通常表示该属性是一个指向另一个配置节点的指针。配置节点是 Node-RED 中用于存储和共享配置信息的特殊节点,如数据库连接、API 凭据等。

Reserved property names(保留的属性名称)

在 Node-RED 中,有一些保留的属性名称,这些名称不应被用作自定义节点的属性。以下是这些保留名称的列表,以及关于 outputs 属性的一些额外说明:
保留的属性名称

  • 单个字符名称,如 x, y, z, d, g, l 等,这些通常用于节点的坐标或特定于 Node-RED 的内部用途。
  • id: 每个节点在 Node-RED 中都有一个唯一的 ID。
  • type: 用于标识节点的类型。
  • wires: 用于描述节点之间连接的数据流。
  • inputs: 节点可以接收的输入数量。
  • outputs: 节点可以发送的输出数量。

Property validation(属性校验)

在 Node-RED 中,编辑器会尝试验证所有属性,以在给定无效值时向用户发出警告。验证功能可以通过 required 属性和 validate 属性来实现。

  • required 属性
    required 属性用于指示一个属性必须是非空和非空白的。如果该属性未设置或设置为空字符串,编辑器将显示一个警告。
  • validate 属性
    如果需要更具体的验证,可以使用 validate 属性提供一个函数来检查值是否有效。该函数会接收属性值作为参数,并应返回 truefalse。验证函数在节点上下文中被调用,这意味着它可以使用 this 关键字来访问节点的其他属性。这使得验证可以依赖于其他属性值。
    在编辑节点时,this 对象反映了节点的当前配置,而不是当前表单元素的值。验证器函数应该尝试访问属性配置元素,并以 this 对象作为回退来实现正确的用户体验。
    Node-RED 提供了一组常见的验证函数:
  • RED.validators.number() - 检查值是否为数字。
  • RED.validators.regex(re) - 检查值是否与提供的正则表达式匹配。
    以下示例展示了如何应用这些验证器:
defaults: {
   minimumLength: { value:0, validate:RED.validators.number() },
   lowerCaseOnly: {value:"", validate:RED.validators.regex(/[a-z]+/) },
   custom: { value:"", validate:function(v) {
      var minimumLength=$("#node-input-minimumLength").length?$("#node-input-minimumLength").val():this.minimumLength;
      return v.length > minimumLength
   } }
},

这个例子中

  • minimumLength 属性使用 validate 属性和一个内置的验证函数 RED.validators.number(),确保它只能包含数字。
  • custom 属性使用 validate 属性和一个自定义函数,当该属性的值长度大于minimumLength属性的值或minimumLength表单元素的值时,该属性才有效。

Property edit dialog(编辑模板属性)

在 Node-RED 中,当编辑对话框打开时,编辑器会使用节点的编辑模板来填充对话框。
对于 defaults 数组中的每个属性,编辑器会查找一个具有特定 ID 的 <input> 元素,这个 ID 通常设置为 node-input-<propertyname>(对于普通节点)或 node-config-input-<propertyname>(对于配置节点)。然后,这个输入框会自动填充属性的当前值。当编辑对话框关闭时,属性将采用输入框中的值。
详情请参阅章节节点编辑对话框

Custom edit behavior(定制编辑行为)

在 Node-RED 中,虽然默认行为在许多情况下都适用,但有时需要定义一些特定于节点的行为。例如,如果某个属性不能作为简单的 <input><select> 元素进行编辑,或者如果编辑对话框的内容本身需要根据所选选项具有某些行为。
节点定义可以包含几个函数来定制编辑行为:

  • oneditprepare:在对话框显示之前立即调用。这个函数通常用于初始化对话框的状态,或者根据节点的当前配置来设置输入字段的值。
  • oneditsave:当编辑对话框被确认(通常是点击“确定”或“保存”按钮)时调用。这个函数用于从对话框的输入字段中收集值,并将它们应用到节点的配置中。
  • oneditcancel:当编辑对话框被取消(通常是点击“取消”按钮)时调用。这个函数通常用于清理或重置对话框的状态。
  • oneditdelete(仅配置节点):当配置节点的编辑对话框中的“删除”按钮被按下时调用。这个函数通常用于从配置上下文中删除节点的配置。
  • oneditresize:当编辑对话框被调整大小时调用。这个函数可以用于根据对话框的新大小来重新布局或调整对话框的内容。
    以 Inject 节点为例,当它被配置为重复时,它会将配置存储为一个类似 cron 的字符串,如 “1,2 * * * *”。该节点定义了一个 oneditprepare 函数,该函数可以解析这个字符串并呈现一个更友好的用户界面。它还定义了一个 oneditsave 函数,该函数将用户选择的选项编译回相应的 cron 字符串。
    下面是一个简化的示例,展示了如何在节点定义中使用这些函数:
RED.nodes.registerType('my-custom-node', {  
    // ... 其他节点定义属性 ...  
  
    oneditprepare: function() {  
        // 假设我们有一个名为 'repeatPattern' 的属性,它存储了一个 cron-like 字符串  
        // 我们可以在这里解析它,并设置相应的 UI 元素  
  
        // 获取 repeatPattern 属性的当前值  
        var repeatPattern = this.repeatPattern;  
  
        // 解析 repeatPattern 并设置 UI 元素的值(这里只是一个示例)  
        // ...  
  
        // 也可以添加事件监听器或执行其他初始化操作  
    },  
  
    oneditsave: function() {  
        // 当用户点击“确定”或“保存”时,这里会收集 UI 元素的值  
  
        // 假设我们有一个 UI 元素,用户可以在其中选择重复模式  
        // 我们将获取这些值,并将它们组合成一个 cron-like 字符串  
  
        // 获取 UI 元素的值  
        var cronValues = {  
            // ... 从 UI 元素中获取的值 ...  
        };  
  
        // 将值组合成 cron-like 字符串  
        var repeatPattern = cronValues.toCronString(); // 假设有这样一个方法  
  
        // 将 repeatPattern 保存到节点的配置中  
        this.repeatPattern = repeatPattern;  
    },  
  
    // ... 其他节点定义方法 ...  
});

节点凭证

在 Node-RED 中,某些节点属性可能被定义为凭据(credentials),这些凭据是单独存储的,不会包含在从编辑器导出的主流程文件中。以下是如何将凭据添加到节点的步骤:

  1. 向节点的定义中添加凭据条目
credentials: {
   username: {type:"text"},
   password: {type:"password"}
},
  • 在节点的定义中,为每个凭据条目添加一个新的条目。
  • 这些条目通常只包含一个选项 - 它们的类型,可以是文本(text)或密码(password)
  1. 向节点的编辑模板中添加适当的条目
 <div class="form-row">
     <label for="node-input-username"><i class="fa fa-tag"></i> Username</label>
     <input type="text" id="node-input-username">
 </div>
 <div class="form-row">
     <label for="node-input-password"><i class="fa fa-tag"></i> Password</label>
     <input type="password" id="node-input-password">
 </div>
- 在节点的编辑模板中,添加与凭据条目相对应的输入字段。
- 请注意,模板使用与常规节点属性相同的元素 ID 约定。对于凭据,通常使用 `node-input-credentials-<propertyname>` 或 `node-config-input-credentials-<propertyname>` 作为输入字段的 ID。
  1. 在节点的 .js 文件中更新 RED.nodes.registerType 调用以包含凭据
 RED.nodes.registerType("my-node",MyNode,{
     credentials: {
         username: {type:"text"},
         password: {type:"password"}
     }
 });
- 在节点的 JavaScript 文件中,找到 `RED.nodes.registerType` 的调用。
- 在这个调用中,你需要添加一个 `credentials` 属性,它是一个对象,列出了该节点需要的所有凭据属性。

访问凭证

在 Node-RED 中,节点在运行时和编辑器中访问凭据的方式有所不同。

运行时使用凭据

在运行时,节点可以通过 credentials 属性来访问其凭据。这个属性是一个对象,包含了在节点定义中声明的所有凭据属性。例如,如果你有一个名为 username 的文本凭据和一个名为 password 的密码凭据,你可以在节点的 JavaScript 代码中这样访问它们:

function MyCustomNode(n) {  
    RED.nodes.createNode(this,n);  
    // ... 其他节点初始化代码 ...  
  
    var username = this.credentials.username; 
	var password = this.credentials.password;
    // 注意:这里不能直接访问密码凭据,因为出于安全考虑,密码在运行时是不可见的  
  
    // 使用凭据执行某些操作...  
}  
RED.nodes.registerType('my-custom-node', {  
    // ... 其他节点定义属性 ...  
  
    credentials: {  
        username: {type: 'text'},  
        password: {type: 'password'}  
    },  
  
    // 指定节点构造函数  
    constructor: MyCustomNode  
});

编辑器中的凭据

在 Node-RED 的编辑器中,节点对凭据的访问是受限的。对于类型为 text 的凭据,节点可以在其配置和编辑函数中通过 credentials 属性访问它们,就像在运行时一样。但是,出于安全考虑,类型为 password 的凭据在编辑器中是不可见的。
对于密码凭据,Node-RED 提供了一个额外的机制:一个名为 has_<property-name> 的布尔属性。这个属性用于指示相应的凭据是否具有非空值。你可以在节点的 HTML 模板中使用这个属性来根据密码凭据是否存在来显示或隐藏某些元素(比如密码输入字段的显示/隐藏提示)。
在编辑模板的 HTML 中,你可以这样使用它:

<div class="form-row">  
    <label for="node-input-credentials-password"><i class="fa fa-lock"></i> 密码</label>  
    <input type="password" id="node-input-credentials-password" placeholder="密码" style="display: none;">  
    <div class="node-text-label" ng-show="!node.has_password && node.type === 'my-custom-node'">  
        密码未设置  
    </div>  
</div>

请注意,上述的 ng-show 指令是基于 AngularJS 的,这是 Node-RED 编辑器的一部分。根据你的具体情况和使用的技术栈,你可能需要使用不同的方法来控制 UI 元素的显示和隐藏。

高级凭证的使用

在 Node-RED 中,对于高级凭据的使用,有时需要存储比用户提供的凭据更多的值。例如,当一个节点支持 OAuth 工作流程时,它必须保留服务器分配的令牌,这些令牌用户从未见过。Twitter 节点提供了一个很好的示例来说明如何实现这一点。
对于这种情况,Node-RED 允许节点在运行时动态地更新和存储凭据值。这些值可以由服务器分配或生成,而用户则无法直接访问它们。为了安全地处理这些值,Node-RED 提供了以下建议:

  1. 使用安全的 API 调用:当从服务器获取令牌或其他敏感信息时,确保使用安全的 API 调用(如 HTTPS)。这有助于保护数据在传输过程中免受恶意攻击。
  2. 限制对凭据的访问:即使在节点内部,也应限制对凭据的访问。只有那些确实需要这些凭据来执行其功能的部分才应该能够访问它们。避免在不必要的地方存储或传输凭据。
  3. 使用加密存储:Node-RED 默认会加密存储凭据,但如果你需要额外的安全性,可以考虑使用更强大的加密算法或密钥管理系统来加密存储敏感信息。
  4. 定期更新和撤销凭据:对于服务器分配的令牌或其他敏感凭据,定期更新和撤销它们是一个好习惯。这有助于减少凭据泄露或滥用的风险。

在 Twitter 节点的情况下,它可能使用 OAuth 工作流程来获取访问令牌和刷新令牌。这些令牌由 Twitter 服务器分配,并且用户从未直接看到它们。Twitter 节点将这些令牌作为凭据存储,并在需要时使用它们来访问 Twitter API。为了安全地处理这些令牌,Twitter 节点可能遵循上述建议,并使用安全的 API 调用、限制对令牌的访问、加密存储以及定期更新和撤销令牌。

节点外观

节点的外观有三个方面可以自定义;图标、背景颜色及其标签。

图标 (Icon)

节点的图标可以通过其定义中的icon属性来指定。这个属性的值可以是字符串或函数。

  1. 字符串值
    • 如果icon属性的值是一个字符串,那么这个字符串通常会被解释为图标的路径(在前端资源中)或者直接是某个图标库中的图标名称(如Font Awesome的类名)。
    • 例如,如果使用的是Font Awesome图标库,icon属性的值可能是"fas fa-user",这将显示一个用户图标。
// 在节点定义中  
{  
  "id": "my-node",  
  "type": "my-node-type",  
  "label": "My Node",  
  "icon": "fas fa-user" // 使用Font Awesome的图标类名  
}
  1. 函数值
    • 如果icon属性的值是一个函数,那么这个函数会在节点首次加载时或编辑后被评估。
    • 函数预期返回一个值,这个值将作为图标的来源。这个返回值可以是一个字符串(如上所述),也可以是其他能够唯一标识图标的值。
    • 函数会在两种情况下被调用:一是对于工作空间中的节点实例(此时this引用该节点实例),二是对于调色板中的节点条目(此时this不引用特定节点实例,因此函数必须能够返回一个有效的值)。
// 在节点定义中  
{  
  "id": "my-node",  
  "type": "my-node-type",  
  "label": "My Node",  
  "icon": function() {  
    // 根据某些条件动态返回图标  
    if (this.someProperty) {  
      return "fas fa-user"; // 如果节点具有某些属性,则显示用户图标  
    } else {  
      return "fas fa-cog"; // 否则显示齿轮图标  
    }  
  }  
}

在Node-RED中,一个节点的图标可以是以下三种类型之一:

  1. Node-RED自带的标准图标名称:Node-RED自身提供了一些标准的图标,这些图标的名称可以直接用作icon属性的值。例如,可能有一些代表不同功能的默认图标(如表示函数节点、输入节点、输出节点等的图标)。
  2. 模块提供的自定义图标名称:如果你正在使用某个特定的Node-RED模块,该模块可能会提供自己的图标集。这些图标的名称也可以用作icon属性的值。这允许模块开发者为其节点定义独特的视觉表示。
  3. Font Awesome 4.7图标:Font Awesome是一个流行的图标字体库,提供了大量的矢量图标。Node-RED支持使用Font Awesome图标,特别是4.7版本(尽管更新的版本可能也受支持,但这里明确提到了4.7)。你可以通过指定Font Awesome 4.7图标的类名(如fa fa-user)来在Node-RED中使用这些图标。

标准图标Stock icons

在这里插入图片描述
在Node-RED 1.0中,关于标准图标的注意事项是:所有的标准图标都已经被SVG(可缩放矢量图形)替代,以提供更好的外观。为了确保向后兼容性,编辑器会自动将任何对PNG版本图标的请求替换为可用的SVG版本(如果SVG版本可用的话)。
这意味着如果你在Node-RED 1.0或更新版本中定义一个节点,并指定了一个传统的PNG图标名称,Node-RED将尝试找到对应的SVG版本(如果它存在)并使用它,而不是PNG版本。这样,你可以继续使用旧的图标名称,但你的用户将看到更高质量的SVG图标。
对于开发者来说,如果你正在创建一个新的Node-RED节点并希望提供图标,最好直接提供SVG图标,因为它们是矢量图形,可以无限缩放而不失真。但是,如果你只有PNG图标,Node-RED仍然会尝试使用它,尽管SVG版本通常提供更好的用户体验。

自定义图标Custom icon

关于自定义图标,Node-RED允许节点在其.js.html文件所在的同一目录下,创建一个名为icons的目录来提供自己的图标。当编辑器查找特定图标文件名时,这些目录会被添加到搜索路径中。因此,图标文件名必须是唯一的,以避免与其他节点或模块的图标发生冲突。
自定义图标应该满足以下要求:

  1. 背景透明:图标的背景应该是透明的,以便在各种背景和主题下都能正确显示。
  2. 白色图标:图标本身通常是白色的,这样在深色或浅色背景下都能保持清晰可见。
  3. 2:3 纵横比:图标的纵横比应为2:3,这是为了在Node-RED的用户界面中保持一致的视觉效果。
  4. 最小尺寸:图标的尺寸至少应为40x60像素。虽然更大尺寸的图标可以提供更高的清晰度,但40x60像素是最低要求,以确保在所有屏幕尺寸下都能正确显示。
  5. 文件名唯一性:如上所述,由于图标文件名会被添加到搜索路径中,因此文件名必须是唯一的,以避免与其他图标发生冲突。

在创建自定义图标时,请确保遵循这些指导原则,以确保图标在Node-RED中能够正确显示并与其他元素相协调。

Font Awesome icon

在Node-RED中,你可以使用Font Awesome 4.7 的完整图标集。要指定一个Font Awesome(FA)图标,属性应该采用以下形式:

"icon": "fa fa-user",

在Node-RED的节点定义中,将这个字符串设置为icon属性的值,该节点就会在节点编辑器和工作空间中显示相应的Font Awesome图标。
请注意,虽然Node-RED可能包括Font Awesome 4.7的图标集,但未来的版本可能会更新到更高版本的Font Awesome。因此,在编写依赖于Font Awesome图标的代码时,最好检查你正在使用的Node-RED版本的文档,以确定可用的图标版本和名称。

用户自定义图标

在Node-RED中,用户可以在节点的编辑对话框的“外观”(appearance)选项卡中自定义单个节点的图标。然而,如果节点在其默认对象(defaults object)中有一个icon属性,那么该节点的图标就不能被用户自定义。
例如,对于node-red-dashboard中的ui_button节点,由于其已经在默认配置中定义了图标,因此用户不能在编辑对话框中更改其图标。
如果你想要允许用户自定义你的节点的图标,你应该确保你的节点定义中没有在默认对象(defaults)中指定icon属性。这样,当用户在编辑对话框的“外观”选项卡中更改图标时,他们的选择就会被保存并应用到该节点上。
要在你的节点定义中省略icon属性,只需确保在定义defaults对象时不要包含它。例如:

module.exports = function(RED) {  
    "use strict";  
    function MyNode(n) {  
        RED.nodes.createNode(this,n);  
        // ... 节点功能的代码 ...  
    }  
    RED.nodes.registerType("my-node", MyNode, {  
        category: "function",  
        // 注意:这里没有指定 'icon' 属性  
        // 其他节点属性定义 ...  
    });  
};

然后,在Node-RED编辑器中,用户就可以为my-node类型的节点选择他们喜欢的图标了。如果他们之前没有选择过图标,节点可能会使用一个默认的图标(通常是Node-RED的标志或其他占位符)。

背景色

节点背景色是快速区分不同节点类型的主要方式之一。它通过在节点定义中的color属性来指定。

...
color: "#a6bbcf",
...

Node-RED采用了一种柔和的色彩方案。新节点应该尽量找到一个与该色彩方案相匹配的颜色。
以下是一些常用的颜色:
在这里插入图片描述

标签 Labels

节点有四种标签属性:labelpaletteLabeloutputLabelinputLabel

节点标签Node label

节点在工作区中的标签可以是静态文本,也可以根据节点的属性动态设置每个节点的标签。
标签属性的值可以是一个字符串或一个函数。

  • 如果值是一个字符串,那么该字符串将用作标签。
  • 如果值是一个函数,它将在节点首次加载或编辑后被评估。该函数应返回要用作标签的值。

如之前部分所述,有一个约定是节点应该有一个name属性来帮助区分它们。以下示例展示了如何将标签设置为获取此属性的值,或者在未设置时采用一个合理的默认值。

label: function() {
    return this.name||"lower-case";
},

请注意,在标签函数中无法使用凭据属性(credential properties)。

Palette label

默认情况下,节点的类型在节点面板中被用作其标签。但是,你可以使用paletteLabel属性来覆盖这个默认行为。
label属性类似,paletteLabel也可以是一个字符串或一个函数。如果它是一个函数,它会在节点被添加到面板时评估一次。
这是一个使用paletteLabel属性的例子:

//javascript
module.exports = function(RED) {  
    function MyCustomNode(config) {  
        RED.nodes.createNode(this, config);  
        // 节点功能实现...  
    }  
  
    RED.nodes.registerType("my-custom-node", MyCustomNode, {  
        category: "function",  
        color: "#DBEDF3",  
        defaults: {  
            // 默认属性...  
        },  
        // 使用自定义的paletteLabel  
        paletteLabel: "自定义节点",  
        // 其他选项...  
    });  
}

在这个例子中,my-custom-node类型在节点面板中将以“自定义节点”作为标签显示,而不是使用默认的节点类型名“my-custom-node”。
如果你希望根据节点的某些属性动态地设置面板标签,你可以提供一个函数作为paletteLabel的值。但是,请注意,由于这个函数只会在节点添加到面板时评估一次,因此它不能用于基于节点的实时状态或属性来动态改变面板标签。如果你需要这样的功能,你可能需要考虑其他方法来实现,比如使用一个自定义的侧边栏或弹出窗口来显示节点的详细信息。

Label style

在Node-RED中,可以使用labelStyle属性来动态设置标签的CSS样式。目前,这个属性必须指定要应用的CSS类。如果没有指定,它将使用默认的node_label类。除了默认的类之外,还有一个预定义的类node_label_italic用于将标签设置为斜体。
在这里插入图片描述

以下是一个示例,展示了如何根据name属性是否已设置来将labelStyle设置为node_label_italic

...
labelStyle: function() {
    return this.name?"node_label_italic":"";
},
...

对齐

在Node-RED中,图标和标签在节点内默认是左对齐的。然而,对于位于流程末端的节点,按照惯例,内容应该是右对齐的。这可以通过在节点定义中设置align属性为right来实现。

...
align: 'right',
...

在这里插入图片描述

Port labels

在Node-RED中,节点可以在其输入和输出端口上提供标签,这些标签在鼠标悬停在端口上时可见。
在这里插入图片描述

这些标签可以通过节点的HTML文件静态设置

...
inputLabels: "parameter for input",
outputLabels: ["stdout","stderr","rc"],
...

也可以通过一个函数动态生成,该函数接收一个索引参数以指示输出引脚(从0开始)。

...
outputLabels: function(index) {
    return "my port number "+index;
}
...

在两种情况下,用户都可以在配置编辑器的节点设置部分中覆盖这些标签。
在这里插入图片描述

请注意,端口标签不是动态生成的(除了通过节点定义的函数),并且不能通过msg属性设置。它们是静态的或基于节点配置和状态的,但一旦节点被配置,用户就有能力覆盖这些标签。

按钮Buttons

在Node-RED中,节点可以在其左侧或右侧边缘有一个按钮,就像核心的Inject和Debug节点那样。然而,一个关键的原则是编辑器不是用于控制流程的仪表板。因此,一般来说,节点上不应该有按钮。Inject和Debug节点是特殊情况,因为它们的按钮在流程开发中起到了作用。
在节点定义中,button属性用于描述按钮的行为。它必须至少提供一个onclick函数,该函数将在按钮被点击时调用。

...
button: {
    onclick: function() {
        // Called when the button is clicked
    }
},
...

此外,button属性还可以定义以下功能:

  • enabled函数:用于根据节点的当前配置动态启用或禁用按钮。
  • visible函数:用于确定按钮是否应该显示。
...
button: {
    enabled: function() {
        // return whether or not the button is enabled, based on the current
        // configuration of the node
        return !this.changed
    },
    visible: function() {
        // return whether or not the button is visible, based on the current
        // configuration of the node
        return this.hasButton
    },
    onclick: function() { }
},
...

按钮还可以配置为切换按钮,就像Debug节点那样。这是通过在button属性中添加一个名为toggle的属性来完成的,该属性标识节点defaults对象中的一个属性,该属性应用于存储一个布尔值。每当按钮被按下时,这个布尔值就会切换。

...
defaults: {
    ...
    buttonState: {value: true}
    ...
},
button: {
    toggle: "buttonState",
    onclick: function() { }
}
...

节点编辑对话框

节点的编辑对话框是用户配置节点以满足其需求的主要方式。编辑对话框应该直观易用,并且与其他节点相比,其设计和外观应保持一致。

编辑对话框在节点的 HTML 文件中提供,位于 <script> 标签内

<script type="text/html" data-template-name="node-type">
    <!-- edit dialog content  -->
</script>
  • <script> 标签的 type 属性应设置为 text/html,以便文本编辑器提供适当的语法高亮,并防止浏览器在将节点加载到编辑器中时将其视为正常的HTML内容。
  • 标签的 data-template-name 属性应设置为节点的类型,这样编辑器就知道在编辑特定节点时应显示什么内容。

对话框通常由一系列行组成,每行包含一个标签和一个输入字段,用于不同的属性

<div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
</div>
  • 每行由带有 form-row 类的 <div> 创建。
  • 典型的行将包含一个 <label>,其中包含一个图标和属性名称,后跟一个 <input> 元素。
  • 图标使用带有从 Font Awesome 4.7 提供的类名的 <i> 元素创建。

Node-RED 提供了一些标准 UI 控件,节点可以使用这些控件来创建更丰富、更一致的用户体验。这些控件包括颜色选择器、下拉列表、文件上传器等。
使用这些标准控件可以确保节点的编辑对话框与其他节点保持一致,并提供熟悉的用户界面。

按钮控件Buttons

要在Node-RED节点的编辑对话框中添加一个按钮,您可以使用标准的<button> HTML元素,并为其添加一个特定的类,通常是red-ui-button,以符合Node-RED UI的样式。这样做可以确保按钮与Node-RED的其他UI元素保持一致。

Plain button

在这里插入图片描述

<button type="button" class="red-ui-button">Button</button>
Small button

在这里插入图片描述

<button type="button" class="red-ui-button red-ui-button-small">Button</button>
Toggle button group

HTML

<span class="button-group">
	<button type="button" class="red-ui-button toggle selected my-button-group">b1</button><button type="button" class="red-ui-button toggle my-button-group">b2</button><button type="button" class="red-ui-button toggle my-button-group">b3</button>
</span>
$(".my-button-group").on("click", function() {
    $(".my-button-group").removeClass("selected");
    $(this).addClass("selected");
})

oneditprepare

在Node-RED的节点编辑对话框中,如果您想要在用户点击按钮时切换活动按钮的选中状态(即添加或移除selected类),您需要在oneditprepare函数中为按钮添加事件监听器。
同时,需要注意按钮之间的空白字符,因为当前的button-group可能不会正确地处理空白字符,这可能导致布局上的问题。在未来的版本中,这个问题可能会被修复。
下面是一个例子,展示了如何在oneditprepare函数中为按钮添加事件监听器,并在点击时切换selected类:

RED.nodes.registerType('my-node-type', {  
    // ... 其他节点配置 ...  
  
    oneditprepare: function() {  
        // 假设您有两个按钮,button1 和 button2  
        var button1 = this.editor.find('#button1')[0];  
        var button2 = this.editor.find('#button2')[0];  
  
        // 初始时,假设 button1 是选中的  
        button1.classList.add('selected');  
  
        // 为 button1 添加点击事件监听器  
        button1.addEventListener('click', function() {  
            // 移除 button2 的 selected 类(如果有的话)  
            button2.classList.remove('selected');  
            // 为 button1 添加 selected 类  
            this.classList.add('selected');  
            // 执行其他与 button1 相关的操作(如果有的话)  
        });  
  
        // 为 button2 添加点击事件监听器  
        button2.addEventListener('click', function() {  
            // 移除 button1 的 selected 类(如果有的话)  
            button1.classList.remove('selected');  
            // 为 button2 添加 selected 类  
            this.classList.add('selected');  
            // 执行其他与 button2 相关的操作(如果有的话)  
        });  
  
        // ... 其他与编辑对话框相关的代码 ...  
    },  
  
    // ... 其他节点方法 ...  
});

在上面的代码中,oneditprepare函数首先通过this.editor.find方法获取了两个按钮元素(button1button2),并为它们分别添加了点击事件监听器。当点击按钮时,监听器会检查当前哪个按钮被选中,并相应地切换selected类。
请注意,上面的代码假设button1button2是您在HTML模板中定义的按钮的ID。您需要将它们替换为您实际使用的ID。
另外,请确保在HTML模板中不要在按钮元素之间添加不必要的空白字符,以避免布局问题。如果需要在按钮之间添加分隔符或空间,可以使用CSS样式来实现。

输入控件Inputs

Node-RED 的节点编辑器允许开发者使用多种输入控件来收集用户输入,包括标准的 HTML <input> 元素以及更高级的 TypedInput 控件。TypedInput 控件为开发者提供了一种更灵活的方式来指定属性的类型和值,适用于那些可能需要多种数据类型或上下文引用的属性。
例如,TypedInput 控件支持多种内置类型,如字符串、数字、布尔值、数组、枚举、JSON 编辑器、消息引用等。或者该属性是否用于标识消息、流或全局上下文属性。
TypedInput 控件是一个基于 jQuery UI 的控件,需要将代码添加到节点的oneditprepare函数中才能将其添加到页面中。
这里提供了TypedInput小部件的完整API文档 ,包括可用内置类型的列表。

Plain HTML Input

在这里插入图片描述

<input type="text" id="node-input-name">
TypedInput String/Number/Boolean

在这里插入图片描述

<input type="text" id="node-input-example1">
<input type="hidden" id="node-input-example1-type">

HTML

$("#node-input-example1").typedInput({
    type:"str",
    types:["str","num","bool"],
    typeField: "#node-input-example1-type"
})

oneditprepare

TypedInput(这里可能指的是某种支持多种输入类型的控件或组件)能够设置为多种类型时,通常需要一种方式来存储和标识当前的类型信息。在这种情况下,添加一个额外的节点属性(或数据属性)来存储类型信息是很常见的做法。
为了实现这一点,并且不干扰用户与界面的正常交互,开发人员可能会选择在编辑对话框中添加一个隐藏的<input>元素。这个隐藏的<input>元素不会在页面上显示出来,但会作为HTML文档结构的一部分存在,并可以通过JavaScript或其他客户端脚本语言来访问和修改其值。

TypedInput JSON

在这里插入图片描述

<input type="text" id="node-input-example2">

HTML

$("#node-input-example2").typedInput({
    type:"json",
    types:["json"]
})

oneditprepare

如果您在描述一个JSON类型的输入字段,它包含一个按钮,该按钮在点击时会打开一个专门的JSON编辑对话框(但在某个演示中被禁用了),那么这通常意味着这个输入字段旨在让用户输入或编辑JSON格式的数据。
为了提供这样的功能,您可能需要设计一个复杂的用户界面,该界面包括一个文本输入区(可能是一个<textarea>元素,因为它允许输入多行文本),以及一个按钮,用于触发JSON编辑对话框的打开。
JSON编辑对话框通常会提供一个更友好的方式来编辑JSON数据,比如通过树形结构、表单字段或其他可视化工具。这样的对话框可能会使用JavaScript和某种前端框架(如React、Vue或Angular)来实现。

TypedInput msg/flow/global

在这里插入图片描述

<input type="text" id="node-input-example3">
<input type="hidden" id="node-input-example3-type">

HTML

$("#node-input-example3").typedInput({
    type:"msg",
    types:["msg", "flow","global"],
    typeField: "#node-input-example3-type"
})

oneditprepare

TypedInput Select box

在这里插入图片描述

<input type="text" id="node-input-example4">

HTML

$("#node-input-example4").typedInput({
    types: [
        {
            value: "fruit",
            options: [
                { value: "apple", label: "Apple"},
                { value: "banana", label: "Banana"},
                { value: "cherry", label: "Cherry"},
            ]
        }
    ]
})

oneditprepare

TypedInput Multiple Select box

在这里插入图片描述

<input type="text" id="node-input-example5">

HTML

$("#node-input-example5").typedInput({
    types: [
        {
            value: "fruit",
            multiple: "true",
            options: [
                { value: "apple", label: "Apple"},
                { value: "banana", label: "Banana"},
                { value: "cherry", label: "Cherry"},
            ]
        }
    ]
})

oneditprepare

当涉及到一个多选(multiple select)元素时,其最终的输出值通常是一个由选定的选项组成的、由逗号分隔的列表(comma-separated list)。这种格式在HTML表单提交时特别常见,因为多选元素允许用户选择多个选项。

多行编辑器Multi-line Text Editor

在Node-RED中,使用基于Ace代码编辑器 (或如果通过用户设置启用,则使用Monaco编辑器 )的多行文本编辑器来编辑节点属性是很常见的。
以下是一个例子,展示了如何在一个Node-RED节点中集成这样的编辑器来编辑名为exampleText的节点属性。
首先,在你的HTML文件中,为编辑器添加一个<div>占位符。这个<div>必须包含node-text-editor这个CSS类,并且你需要为这个元素设置一个高度。

<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-example-editor"></div>

然后,在你的Node-RED节点的JavaScript代码中,你需要在oneditprepare函数中使用RED.editor.createEditor初始化文本编辑器。

this.editor = RED.editor.createEditor({
   id: 'node-input-example-editor',
   mode: 'ace/mode/text',
   value: this.exampleText
});

同时,你还需要oneditsaveoneditcancel函数来在对话框关闭时从编辑器中获取值,并确保编辑器正确地从页面中移除。

oneditsave: function() {
    this.exampleText = this.editor.getValue();
    this.editor.destroy();
    delete this.editor;
},
oneditcancel: function() {
    this.editor.destroy();
    delete this.editor;
},

节点上下文Node context

在Node-RED中,节点可以在其上下文(context)对象中存储数据。
更多context信息请参考[Working with Context guide]
上下文对象是一种用于在节点之间共享或持久化数据的机制。正如你提到的,有三种上下文作用域可供节点使用:

  1. 节点作用域(Node):仅对设置该值的节点可见。
  2. 流作用域(Flow):对同一流(或在编辑器中的同一标签页)上的所有节点可见。
  3. 全局作用域(Global):对所有节点可见。

与Function节点提供预定义变量以访问这些上下文的方式不同,自定义节点需要自行访问这些上下文。

// Access the node's context object
var nodeContext = this.context();

var flowContext = this.context().flow;

var globalContext = this.context().global;

每种上下文对象都具有在编写函数指南Writing Functions guide.中描述的相同的getset函数。
对于配置节点(Configuration nodes),它们通常被其他节点使用并共享。默认情况下,这些配置节点是全局的,除非节点用户另有指定。因此,不能假设它们可以访问流上下文(Flow context)。
在Node-RED中,你可以通过node.context().get()node.context().set()(对于节点作用域),flow.get()flow.set()(对于流作用域),以及global.get()global.set()(对于全局作用域)来访问和设置上下文值。

在Node-RED中,配置节点通常用于存储和管理可以被多个节点共享的配置数据。这些配置节点默认是全局的(Global scope),这意味着它们的数据可以在整个Node-RED实例中被任何节点访问。
由于配置节点是全局的,它们并不直接关联到特定的流(Flow),因此它们没有流作用域(Flow context)的访问权限。如果你需要在配置节点中存储与特定流相关的数据,你可能需要设计一种不同的数据架构,例如使用全局上下文(Global context)并存储一个键-值对映射,其中键是流的标识符,值是特定于该流的数据。
但是,请注意,尽管配置节点默认是全局的,但Node-RED的某些节点或自定义节点可能会提供额外的选项来限制配置节点的作用域。然而,这通常不是配置节点的标准行为,需要特定的节点实现来支持。
最后,当在Node-RED中设计节点和流时,了解并正确使用上下文作用域是非常重要的,以确保数据的正确性和可维护性。

节点状态

节点状态允许节点在运行时与编辑器UI共享其当前状态信息。这对于向用户传达节点的当前状况(如是否已连接、是否正在处理消息等)非常有用。
在这里插入图片描述
节点可以使用status函数来设置其当前状态。正如您所提到的,MQTT节点可能会使用类似于以下示例的调用来设置状态:

this.status({fill:"red",shape:"ring",text:"disconnected"});  this.status({fill:"green",shape:"dot",text:"connected"});

在上面的例子中,fill属性用于定义状态图标的颜色,shape属性定义图标的形状(如环形或点),而text属性(可选)则用于在图标旁边显示简短的文本。
默认情况下,节点状态信息在编辑器中显示,但用户可以通过下拉菜单中的“Display Node Status”选项来禁用或重新启用它。

Status object

状态对象(status对象)的属性包括:

  • fill:定义状态图标的填充颜色,可以是redgreenyellowbluegrey
    这允许使用以下图标
    在这里插入图片描述
  • shape:定义状态图标的形状,可以是ringdot
  • text:一个可选的简短文本字符串(少于20个字符),用于在图标旁边显示。

如果状态对象是一个空对象{},则节点的状态条目将被清除。

注意事项

从Node-RED v0.12.x开始,引入了“Status”节点,它可以捕获任何节点的状态更新,例如连接和断开连接的消息,以触发其他流。这使得状态更新可以在整个Node-RED应用中传播,并基于这些更新执行其他操作。

配置节点

配置节点允许你在多个节点之间共享配置信息,以减少重复配置,并提高配置的可管理性。
以MQTT In和MQTT Out节点为例,它们可以共享MQTT代理的配置,从而复用连接。这种配置节点的状态在默认情况下是全局的,意味着它会在不同的流(Flows)之间共享。

定义配置节点

定义配置节点时,你需要遵循与其他节点相同的步骤,但有两个关键差异:

  1. Category 属性:配置节点的 category 属性应设置为 config。这告诉Node-RED这个节点是一个配置节点,而不是一个处理消息流的节点。
  2. 编辑模板的 input 元素 ID:配置节点的编辑模板中的 <input> 元素应该具有特定的ID格式,即 node-config-input-<propertyname>,其中 <propertyname> 是配置属性的名称。这种命名约定允许Node-RED正确地将编辑模板中的输入字段与配置节点的属性进行映射。

remote-server.html

<script type="text/javascript">
    RED.nodes.registerType('remote-server',{
        category: 'config',
        defaults: {
            host: {value:"localhost",required:true},
            port: {value:1234,required:true,validate:RED.validators.number()},
        },
        label: function() {
            return this.host+":"+this.port;
        }
    });
</script>

<script type="text/html" data-template-name="remote-server">
    <div class="form-row">
        <label for="node-config-input-host"><i class="fa fa-bookmark"></i> Host</label>
        <input type="text" id="node-config-input-host">
    </div>
    <div class="form-row">
        <label for="node-config-input-port"><i class="fa fa-bookmark"></i> Port</label>
        <input type="text" id="node-config-input-port">
    </div>
</script>

remote-server.js

module.exports = function(RED) {
    function RemoteServerNode(n) {
        RED.nodes.createNode(this,n);
        this.host = n.host;
        this.port = n.port;
    }
    RED.nodes.registerType("remote-server",RemoteServerNode);
}

在这个例子中,节点作为配置的简单容器,实际上并没有运行时的行为。但是,配置节点在Node-RED中有许多常见的应用场景。
一个常见的用法是表示与远程系统的共享连接。在这种情况下,配置节点不仅包含连接所需的配置信息,还可能负责建立这个连接,并使其可供使用此配置节点的其他节点使用。例如,MQTT配置节点可能包含MQTT代理的URL、端口、用户名和密码等信息,并负责建立与MQTT代理的连接。
当配置节点负责建立连接时,它也应该在自身被停止时处理关闭事件以断开连接。这是为了确保资源的正确管理和释放,避免在不再需要连接时仍然保持打开状态。
在Node-RED中,配置节点通常通过实现特定的生命周期事件来处理连接的建立和断开。例如,当配置节点被部署时,它可以触发一个事件来建立连接;当配置节点被停止或删除时,它可以触发另一个事件来断开连接。这些事件可以由与配置节点相关联的其他节点监听,以便在连接状态变化时执行相应的操作。
总之,配置节点在Node-RED中是一种强大的工具,允许开发者在多个节点之间共享配置信息,并处理与这些配置相关的运行时行为(如建立连接和断开连接)

Using a config node

当一个节点需要使用配置节点时,它会在其defaults数组中注册一个属性,该属性的type属性设置为配置节点的类型。
例如,在defaults数组中定义了一个名为server的属性,其类型为remote-server,这表示该节点将使用类型为remote-server的配置节点:

defaults: {
   server: {value:"", type:"remote-server"},
},

在编辑模板(edit template)中,Node-RED会查找一个具有idnode-input-<propertyname><input>元素,其中<propertyname>defaults数组中定义的属性名(在这个例子中是server)。但是,与其他属性不同,Node-RED会替换这个<input>元素为一个<select>元素,该元素被填充为可用的配置节点实例列表,并且会附带一个按钮,用于打开配置节点的编辑对话框。
在这里插入图片描述
在运行时,节点可以使用这个属性(在这个例子中是server)来访问配置节点。

module.exports = function(RED) {
    function MyNode(config) {
        RED.nodes.createNode(this,config);

        // Retrieve the config node
        this.server = RED.nodes.getNode(config.server);

        if (this.server) {
            // Do something with:
            //  this.server.host
            //  this.server.port
        } else {
            // No config node configured
        }
    }
    RED.nodes.registerType("my-node",MyNode);
}

一旦用户通过编辑器选择了一个配置节点实例,Node-RED就会在节点实例和配置节点实例之间建立一种关联。
在节点的代码中,你可以通过this.server(在这个例子中)来访问配置节点的配置信息。这通常包括配置节点中定义的所有属性,这些属性可以在节点的oneditprepareoninputsoneditsave等函数中访问和使用。
通过这种方式,配置节点提供了一种在多个节点之间共享配置信息的机制,使得配置更加集中和易于管理。

帮助样式指南

选择Node节点后,其帮助文本将显示在信息选项卡中。此帮助应为用户提供使用节点所需的所有信息。
为了确保Node的帮助文本在多个节点之间保持一致的外观,以下是一个关于如何结构化Node帮助文本的样式指南。这个指南特别强调了当使用Markdown编写帮助文本时需要注意的事项。
自2.1.0起:帮助文本可以作为markdown而不是HTML提供。在这种情况下,<script>标记的type属性必须是text/markdown
在创建markdown帮助文本时,请小心缩进,markdown对空格敏感,因此所有行在<script>标记内都不应有前导空格。

  1. 以下是一个关于Node帮助文本的高层次介绍部分的示例,它应该不超过2到3行,并且第一行(<p>标签内)将用作调色板中悬停在节点上时的工具提示。
    在这里插入图片描述
  2. 当节点具有输入时,以下是一个描述节点将使用的消息属性的部分示例。同时,也提供了每个属性预期的类型。描述应保持简洁,如果需要进一步描述,应在“Details”部分中进行。
    在这里插入图片描述
  3. 当节点具有输出时,与输入部分类似,这个部分描述了节点将发送的消息的属性。如果节点具有多个输出,可以为每个输出提供一个单独的属性列表。
    在这里插入图片描述
  4. 这个部分提供了关于节点的更多详细信息,解释了如何使用它,并提供了关于其输入/输出的更多信息。.
    在这里插入图片描述
  5. 这个部分可以用来提供外部资源的链接,例如:
  • 任何相关的附加文档。比如,如果是一个使用Mustache模板的节点,它可能会链接到Mustache语言的官方指南。
  • 节点的Git仓库或npm页面——用户可以在这些页面上获取额外的帮助和支持。.
    在这里插入图片描述
    上面的示例是使用以下内容创建的:
    HTML
<script type="text/html" data-help-name="node-type">
<p>Connects to a MQTT broker and publishes messages.</p>

<h3>Inputs</h3>
    <dl class="message-properties">
        <dt>payload
            <span class="property-type">string | buffer</span>
        </dt>
        <dd> the payload of the message to publish. </dd>
        <dt class="optional">topic <span class="property-type">string</span></dt>
        <dd> the MQTT topic to publish to.</dd>
    </dl>

 <h3>Outputs</h3>
     <ol class="node-ports">
         <li>Standard output
             <dl class="message-properties">
                 <dt>payload <span class="property-type">string</span></dt>
                 <dd>the standard output of the command.</dd>
             </dl>
         </li>
         <li>Standard error
             <dl class="message-properties">
                 <dt>payload <span class="property-type">string</span></dt>
                 <dd>the standard error of the command.</dd>
             </dl>
         </li>
     </ol>

<h3>Details</h3>
    <p><code>msg.payload</code> is used as the payload of the published message.
    If it contains an Object it will be converted to a JSON string before being sent.
    If it contains a binary Buffer the message will be published as-is.</p>
    <p>The topic used can be configured in the node or, if left blank, can be set
    by <code>msg.topic</code>.</p>
    <p>Likewise the QoS and retain values can be configured in the node or, if left
    blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p>

<h3>References</h3>
    <ul>
        <li><a>Twitter API docs</a> - full description of <code>msg.tweet</code> property</li>
        <li><a>GitHub</a> - the nodes github repository</li>
    </ul>
</script>

Markdown

<script type="text/markdown" data-help-name="node-type">
Connects to a MQTT broker and publishes messages.

### Inputs

: payload (string | buffer) :  the payload of the message to publish.
: *topic* (string)          :  the MQTT topic to publish to.


### Outputs

1. Standard output
: payload (string) : the standard output of the command.

2. Standard error
: payload (string) : the standard error of the command.

### Details

`msg.payload` is used as the payload of the published message.
If it contains an Object it will be converted to a JSON string before being sent.
If it contains a binary Buffer the message will be published as-is.

The topic used can be configured in the node or, if left blank, can be set
`msg.topic`.

Likewise the QoS and retain values can be configured in the node or, if left
blank, set by `msg.qos` and `msg.retain` respectively.

### References

 - [Twitter API docs]() - full description of `msg.tweet` property
 - [GitHub]() - the nodes github repository
</script>

Section headers

主要部分的标题应该使用 <h3> 标签来标记。如果“Details”部分需要子标题(也就是二级标题),那么这些子标题应该使用 <h4> 标签来标记。
HTML

<h3>Inputs</h3>
...
<h3>Details</h3>
...
 <h4>A sub section</h4>
 ...

Markdown

### Inputs
...
### Details
...
#### A sub section
...

消息属性Message properties

当你想列出消息的属性时,你可以使用HTML的<dl>(描述列表)元素,并为其添加一个class属性值为message-properties,以便可以通过CSS进行样式定制。

  • 每个属性都应当包含在一个<dt>(定义术语)和<dd>(描述定义)的组合中。
  • <dt> 标签包含属性名。如果属性是可选的,<dt> 标签会添加 class="optional" 属性。如果属性的类型被指定,类型会被包裹在带有 class="property-type"<span> 标签内。
  • <dd> 标签包含对属性的简短描述。
    HTML
<dl class="message-properties">
    <dt>payload
        <span class="property-type">string | buffer</span>
    </dt>
    <dd> the payload of the message to publish. </dd>
    <dt class="optional">topic
        <span class="property-type">string</span>
    </dt>
    <dd> the MQTT topic to publish to.</dd>
</dl>

Markdown

: payload (string | buffer) :  the payload of the message to publish.
: *topic* (string)          :  the MQTT topic to publish to.

多个输出Multiple outputs

如果一个节点具有多个输出,每个输出都应该拥有自己独立的消息属性列表,正如之前描述的那样。这些列表应该被包装在一个带有class属性为node-ports<ol>(有序列表)中。
每个列表项应该包含对输出的简短描述,后面跟着一个<dl>消息属性列表。
注意:如果节点只有一个输出,那么它不应该被包装在这样的列表中,只需要使用<dl>即可。
HTML

<ol class="node-ports">
    <li>Standard output
        <dl class="message-properties">
            <dt>payload <span class="property-type">string</span></dt>
            <dd>the standard output of the command.</dd>
        </dl>
    </li>
    <li>Standard error
        <dl class="message-properties">
            <dt>payload <span class="property-type">string</span></dt>
            <dd>the standard error of the command.</dd>
        </dl>
    </li>
</ol>

Markdown

1. Standard output
: payload (string) : the standard output of the command.

2. Standard error
: payload (string) : the standard error of the command.

通用指导General guidance

当在描述中引用上述消息属性列表之外的消息属性时,应该使用msg.作为前缀,以明确告知读者这是消息的一个属性。这些属性应该被包裹在<code>标签中,以便在文档中清晰地显示它们。
HTML

The interesting part is in <code>msg.payload</code>.

Markdown

The interesting part is in `msg.payload`.

在帮助文档的正文中,除了<code>标签用于标识代码或变量外,不应使用其他样式标记(如<b><i>等)。保持文档文本清晰简洁,避免过多的样式干扰。
帮助文档不应假设读者是经验丰富的开发者或对该节点暴露的内容有深入了解。它应该是对所有读者都易于理解的,提供足够的背景信息,并清晰地解释节点的工作原理、配置选项和输出。

示例流程

在Node-RED中,节点开发者可以提供示例流程,以展示其节点如何使用。这些示例流程将出现在库导入菜单的“示例”部分。
理想情况下,示例应该简短,并包含一个注释节点来描述功能,而且不应使用需要安装的其他第三方节点。
为了创建一个示例,你需要在你的节点包中添加一个examples目录,并在该目录中放置流程文件。有关更多详细信息,请参阅上面的打包章节。
流程文件的名称将作为菜单列表中显示的菜单项。例如:

examples\My Great Example.json

国际化

,如果你将节点打包为一个模块,并且想要提供编辑器和运行时中的翻译内容,你可以包含消息目录(message catalogs)。这些目录包含了不同语言下的翻译字符串,使得Node-RED界面能够显示用户所选择的语言。
对于在模块的package.json中标识的每个节点,你可以在其.js文件旁边包含一组对应的消息目录和帮助文件。
给定节点标识为:

"name": "my-node-module",
"node-red": {
    "myNode": "myNode/my-node.js"
}

需要包含以下消息目录(message catalogs):

myNode/locales/__language__/my-node.json
myNode/locales/__language__/my-node.html

你可以按照以下结构组织文件和目录:

my-node-module/  
├── my-node.js  
└── locales/  
    ├── en-US/  
    │   └── my-node.json  
    ├── fr-FR/  
    │   └── my-node.json  
    └── ... (其他语言的目录)

locales目录与.js文件位于同一目录下,以便Node-RED能够正确地加载和使用这些翻译文件。
__language__部分(即en-USfr-FR等)标识了相应文件提供的语言。默认情况下,如果Node-RED找不到与用户语言设置相匹配的消息目录,它将回退到使用en-US

消息目录Message Catalog

消息目录(Message Catalog) 消息目录是一个JSON文件,包含了节点可能在编辑器中显示或在运行时记录的任何文本片段。
例如:

{
    "myNode" : {
        "message1": "This is my first message",
        "message2": "This is my second message"
    }
}

目录在特定于节点的命名空间下加载。对于上面定义的节点,这个目录将在my-node-module/myNode命名空间下可用。
核心节点使用node-red命名空间。

帮助文本Help Text

帮助文件提供了节点帮助文本的翻译版本,这些翻译在编辑器的“信息”侧边栏选项卡中显示。

使用i18n消息

在运行时和编辑器中,都为节点提供了从目录中查找消息的函数。这些函数预先设置到节点自己的命名空间,因此不必在消息标识符中包含命名空间。

Runtime

在Node-RED中,节点的运行时部分可以使用RED._()函数来访问消息目录中的消息。
例如:

console.log(RED._("myNode.message1"));

状态消息Status messages

如果节点向编辑器发送状态消息,它应该将状态的文本设置为消息标识符。

this.status({fill:"green",shape:"dot",text:"myNode.status.ready"});

在Node-RED的核心消息目录中,有一些常用的状态消息。要使用这些消息,你可以在消息标识符中包含正确的命名空间。

this.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});

编辑器

在Node-RED的编辑器中,任何在节点模板中提供的HTML元素都可以指定一个data-i18n属性来提供要使用的消息标识符。例如:

<span data-i18n="myNode.label.foo"></span>

默认情况下,元素的文本内容会被指定的消息替换。此外,还可以设置元素的属性,例如<input>元素的placeholder

<input type="text" data-i18n="[placeholder]myNode.placeholder.foo">

可以组合这些用法来指定多个替换。例如,要同时设置title属性和显示的文本:

<a href="#" data-i18n="[title]myNode.label.linkTitle;myNode.label.linkText"></a>

对于HTML元素的data-i18n属性之外,所有的节点定义函数(例如oneditprepare)都可以使用this._()来检索消息。this._()函数是Node-RED在节点编辑器环境中为节点提供的,它允许你根据当前的语言环境获取翻译后的消息。

编辑器资源

在编辑器中加载额外资源

在 Node-RED 1.3 或更高版本中,节点可能需要在编辑器中加载额外的资源。例如,在帮助中包含图像或使用外部的 JavaScript 和 CSS 文件。
在 Node-RED 的早期版本中,节点需要创建自定义的 HTTP Admin 终端点来提供这些资源。
但是,从 Node-RED 1.3 开始,如果一个模块在其顶层有一个名为 resources 的目录,运行时将使该目录下的任何内容都可通过 URL /resources/<模块名称>/<资源路径> 在编辑器中可用。
这意味着,如果你的模块有一个 resources 目录,并且该目录中包含了一些图像、JavaScript 文件或 CSS 文件,你可以直接在节点的 HTML 模板中引用它们,而无需设置自定义的 HTTP 终端点。
例如,给定以下模块结构:

node-red-node-example
 |- resources
 |   |- image.png
 |   \- library.js
 |- example-node.js
 |- example-node.html
 \- package.json

在 Node-RED 的默认配置中,如果模块在其根目录下有一个 resources 目录,并且该目录包含一些资源文件,那么这些文件将通过特定的 URL 路径在编辑器中变得可用。
对于没有作用域(scope)的模块,如 node-red-node-example,资源文件的 URL 路径将是:

  • http://localhost:1880/resources/node-red-node-example/image.png
  • http://localhost:1880/resources/node-red-node-example/library.js

然而,如果模块使用了 npm 作用域(scoped),如 @scope/node-red-contrib-your-package,那么作用域名称必须包含在 URL 路径中

  • http://localhost:1880/resources/@scope/node-red-contrib-your-package/somefile

在编辑器中加载资源

在 Node-RED 的编辑器中加载资源时,节点应该使用相对 URL 而不是绝对 URL。这是因为相对 URL 会让浏览器根据编辑器的 URL 来解析资源的位置,这样节点就不需要关心其根路径是如何配置的。
使用您给出的示例,在 HTML 模板中加载资源的正确方式是使用如下的相对 URL:

  • <img src="resources/node-red-node-example/image.png" />
  • <script src="resources/node-red-node-example/library.js">

请注意,这里的 URL 没有以 / 开头,这是因为它们被解释为相对于当前页面(即 Node-RED 编辑器)的路径。在编辑器加载节点配置页面时,Node-RED 会在生成的 HTML 中自动将这些相对 URL 转换为适当的完整 URL(例如 http://localhost:1880/resources/node-red-node-example/image.png),这样浏览器就可以正确地加载这些资源了。

子流模块

在 Node-RED 1.3 中引入了子流模块(Subflow Modules)功能,目前这项功能还处于实验阶段。如果你选择发布自己的子流,请确保它已经经过了充分的测试。

子流可以被打包为 npm 模块,并像其他节点一样进行分发。
当这些子流模块被安装后,它们会作为常规节点出现在节点面板(palette)中。用户无法查看或修改子流内部的流程。
在当前阶段,创建子流模块是一个需要手动编辑子流 JSON 的过程。未来我们会提供工具来帮助自动化这个过程,但现在,这些指导应该能帮助你开始。

创建子流

在创建子流(Subflow)之前,确实需要考虑其用途和如何将其打包为模块。以下是一个有用的检查列表,列出了在创建子流时需要考虑的事项:

  • 配置 - 用户需要在子流中配置什么?你可以通过子流属性编辑对话框定义子流属性,并提供相应的用户界面来设置这些属性。
  • 错误处理 - 你的子流是否正确地处理了错误?一些错误可能在子流内部处理是有意义的,而一些错误可能需要传递到子流外部,以便最终用户处理它们。
  • 状态 - 你可以为你的子流添加一个自定义状态输出,该输出可以由“状态”节点处理。这允许子流向父流程报告其状态,以便在需要时采取相应的行动。
  • 外观 - 确保为你的子流提供一个与其功能相符的图标、颜色和类别。这有助于在节点面板中轻松识别和使用子流。

添加子流元数据

在Node-RED中,当你想将一个子流(Subflow)打包为npm模块时,子流可以包含额外的元数据来定义它将被打包在哪个模块中。这些元数据可以在子流模块属性编辑对话框中设置。

  1. Module
    • 描述:npm包名
    • 重要性:此属性定义了子流将被打包的npm模块的名称。
  2. Node Type
    • 描述:默认情况下,这将设置为子流的id属性。但提供一个更好的类型值是有帮助的。与常规节点类型一样,它必须是唯一的,以避免与其他节点发生冲突。
    • 重要性:此属性定义了子流在Node-RED编辑器中的类型标识。确保它是唯一的,以便正确识别和导入子流。
  3. Version
    • 描述:模块的版本号
    • 重要性:版本信息对于追踪和管理模块的更新和更改至关重要。
  4. Description
    • 描述:模块的简短描述
    • 重要性:为模块提供一个清晰的描述,帮助其他用户了解它的用途和功能。
  5. License
    • 描述:模块的许可证信息
    • 重要性:许可证信息对于遵循开源软件的法律和道德准则至关重要。
  6. Author
    • 描述:模块的作者或开发者的信息
    • 重要性:提供作者信息有助于其他用户联系你以获取支持或贡献。
  7. Keywords
    • 描述:与模块相关的关键字列表
    • 重要性:关键字可以帮助其他用户在npm或其他搜索平台上更容易地找到你的模块。

创建模块

创建Node-RED子流模块的过程涉及在Node-RED外部进行的一些手动工作。以下是创建模块的步骤:

  1. 创建目录
    首先,使用你希望给模块的命名来创建一个目录。在这个例子中,我们将使用node-red-example-subflow
  2. 初始化npm项目
    使用npm init命令来创建一个package.json文件。这将询问你一系列问题,你可以根据之前添加到子流元数据的值来提供匹配的答案。
npm init

在回答这些问题时,确保name字段与你的模块名匹配(在这个例子中是node-red-example-subflow),versiondescriptionauthorlicense等字段也应该与你之前设置的子流元数据相匹配。
3. 添加README文件
所有好的模块都应该有一个README.md文件,用来解释模块的功能、如何安装和使用它,以及任何相关的说明。
4. 创建JavaScript包装器
接下来,你需要为你的模块创建一个JavaScript包装器。在这个例子中,我们将使用example.js
编辑example.js文件,添加类似以下内容的代码:

 const fs = require("fs");
 const path = require("path");

 module.exports = function(RED) {
     const subflowFile = path.join(__dirname,"subflow.json");
     const subflowContents = fs.readFileSync(subflowFile);
     const subflowJSON = JSON.parse(subflowContents);
     RED.nodes.registerSubflow(subflowJSON);
 }

这个脚本将读取一个名为subflow.json的文件(我们稍后会创建它),解析其内容,并将其传递给RED.nodes.registerSubflow函数。

创建 subflow json文件

在准备好上述所有内容之后,你现在可以将你的子流添加到模块中。这需要对子流的 JSON 进行一些仔细的编辑。

  1. 在 Node-RED 编辑器中,将你的子流的一个新实例添加到工作区中。
  2. 选中该实例后,导出节点(使用 Ctrl-E 或菜单中的 “导出” 选项),然后将 JSON 粘贴到文本编辑器中。如果你在导出对话框的 JSON 标签页上选择了 “格式化” 选项,接下来的步骤将更容易。
    导出的 JSON 是由节点对象组成的数组。倒数第二个条目是子流定义,而最后一个条目是你添加到工作区的子流实例。
[
   { "id": "Node 1", ... },
   { "id": "Node 2", ... },
   ...
   { "id": "Node n", ... },
   { "id": "Subflow Definition Node", ... },
   { "id": "Subflow Instance Node", ... }
]

在按照之前的步骤导出子流的JSON后,你需要调整JSON的结构以符合Node-RED模块中子流定义的格式。以下是具体步骤:

  1. 删除子流实例节点
    在JSON数组中,找到并删除最后一个条目,这通常是你在工作区中添加的子流实例节点。
  2. 移动子流定义节点
    在剩余的条目中,找到子流定义节点(它通常包含type属性,其值匹配你在子流模块中设置的节点类型)。将该节点移动到整个数组的开头,即[符号的上方。但是,由于JSON的根必须是一个数组或对象,我们实际上是将它作为数组中的一个对象元素,而不是完全移出数组。
  3. 将剩余节点数组移动到子流定义内
    除了子流定义节点之外,数组中通常包含子流内部使用的所有节点的定义。你需要将这些节点数组移动到子流定义节点内部,并作为一个新的属性来添加,这个属性通常被称为flow
  4. 清理多余的逗号
    在移动和编辑JSON条目时,确保删除任何可能导致解析错误的尾随逗号。在JSON对象中,每个键值对之间用逗号分隔,但在最后一个键值对之后不应该有逗号。同样,在JSON数组的最后一个元素之后也不应该有逗号。
  5. 保存文件
    将编辑后的JSON文件保存为subflow.json,并确保它位于你的模块目录中
{
    "id": "Subflow Definition Node",
    ...
    "flow": [
       { "id": "Node 1", ... },
       { "id": "Node 2", ... },
       ...
       { "id": "Node n", ... }
    ]
}

更新 package.json文件

要在package.json中更新你的Node-RED模块信息,你需要添加一个"node-red"部分,并在其中包含一个"nodes"部分,列出你的.js文件。这样Node-RED就能知道你的模块包含哪些节点。

{
    "name": "node-red-example-subflow",
    ...
    "node-red": {
        "nodes": {
            "example-node": "example.js"
        }
    }
}

添加依赖

在Node.js和Node-RED项目中,如果你使用了非默认的Node-RED节点作为你的子流或流的依赖,你需要在package.json文件中将它们列为依赖项。但是,有一个常见的误解是关于在"node-red"部分也需要列出依赖项。实际上,你只需要在标准的顶层"dependencies"部分列出它们即可。
以下是如何在package.json中列出依赖项的示例:

{
    "name": "node-red-example-subflow",
    ...
    "node-red": {
        "nodes": {
            "example-node": "example.js"
        },
        "dependencies": [
            "node-red-node-random"
        ]
    },
    "dependencies": {
        "node-red-node-random": "1.2.3"
    }
}
  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yoyo勰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值