本文以磁吸门锁作为采集制对象,使用海创微联采集控制系统对磁吸门锁设备数据进行采集控制,然后将采集到的数据上传到阿里云物联网平台,阿里云物联网平台实现数据实时可视化。
文章分为3部分:
- 设备接线配置
磁吸门锁的联网参数配置及实施接线。 - 数据采集
使用海创微联采集控制系统进行控件的部署、数据采集。 - 数据上传
阿里云平台物联网平台产品和设备的创建、数据可视化。
一 设备接线配置
1.1 准备工具
实现磁吸门锁数据采集控制,主要需要准备以下产品工具:
1.海创Box智能采集网关 |
2.密码锁 |
3.磁吸门锁控制板 |
1.2 设备接线
将网关盒子与磁吸门锁主板都接入交换机,设备上电后,与PC端实现网络通信。
1.3 联网参数配置
打开磁吸门锁软件--管理中心V6.9进行ip配置,实现通信。输入如图所示用户和密码。
打开控制器,搜索局域网络初始ip,配置参数。
1.4 远程开门
通信成功后打开总控制台,选择初始配置的门号,点击远程开门,查看运行信息。
1.5 密码权限修改删除
打开密码管理,输入要设置的密码选择添加,添加完成后前往上传设置,将数据上传。
二 数据采集
使用海创微联采集控制系统对磁吸门锁设备数据进行采集、处理、转发等操作。
打开海创微联采集控制系统,从左边的采集引擎中选择定时器、udp、function、json、调试器和阿里云节点,按下图连接。
2.1.1 定时器配置
定时器周期性触发输入时间戳或者相应的字符,主要当触发流程使用,具体配置如下图
具体代码如下所示:
{
"type": "status",
"address": 62393,
"value": 0
}
具体代码如下所示:
{
"type": "open",
"address": 62393,
"value": 1
}
2.1.2 udp配置
Udp发送msg.payload到指定的UDP主机和端口,支持组播!输入以及输出控件设置如下配置,配置地址以及端口。
2.1.3 function配置
JavaScript函数块,使用js语言,用于定义、赋值、指定规则等等,是最多变的控件。
具体代码如下所示:
解析:
let payload = msg.payload;
let type, value, address, code, data = {};
if (!Buffer.isBuffer(payload)) {
Node.error(msg, "msg.payload不是Buffer类型");
return;
}
let dataBuf = payload;
if (dataBuf.length != 34) {
Node.error(msg, "msg.payload报文解析错误,报文长度不为34:" + dataBuf.toString('hex'));
return;
}
if (dataBuf[0] != 0x7E || dataBuf[dataBuf.length - 1] != 0x0D) {
Node.error(msg, "msg.payload报文解析错误,报文头或者报文尾不正确:" + dataBuf.toString('hex'));
return;
}
let count = 0;
for (let i = 1, len = dataBuf.length - 3; i < len; i++) {
count += dataBuf[i];
}
if (count != dataBuf.readUInt16LE(dataBuf.length - 3)) {
Node.error(msg, `msg.payload报文校验失败,累计计算和为${Buffer.allocUnsafe(2).writeUInt16LE(count).toString('hex')},实际收到为:${dataBuf.subarray(dataBuf.length - 3, dataBuf.length - 1)}`);
return;
}
let success = function (data, message) {
if (typeof (data) == "undefined") {
data = {};
message = "操作成功";
}
if (typeof (message) == "undefined") {
message = "操作成功";
}
if (typeof (message) != "string") {
message = String(message);
}
return { "code": 0, "msg": message, "data": data };
}
let error = function (code, message) {
if (typeof (code) == "undefined" && typeof (message) == "undefined") {
code = 500;
message = "程序发生错误";
}
else if (typeof (code) != "undefined" && typeof (message) == "undefined") {
message = code;
code = 500;
}
if (typeof (message) != "string") {
message = String(message);
}
return { "code": code, "msg": message }
}
let isEror = function (result) {
if (typeof (result) == "boolean") {
if (result == true) {
return false;
}
else {
return true;
}
}
else if (typeof (result) == "object") {
if (result.code == 0) {
return false;
}
else {
return true;
}
}
else {
if (result) {
return false;
}
else {
return true;
}
}
}
let getDateTime = function (dateTimeBuf) {
if (!Buffer.isBuffer(dateTimeBuf)) {
dateTimeBuf = Buffer.from(dateTimeBuf, 'hex');
}
let dateTime, year, month, day, time, min, second;
if (dateTimeBuf.length == 7) {
year = "20" + dateTimeBuf.subarray(0, 1).toString('hex');
month = dateTimeBuf.subarray(1, 2).toString('hex');
day = dateTimeBuf.subarray(2, 3).toString('hex');
time = dateTimeBuf.subarray(4, 5).toString('hex');
min = dateTimeBuf.subarray(5, 6).toString('hex');
second = dateTimeBuf.subarray(6, 7).toString('hex');
}
else if (dateTimeBuf.length == 4) {
year = (dateTimeBuf.readUInt16LE(0) >> 9) + 2000;
month = dateTimeBuf.readUInt16LE(0) >> 5 & 0x000F;
day = dateTimeBuf.readUInt16LE(0) & 0x001F;
time = dateTimeBuf.readUInt16LE(2) >> 11;
min = dateTimeBuf.readUInt16LE(2) >> 5 & 0x003F;
second = dateTimeBuf.readUInt16LE(2) & 0x001F * 2;
}
else {
return error(1, "获取日期报文长度不正确:正确长度为7或者4,得到" + dateTimeBuf.length);
}
dateTime = `${year}-${month}-${day} ${time}:${min}:${second}`;
return success(dateTime);
}
let getBisStatus = function (num) {
if (typeof (num) != "number") {
num = Number(num);
}
let numArr = (Array(8).join('0') + num.toString(2)).slice(-8).split("");
// node.log(numArr);
let data = [];
numArr.forEach(element => {
let value = false;
if (element == "1") {
value = true;
}
data.push(value);
});
return data;
}
const recordingStatus = {
0x00: { "type": "open", "msg": "1号读卡器刷卡开门" },
0x01: { "type": "open", "msg": "2号读卡器刷卡开门" },
0x02: { "type": "open", "msg": "3号读卡器刷卡开门" },
0x03: { "type": "open", "msg": "4号读卡器刷卡开门" },
0x80: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 原因不明" },
0x81: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 原因不明" },
0x82: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 原因不明" },
0x83: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 原因不明" },
0x90: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 没有权限" },
0x91: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 没有权限" },
0x92: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 没有权限" },
0x93: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 没有权限" },
0xA0: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 密码不对" },
0xA1: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 密码不对" },
0xA2: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 密码不对" },
0xA3: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 密码不对" },
0xB0: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 系统有故障" },
0xB1: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 系统有故障" },
0xB2: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 系统有故障" },
0xB3: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 系统有故障" },
0xC0: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 反潜回, 多卡开门或多门互锁" },
0xC1: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 反潜回, 多卡开门或多门互锁" },
0xC2: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 反潜回, 多卡开门或多门互锁" },
0xC3: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 反潜回, 多卡开门或多门互锁" },
0xC4: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 反潜回" },
0xC5: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 反潜回" },
0xC6: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 反潜回" },
0xC7: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 反潜回" },
0xC8: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 多卡" },
0xC9: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 多卡" },
0xCA: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 多卡" },
0xCB: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 多卡" },
0xCC: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 首卡" },
0xCD: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 首卡" },
0xCE: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 首卡" },
0xCF: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 首卡" },
0xD0: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 门为常闭" },
0xD1: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 门为常闭" },
0xD2: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 门为常闭" },
0xD3: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 门为常闭" },
0xD4: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 互锁" },
0xD5: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 互锁" },
0xD6: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 互锁" },
0xD7: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 互锁" },
0xE0: { "type": "prohibit", "msg": "(1)号读卡器刷卡禁止通过: 卡过期或不在有效时段" },
0xE1: { "type": "prohibit", "msg": "(2)号读卡器刷卡禁止通过: 卡过期或不在有效时段" },
0xE2: { "type": "prohibit", "msg": "(3)号读卡器刷卡禁止通过: 卡过期或不在有效时段" },
0xE3: { "type": "prohibit", "msg": "(4)号读卡器刷卡禁止通过: 卡过期或不在有效时段" }
};
const noCardRecordingStatus = {
0: { 0x00: { "type": "button", "msg": "1号门按钮动作" }, 0x03: { "type": "remoteOpen", "msg": "1号门远程开门动作" }, 0x81: { "type": "coercion", "msg": "1号读卡器胁迫报警" }, 0x82: { "type": "noClose", "msg": "1号门长时间未关报警" }, 0x84: { "type": "illegal", "msg": "1号门非法闯入报警" } },
1: { 0x00: { "type": "button", "msg": "2号门按钮动作" }, 0x03: { "type": "remoteOpen", "msg": "2号门远程开门动作" }, 0x81: { "type": "coercion", "msg": "2号读卡器胁迫报警" }, 0x82: { "type": "noClose", "msg": "2号门长时间未关报警" }, 0x84: { "type": "illegal", "msg": "2号门非法闯入报警" } },
2: { 0x00: { "type": "button", "msg": "3号门按钮动作" }, 0x03: { "type": "remoteOpen", "msg": "3号门远程开门动作" }, 0x81: { "type": "coercion", "msg": "3号读卡器胁迫报警" }, 0x82: { "type": "noClose", "msg": "3号门长时间未关报警" }, 0x84: { "type": "illegal", "msg": "3号门非法闯入报警" } },
3: { 0x00: { "type": "button", "msg": "4号门按钮动作" }, 0x03: { "type": "remoteOpen", "msg": "4号门远程开门动作" }, 0x81: { "type": "coercion", "msg": "4号读卡器胁迫报警" }, 0x82: { "type": "noClose", "msg": "4号门长时间未关报警" }, 0x84: { "type": "illegal", "msg": "4号门非法闯入报警" } },
4: { 0xA0: { "type": "fireAlarm", "msg": "火警动作" } },
5: { 0x00: { "type": "superOpen", "msg": "1号读卡器超级密码开门" }, 0x01: { "type": "superOpen", "msg": "2号读卡器超级密码开门" }, 0x02: { "type": "superOpen", "msg": "3号读卡器超级密码开门" }, 0x03: { "type": "superOpen", "msg": "4号读卡器超级密码开门" } },
6: { 0xA0: { "type": "mandatory", "msg": "强制锁门" } },
8: { 0x00: { "type": "sensorOpen", "msg": "1号门打开[门磁信号]" } },
9: { 0x00: { "type": "sensorOpen", "msg": "2号门打开[门磁信号]" } },
10: { 0x00: { "type": "sensorOpen", "msg": "3号门打开[门磁信号]" } },
11: { 0x00: { "type": "sensorOpen", "msg": "4号门打开[门磁信号]" } },
12: { 0x00: { "type": "sensorClose", "msg": "1号门关闭[门磁信号]" } },
13: { 0x00: { "type": "sensorClose", "msg": "2号门关闭[门磁信号]" } },
14: { 0x00: { "type": "sensorClose", "msg": "3号门关闭[门磁信号]" } },
15: { 0x00: { "type": "sensorClose", "msg": "4号门关闭[门磁信号]" } }
};
let getErrorCode = function (num) {
let errorBisArr = getBisStatus(num);
let result = errorBisArr.slice(0, 5);
let errorMsg = [
"系统故障1",
"系统故障2",
"控制器时钟有故障",
"系统故障4",
"网络芯片有故障"
];
let data = [];
for (let item in result) {
if (result[item]) {
data.push(errorMsg[item]);
}
}
return data;
}
let getRecord = function (recordBuf) {
if (!Buffer.isBuffer(recordBuf)) {
recordBuf = Buffer.from(recordBuf, 'hex');
}
if (recordBuf.length != 8) {
return error(1, "获取记录报文长度不正确:正确长度为7,得到" + recordBuf.length);
}
if (recordBuf.toString('hex') == "ffffffffffffffff") {
return success(null);
}
let cardId = String(recordBuf[2]) + String(recordBuf.readUInt16LE(0));
let code, dateTime, message;
if (Number(cardId) > 100) {
code = recordingStatus[recordBuf[3]]['type'];
message = recordingStatus[recordBuf[3]]['msg'];
}
else {
code = noCardRecordingStatus[Number(cardId)][recordBuf[3]]['type'];
message = noCardRecordingStatus[Number(cardId)][recordBuf[3]]['msg'];
}
dateTime = getDateTime(recordBuf.subarray(4));
if (isEror(dateTime)) {
return dateTime;
}
dateTime = dateTime.data;
let data = {
"cardId": cardId,
"type": code,
"dateTime": dateTime,
"msg": message
}
return success(data);
}
address = dataBuf.readUInt16LE(1);
code = dataBuf.subarray(3, 5).toString('hex');
data = {
"address": address,
"code": code
};
if (dataBuf.readUInt16BE(3) == 0x8110) {
let dateTimeResult = getDateTime(dataBuf.subarray(5, 12));
if (isEror(dateTimeResult)) {
node.error(msg, dateTimeResult.msg);
return;
}
let dateTime = dateTimeResult.data;
let recordResult = getRecord(dataBuf.subarray(17, 25));
if (isEror(recordResult)) {
node.error(msg, recordResult, msg);
return;
}
let record = recordResult.data;
let swipeCount = dataBuf.readUIntLE(12, 3);
let authCount = dataBuf.readUInt16LE(15);
let relayStatus = getBisStatus(dataBuf[25]);
let doorBtnStatus = getBisStatus(dataBuf[26]);
let relay = relayStatus.slice(0, 4);
let doorSensor = doorBtnStatus.slice(0, 4).reverse();
let button = doorBtnStatus.slice(4);
let errorMsg = getErrorCode(dataBuf[28]);
data.dateTime = dateTime;
data.record = record;
data.swipeCount = swipeCount;
data.authCount = authCount;
data.relay = relay;
data.doorSensor = doorSensor;
data.button = button;
data.errorMsg = errorMsg;
data.type = "status";
}
else if (dataBuf.readUInt16BE(3) == 0x9D10) {
value = true;
if (dataBuf[5] != 0) {
node.error(msg, "开门失败");
code = false;
}
data.value = value;
data.type = "open";
}
else if (dataBuf.readUInt16BE(3) == 0x0711) {
value = true;
if (dataBuf[5] != 1) {
node.error(msg, "添加用户权限失败");
code = false;
}
data.value = value;
data.type = "addUser";
}
else if (dataBuf.readUInt16BE(3) == 0x0811) {
value = true;
if (dataBuf[5] != 1) {
node.error(msg, "删除用户权限失败");
code = false;
}
data.value = value;
data.type = "deleteUser";
}
else {
node.error(msg, "功能码不存在:" + dataBuf.subarray(3, 5).toString('hex'));
return;
}
msg.payload = data;
return msg;
控制
let payload = msg.payload;
if(payload.lock == true){
return {payload:{"type":"open","address":62393,"value":1}};
}
权限设置
let payload = msg.payload;
let type = payload.type;
let value = payload.value;
let address = payload.address;
if (!type) {
node.error(msg, "type不能为空!");
return;
}
if (!address) {
node.error(msg, "address不能为空!");
return;
}
let dataBuf = Buffer.alloc(34);
dataBuf[0] = 0x7E;
dataBuf.writeUInt16LE(Number(address), 1);
dataBuf[33] = 0x0D;
if(type == "status"){
dataBuf[3] = 0x81;
dataBuf[4] = 0x10;
if (value) {
value = Number(value);
dataBuf.writeUInt32LE(value, 5);
}
}
else if(type == "open"){
dataBuf[3] = 0x9d;
dataBuf[4] = 0x10;
switch(Number(value))
{
case 1:
dataBuf[5] = 0x01;
break;
case 2:
dataBuf[5] = 0x02;
break;
case 3:
dataBuf[5] = 0x03;
break;
case 4:
dataBuf[5] = 0x04;
break;
default:
node.error(msg, "需要开门的门号值不正确:" + value);
break;
}
}
else if(type == "addUser"){
//功能码
dataBuf[3] = 0x07;
dataBuf[4] = 0x11;
//权限索引
dataBuf[5] = 0x00;
dataBuf[6] = 0x00;
//卡ID
let cardId = Number(value.cardId);
if(cardId >= 10000)
{
node.error(msg, "添加权限卡号值不正确,值要小于10000:" + cardId);
return;
}
dataBuf[7] = cardId & 0x00ff;
dataBuf[8] = cardId >> 8;
dataBuf[9] = 0x00;
//门号 1~4
switch(Number(value.door))
{
case 1:
dataBuf[10] = 0x01;
break;
case 2:
dataBuf[10] = 0x02;
break;
case 3:
dataBuf[10] = 0x03;
break;
case 4:
dataBuf[10] = 0x04;
break;
default:
node.error(msg, "需要开门的门号值不正确:" + value);
return;
}
//起始时间
dataBuf[11] = 0x21;
dataBuf[12] = 0x00;
//终止时间
dataBuf[13] = 0x9f;
dataBuf[14] = 0x29;
//时段
dataBuf[15] = 0x01;
//密码
dataBuf[16] = 0x00;
dataBuf[17] = 0x00;
dataBuf[18] = 0x00;
}
else if(type == "deleteUser"){
//功能码
dataBuf[3] = 0x08;
dataBuf[4] = 0x11;
//权限索引
dataBuf[5] = 0x00;
dataBuf[6] = 0x00;
//卡ID
let cardId = Number(value.cardId);
if(cardId >= 10000)
{
node.error(msg, "删除权限卡号值不正确,值要小于10000:" + cardId);
return;
}
dataBuf[7] = cardId & 0x00ff;
dataBuf[8] = cardId >> 8;
dataBuf[9] = 0x00;
//门号 1~4
switch(Number(value.door))
{
case 1:
dataBuf[10] = 0x01;
break;
case 2:
dataBuf[10] = 0x02;
break;
case 3:
dataBuf[10] = 0x03;
break;
case 4:
dataBuf[10] = 0x04;
break;
default:
node.error(msg, "门号值不正确:" + value);
return;
}
}
else {
node.error(msg, "编码类型不存在:" + type);
return;
}
//校验和计算 计算位 bit1~bit30
let check_sum = 0;
for(let index = 1;index < 31;index++)
{
check_sum += dataBuf[index];
}
dataBuf.writeUInt16LE(check_sum, 31);
msg.payload = dataBuf;
return msg;
2.1.4 json配置
JOSN格式在任一方向上在JSON字符串及其JavaScript对象表示之间进行转换!
全部节点设置后,连线,点击部署,设置定时器将消息注入流中,点击触发可以观察到流程调试成功。
三 数据上传
阿里云物联网平台能够与海创微联采集控制系统进行联动,将采集到的数据可视化。
3.1 产品创建
登录阿里云物联网平台,在左侧导航栏,选择设备管理 > 产品,单击创建产品。
在新建产品页签,按照页面提示填写信息,然后单击确认。
3.2 设备创建
在左侧导航栏,选择设备管理 > 设备。在设备页面,单击添加设备。
在添加设备对话框中,输入设备信息,单击确认。
返回选择设备管理 > 设备,即可查看设备详情。
3.3 采集量的定义
返回查看刚刚创建的产品,在功能定义中,点击编辑草稿。
点击添加自定义功能。
注:标识符一定要和调试窗口中的字段一致。在功能定义中创建需要上传云端的数据标识符,创建完成后点击左下角的发布上线。
返回查看创建的设备,点击下图位置的查看按钮,复制好设备的3个标识符,即ProductKey(产品标识)、DeviceKey(设备标识)、DeviceSecret(设备密钥)。
3.4 数据可视化
前往海创微联采集控制系统,在阿里云IOT输入3个唯一标识。
点击部署,阿里云IOT连接成功。
返回阿里云物联网平台,设备显示在线。在设备的物模型数据中实时显示磁吸门锁设备数据。