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":[]}]