Xiaopei's DokuWiki

These are the good times in your life,
so put on a smile and it'll be alright

User Tools

Site Tools


it:node-red

Node-RED

A visual tool for wiring the Internet of Things

releases

    1. Faster start-up time
    2. A faster Function node
    3. RED.nodes.registerType() 注册同名 node 会出错停止 flow 加载
    4. 引入了 fontawesome
    5. 增加了不开 editor,但 All of the admin api endpoints remain available(可供自定制的 App 调用),的 disableEditor option
    6. Node 增加了 Credential 类型的属性,这些属性不会记在 flows.json 里,更安全
    7. npm installable nodes
    1. A subtle new look for nodes
    2. Documentation updates
    3. Node status indicators
    4. npm installable nodes
      npm install node-red-contrib-powermate
    5. Touch UI
    6. Flow file handling
    7. Node updates…
    1. Headless Mode
    2. UI updates
    3. Removing old deprecated nodes
    4. Deprecating HTTPGet
    5. Other node updates…

tips

function node

Node-RED : Writing Functions

每个 function 中可以使用的变量如下:

  • msg,即输入
  • context function node 每次执行时共享的对象
  • context.global, 所有 function nodes 间共享的对象
    • The global context can also be pre-populated with objects when Node-RED starts. This is defined in the main settings.js file under the functionGlobalContext property.
      // For example, the built-in os module can be 
      // made available to, all functions:
      functionGlobalContext: {
          osModule:require('os')
      }
    • function 中不能用 require
  • console - useful for making calls to console.log whilst debugging
  • util - the Node.js util module
  • Buffer - the Node.js Buffer module

function 可 return [],同时可设置 outputs 为 n,设置后,function 便可接出多个 output,每个 output 的输入为 function 返回数组的第 n 个元素

creating nodes

General guidance:

  • be well-defined in their purpose.
    A node that exposes every possible option of an API is potentially less useful that a group of nodes that each serve a single purpose.
  • be simple to use, regardless of the underlying functionality.
    Hide complexity and avoid the use of jargon or domain-specific knowledge.
  • be forgiving in what types of message properties it accepts.
    Message properties can be strings, numbers, booleans, Buffers, objects, arrays or nulls. A node should do The Right Thing when faced with any of these.
  • be consistent in what they send.
    Nodes should document what properties they add to messages, and they should be consistent and predictable in their behaviour.
  • sit at the beginning, middle or end of a flow - not all at once.
  • catch errors

Components:

  • a JavaScript file that defines what the node does
  • an html file that defines the node’s properties, edit dialog and help text.

Example:

module.exports = function(RED) {
    function LowerCaseNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        this.on('input', function(msg) {
            msg.payload = msg.payload.toLowerCase();
            node.send(msg);
        });
    }
    RED.nodes.registerType("lower-case",LowerCaseNode);
}

修改前台 node 的显示

node 可以设置 button 和 badge,详细用法如下:

// 在 node.html 中...
RED.nodes.registerType('MyNode',{
    // ...
    defaults: {
        active: {value:true},
    },
    badge: function() {
        // badge 就是定义函数返回 text 这种用法...
        return 'badge';
    },
    onbadgeclick: function() {
        console.log('onbadgeclick');
    },
    // node 整体 align right,button 的 toggle 显示效果更好
    aligh: "right",
    button: {
        toggle: "active",
        // toggle 可关联一项 node 的属性
 
        onclick: function() {
            console.log('button.onclick');
        }
    }
};

admin 界面与后台的通信

AJAX

  1. inject 与后台是通过 AJAX d3.xhr() 通信的
    button: {
      onclick: function() {
        var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
        d3.xhr("inject/"+this.id).post(function(err,resp) {
          if (err) {
            // error handle
          } else if (resp.status == 200) {
            RED.notify("Successfully injected: "+label,"success");
          } else {
            RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
          }
        });
      }
    }

WebSocket

  1. debug 与后台是通过 RED.commspub/sub 通信的
    $ grep -nr 'RED.comms' nodes/
    nodes/core/core/58-debug.js:89:      RED.comms.publish("debug",msg);
    nodes/core/core/58-debug.html:173:   RED.comms.subscribe("debug",handleDebugMessage);
  2. node 的 status 也是 RED.comms 推送的
    $ grep -nr 'comms.publish' red/
    red/nodes/Node.js:147:    comms.publish("status/"+this.id,status,true);
    $ grep -nr 'comms.sub' public/
    public/red/main.js:159:   RED.comms.subscribe("status/#",function(topic,msg) {

embedded mode / Embedding into an existing app

Node-RED : Embedding into an existing app

使用 express 的方式如下:

var http = require('http');
var express = require("express");
var RED = require("node-red");
 
// Create an Express app
var app = express();
 
// Add a simple route for static content served from 'public'
app.use("/",express.static("public"));
 
// Create a server
var server = http.createServer(app);
 
// Create the settings object
var settings = {
    httpAdminRoot:"/red",
    httpNodeRoot: "/api",
    userDir:"/home/nol/.nodered/"
};
 
// Initialise the runtime with a server and settings
RED.init(server,settings);
 
// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);
 
// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);
 
server.listen(8000);
 
// Start the runtime
RED.start();

headless mode

目前只有 standalone 模式可以使用 headless mode:

  1. 根据 node-red/settings.js 复制一份 settings.js
  2. 修改设置 (参考 node-red/red.js)
    • 设置 httpAdminRoot: false,httpNodeRoot: false,
    • 或仅设置 httpRoot: false,
  3. node red.js -s settings_headless.js
  4. 在 headless 模式下
    1. If you have HTTP-In nodes in a flow and you have httpNodeRoot set to false, you will now get warning messages letting you know those nodes are not accessible.
    2. If you have debug nodes and httpAdminRoot is false, you won't have any access to the debug node output.

headless + embedded 的运行方式如下:

var RED = require('node-red');
 
var settings = {
    httpAdminRoot: false,
    httpNodeRoot: false
    // 不设置应该也可以
};
 
/**
 * the 1st param should be a http server, headless mode left null
 */
RED.init(null, settings);
RED.start();

参考:

headonly mode

希望可以有 “前台 httpAdmin + 生成 json 但不运行的后台” 的运行 mode,但现在没有,github issues 中也没有人提过。而邮件组讨论中作者说过:

We are not actively looking at separating the UI and runtime, so any discussion of changing the communication between the two is premature.

所以需要自己想办法,比如就运行完整的 RED,但 watch flow.json,发现改变后 somehow push 到 runtime。

validate 配置检查

目前发现 node-red 并无很完备的配置检查方案,以 function 为例,如果 function 有语法/运行时错误,在点 deploy 后,保存都是成功的(一闪的绿条),立即加载会发现语法错误,但运行时错误必须运行到才会发现。

Architecture

以下为 release 0.8.1 下的分析

  • require('node-red') 的入口为 red/red.js, 它会 export RED 对象。注意是对象而非 constructor
  • RED 一般的用法是
    var RED = require("node-red");
     
    var server = http.createServer();
     
    RED.init(server,settings);
     
    RED.start();
  • RED.init(server, setting)(封装在 server.js)中,RED 根据 setting,使用 express 创建 app(封装在 ui.js) 和 nodeApp, 其中 app 是 admin 界面,而 nodeApp 是一个 RED 的 http node,供 flow 相关的 I/O 使用
  • RED.start()server.start(),逻辑如下
    function start() {
      var RED = require("./red");
      var defer = when.defer();
     
      storage.init(settings).then(function() {
     
        redNodes.init(settings,storage);
        // 包括:
        // credentials.init(storage);
        // flows.init(storage);
        // registry.init(_settings); // registry 是与 RED 使用的 nodes 的类型相关的部分
     
        redNodes.load().then(function(nodeErrors) {
        // 即 registry.load(),读取所有可用的 nodes
     
          if (nodeErrors.length > 0) {
            // log errors
          }
          defer.resolve();
     
          redNodes.loadFlows();
          // 即 flows 中的 flows.load(),具体操作为
          // storage.getFlows().then(credentials.load()).then(parseConfig(flows))
          // 在 parseConfig() 中,会将 flows 中用到的 nodes 按类型 new,
          // 所有的 nodes run 了 flow 也就 run 了
        });
     
        // 使用 ws 开启与 browser 的实时通信
        comms.start();
      });
     
      // start 整体是支持 promise 的
      return defer.promise;
    }
    • parseConfig() 的逻辑如下,如果不做该步,就不会 run flow 了
      var parseConfig = function() {
       
        // 检查 missing types
        // 如果有 missing types,则不 run nodes,报错返回
       
        events.emit("nodes-starting");
       
        // 实例化所有 node
        for (i=0;i<activeConfig.length;i++) {
          var nn = null;
          nt = typeRegistry.get(activeConfig[i].type);
          nn = new nt(activeConfig[i]);
          // 循环中并没保留 nn 的引用?难道不会被 GC 掉么?
        }
       
        // Clean up any orphaned credentials
        // credentials 都是干嘛的还不清楚
        credentials.clean(flowNodes.get);
       
        events.emit("nodes-started");
      }

node-red 中有很多类似

module.exports.__defineGetter__("app", function() { return app });'' 

的用法,作用为暴露出 getter(且 getter 方法不可修改)

flows.json

flows.json 是对象数组,没有嵌套关系

[// tab 也会在 flows.json 中,但在 flow 启动时不会 new Node()
 {"type":"tab",
  "id":"d80082a9.27ff8",
  "label":"Sheet 1"},
 // 之后是每个 node,每个 node 的必备信息包括 type, name, x, y, z(z 即 tab)
 {"id":"e9c6ec99.16391",
  "type":"inject",
  "name":"",
  "topic":"",
  "payload":"",
  "payloadType":"date",
  "repeat":"",
  "crontab":"",
  "once":false,
  "x":72,
  "y":163,
  "z":"d80082a9.27ff8",
  // wires 记录了本 node 的 output 连接的 nodes
  "wires":[["ba3f6475.45c098"]]},
 {"id":"ba3f6475.45c098",
  "type":"debug",
  "name":"",
  "active":true,
  "console":false,
  "complete":false,
  "x":247,
  "y":169,
  "z":"d80082a9.27ff8",
  "wires":[]}]

Examples

  • dateToString
    • 其中 function 如下:
      var date = new Date(msg.payload);
      msg.payload = date.toString();
      return msg;

FAQ

  • node-red 必须 deploy,由后台执行,再将 debug 信息传至前台显示,而非 browser only 的
  • inject/debug 等 node 的消息是使用 ws (web socket) 与后台通信的,如果出现 Error: Lost connection to servercan't establish a connection to the server at ws://localhost:1880/comms,则可能是代理导致 ws 不正常造成
it/node-red.txt · Last modified: 2014/09/29 18:26 by admin