DLT645 communication demo

使用串口转网口模块,实现电能表数据采集。制作了一个简单通讯案例,只是读取总电能,具体还需要完善电能表其他功能和数据采集。由于现场使用RS485通讯线,使用西门子PLC直接实现轮询。node-red平台读取电能表只是做了一个测试案例,还有很多需要完善。

[{"id":"5343d78c384ba857","type":"tab","label":"电能表数据","disabled":false,"info":"","env":[]},{"id":"a67863b28436a766","type":"function","z":"5343d78c384ba857","name":"DLT645 Convert","func":"\nconst DLT645_START_CODE=0x68;\nconst DLT645_STOP_CODE=0x16;\n\nconst DLT645_GADDR_CODE=0xAA ;  //万能地址码\nconst DLT645_PREMBLE_CODE=0xFE; //前导码\n\n\nconst DLT645_ADDR_LEN=6  ;  //设备地址长度\n\nconst DLT645_START_POS=0;\nconst DLT645_ADDR_POS= 1    ;//设备地址\nconst DLT645_CONTROL_POS= 8 ;//控制码位置\nconst DLT645_LEN_POS=9   ;  //长度位置\nconst DLT645_DATA_POS = 10; //数据位置\n\nconst DLT645_WR_LEN=50;     //写入数据命令的长度\nconst DLT645_RESP_LEN =60;   //读取数据命令的长度\n\nconst C_TD_MASK =0x80 ;     //主从标志位\nconst C_TD_POS =7  ;        //主从标志位比特位\nconst C_TD_MASTER =0  ;     //主站发出的命令帧\nconst C_TD_SLAVE =1  ;      //从站发出的应答帧\n\nconst C_ACK_MASK= 0x40 ;    //从站是否正确应答标志位\nconst C_ACK_POS =6     ;    //从站应答标志位比特位\nconst C_ACK_OK= 0     ;     //从站应答正确\nconst C_ACK_ERR =1     ;    //从站应答错误\nconst C_FU_MASK =0x20  ;    //是否有后续帧标志位\nconst C_FU_POS =5        ;  //后续帧标志位比特位\nconst C_FU_NONE =0     ;    //无后续帧\nconst C_FU_EXT =1      ;    //有后续帧\nconst C_CODE_MASK= 0x1F;    //功能码标志位\n\n\n var msg1,flag=1,sum=0,flag2=1;\n var i=1,j=1;\n//msg1={payload:msg.payload[0]};\n//msg2={payload:msg.payload[1]};\n\n//return [msg1,msg2];\n\n\n //msg2={payload:10};\n msg1={payload:10,topic:1};\n\n//let _str=msg.payload.toString().replace(/\\s/g,'');\nfunction checkSum(allBuffer,len){\n\n    var sum=0;\n    var tempLen=len;\n    var temp=0;\n\n    if(len<0 ){\n        console.log(\"error check sum length\");\n        return 0;\n    }\n    for(;temp<=len;temp++){\n        sum+= allBuffer[temp];\n    }\n    sum= sum&0xff;\n    if (sum == allBuffer[tempLen+1]){\n        return 1;\n    }else{\n        console.log(sum);\n        return 0;\n    }\n}\n\n\nconst searchTerm=0x68;\n// let indexFirst =_str.indexOf(searchTerm);\n// if (indexFirst <0)\n// {\n//     msg1={payload:88};\n//     msg2={payload:99};\n\n//     return [msg1,msg2];\n// }else{\n//     msg1={payload:11};\n//     msg2={payload:11};\n\n//     return [msg1,msg2]; \n// }\n\nwhile (msg.payload[i]!=searchTerm)\n{\n    // fla g=0;\n    i++;\n    if(isNaN(msg.payload[i]))\n    {\n        flag=0;\n      \n        msg1={payload:12};\n        break;\n    }\n}\n\n\nlet newBufferDLT645 = msg.payload.slice(i);\n\nconsole.log(newBufferDLT645);\nconsole.log(\"Hello world!\");\n//msg2={payload:i};\n//flag=0;\n\nwhile (newBufferDLT645[j]!=0x16)\n{\n    // fla g=0;\n    j++;\n    if(isNaN(newBufferDLT645[j]))\n    {\n        flag=0;\n        //msg2={payload:12};\n        msg1={payload:12,topic:50};\n        break;\n    }\n}\n\nlet checkSumLen= j-2;\nif(checkSumLen<0)\n{\n    \tconsole.log(\"check sum error \");\n\tmsg1={payload:50,topic:50};\n\treturn msg1;\n}\nflag=checkSum(newBufferDLT645,checkSumLen);\nif(flag){\n    console.log(newBufferDLT645[j]);\n}else{\n\tconsole.log(\"check sum error \");\n\tmsg1={payload:50,topic:50};\n\treturn msg1;\n}\n\n\n\nif(flag>0)\n{\n    if (msg.payload[i+7]===0x68)\n    {\n\n        for(var t=0;t<10+msg.payload[i+9];t++)\n            sum+=msg.payload[i+t];\n        sum=sum&0xff;\n    }\n    if(sum===msg.payload[i+10+msg.payload[i+9]])\n    {\n\n    }else{\n        //msg2={payload:sum&0xFF};\n        msg1={payload:msg.payload[i+10+msg.payload[i+9]]};\n        flag2=0;\n    }\n\n    return msg1;\n\n}\nelse\n{\n    //msg2={payload:i};\n    msg1={payload:3};\n    return msg1;\n}\n\nreturn msg1;\n\n//测试帧fe fe fe fe 68 75 34 01 52 06 23 68 91 08 33 33 34 33 94 77 3c 34 d6 16","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":180,"wires":[["4d662021d5b4ea34","b838cec530d75d15"]]},{"id":"ecf92ddc480df266","type":"tcp in","z":"5343d78c384ba857","name":"","server":"client","host":"192.168.3.55","port":"1030","datamode":"stream","datatype":"buffer","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":220,"y":180,"wires":[["a67863b28436a766","e5fdef7464af2c5c"]]},{"id":"4d662021d5b4ea34","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":120,"wires":[]},{"id":"b838cec530d75d15","type":"switch","z":"5343d78c384ba857","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"},{"t":"eq","v":"3","vt":"str"},{"t":"eq","v":"4","vt":"str"},{"t":"eq","v":"5","vt":"str"},{"t":"eq","v":"6","vt":"str"},{"t":"eq","v":"50","vt":"str"}],"checkall":"true","repair":false,"outputs":7,"x":610,"y":320,"wires":[["ee7a110a95e937f4","b4acc5e6f1e2f518"],["fde51f19cab81f57"],["5aecb877db129c47"],["84fabf2c10001332"],["3b10b63ecb0c7b2d"],["dbf0dfa5cf164d37"],["1a6f1bde138f43e6"]]},{"id":"ee7a110a95e937f4","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":220,"wires":[]},{"id":"fde51f19cab81f57","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":300,"wires":[]},{"id":"5aecb877db129c47","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":360,"wires":[]},{"id":"84fabf2c10001332","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":420,"wires":[]},{"id":"3b10b63ecb0c7b2d","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":480,"wires":[]},{"id":"dbf0dfa5cf164d37","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":910,"y":540,"wires":[]},{"id":"77c72a45a3104833","type":"function","z":"5343d78c384ba857","name":"function 5","func":"//import { HexTools as EMeter } from './HexTools.js'\n\n//let t = EMeter.hex2Number('0x11');\n//let b = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);\n\n\n\nlet em_Send = Buffer.alloc(16);\n\nem_Send[0] = 0x68;\n\n\nfor (var i = 0; i < 6; i++) {\n    em_Send[i+1] =msg.payload[i];\n}\nem_Send[7] = 0x68;\nem_Send[8] = 0x11;\nem_Send[9] = 0x04;\nem_Send[10] = 0x33;\nem_Send[11] = 0x33;\nem_Send[12] = 0x34;\nem_Send[13] = 0x33;\n\nlet sum=0;\nfor (var j = 0; j < 14; j++) {\n    sum +=  em_Send[j];\n}\n\nem_Send[14]= sum;\nem_Send[15]=0x16;\nvar msg1 = { payload: em_Send };\n\nreturn msg1;\n\n\n// export default class HexTools {\n//     /**\n//      * 将16进制字符串转为十进制数字\n//      * 如  HexTools.hex2Number('0x11') //17\n//      *     HexTools.hex2Number('21') //33\n//      *     HexTools.hex2Number('0xffff') //65535\n//      *     HexTools.hex2Number('ffff') //65535\n//      * @param str 可传入16进制的8位或16位字符串\n//      * @returns {number}\n//      */\n//     static hex2Number(str = '') {\n//         if (str.indexOf('0x') === 0) {\n//             str = str.slice(2);\n//         }\n//         return parseInt(`0x${str}`, 16);\n//     }\n\n//     /**\n//      * 将16进制字符串转为指定字节的字符串\n//      * @param {string} 十六进制字符串\n//      * @param {byteLen} 字节大小  \n//      * @return {string}\n//      */\n//     static hex2ByteString(str = '', byteLen = 2) {\n//         if (str.indexOf('0x') === 0) {\n//             str = str.slice(2);\n//         }\n//         let len = str.length;\n//         let total = byteLen * 2 - len;\n\n//         if (total > 0) {\n//             while (total) {\n//                 str = '0' + str;\n//                 total--;\n//             }\n//         }\n//         return str;\n//     }\n\n//     /**\n//      * 十进制数字转为指定字节的16进制字符串\n//      * @param num\n//      * @param {byteLen} 字节大小\n//      * @returns {string} 得到n字节的16进制字符串\n//      */\n//     static num2HexBytes(num = 0, byteLen = 1) {\n//         const str = num.toString(16);\n//         return HexTools.hex2ByteString(str, byteLen);\n//     }\n\n//     /**\n//      * 十进制数字(这里最大为255)转为8位16进制字符串\n//      * @param num\n//      * @returns {string} 得到8位的16进制字符串\n//      */\n//     static num2Hex(num = 0) {\n//             return ('00' + num.toString(16)).slice(-2);\n//     }\n\n//     /**\n//      * hex数组转为num\n//      * 数组中每一元素都代表一个8位字节,16进制的数字\n//      * 比如:一个精确到毫秒位的时间戳数组为[ 1, 110, 254, 149, 130, 160 ],可以用这个函数来处理,得到十进制的时间戳1576229241504\n//      * 比如:一个精确到秒位的时间戳数组为[ 93, 243, 89, 121 ],可以用这个函数来处理,得到十进制的时间戳1576229241\n//      * @param array 按高位在前,低位在后来排列的数组,数组中每一元素都代表一个8位字节,16进制的数字\n//      * @return {Number}\n//      */\n//     static hexArrayToNum(array) {\n//         let count = 0, divideNum = array.length - 1;\n//         array.forEach((item, index) => count += item << (divideNum - index) * 8);\n//         return count;\n//     }\n\n//     /**\n//      * num转为hex数组\n//      * 与{hexArrayToNum}含义相反\n//      * @param num\n//      * @returns {*} 一个字节代表8位\n//      */\n//     static num2HexArray(num) {\n//         if (num === void 0) {\n//             return [];\n//         }\n//         num = parseInt(num);\n//         if (num === 0) {\n//             return [0];\n//         }\t\n//         let str = num.toString(16);\n//         str.length % 2 && (str = '0' + str);\n//         const array = [];\n//         for (let i = 0, len = str.length; i < len; i += 2) {\n//             array.push(`0x${str.substr(i, 2)}`);\n//         }\n//         return array;\n//     }\n\n//     /**\n//      * 获取数据的低八位\n//      * @param data\n//      * @returns {{lowLength: number, others: Array}}\n//      */\n//     static getDataLowLength({data}) {\n//         // const dataPart = [];\n//         // data.map(item => HexTools.num2HexArray(item)).forEach(item => dataPart.push(...item));\n//         // const lowLength = HexTools.hex2Num((dataPart.length + 1).toString(16));\n//         // return {lowLength, others: dataPart};\n//     }\n\n//     /**\n//      * ArrayBuffer转16进制字符串\n//      * @param {Object} buffer\n//      * @returns {string} hex\n//      */\n//     static arrayBuffer2hex(buffer) {\n//       const hexArr = Array.prototype.map.call(\n//         new Uint8Array(buffer),\n//         function (bit) {\n//           return ('00' + bit.toString(16)).slice(-2);\n//         }\n//       )\n//       return hexArr.join('');\n//     }\n\n//     /**\n//      * ArrayBuffer转16进制字符串数组\n//      * @param {Object} buffer\n//      * @returns {Array} hexArr\n//      */\n//     static arrayBuffer2hexArray(buffer) {\n//       const hexArr = Array.prototype.map.call(\n//         new Uint8Array(buffer),\n//         function (bit) {\n//           return ('00' + bit.toString(16)).slice(-2);\n//         }\n//       )\n//       return hexArr;\n//     }\n\n//     /**\n//      * 16进制字符串转ArrayBuffer(默认两个字节,小端序)\n//      * @param {Object} dataview\n//      * @param {string} hex 16进制字符串\n//      * @param {number} offset\t偏移量\n//      * @param {number} bytes\t几个字节\n//      * @returns {Object} dataview\n//      */\t\n//     static hex2ArrayBuffer(dataview, hex, offset, bytes = 2) {\n//         var num = HexTools.hex2Number(hex);\n//         switch(bytes) {\n//             case 1:\n//                 dataview.setUint8(offset, num);\n//                 break;\n//             case 2:\n//                 dataview.setUint16(offset, num, true);\n//                 break;\n//             case 4:\n//                 dataview.setUint32(offset, num, true);\n//                 break;\n//             default:\n//         }\n//         return dataview;\n//     }\n\n//     /**\n//      * number 转 小端序 ArrayBuffer (默认两个字节)\n//      * @param {Object} dataview \n//      * @param {string} num\t\t数字\n//      * @param {number} offset\t偏移量\n//      * @param {number} bytes\t几个字节\n//      * @returns {Object} dataview\n//      */\t\n//     static num2ArrayBuffer(dataview, num, offset, bytes = 2) {\n//         switch(bytes) {\n//             case 1:\n//                 dataview.setUint8(offset, num);\n//                 break;\n//             case 2:\n//                 dataview.setUint16(offset, num, true);\n//                 break;\n//             case 4:\n//                 dataview.setUint32(offset, num, true);\n//                 break;\n//             default:\n//         }\n//         return dataview;\n//     }\n\n//     /**\n//      * 小端序 ArrayBuffer 获取number (默认两个字节)\n//      * @param {Object} dataview \n//      * @param {number} offset\t偏移量\n//      * @param {number} bytes\t几个字节\n//      * @returns {number} count\n//      */\t\n//     static arrayBuffer2number(dataview, offset, bytes = 2) {\n//         let count = 0;\n//         switch(bytes) {\n//             case 1:\n//                 count = dataview.getUint8(offset, true);\n//                 break;\n//             case 2:\n//                 count = dataview.getUint16(offset, true);\n//                 break;\n//             case 4:\n//                 count = dataview.getUint32(offset, true);\n//                 break;\n//             default:\n//         }\n//         return count;\n//     }\n\n//     /**\n//      * arraybuffer(ASCLL码) 转为字符型字符串(非16进制)\n//      * 1个字节一个字符 Uint8\n//      * @param {Object} buffer\n//      * @return {String} 字符串\n//      */\n//     static arraybuffer2String(buffer) {\n//       return String.fromCharCode.apply(null, new Uint8Array(buffer));\n//     }\n\n//     /**\n//      * string 转为 arraybuffer ASCLL码\n//      * @param {String} str\n//      * @return {Object} arraybuffer\n//      */\n//     static string2Arraybuffer(str) {\n//       const buf = new ArrayBuffer(str.length); // 1 bytes for each char\n//       const bufView = new Uint8Array(buf);\n//       for (let i = 0, strLen = str.length; i < strLen; i++) {\n//         bufView[i] = str.charCodeAt(i);\n//       }\n//       return buf;\n//     }\n// }\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":720,"wires":[["f84eab072837c57a"]]},{"id":"ec0a18f93fce4de8","type":"function","z":"5343d78c384ba857","name":"function 4","func":"let addr = Buffer.from([0x75, 0x34, 0x01, 0x52, 0x06, 0x23]);\nmsg.payload=addr;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":720,"wires":[["77c72a45a3104833"]]},{"id":"9b70d203994fe3f1","type":"inject","z":"5343d78c384ba857","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":290,"y":720,"wires":[["ec0a18f93fce4de8"]]},{"id":"f84eab072837c57a","type":"debug","z":"5343d78c384ba857","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":870,"y":820,"wires":[]},{"id":"b4acc5e6f1e2f518","type":"delay","z":"5343d78c384ba857","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":520,"y":520,"wires":[["ec0a18f93fce4de8"]]},{"id":"1a6f1bde138f43e6","type":"debug","z":"5343d78c384ba857","name":"debug 8","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":900,"y":580,"wires":[]},{"id":"e5fdef7464af2c5c","type":"debug","z":"5343d78c384ba857","name":"debug 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":440,"y":100,"wires":[]}]

Flow Info

Created 8 months, 1 week ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • debug (x10)
  • delay (x1)
  • function (x3)
  • inject (x1)
  • switch (x1)
  • tcp in (x1)
Other
  • tab (x1)

Tags

  • DLT645
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option