JavaScript使用Modbus协议实现RTU设备连云

1、功能简介

在阿里云物联网平台下发物模型属性设置数据,HaaS600Kit 接收并解析云端数据后控制 Modbus 继电器设备进行开关动作。

2、硬件连接

硬件平台:HaaS600开发板(EC600S-CN)、TTL转485串口板、Modbus继电器设备。

注意本示例中 HaaS600开发板需要5V DC接口供电,否则串口转接板工作时有可能因为供电不足导致系统重启。

modbus-1

2.1、HaaS600串口使用介绍

HaaS600 用户串口只有一个UART2,为了方便开发,板子上提供了USB2、J5和 J6 三处UART2 接口,其中 J5 和 J6 接口电压是 3.3V。注意使用 J5 和 J6 接口要把开关 K1 拨到丝印 JP 侧,开关 K3 用于调整串口TX RX顺序。本示例使用的TTL转485接口板使用J5接口,K3 拨到丝印 MASTER 侧。

modbus-2

3、云端控制

参考阿里云物联网平台帮助文档登陆阿里云官网,依次创建产品创建设备后,添加物模型属性 PowerSwitch。

自定义的 PowerSwitch 属性和应用代码直接关联,下面介绍如何单个添加自定义物模型属性。

单个添加物模型属性

功能定义

modbus-3

编辑草稿

modbus-4

添加自定义功能

modbus-5

发布上线

modbus-6

在线调试

modbus-7

设置开启、关闭

modbus-8

4、串口日志

cloud onProps, msg_id is 1399004153
cloud onProps, params_len is 17
cloud onProps, params is {"PowerSwitch":1}
turn on relay
write send 1,5,0,0,255,0,140,58
rtn onData
onData: 1,5,0,0,255,0,140,58

cloud onProps, msg_id is 1435839973
cloud onProps, params_len is 17
cloud onProps, params is {"PowerSwitch":0}
turn off relay
write send 1,5,0,0,0,0,205,202
rtn onData
onData: 1,5,0,0,0,0,205,202

参考代码

agriculture_demo.zip

app.js

把已创建设备的 ProductKey、ProductKey 和 DeviceSecret 填入到 app.js 代码中。

var iot = require('iot');
var network = require('network');

var net = network.openNetWorkClient();

var productKey = 'xxxx';      /* your productKey */
var deviceName = 'xxxx';      /* your deviceName */
var deviceSecret = 'xxxx';  /* your deviceSecret */

var device;

var modbus = require('./modbus.js');

var deviceAddr = 1;
var timeout = 300;
var modbus_device = modbus(
  { id: 'UART2' },
  { id: 'D13', polarity: 1 },
  deviceAddr
);

function createDevice() {
    device = iot.device({
        productKey: productKey,
        deviceName: deviceName,
        deviceSecret: deviceSecret
    });
    var powerSwitch = 0;
    modbus_device.write(deviceAddr, 'c1', powerSwitch, timeout).then(function (data) {
        console.log('write c1 data ' + data);
    });

    device.on('connect', function () {
        console.log('(re)connected');
        /* post props */
        device.postProps(JSON.stringify({
            PowerSwitch: powerSwitch
        }));

        /* 云端下发服务事件 */
        device.onService(function (res) {
            console.log('cloud onService, msg_id is ' + res.msg_id);
            console.log('cloud onService, service_id is ' + res.service_id);
            console.log('cloud onService, params_len is ' + res.params_len);
            console.log('cloud onService, params is ' + res.params);
        });

        /* 云端设置属性事件 */
        device.onProps(function (res) {
            console.log('cloud onProps, msg_id is ' + res.msg_id);
            console.log('cloud onProps, params_len is ' + res.params_len);
            console.log('cloud onProps, params is ' + res.params);
            var payload = JSON.parse(res.params);
            powerSwitch = parseInt(payload.PowerSwitch);
            if(powerSwitch == 1) {
                console.log('turn on relay');
            } else {
                console.log('turn off relay');
            }
            modbus_device.write(deviceAddr, 'c1', powerSwitch, timeout).then(function (data) {
                console.log('write c1 data ' + data);
            });
            device.postProps(JSON.stringify({
                PowerSwitch: powerSwitch
            }));
        });
    });

    /* 网络断开事件 */
    device.on('disconnect', function () {
        console.log('disconnect ');
    });

    /* 关闭连接事件 */
    device.on('end', function () {
        console.log('iot client just closed');
    });

    /* 发生错误事件 */
    device.on('error', function (err) {
        console.log('error ' + err);
    });
}

var status = net.getStatus();
console.log('net status is: ' + status);


if (status == 'connect') {
    createDevice();
} else {
    net.on('connect', function () {
        console.log('net is connected success!');
        createDevice();
    });
}

modbus.js

function Modbus(uartPort, dirPort) {
    var rtn = {}
    var lastTid = 1
    rtn.readTimeout = 500;
    rtn.readTimeouthandler;
    rtn.packetBufferLength = 100
    rtn.packets = []

    rtn.stream = Serial(uartPort, dirPort)
    rtn.protocal = 'rtu'

    rtn.read = function (unitId, address, timeout, callback) {
        var parsedAddress = parseAddress(address)
        var funcCode = parsedAddress.fcRead
        var length = parsedAddress.length
        address = parsedAddress.address

        rtn.readTimeout = timeout
        rtn.readTimeouthandler = setTimeout(function() {
            rtn.packets[tid].promiseReject('timeout')
        }, rtn.readTimeout)

        var tid = getTid()

        var buff = makeDataPacket(tid, 0, unitId, funcCode, address, null, length)
        if (rtn.protocal == 'rtu') { buff = buff.rtu }

        var packet = {
            onResponce: callback,
            tx: {
                funcCode: funcCode,
                tid: tid,
                address: address,
                hex: buff.toString('hex')
            },
            promiseResolve: null,
            promiseReject: null,
            rx: null
        }

        rtn.packets[tid] = packet

        rtn.stream.send(buff)
        console.log('read send ' + buff.toString('hex'))
        return new Promise(function (resolve, reject) {
            rtn.packets[tid].promiseResolve = resolve
            rtn.packets[tid].promiseReject = reject
        })

    }
    rtn.write = function (unitId, address, value, timeout, callback) {
        var parsedAddress = parseAddress(address)
        var funcCode = parsedAddress.fcWrite
        var length = parsedAddress.length
        address = parsedAddress.address
        var tid = getTid()

        rtn.readTimeout = timeout
        rtn.readTimeouthandler = setTimeout(function() {
            rtn.packets[tid].promiseReject('timeout')
        }, rtn.readTimeout)
    
        if (funcCode == 5 && value == true) { value = 65280 } // To force a coil on you send FF00 not 0001

        var buff = makeDataPacket(tid, 0, unitId, funcCode, address, value, length)
        if (rtn.protocal == 'rtu') { buff = buff.rtu }
        // console.log('buff ' + buff)
        var packet = {
            onResponce: callback,
            tx: {
                funcCode: funcCode,
                tid: tid,
                address: address,
                hex: buff.toString('hex')
            },
            rx: null
        }
        rtn.packets[tid] = packet

        rtn.stream.send(buff)
        console.log('write send ' + buff.toString('hex'))

        return new Promise(function (resolve, reject) {
            rtn.packets[tid].promiseResolve = resolve
            rtn.packets[tid].promiseReject = reject
        })
    }
    var getTid = function () {
        if (lastTid > rtn.packetBufferLength) { lastTid = 0 }
        lastTid++
        if (rtn.protocal == 'rtu') { lastTid = 0 }
        return lastTid
    }

    rtn.stream.onData = function (buf) {
        console.log('onData: ' + buf.toString('hex'))

        var modbusRes
        if (rtn.protocal == "rtu") { modbusRes = parseResponseRtu(buf) }

        var value = modbusRes.value
        var tid = modbusRes.tid
        if (rtn.protocal == "rtu") { tid = 0 }

        var err = null
        if (modbusRes.exceptionCode) { err = 'Exception Code: 02 - Illegal Data Address' }

        rtn.packets[tid].rx = modbusRes
        rtn.packets[tid].rx.hex = buf.toString('hex')
        if (typeof (rtn.packets[tid].onResponce) == "function") {
            rtn.packets[tid].onResponce(err, value)
        }

        if (err) {
            rtn.packets[tid].promiseReject(err)
        }
        else {
            // console.log('raw data:', value)
            rtn.packets[tid].promiseResolve(value)
            clearTimeout(rtn.readTimeouthandler)
        }

    }
    return rtn
}

function parseResponseRtu(buffer) {
    var res = {}
    res.tid = 1
    var buf = new Buffer(buffer)
    res.unitId = buf.readInt8(0)                     //Unit Id        - Byte 6
    res.funcCode = buf.readInt8(1)                     //Function Code  - Byte 7
    res.byteCount = Math.abs(buf.readInt8(2))           //Byte Count     - Byte 8
    if (buf.length > 3) {
        // res.value = buf.readIntBE(3, buf.length - 5)       //Data           - Bytes 9+
        // var arr = Array.prototype.slice.call(new Uint8Array(buf))
        // res.value = new Buffer(arr.slice(3, buf.length - 5))
        var dataBuffer = buf.subarray(3, buf.length - 2)
        res.value = Array.prototype.slice.call(new Uint8Array(buf))
    }

    return res
}

function crc16modbus(buf, previous) {
    var TABLE = [0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040];

    var crc = typeof previous !== 'undefined' ? ~~previous : 0xffff;

    for (var index = 0; index < buf.length; index++) {
        var byte = buf[index];
        crc = (TABLE[(crc ^ byte) & 0xff] ^ crc >> 8) & 0xffff;
    }

    return crc;
}

function makeDataPacket(transId, protoId, unitId, funcCode, address, data, length) {

    if (typeof (data) == "boolean" && data) { data = 1 }
    if (typeof (data) == "boolean" && !data) { data = 0 }


    if (address == 0) { address = 65535 }
    else { address = address - 1 }

    var dataBytes = 0
    if (funcCode == 15) { dataBytes = length }
    if (funcCode == 16) { dataBytes = length * 2 }

    var bufferLength = 12
    if (funcCode == 15 || funcCode == 16) { bufferLength = 13 + dataBytes }

    var byteCount = bufferLength - 6

    var buf = new Buffer(bufferLength)

    buf.writeUInt16BE(transId, 0)
    buf.writeUInt16BE(protoId, 2)
    buf.writeUInt16BE(byteCount, 4)
    buf.writeUInt8(unitId, 6)
    buf.writeUInt8(funcCode, 7)
    buf.writeUInt16BE(address, 8)

    if (funcCode == 1 || funcCode == 2 || funcCode == 3 || funcCode == 4) {
        buf.writeUInt16BE(length, 10)
    }
    if (funcCode == 5 || funcCode == 6) {
        buf.writeInt16BE(data, 10)
    }
    if (funcCode == 15 || funcCode == 16) {
        buf.writeInt16BE(length, 10)
        buf.writeUInt8(dataBytes, 12)
        buf.writeInt32BE(data, 13)
    }

    var makeCrc = crc16modbus

    var bufRtu = buf.slice(6, bufferLength)
    var crc = makeCrc(bufRtu)
    var arr = Array.prototype.slice.call(new Uint8Array(bufRtu))
    arr.push(0);
    arr.push(0);
    var buffRtu = new Buffer(arr);
    buffRtu.writeUInt16LE(crc, buffRtu.length - 2)
    //console.log('buffRtu:', buffRtu);
    return { rtu: Array.prototype.slice.call(new Uint8Array(buffRtu)) }
}

function parseAddress(address) {
    var rtn = {}
    address = address.toLowerCase()

    // 提取地址
    // Data Type                  Short Hand   Size        Accessibility
    // Descrite Input             i            1 Bit       Read Only
    // Coil                       c            1 Bit       Read / Write
    // Input Register             ir           16 Bits     Read Only
    // Holding Register           hr           16 Bits     Read / Write
    var isRegister = address.replace('r', '').length !== address.length;
    if (isRegister) {
        rtn.address = address.substr(2)
        rtn.type = address.substr(0, 2)
    }
    if (!isRegister) {
        rtn.address = address.substr(1)
        rtn.type = address.substr(0, 1)
    }

    var isRange = rtn.address.replace('-', '').length !== rtn.address.length
    if (isRange) {
        var range = rtn.address.split('-')
        rtn.length = range[0] - range[1]
        if (rtn.length < 0) { rtn.address = range[0] }
        if (rtn.length > 0) { rtn.address = range[1] }
        rtn.length = Math.abs(rtn.length) + 1
    }
    if (!isRange) {
        rtn.length = 1
    }

    rtn.address = parseInt(rtn.address)

    // 获取类型
    // FC1 - Read Coil
    // FC2 - Read Input
    // FC3 - Read Holding Registers
    // FC4 - Read Input Registers
    // FC5 - Write Single Coil
    // FC6 - Write Single Register
    // FC15 - Write Multiple Coils
    // FC16 - Write Multiple Registers
    if (rtn.type == 'c') {
        rtn.fcRead = 1
        rtn.fcWrite = 5
    }
    if (rtn.type == 'i') {
        rtn.fcRead = 2
    }
    if (rtn.type == 'hr' && !isRange) {
        rtn.fcRead = 3
        rtn.fcWrite = 6
    }
    if (rtn.type == 'hr' && isRange) {
        rtn.fcRead = 3
        rtn.fcWrite = 16
    }
    if (rtn.type == 'ir') {
        rtn.fcRead = 4
    }

    return rtn
}

function Serial(uartPort, dirPort) {
    var rtn = {}
    var Uart = require('uart')

    rtn.port = Uart.open(uartPort)

    if (dirPort) {
        var events = require('events');
        var event = new events();
        var Gpio = require('gpio')
        var option = {};
        option.id = dirPort.id;
        rtn.io = Gpio.open(option);
    }

    rtn.send = function (data) {
        rtn.io.writeValue(dirPort.polarity === 0 ? 0 : 1);
        rtn.port.write(data)
        event.emit('send')
    }

    rtn.onData = function () { }

    rtn.port.on('data', function (data) {
        console.log("rtn onData");
        rtn.onData(data);
    })

    event.on('send', function () {
        rtn.io.writeValue(dirPort.polarity === 0 ? 1 : 0);
    })

    return rtn
}

module.exports = Modbus;
app.json

{
    "version": "1.0.0",
    "io": {
      "UART2": {
        "type": "UART",
        "port": 2,
        "dataWidth": 8,
        "baudRate": 9600,
        "stopBits": 1,
        "flowControl": "disable",
        "parity": "none"
      },
      "D13": {
        "type": "GPIO",
        "port": 33,
        "dir": "output",
        "pull": "pulldown"
      }
    },
    "debugLevel": "DEBUG",
    "repl": "disable"
  }

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值