本系统的设计流程大致如下:
其中传感器模块和执行器模块使用Modbus slave软件代替。
服务程序和从机之间的通信采用Modbus通信协议,因为Modbus协议是现在国内工业领域应用最多的协议,不仅仅用于PLC设备,还用于各种终端设备,如水表、电表、各种采集设备等等。
其中Modbus通信协议有许多变种,最著名的三种版本有支持以太网的Modbus TCP,和支持串口的Modbus RTU、Modbus ASCII。
本系统使用Modbus TCP协议:
1.特点:
(1)采用主从问答方式通信。
(2)属于应用层协议,顾名思义,他是基于TCP实现通信。
(3)一般使用Modbus TCP的默认端口号是502,但也可以使用其他端口号,注意不要冲突就好。
2.协议格式
使用此协议发送的请求码包括三个部分:MBAP报文头(7字节)、功能代码(1字节)、数据。
其中报文头包含四个部分:
3.功能代码
其通过离散量输入或者线圈来控制设备,输入和保持寄存器来存储设备。根据四种不同的寄存器设置了多种功能码,其中最常用的八种如下:
服务程序-----从机
我们先从服务程序和从机之间的通信开始,如流程图所示,本系统只是实现了实时采集温湿度和控制LED灯、蜂鸣器基本功能。
1.设置slave端
<-----设置读两个保持寄存器
<-------给其初始温湿度
<---------设置写两个项圈:LED BEEP
<---------初始值设为关闭
2.写服务程序
先创建套接字,进行连接,连接成功后给从机发送请求码,从机收到后会返回数据。
下面的是封装请求码的函数,因为使用的是TCP所以req[2]和req[3]都是0;req[6]是我们从机的ID,通过func来判断是要读取保持寄存器还是写线圈:当func为3时是读保持寄存器,func为1时是写线圈。
//func:功能码 addr:地址 nb:保持寄存器的个数或是线圈的发送数据 req:从机回过来的数据
int buildRequest(int func, int addr, int nb, uint8_t *req)
{
req[0] = 0x00;
req[1] = 0x00;
req[2] = 0x00;
req[3] = 0x00;
req[4] = 0x00;
req[5] = 0x06;
req[6] = 0x01;
req[7] = (uint8_t)func;
req[8] = (uint8_t)(addr >> 8);
req[9] = (uint8_t)(addr & 0xff);
if (func == 0x03)
{
req[10] = (uint8_t)(nb >> 8);
req[11] = (uint8_t)(nb & 0xff);
return 12;
}
else if (func == 0x05)
{
if (nb == 0)
req[10] = 0;
else
req[10] = 0xff;
req[11] = 0x00;
return 12;
}
}
读保持寄存器和写线圈:
void read_registers(int sockfd, int addr, int nb, uint16_t *dest)
{
uint8_t data[32] = "";
uint8_t req[32] = "";
int len = buildRequest(3, addr, nb, req);
send(sockfd, req, len, 0);
//接收从机数据
recv(sockfd, data, 32, 0);
for (int i = 0, j = 0; i < data[8]; i += 2, j++)
{
dest[j] = data[9 + i] << 8 | data[10 + i];
}
}
void write_bit(int sockfd, int addr, int val)
{
uint8_t req[32] = "";
int len = buildRequest(5, addr, val, req);
send(sockfd, req, len, 0);
}
3.连接
<--------先连接上slave端
<------启动服务程序,成功获得了温湿度的值;
服务器------CGI
本项目采用的是Lighttpd服务器。
LigHttpd是一个开源的轻量级嵌入式Web server,是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的web server环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。
Lighttpd 适合静态资源类的服务,比如图片、资源文件、静态HTML等等的应用,性能比较好,同时也适合简单的CGI应用的场合,lighttpd可以很方便的通过fastcgi支持php。
CGI:
CGI(Common Gateway Interface)通用网关接口,是外部扩展应用程序与 Web 服务器交互的一个标准接口。
配置完成后运行Lighttpd服务器,
打开xterm:
进入cgi程序中,把标红的部分改成自己的。
CGI------服务程序
通过共享内存获得从机中温湿度的值、消息队列来控制LED灯和蜂鸣器。具体代码如下:
CGI:当发送GET时获得温湿度的值,LED 0/1 BEEP 0/1进行控制
char buf[100]="";
key_t key = ftok("/home/hq/work/web/cgi_demo/ftok.txt", 'A');
if (key < 0)
{
perror("ftok err");
return -1;
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid < 0)
{
if (errno == EEXIST)
{
msgid = msgget(key, 0666);
}
else
{
perror("msgget err");
return -1;
}
}
struct msgbuf msg;
msg.mtype = 'A';
if (strncmp(input, "GET",3) == 0)
{
key_t key1 = ftok("/home/hq/work/web/cgi_demo/shm.txt", 'C');
if (key1 < 0)
{
perror("ftok err");
return -1;
}
int shmid = shmget(key1, 16, IPC_CREAT | IPC_EXCL |0666);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key1, 16,0666);
}
else
{
perror("shmget err");
return -1;
}
}
char *sp = (char *)shmat(shmid, NULL, 0);
if (sp == (char *)-1)
{
perror("shmat err");
return -1;
}
strcpy(buf, sp);
}
else if (strncmp(input, "LED 0",5)==0)
{
strcpy(msg.mtext, val_buf);
msgsnd(msgid, &msg, sizeof(msg), 0);
strcpy(buf,"灯已关闭");
}
else if (strncmp(input, "LED 1",5)==0)
{
strcpy(msg.mtext, val_buf);
msgsnd(msgid, &msg, sizeof(msg), 0);
strcpy(buf,"灯已打开");
}
else if (strncmp(input, "BEEP 0",6)==0)
{
strcpy(msg.mtext, val_buf);
msgsnd(msgid, &msg, sizeof(msg), 0);
strcpy(buf,"蜂鸣器关闭");
}
else if (strncmp(input, "BEEP 1",6)==0)
{
strcpy(msg.mtext, val_buf);
msgsnd(msgid, &msg, sizeof(msg), 0);
strcpy(buf,"蜂鸣器打开");
}
strcpy(val_buf,buf);
服务程序收到信号后给出相应的回复或控制设备即可。
程序测试:
接着再简单的制作一个小网页后即可通过网页获取数据和控制设备了。
注意:子系统自能在同一局域网中进行
<-----点击获取温度
<-------点击获取湿度
<-------点击控制开关