本实验基于嵌入式高性能实验箱及其配套传感器,采集温度、光照、PM2.5、CO2等信息,并将信息远程发送到云端,智能手机下载云端数据并显示在手机主界面,当值高于报警值时,系统报警。
将整个系统分为客户端与服务端,客户端负责收集传感器数据并发送到服务器,服务端负责接收数据,更新数据库并且显示到网页上,如图1所示。
图1 智能家居系统结构逻辑
开发板采用嵌入式6410实验箱,每秒收集数据并且以数据报的形式更新到云服务器上,云服务器接收数据并存入数据库中,网页读取数据库中的内容,用户手动刷新更新网页中有关传感器的相关信息。开发板安装了linux系统,根据linux系统驱动规范来编写相关传感器的驱动程序并加载到linux系统中。
客户端的系统设计如下:
服务器端系统设计如下:
应用程序软件流程图如下:
(二)构建嵌入式Linux系统
本次实验利用Linux系统来构建虚拟实验平台,我用的是ubuntu 64位的系统。构建好ubuntu系统如下:
接下来就是配置交叉编译环境了,我使用的是arm-linux-gcc-v6
然后进行解压缩命令,首先进入arm-linux-gcc的目录。
首先切入到编译器所在目录中:cd /tmp
然后进行解压缩: tar xvzf arm-linux-gcc-4.5.1-v6-vfp-20101103.tgz -C/
后面的大写是C是为了将arm-linux-gcc安装到opt/FriendlyARM/toolschain/4.5.1目录。
接下来就要将其加入环境变量了,我直接修改etc/profile然后每一个用户执行source etc/profile就不必单独修改单个用户的用户环境变量了。使用vim编辑器,结果如下:
每个用户都执行语句:source /etc/profile 语句后,环境变量便设置完成。
到此,交叉编译环境便配置完成。
(三)基于QT的图形主界面设计
接下来就是配置QT图形界面了,首先因为我使用的是64位的Ubuntu的操作系统,所以要安装许多配置文件和32位的文件,在这里浪费了较多的时间。
老师提供的qt-sdk-linux-x86-opensouce-2010.05.1.bin文件在我的虚拟机中老是出错,如下:
经过查阅资料,发现是qmake的配置问题,于是我又自己下载了一个QT,重新位置了一下。
然后按照步骤一步一步的安装,基本上都是选择默认安装,安装完成以后在目录下出现了相应的qtsdk-2010.05文件夹,如下所示:
接下来还是需要来配置环境变量,我还是安装上面的方法修改了etc/profile文件,将QT的环境变量加入,如下:
然后执行:source /etc/profile命令,将刚才所修改的profile文件立即生效。
这个时候我执行qtcreator命令,发现竟然找不到文件?
经过搜索发现,原来是因为自己缺少了很多配置文件没有装,然后利用dpkg来装:
本来是有很多配置文件没有装的,但是当时只顾着找,便忘了截图,就只截了一张,就是利用该方法来需要需要安装的软件包然后再利用apt-get install 来安装。终于,在n次安装软件包只会,成功启动安装了qtcreator,我首先进行了测试的过程如下:
编写智能家居设备端控制软件总体架构界面如下图所示:
功能如下:红外接收红外发射
在QT中设计制作效果如下所示:
(四)传感器采集及设计
LED灯点亮熄灭控制,打开关闭窗帘电动控制,当前温度传感器采集显示控制界面设计:
部分使用如下代码:
main_form.h
#if !defined (__MAIN_FORM_H__)
# define __MAIN_FORM_H__
#include "main_form_base.h"
class TMainForm: public TMainFormBase {
Q_OBJECT
public:
TMainForm(QWidget * parent = 0, const char * name = 0, WFlags f = WType_TopLevel);
virtual ~TMainForm();
private slots:
void buttonClicked();
private:
int m_fd;
};
#endif
main_form.cpp
#include "main_form.h"
#include <qlabel.h>
#include <qpushbutton.h>
#include <qlayout.h>
#include <qspinbox.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <qapplication.h>
#define PWM_IOCTL_SET_FREQ 1
#define PWM_IOCTL_STOP 0
TMainForm::TMainForm(QWidget * parent, const char * name, WFlags f)
: TMainFormBase(parent, name, f)
{
m_fd = ::open("/dev/pwm", O_RDONLY);
::ioctl(m_fd, PWM_IOCTL_STOP);
connect(m_startButton, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
connect(m_stopButton, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
connect(m_closeButton, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
}
TMainForm::~TMainForm()
{
::ioctl(m_fd, PWM_IOCTL_STOP);
::close(m_fd);
}
void TMainForm::buttonClicked()
{
QPushButton* obj = (QPushButton*)sender();
if (obj == m_startButton) {
::ioctl(m_fd, PWM_IOCTL_SET_FREQ, 1000);
} else if (obj == m_stopButton) {
::ioctl(m_fd, PWM_IOCTL_STOP);
} else if (obj == m_closeButton) {
close();
}
}
(五)远程web服务器设计及实现
首先为了完成远程控制我需要先构建BOA服务器,Boa是一种非常小巧的Web服务器,其可执行代码只有大约60KB左右。作为一种单任务Web服务器,Boa只能依次完成用户的请求,而不会fork出新的进程来处理并发连接请求。但Boa支持CGI,能够为CGI程序fork出一个进程来执行。
首先下载Boa源码boa-0.94.13.tar.gz,并解压:
# tar xzf boa-0.94.13.tar.gz
解压:
然后安装需要工具bison和flex,通过命令:sudo apt-get install bison flex来进行安装。否则会出现错误。
然后修改文件src/compat.h、修改src/boa.c和src/log.c。找到#define TIMEZONE_OFFSET(foo) foo##->tm_gmtoff修改成:#define TIMEZONE_OFFSET(foo) (foo)->tm_gmtoff。
然后在src/log.c注释掉如下内容:
if (dup2(error_log, STDERR_FILENO) == -1)
{ DIE("unable to dup2 the error log");}
在src/boa.c中注释掉下面两句话:
if (passwdbuf == NULL)
{DIE(”getpwuid”);}
if (initgroups(passwdbuf->pw_name, passwdbuf->pw_gid) == -1)
{DIE(”initgroups”);}
和
if (setuid(0) != -1)
{DIE(”icky Linux kernel bug!”);}
然后需要生成Makefile文件,执行:#cd boa-0.94.13/src
#./configure
修改Makefile:
#cd src
#vim Makefile
修改CC = gcc 为 CC = arm-linux-gcc
修改CPP = gcc -E 为 CC = arm-linux-gcc -E
然后进行编译:
#ls -l boa -rwxr-xr-x 1 david david 189223 2009-05-31 13:44 boa
然后为生成的二进制文件boa瘦身:
#ls -l boa -rwxr-xr-x 1 david david 61052 2009-05-31 13:51 boa
然后需要对Boa进行配置,这一步的工作也在电脑主机上完成。在boa-0.94.13目录下已有一个示例boa.conf,可以在其基础上进行修改。如下:
#vi boa.conf
(1)修改 Group nogroup为 Group 0
(2)修改User nobody为 User 0
(3)修改ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/为 ScriptAlias /cgi-bin/ /var/www/cgi-bin/
(5)修改DoucmentRoot /var/www
(6)修改#ServerName www.your.org.here为 ServerName www.your.org.here
(7)修改AccessLog /var/log/boa/access_log为#AccessLog /var/log/boa/access_log
以下步骤在开发板上进行:
创建目录/etc/boa并且把boa 和 boa.conf拷贝到这个目录下mkdir /etc/boa创建HTML文档的主目录/wwwmkdir /var/www/cgi-bin
CGI脚本所在录 /var/www/cgi-bin
以下步骤在ubuntu下进行:
将boa.conf拷贝到开发板根文件系统的/etc/boa下
#cp boa.conf /source/rootfs/etc/boa
将boa拷贝到开发板根文件系统的/bin下
#cp src/boa /source/rootfs/bin
将ubuntu下/etc/mime.types拷贝到开发板根文件系统的/etc下
#cp /etc/mime.types /source/rootfs/etc
将你的主页index.html拷贝到 /var/www 目录下。
这样环境就搭建完成了。接下来将进入实际应用阶段。在这里以控制LED为例。
1.客户端控制界面
采用CGI将浏览器端的控制信息传到boa服务器端,index.html文件
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>web控制mini2440开发板led</title>
</head>
<body>
<h1 align="center">基于mini2440的web控制GPIO口</h1>
<form action="/cgi-bin/cgi_led.cgi" method="get">
<p align="center">led的测试工作</p>
<p align="center">请输入需要控制的led <input type="text" name="led_control"/></p>
<p align="center">请输入控制led的动作 <input type="text" name="led_state"/></p>
<p align="center"><input type="submit" value="sure"/>
<input type="reset" value="back"/>
</p>
</form>
</body>
</html>
2.服务器端数据处理
通过客户端浏览器的from表单将,控制信息提交到服务器,服务器获取数据并通过有名管道将控制数据传给调用LED驱动的控制程序。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <limits.h>
#include <string.h>
#define MYFIFO "/www/myfifo"
#define MAX_BUFFER_SIZE PIPE_BUF
int main()
{
char* data; //定义一个指针用于指向QUERY_STRING存放的内容
char buff[MAX_BUFFER_SIZE];
int fd, nwrite;
int led_control, led_state;
printf("Content-type: text/html\n\n");
printf("<html>\n");
printf("<head><title>cgi led demo</title></head>\n");
printf("<body>\n");
printf("<p>led is setted successful! you can watch the led's change</p>\n");
printf("<p><a herf=index.html><button>get back</button></a></p>\n");
printf("</body>\n");
data = getenv("QUERY_STRING"); //getenv()读取环境变量的当前值的函数
strcpy(buff, data);
fd = open(MYFIFO, O_WRONLY);
if(-1 == fd)
{
printf("Open fifo file error\n");
exit(1);
}
if((nwrite = write(fd, buff, sizeof(buff))) < 0)
{
printf("\nWrite data error\n");
exit(1);
}
if(sscanf(buff,"led_control=%d&led_state=%d",&led_control,&led_state)!=2)
{ //利用sscnaf()函数的特点将环境变量分别提取出led_control和led_state这两个值
printf("<p>please input right");
printf("</p>");
}
printf("<p>led_control = %d,led_state = %d</p>", led_control, led_state);
if(led_control>3)
{
printf("<p>Please input 0<=led_control<=3!");
printf("</p>");
}
if(led_state>1)
{
printf("<p>Please input 0<=led_state<=1!");
printf("</p>");
}
close(fd);
printf("</html>\n");
return 0;
}
3.控制端
通过管道接受到控制数据,调用驱动程序实现对LED灯的控制
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <linux/micro2440_leds.h>
#define MYFIFO "/www/myfifo"
#define LEDS_DEVICE "/dev/led4s"
#define MAX_BUFFER_SIZE PIPE_BUF
int main(int argc, char* argv[])
{
int led_fd, fifo_fd, led_control,led_state, nread;
struct leds_stat oneStat;
char buff[MAX_BUFFER_SIZE]; //定义一个指针用于指向QUERY_STRING存放的内容
led_fd = open(LEDS_DEVICE, O_RDWR); //打开led设备
if(-1 == led_fd)
{
perror("open led device");
exit(1);
}
if(-1 == access(MYFIFO, F_OK))
{
if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST))
{
printf("Connot create fifo file\n");
exit(1);
}
}
fifo_fd = open(MYFIFO, O_RDONLY);
if(-1 == fifo_fd)
{
perror("open fifo file error\n");
exit(1);
}
while(1)
{
//memset(buff, 0, sizeof(buff));使用会使控制数据出错
if((nread = read(fifo_fd, buff, sizeof(buff))) < 0)
{
perror("Read data error");
exit(1);
}
if(sscanf(buff,"led_control=%d&led_state=%d",&led_control,&led_state)!=2)
{ //利用sscnaf()函数的特点将环境变量分别提取出led_control和led_state这两个值
printf("please input right \n");
exit(1);
}
if(led_control>3)
{
printf("Please input 0<=led_control<=3!");
exit(1);
}
if(led_state>1)
{
printf("Please input 0<=led_state<=1!");
exit(1);
}
oneStat.led_nr = led_control;
oneStat.led_st = led_state;
ioctl(led_fd, LEDS_SET_ONE, &oneStat);
}
close(led_fd);
close(fifo_fd);
return 0;
}