使用Node-RED开发非标自动化应用

关于Node-RED的任何项目、技术咨询都可联系 vx: _0xffffffffffff, qq: 924219829

前言

Node-RED是一个基于流的可视化编程工具,但目前在数据采集、智能家居自动化用的多一些。
但这个工具应该有更多的用处,基于流应该是一个很好的工业自动化工具,但确实很少可看到类似的应用。
博主尝试将Node-RED应用到现实场景中,并且项目也取得不错进展。
从个人视角来看,Node-RED完全可以胜任简单自动化场景,过于复杂的场景可以尝试,也许是个不错的选择。

演示视频:

Node-RED非标自动化尝试

移液称重模块总览

整体架构

模块包含多个Node-RED自定义节点
其中包括

  1. 总线配置节点(RS485)
  2. 电机节点
  3. 夹爪节点
  4. 自定义Modbus节点
  5. 移液器节点
  6. IO模块节点

Node-RED自定义节点结构

Node-RED中自定义的节点包含三个文件

  1. 根目录的package.json文件指定模块所有的节点索引及配置
  2. 模块内部的*.html文件,描述了节点的配置项(颜色,大小,输入输出数目等)
  3. 模块内部的*.js文件,描述了节点的实际功能,当消息到来时会回调input事件(该事件应该在节点构造时注册回调函数)

下对各个节点一一说明

总线配置节点

该节点一共提供一个配置项:串口选择(例如:com3)
由于多台设备挂在一路总线上分站号访问,在软件层面映射为一个串口设备。
由于各个功能节点均需要访问该串口,而串口又是独占设备,因此,通过Event-BUS的技术,共享串口设备,提供多节点的访问。

此处,由于工程全部串行执行,不存在并发问题,未过多考虑并发互斥问题。
但引入了消息队列,虽然是未加锁的数据结构,但在很大程度上减少了并发问题,并且提供了数据的有序性。

// event-bus监听command事件,收到command事件后,将需要的指令放入队列
evBus.on('command', (callerId, buf) => {
    // ... 
    que.push({
        callerId, buf, ts: Date.now()
    });
});
// 每隔100ms从队列中取值,顺序发出,
setInterval(()=>{
    if(que.length <= 0) return ;
    let head = que[0];
    if(Date.now() - head.ts > 1000) {
        // 超时没有返回,踢出队列
        que.shift();
        return;
    }
    port.write(head.buf, (err) => {
        if(err) {
            return node.error('Error on write: ' + err.message);
        }
    });
}, 100);

// 串口收到数据后,讲数据简单处理后返回调用者
parser.on('data', function (raw) {
    // 取出队头(最先调用,但没有返回的请求)
    let head = que.shift();
    if(!head) return ;
    // 数据太短,异常响应
    if(raw.length < 3) return console.error();
    // 返回调用者,并去除crc16校验
    evBus.emit(head.callerId, null, raw.slice(0, raw.length-2));
});

所有需要用到总线的节点,比如电机节点,会把总线节点当做配置项引入。
当引入总线节点后,可以获取到总线的Event-Bus。
通过节点自身id进行划分,实现统一的总线使用。

let bus = RED.nodes.getNode(config.bus);
let evBus = bus.evBus;
// 使用总线发布数据时,带上自身节点id
node.evBus.emit('command', null, node.id, buf);
// 节点内部监听对应id的事件,收到串口端回应的时候进行回调
evBus.on(node.id, (data)=> {
    // ...
});

电机模块节点

电机作为最重要的节点,对系统具有非常重要的影响。
每个电机可以看作是一个运动,绝对移动或相对移动等,主要是完成了对大肯RS485协议的封装,提供易用性。
电机提供了多种配置项

  1. Name 节点名称,用于备注节点,例如:皿盘出
  2. 总线 所使用的总线,见总线配置节点
  3. 站号 每个设备都有不同的站号,此处用于区分
  4. 功能 该节点需要执行的功能,对应电机的通讯协议
  5. 位置详情(当功能选择为绝对移动)
  6. 速度详情(当功能选择为设置速度)
  7. 相对位置详情(当功能选择为相对移动)
  8. 使能 该节点是否有效,可以通过消息动态改变,主要用于急停
  9. 输出原始报文 主要用于调试查看指令是否异常

当需要添加参数时,参数的来源可以有多种

  1. 固定参数 - 直接在配置界面配置完成
  2. 动态参数 - 从消息中取值

当电机移动时,有两种模式

  1. 同步移动 - 电机在未到达目标位置时,不产生任何消息,节点假阻塞等待
  2. 异步移动 - 电机给出目标位之后,直接返回,不等待其运动到指定位置

同步运动模式使用的较多。

具体实现:

function sendCmd(node, config, buf) {
    node.errCount = 0;
    if(config.func === 'D' && config.sync) {
        // 第一次检查
        checkStatus(node, config, 'd');
    } // ...
    node.evBus.emit('command', null, node.id, buf);
}
// 收到第一次检查状态的结果后,进行解析
// 如果未到达位置,延时再次检查
if(config.func === 'D' && config.sync) {
    // 绝对值运动
    let status = parseResp('d', data);
    if(status === statusMapping['00']) {
        // 运行中持续监测
        node.status({fill:"yellow",shape:"dot",text:status});
        // 当检查电机状态
        checkStatus(node, config, recvFunc);
    } // ... 
}
// 200ms后发起状态检查, 此处的延时为了避免出现数据风暴
function checkStatus(node, config, ins) {
    setTimeout(() => {
        node.evBus.emit(
            'command', null, node.id, 
            addChecksum(config.station, ins)
        );
    }, 200);
}

夹爪节点

夹爪节点底层使用Modbus协议,因此该节点主要是根据Modbus协议基于底层的总线节点进行封装。

大致框架与电机节点类似,不赘述。

实现上,将预定义的指令(或部分指令)当做select的value,之后对value进行解析,拼接出需要发送的modbus指令。

function genCmd(msg) {
    // # 定义为站号,替换为16进制配置好的站号
    let withSta = func.replace('#', station.toString(16).padStart(2, '0'));
    // 获取定义的参数
    let arg = parseInt( config.speed || config.torque || config.position || (config.enable ? 1 : 0) || msg.payload);
    // ??被定义为参数, 替换为真实的参数
    withSta = withSta.replace('??', arg.toString(16).padStart(2, '0'));
    // 通过下划线分割 将string转换为Buffer
    let buf = withSta.split('_').map((item)=> parseInt(item, 16));
    // 添加crc16校验
    buf = addCrc16(buf, false);
    return buf;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值