物联网的定义:(来自维基百科)
The Internet of things (IoT) is the network of physical devices, vehicles, home appliances and other items embedded with electronics, software, sensors, actuators, and connectivity which enables these objects to connect and exchange data. Each thing is uniquely identifiable through its embedded computing system but is able to inter-operate within the existing Internetinfrastructure.
The IoT allows objects to be sensed or controlled remotely across existing network infrastructure, creating opportunities for more direct integration of the physical world into computer-based systems, and resulting in improved efficiency, accuracy and economic benefit in addition to reduced human intervention.When IoT is augmented with sensors and actuators, the technology becomes an instance of the more general class of cyber-physical systems, which also encompasses technologies such as smart grids, virtual power plants, smart homes, intelligent transportation and smart cities.
"Things", in the IoT sense, can refer to a wide variety of devices such as heart monitoring implants, biochip transponders on farm animals, cameras streaming live feeds of wild animals in coastal waters, automobiles with built-in sensors, DNA analysis devices for environmental/food/pathogen monitoring, or field operation devices that assist firefighters in search and rescue operations. Legal scholars suggest regarding "things" as an "inextricable mixture of hardware, software, data and service".
These devices collect useful data with the help of various existing technologies and then autonomously flow the data between other devices.
物联网小灯全架构实现
目标:将小灯接入互联网使其使其与网络互连,能查询小灯的实时状态,能对小灯的状态进行更改。
从服务器的API服务到下位机的入网。
所需设备:
嵌入式开发板一块(51板,正点原子32开发板或则Linux嵌入式开发板都行,<前提是你会那样>)
WiFi模块一个:(ESP8266模块或则使用NodeMCU开发板)
自己做一个小灯一个(自己手焊,打板都行)
框架图(源自百度图库):
服务器搭建:
步骤:
1、租用一台服务器
2、搭建一个LNMP服务器(附LNMP一键安装包,过程就省略了,有教程,Linux+Nginx+MySQL+PHP)
3,数据库构建:
远程终端命令行代码(SQL代码):
登录:
[root@host]# mysql -u root -p
Enter password:******
创建数据库:
CREATE DATABASE api;
创建数据表:
USE api;
CREATE TABLE `device`(
`id` VARCHAR(20) NOT NULL,
`status` VARCHAR(20) NOT NULL);
插入数据:
INSERT INTO device
-> (id, status)
-> VALUES
-> ("Light", "Low_brightness");
INSERT INTO device
-> (username, passwd)
-> VALUES
-> ("账户名", "密码");
数据库搭建成功测试代码:
mysql> select * from device;
+-------+----------------+
| id | status |
+-------+----------------+
| Light | Low_brightness |
+-------+----------------+
1 row in set (0.00 sec)
mysql> select * from user;
+----------+--------+
| username | passwd |
+----------+--------+
| test | Li34 |
+----------+--------+
1 row in set (0.00 sec)
服务端搭建API接口服务(PHP代码):
1、用于将数据库的数据封装成JSON或XML格式
<?php
class Response {
const JSON = "json";
/**
* 按综合方式输出通信数据
* @param integer $code 状态码
* @param string $message 提示信息
* @param array $data 数据
* @param string $type 数据类型
* return string
* 调用方式 Response::show($code, $message, $data, $type); //其中type可以来自用户
*/
public static function show($code, $message = '', $data = array(), $type = self::JSON) {
if(!is_numeric($code)) {
return '';
}
$type = isset($_GET['format']) ? $_GET['format'] : self::JSON;
$result = array(
'code' => $code,
'message' => $message,
'data' => $data,
);
if($type == 'json') {
self::json($code, $message, $data);
exit;
} elseif($type == 'array') {
var_dump($result);
} elseif($type == 'xml') {
self::xmlEncode($code, $message, $data);
exit;
} else {
// TODO
}
}
/**
* 按json方式输出通信数据
* @param integer $code 状态码
* @param string $message 提示信息
* @param array $data 数据
* return string
* 调用方式 require_once('./response.php'); 调用response.php号文件
* Response::json($code, $message, $data);调用Response的json方法
*/
public static function json($code, $message = '', $data = array()) {
if(!is_numeric($code)) {
return '';
}
$result = array(
'code' => $code,
'message' => $message,
'data' => $data
);
echo json_encode($result);
exit;
}
/**
* 按xml方式输出通信数据
* @param integer $code 状态码
* @param string $message 提示信息
* @param array $data 数据
* return string
* 调用方式 Response::xmlEncode($code, $message, $data);
*/
public static function xmlEncode($code, $message, $data = array()) {
if(!is_numeric($code)) {
return '';
}
$result = array(
'code' => $code,
'message' => $message,
'data' => $data,
);
header("Content-Type:text/xml");
$xml = "<?xml version='1.0' encoding='UTF-8'?>\n";
$xml .= "<root>\n";
$xml .= self::xmlToEncode($result);
$xml .= "</root>";
echo $xml;
}
public static function xmlToEncode($data) {
$xml = $attr = "";
foreach($data as $key => $value) {
if(is_numeric($key)) {
$attr = " id='{$key}'";
$key = "item";
}
$xml .= "<{$key}{$attr}>";
$xml .= is_array($value) ? self::xmlToEncode($value) : $value; //递归调用xmlToEncode
$xml .= "</{$key}>\n";
}
return $xml;
}
}
2、用于返回下位机数据
<?php
$dbms='mysql'; //数据库类型
$host='localhost'; //数据库主机名
$dbName='api'; //使用的数据库
$user='用户名'; //数据库连接用户名
$pass='密码'; //对应的密码
$dsn="$dbms:host=$host;dbname=$dbName";
require_once('./response.php');
try {
$dbh = new PDO($dsn, $user, $pass);
foreach($dbh->query('SELECT * from device') as $row) {
}
$dbh = null;
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
$data = array(
'id' => $row['id'],
'status' => $row['status'],
);
Response::show(200,'success',$data);
?>
服务器API服务数据返回测试:
在浏览器中输入:服务器IP地址/test.php?format=xml
执行结果:
<?xml version='1.0' encoding='UTF-8'?>
<root>
<code>200</code>
<message>success</message>
<data><id>Light</id>
<status>Low_brightness</status>
</data>
</root>
实现网页控制小灯代码:
(HTML代码)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>智能灯网页控制</title>
</head>
<body>
<h2 class="headingOuter">智能灯网页控制</h2>
<form action="control.php" method="get">
亮度:
<table width="200">
<tr>
<td><label>
<input type="radio" name="control" value="Lights_off" id="control_0" />
关灯</label></td>
</tr>
<tr>
<td><label>
<input type="radio" name="control" value="Low_brightness" id="control_1" />
低亮度</label></td>
</tr>
<tr>
<td><label>
<input type="radio" name="control" value="Medium_brightness" id="control_2" />
中亮度</label></td>
</tr>
<tr>
<td><label>
<input type="radio" name="control" value="high_brightness" id="control_3" />
高亮度</label></td>
</tr>
</table>
<!--<input type="text" name="brightness">-->
<input type="submit" value="提交">
</form>
</body>
</html>
(PHP代码)
<?php
$get_temp = $_GET["control"];
$dbms='mysql'; //数据库类型
$host='localhost'; //数据库主机名
$dbName='api'; //使用的数据库
$user='root'; //数据库连接用户名
$pass='密码'; //对应的密码
$dsn="$dbms:host=$host;dbname=$dbName";
$dup="update device set status='$get_temp' where 1";
$dbh = new PDO($dsn, $user, $pass);
$count = $dbh->exec($dup);//执行SQL语句
print "Upate $count rows success!\n";
sleep(10);
$dbh = null;
header("Location: http://服务器的网址/control.html");
测试图片:
简单的网页登录认证:
(HTML代码)
<html>
<head>智能灯网页控制用户登录</head>
<form name="LoginForm" method="post" action="login.php" onSubmit="return InputCheck(this)">
<p>
<label for="username" class="label">用户名:</label>
<input id="username" name="username" type="text" class="input" />
<p/>
<p>
<label for="password" class="label">密 码:</label>
<input id="password" name="password" type="password" class="input" />
<p/>
<p>
<input type="submit" name="submit" value=" 确 定 " class="left" />
</p>
</form>
</html>
(PHP代码)
<?php
if(!isset($_POST['submit']))
{
exit('Unauthorized access!');
}
$username = htmlspecialchars($_POST['username']);
$password = htmlspecialchars($_POST['password']);
include('conn.php');
foreach($dbh->query('SELECT username, passwd from user where 1') as $row)
{
}
if($username == $row['username'] && $password == $row['passwd'])
{
header("Location: http://服务器IP地址/control.html");
}
else
{
exit('Login failed! click here <a href="javascript:history.back(-1);">come back</a> try again');
}
?>
<?php
$dbms='mysql';
$host='localhost';
$dbName='api';
$user='用户名';
$pass='密码';
$dsn="$dbms:host=$host;dbname=$dbName";
try
{
$dbh = new PDO($dsn, $user, $pass);
}
catch (PDOException $e)
{
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
?>
成功图片:
下位机代码(我是使用的是STM32+人工焊的小灯采用164芯片):
由于我买的是ALIENTEK STM32开发板,它里面带有ESP8266的测试模板,直接在上面进行修改即可,至于模块烧写AT指令集,我已写过可以看固件下载
小灯驱动(C语言代码):
164模块编写:
#include "164.h"
#include "delay.h"
//unsigned char Tab[]={0x00,0X01,0X02,0X04,0X08,0X80,0X40,0X20,0X10}; //共阴数码管
unsigned char Tab[]={0x00,0X04,0X06,0X07}; //LED灯的亮度
/*************************
0x00 灭
0x04 低亮
0x06 中亮
0x07 高亮
*************************/
unsigned char DS_data[6];
void HC164_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_SetBits(GPIOC,GPIO_Pin_4); //PA.8 输出高
}
void separateData(unsigned long dat) // dat为6位数
{
DS_data[0]=dat%10;
DS_data[1]=dat/10%10;
DS_data[2]=dat/100%10;
DS_data[3]=dat/1000%10;
DS_data[4]=dat/10000%10;
DS_data[5]=dat/100000%10;
}
void write_164(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
CLK=0;
if(dat&0x80)
MOSI=1;
else
MOSI=0;
CLK=1;
dat<<=1;
}
}
void display()
{
unsigned char i;
for(i=0;i<6;i++)
{
write_164(Tab[DS_data[0]]);
delay_ms(1);
}
}
小灯控制实现
#include "eled.h"
#include "164.h"
//初始化PB5和PE5为输出口.并使能这两个口的时钟
//LED IO初始化
void ELED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 输出高
}
void control_brightness(uint8_t brightness)
{
if(brightness == Lights_off)
{
separateData(0);//灭
display();
}
else if(brightness == Low_brightness)
{
separateData(1);//低亮度
display();
}
else if(brightness == Medium_brightness)
{
separateData(2);//中亮度
display();
}
else if(brightness == high_brightness)
{
separateData(3);//中亮度
display();
}
}
核心代码
#include "common.h"
#include "stdlib.h"
#include "164.h"
#include "Photoresistor.h"
#include "eled.h"
#include <string.h>
u8 USART2_RX_BUF_TEMP[1024];
int flag_lights_test=0;//灯的标志位默认0为灭,1为低亮度,2为中亮度,3高亮度
char *buf1 = "Lights_off", \
*buf2 = "Low_brightness", \
*buf3 = "Medium_brightness", \
*buf4 = "high_brightness";
u8 netpro=0; //网络模式
void light_Status_changes_test(int flag);
u8 atk_8266_send_cmd_test(u8 *cmd,u8 *ack,u16 waittime);
int StringFind_test(const char *pSrc, const char *pDst);
u8 atk_8266_wifista_test(void)
{
int flag_netpro=1;
int flag_lights_test_temp=1;
u8 key;
u8 timex=0;
u8 ipbuf[16]; //IP缓存
u8 *p;
u16 t=999; //加速第一次获取链接状态
u8 res=0;
u16 rlen=0;
u8 constate=0; //连接状态
p=mymalloc(32); //申请32字节内存
atk_8266_send_cmd("AT+CWMODE=1","OK",50); //设置WIFI STA模式
atk_8266_send_cmd("AT+RST","OK",20); //DHCP服务器关闭(仅AP模式有效)
delay_ms(1000); //延时3S等待重启成功
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
//设置连接到的WIFI网络名称/加密方式/密码,这几个参数需要根据您自己的路由器设置进行修改!!
sprintf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",wifista_ssid,wifista_password);//设置无线参数:ssid,密码
while(atk_8266_send_cmd(p,"WIFI GOT IP",300)); //连接目标路由器,并且获得IP
PRESTA:
netpro|=atk_8266_netpro_sel(50,30,(u8*)ATK_ESP8266_CWMODE_TBL[0]); //选择网络模式
if(netpro&0X01) //TCP Client 透传模式测试
{
LCD_Clear(WHITE);
POINT_COLOR=RED;
Show_Str_Mid(0,30,"ATK-ESP WIFI-STA 测试",16,240);
Show_Str(30,50,200,16,"正在配置ATK-ESP模块,请稍等...",12,0);
if(atk_8266_ip_set("WIFI-STA 远端IP设置",(u8*)ATK_ESP8266_WORKMODE_TBL[netpro],(u8*)portnum,ipbuf))goto PRESTA; //IP输入
atk_8266_send_cmd("AT+CIPMUX=0","OK",20); //0:单连接,1:多连接
sprintf((char*)p,"AT+CIPSTART=\"TCP\",\"%s\",%s",ipbuf,(u8*)portnum); //配置目标TCP服务器
while(atk_8266_send_cmd(p,"OK",200))
{
LCD_Clear(WHITE);
POINT_COLOR=RED;
Show_Str_Mid(0,40,"WK_UP:返回重选",16,240);
Show_Str(30,80,200,12,"ATK-ESP 连接TCP失败",12,0); //连接失败
key=KEY_Scan(0);
if(key==WKUP_PRES)goto PRESTA;
}
atk_8266_send_cmd("AT+CIPMODE=1","OK",200); //传输模式为:透传
}
else //TCP Server
{
LCD_Clear(WHITE);
POINT_COLOR=RED;
Show_Str_Mid(0,30,"ATK-ESP WIFI-STA 测试",16,240);
Show_Str(30,50,200,16,"正在配置ATK-ESP模块,请稍等...",12,0);
atk_8266_send_cmd("AT+CIPMUX=1","OK",20); //0:单连接,1:多连接
sprintf((char*)p,"AT+CIPSERVER=1,%s",(u8*)portnum); //开启Server模式(0,关闭;1,打开),端口号为portnum
atk_8266_send_cmd(p,"OK",50);
}
LCD_Clear(WHITE);
POINT_COLOR=RED;
Show_Str_Mid(0,30,"ATK-ESP WIFI-STA 测试",16,240);
Show_Str(30,50,200,16,"正在配置ATK-ESP模块,请稍等...",12,0);
LCD_Fill(30,50,239,50+12,WHITE); //清除之前的显示
Show_Str(30,50,200,16,"WK_UP:退出测试 KEY0:发送数据",12,0);
LCD_Fill(30,80,239,80+12,WHITE);
atk_8266_get_wanip(ipbuf);//服务器模式,获取WAN IP
sprintf((char*)p,"IP地址:%s 端口:%s",ipbuf,(u8*)portnum);
Show_Str(30,65,200,12,p,12,0); //显示IP地址和端口
Show_Str(30,80,200,12,"状态:",12,0); //连接状态
Show_Str(120,80,200,12,"模式:",12,0); //连接状态
Show_Str(30,100,200,12,"发送数据:",12,0); //发送数据
Show_Str(30,115,200,12,"接收数据:",12,0); //接收数据
atk_8266_wificonf_show(30,180,"请设置路由器无线参数为:",(u8*)wifista_ssid,(u8*)wifista_encryption,(u8*)wifista_password);
POINT_COLOR=BLUE;
Show_Str(120+30,80,200,12,(u8*)ATK_ESP8266_WORKMODE_TBL[netpro],12,0); //连接状态
USART2_RX_STA=0;
ELED_Init();
HC164_Init();
while(1)
{
if(flag_lights_test!=flag_lights_test_temp)
{
light_Status_changes_test(flag_lights_test);
}
key=KEY_Scan(0);
if(key==WKUP_PRES) //WK_UP 退出测试
{
res=0;
atk_8266_quit_trans(); //退出透传
atk_8266_send_cmd("AT+CIPMODE=0","OK",20); //关闭透传模式
break;
}
else if(key==KEY0_PRES) //KEY0 发送数据
{
if((netpro==1)&&(flag_netpro==1)) //TCP Client
{
atk_8266_quit_trans();
atk_8266_send_cmd("AT+CIPSEND","OK",20); //开始透传
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
sprintf((char*)p,"GET http://服务器的IP地址/api/test.php?format=json\r\n",ATK_ESP8266_WORKMODE_TBL[netpro],t/10);
u2_printf("%s",p);
timex=100;
flag_netpro=0;
}
}
if(flag_netpro==0)
{
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
sprintf((char*)p,"GET http://服务器的IP地址/api/test.php?format=json\r\n",ATK_ESP8266_WORKMODE_TBL[netpro],t/10);
Show_Str(30+54,100,200,12,p,12,0);
u2_printf("%s",p);
timex=100;
delay_ms(1000);
delay_ms(1000);
}
if(timex)timex--;
if(timex==1)LCD_Fill(30+54,100,239,112,WHITE);
t++;
delay_ms(10);
if(USART2_RX_STA&0X8000) //接收到一次数据了
{
rlen=USART2_RX_STA&0X7FFF; //得到本次接收到的数据长度
USART2_RX_BUF[rlen]=0; //添加结束符
printf("%s",USART2_RX_BUF); //发送到串口
sprintf((char*)p,"收到%d字节,内容如下",rlen);//接收到的字节数
LCD_Fill(30+54,115,239,130,WHITE);
POINT_COLOR=BRED;
Show_Str(30+54,115,156,12,p,12,0); //显示接收到的数据长度
POINT_COLOR=BLUE;
LCD_Fill(30,130,239,319,WHITE);
Show_Str(30,130,180,190,USART2_RX_BUF,12,0);//显示接收到的数据
strcpy(USART2_RX_BUF_TEMP,USART2_RX_BUF);
flag_lights_test_temp=flag_lights_test;//存储上一次的flag_lights_test的值
if(StringFind(USART2_RX_BUF_TEMP, buf1) >= 0) flag_lights_test=0;
if(StringFind(USART2_RX_BUF_TEMP, buf2) >= 0) flag_lights_test=1;
if(StringFind(USART2_RX_BUF_TEMP, buf3) >= 0) flag_lights_test=2;
if(StringFind(USART2_RX_BUF_TEMP, buf4) >= 0) flag_lights_test=3;
USART2_RX_STA=0;
if(constate!='+')t=1000; //状态为还未连接,立即更新连接状态
else t=0; //状态为已经连接了,10秒后再检查
}
if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在.
{
constate=atk_8266_consta_check();//得到连接状态
if(constate=='+')Show_Str(30+30,80,200,12,"连接成功",12,0); //连接状态
else Show_Str(30+30,80,200,12,"连接失败",12,0);
t=0;
}
if((t%20)==0)LED0=!LED0;
atk_8266_at_response(1);
}
myfree(p); //释放内存
return res;
}
/*添加开始*/
//向ATK-ESP8266发送命令
//cmd:发送的命令字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
// 1,发送失败
//例如:atk_8288_send_cmd("AT+RST","OK",20);
// 表示发送指令:AT+RST 到 WIFI 模块,重启模块;期待的应答为:OK;等待时间为200ms。
u8 atk_8266_send_cmd_test(u8 *cmd,u8 *ack,u16 waittime)
{
/*添加开始*/
char *test_1 = "success", \
*test_2 = "failure";
/*添加结束*/
u8 res=0;
USART2_RX_STA=0;
u2_printf("%s\r\n",cmd); //发送命令
if(ack&&waittime) //需要等待应答
{
while(--waittime) //等待倒计时
{
delay_ms(10);
if(USART2_RX_STA&0X8000)//接收到期待的应答结果
{
if(StringFind_test(ack, test_1) == 0)
{
printf("ack:%s\r\n",(u8*)ack);
break;//得到有效数据
}
USART2_RX_STA=0;
}
}
if(waittime==0)res=1;
}
return res;
}
/*************************************************
Function : StringFind
Description: 在字符串中查找指定的字符串
Input : pSrc、pDst,在pSrc查找指定pDst子串
Return : 如果查找到返回0,如果没有查到返回-1
English : find string in string, return the first start location or -1 if can not find
*************************************************/
int StringFind_test(const char *pSrc, const char *pDst)
{
int i, j;
for (i=0; pSrc[i]!='\0'; i++)
{
if(pSrc[i]!=pDst[0])
continue;
j = 0;
while(pDst[j]!='\0' && pSrc[i+j]!='\0')
{
j++;
if(pDst[j]!=pSrc[i+j])
break;
}
if(pDst[j]=='\0')
return 0;
}
return -1;
}
void light_Status_changes_test(int flag)
{
if(flag_lights_test == 0) control_brightness(Lights_off);
if(flag_lights_test == 1) control_brightness(Low_brightness);
if(flag_lights_test == 2) control_brightness(Medium_brightness);
if(flag_lights_test == 3) control_brightness(high_brightness);
}
/*添加结束*/