物联网课程设计-通过Wifi控制Zigbee网络中执行器节点
如果要看具体的操作过程和思路,请耐心看完;
如果只是要实现控制功能,可以直接看第三部分python代码
一、基本介绍
陵阳爱普的物联网实验箱的ARM板上自带Zigbee、蓝牙、Wifi、和IPV6的服务器,并对用户提供了3个接口:
- /cgi-bin/topology.cgi 为服务器拓扑结构、网络数据的返回接口
- /cgi-bin/node.cgi 为传感器数据和节点信息的返回接口
- /cgi-bin/send_node.cgi 为向服务器发送控制信息的接口
所有的对硬件的操作都可以通过这三个接口进行
二、通过Wifi控制Zigbee网络中执行器节点基本思路
先方两段代码:
这一段为数据的解析代码,就是实验箱提供的web端的数据解析为.js文件,在进入web页面时会加载这个文件,用于解析上面三个url中的数据,可以在web端的网页源码中找到
具体功能就是把接口中的数据解析并显示到web端界面
function onNodeClicked(addr) {
var qdata;
$("#nodeInfo").html("加载中...");
var qdata = {'addr':addr.addr};
$.ajax({
async:true,
url: "/cgi-bin/node.cgi",
dataType: 'json',
data: qdata,
timeout: 4000,
success: function (res) {
if(nodeInfoContext != null)
displayNodeInfo(nodeInfoContext, res);
},
error: function(xhr) {
if(nodeInfoContext != null)
displayNodeInfo(nodeInfoContext, null);
}
});
}
function sendExecuteBCtrl(btn) {
btn.disabled = true;
var addr = $("#nodeInfoTab .nwkAddrCell").html();
var cell = $(btn).parent();
var id = cell.attr('typeID');
var v = 0;
v += (($("#exeb_V1_"+id).attr('checked') == undefined) ? 0 : 1);
v += (($("#exeb_V2_"+id).attr('checked') == undefined) ? 0 : 2);
v += (($("#exeb_V4_"+id).attr('checked') == undefined) ? 0 : 4);
v += (($("#exeb_V8_"+id).attr('checked') == undefined) ? 0 : 8);
v = v.toString(16);
$.ajax({
async:true,
url: "/cgi-bin/send_node.cgi",
dataType: "json",
data: {'type':cell.attr('typeCode'),'id':cell.attr('typeID'),'data':v},
timeout: 4000,
context: btn,
success: function (json) {
btn.disabled = false;
},
error: function(xhr) {
btn.disabled = false;
}
});
}
function refreshNodeData(btn) {
btn.disabled = true;
var addr = $("#nodeInfoTab .nwkAddrCell").html();
$.ajax({
async:true,
url: "/cgi-bin/node.cgi",
dataType: "json",
data: {'addr':addr},
timeout: 4000,
context: btn,
success: function (json) {
btn.disabled = false;
if(json == null)
return;
var cell = $(this).parent();
var i;
for(i = 0; i < json.length; i++) {
var j;
for(j = 0; j < json[i].funcList.length; j++) {
if(
(parseInt(json[i].funcList[j].typeCode) == parseInt(cell.attr('typeCode')))
&& (parseInt(json[i].funcList[j].id) == parseInt(cell.attr('typeID')))
) {
var data = json[i].funcList[j].data;
var id = json[i].funcList[j].id;
if(json[i].funcList[j].typeCode == 11) {
var html = '';
var v = parseInt('0x'+data);
if(v & 0x01)
html += '<input type="checkbox" id="exeb_V1_'+id+'" checked />AU';
else
html += '<input type="checkbox" id="exeb_V1_'+id+'" />AU';
if(v & 0x02)
html += '<input type="checkbox" id="exeb_V2_'+id+'" checked />AD';
else
html += '<input type="checkbox" id="exeb_V2_'+id+'" />AD';
if(v & 0x04)
html += '<input type="checkbox" id="exeb_V4_'+id+'" checked />BU';
else
html += '<input type="checkbox" id="exeb_V4_'+id+'" />BU';
if(v & 0x08)
html += '<input type="checkbox" id="exeb_V8_'+id+'" checked />BD';
else
html += '<input type="checkbox" id="exeb_V8_'+id+'" />BD';
$(this).parent().prev().html(html);
} else {
$(this).parent().prev().html(data);
}
}
}
}
},
error: function(xhr) {
btn.disabled = false;
}
});
}
function displayNodeInfo(dom, jdata) {
var i;
var html = "";
if(jdata == null) {
dom.html("通信故障!");
return;
}
var func = jdata[0].funcList;
html += "<table id=\"nodeInfoTab\">\n";
html += "<tr><td>网络地址</td><td class=\"nwkAddrCell\">" + jdata[0].nwkAddr +"</td></tr>\n";
html += "<tr><td>父节点地址</td><td>" + jdata[0].parAddr +"</td></tr>\n";
html += "<tr><td>物理地址</td><td>" + jdata[0].macAddr +"</td></tr>\n";
html += "<tr><td colspan=2>\n";
html += "<table width=100%><tr><td colspan=5 style=\"text-align: center;\">功能列表</td></tr>\n";
html += "<tr><td>类型</td><td>序号</td><td>刷新周期</td><td>当前数据</td><td></td></tr>\n";
for(i = 0; i < func.length; i++) {
html += "<tr>";
html += "<td>" + func[i].type + "</td>";
html += "<td>" + func[i].id + "</td>";
html += "<td>" + func[i].cycle + "</td>";
if(func[i].data != null) {
if(func[i].type == '控制B') {
var v = parseInt('0x'+func[i].data);
html += '<td class="funcDataCell">';
if(v & 0x01)
html += '<input type="checkbox" id="exeb_V1_'+func[i].id+'" checked />AU';
else
html += '<input type="checkbox" id="exeb_V1_'+func[i].id+'" />AU';
if(v & 0x02)
html += '<input type="checkbox" id="exeb_V2_'+func[i].id+'" checked />AD';
else
html += '<input type="checkbox" id="exeb_V2_'+func[i].id+'" />AD';
if(v & 0x04)
html += '<input type="checkbox" id="exeb_V4_'+func[i].id+'" checked />BU';
else
html += '<input type="checkbox" id="exeb_V4_'+func[i].id+'" />BU';
if(v & 0x08)
html += '<input type="checkbox" id="exeb_V8_'+func[i].id+'" checked />BD';
else
html += '<input type="checkbox" id="exeb_V8_'+func[i].id+'" />BD';
html += '</td>';
html += '<td typeCode="'+func[i].typeCode+'" typeID="'+func[i].id+'"><input type=button value="刷新" οnclick="refreshNodeData(this);"/><input type=button value="控制" οnclick="sendExecuteBCtrl(this);"/></td>';
} else {
html += "<td class=\"funcDataCell\">" + func[i].data + "</td>";
html += "<td typeCode=\""+func[i].typeCode+"\" typeID=\""+func[i].id+"\"><input type=button value=\"刷新\" οnclick=\"refreshNodeData(this);\"/></td>";
}
} else {
html += "<td colspan=2></td>";
}
html += "</tr>\n";
}
html += "</table></td></tr>\n";
html += "</table>\n";
dom.html(html);
}
第二段为 /cgi-bin/send_node.cgi接口调用时的数据处理代码
当你向这个接口发送数据的时候服务器会通过这段代码解析并给出应答
若你在浏览器直接输入http://xxx.xxx.xxx.xxx/cgi-bin/send_node.cgi服务器会返回代码中
errString = “无效的节点地址” 部分
代码中有如下注释部分:
// input: [get]
// type=%d&id=%d&data=%s
// output: json data or jsonp proc
意思是这段代码的输入数据使用 GET 请求进行发送,其向服务器发送的数据为 json或jsonp的格式
所以,如果你要实现控制就需要使用get请求向接口发送数据
如果在浏览器直接输入这个接口地址+要发送的数据,则可以直接发送到服务器,
接口会返回 "完成"
http://xxx.xxx.xxx.xxx/cgi-bin/send_node.cgi/?type=11&id=3&data=0a
具体的说一下这个url
http://xxx.xxx.xxx.xxx/cgi-bin/send_node.cgi/ 这一部分就是接口本身的url
?type=11&id=3&data=0a 这一部分为要发送的数据
英文的 ? 为数据发送的标志
后面为数据位 type=%d&id=%d&data=%s 这段代码的作用就是解析数据位中的内容并将内容返回到服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libbt4comm.h>
#define MAIN_NODE_CONFIG
#include <node_config.h>
// input: [get]
// type=%d&id=%d&data=%s
// output: json data or jsonp proc
static int _str2bin(void *outBuf, const char *inBuf)
{
int len = 0;
unsigned char *o = (unsigned char *)outBuf;
while(*inBuf)
{
char t[3] = "";
memcpy(t, inBuf, 2);
if(t[1] == 0)
{
inBuf += 1;
char *stopAt = NULL;
unsigned char c = strtoul(t, &stopAt, 16);
if(stopAt != &t[1])
break;
*o++ = c;
len++;
break;
}
inBuf += 2;
char *stopAt = NULL;
unsigned char c = strtoul(t, &stopAt, 16);
if(stopAt != &t[2])
break;
*o++ = c;
len++;
}
return len;
}
int main(int argc, char *argv[])
{
const char *query_string = getenv("QUERY_STRING");
char *callback = NULL;
unsigned char type = 0xFF;
unsigned char id = 0xFF;
char *data = NULL;
int dataLen = 0;
if((query_string != NULL) && (strlen(query_string) > 0))
{
char *bk = strdup(query_string);
char *cb = strstr(bk, "callback=");
char *tp = strstr(bk, "type=");
char *d = strstr(bk, "id=");
char *dt = strstr(bk, "data=");
if(cb != NULL)
{
char *value = strchr(cb, '=');
if(value != NULL)
{
value++;
char *stop = strchr(value, '&');
if(stop != NULL)
*stop = 0;
if(strlen(value) > 0)
callback = strdup(value);
}
}
if(tp != NULL)
{
char *value = strchr(tp, '=');
if(value != NULL)
{
value++;
char *stop = strchr(value, '&');
if(stop != NULL)
*stop = 0;
type = strtoul(value, &stop, 10);
if(stop == value)
type = 0xFF;
}
}
if(d != NULL)
{
char *value = strchr(d, '=');
if(value != NULL)
{
value++;
char *stop = strchr(value, '&');
if(stop != NULL)
*stop = 0;
id = strtoul(value, &stop, 10);
if(stop == value)
id = 0xFF;
}
}
if(dt != NULL)
{
char *value = strchr(dt, '=');
if(value != NULL)
{
value++;
char *stop = strchr(value, '&');
if(stop != NULL)
*stop = 0;
data = (char *)malloc((strlen(value) + 1) >> 1);
dataLen = _str2bin(data, value);
if(dataLen <= 0)
{
free(data);
data = NULL;
}
}
}
free(bk);
}
printf("%s\r\n\r\n", "Content-type: application/json");
if(callback != NULL)
printf("%s(", callback);
int result = 1;
char *errString = "";
if(type == 0xFF)
{
result = 0;
errString = "无效的节点地址";
}
else if((dataLen <= 0) || (data == NULL))
{
result = 0;
errString = "无效的数据";
}
else
{
bt4comm_sendNode_byType("127.0.0.1", type, id, data, dataLen);
result = 1;
errString = "完成";
}
printf("{\"result\":%s,\"errString\":\"%s\"}", result ? "true": "false", errString);
if(callback != NULL)
printf(")");
printf("\n");
printf("\n");
if(callback != NULL)
free(callback);
if(data != NULL)
free(data);
return 0;
}
三、python使用接口实现对Zigbee执行节点的控制
同样先上代码:
import time
import urllib
import requests
while True:
str0 = "0a"
data = {
'type': 11,
'id': 3,
'data': str0
}
postdata = urllib.parse.urlencode(data).encode('utf-8')
r = requests.get('http://192.168.10.130/cgi-bin/send_node.cgi', postdata)
print(postdata)
print()
print(r)
print(r.text)
time.sleep(3)
写成死循环为后续使用过程中要用到,在这里没有什么意义,这段代码只是一段测试代码,可以实现对Zigbee执行节点的控制
// input: [get]
// type=%d&id=%d&data=%s
// output: json data or jsonp proc
可以看到第二部分的数据接收格式为 type=%d&id=%d&data=%s
postdata = urllib.parse.urlencode(data).encode('utf-8')
这一句的作用就是将data中的数据转换为 type=%d&id=%d&data=%s这种格式
r = requests.get('http://192.168.10.130/cgi-bin/send_node.cgi', postdata)
这一句是使用get请求发送数据(其实就相当于对两部分进行拼接)
一定要注意是get请求而不是post请求,博主就是因为一个post看了一上午才发现问题
只不过要注意他会在两部分数据之间添加 ?
http://xxx.xxx.xxx.xxx/cgi-bin/send_node.cgi/ ? type=11&id=3&data=0a
python这面的核心就是这两句
最后说一下数据的格式
type为 /cgi-bin/node.cgi 接口执行器节点的 ‘typeCode’ 字段
id为 /cgi-bin/node.cgi 接口执行器节点的 ‘id’ 字段
data :先看一下四个灯的位置
每个灯的控制代码(数据)在图中已经给出
这些数据与data相对于
全灭为0X00
如果要选择其中两个或三个或四个点亮将代码相加就行
值得注意的是如果直接写这样的16进制格式,在python中并不能直接控制代码相加和为10以上的几个灯
因为 若为 0X0A 则会当0X00处理(在16进制基础上减去10)
其他也一样
此时
data = {
'type': 11,
'id': 3,
'data': “0X0A”
}
这段代码中的data为需要改为对应字符串,而不是16进制数据(如图所示)
控制过程的基本流程就是这样