介绍
物联网(IoT)是由相互连接的计算机,电话,平板电脑和诸如恒温器,车库门开启器,灯泡,门铃摄像头,气象站之类的物理设备组成的网络。 当我们谈论Internet时,通常是指与网页,程序和应用程序通信的计算机,平板电脑,电话和服务器。 物联网通过包含除计算机,电话,平板电脑和服务器以外的设备在Internet上的构建。
物联网设备
我有两个基于ESP8266的WiFi气象站。 这些小设备是物联网的一部分。 气象站由ESP8266微控制器和通过跨接线和面包板连接的温度传感器组成。 基于ESP8266的WiFi气象站将温度测量结果传输到云中的服务器。 这些WiFi气象站是物联网设备。
物联网服务器
与物联网设备交互的服务器(如基于ESP8266的WiFi气象站)称为物联网服务器或IoT服务器。 物联网服务器与物联网设备通信。 在该项目开始时,基于ESP8266的WiFi气象站与ThingSpeak.com物联网服务器进行通信。 WiFi气象站将温度测量结果发送到保存数据的ThingSpeak.com IoT服务器。
物联网服务器需求
IoT服务器需要实现两个主要功能:
- 从两个基于ESP8266的WiFi气象站接收并存储温度测量值
- 将两个基于ESP8266的WiFi气象站测得的温度发布到网页中
项目步骤
使用flask和Python构建IoT服务器是一个多部分的问题。我们可以将问题分解为以下步骤:
- 设置
- 构建在云中运行的基于Flask的服务器(Digital Ocean)
- 组装WiFi气象站的硬件
- 使用flask和Python构造一个Web API,以接受来自Web浏览器的请求并保存数据点
- 将数据库添加到服务器以保存来自WiFi气象站的数据点
- 在基于ESP8266的WiFi气象站上上传代码,以使用服务器的Web API发送温度测量值
设置
在本文中,我们将描述项目中使用的服务器设置和微控制器硬件。
前提
服务器
- Digital Ocean云服务器(从现在开始仅称为服务器)
- 连接到服务器的域名
- PuTTY或可以SSH进入服务器的终端
- 服务器上的非root用户sudo用户
- 以下软件包apt-get安装在服务器上:python3-pip python3-dev python3-setuptools python3-venv build-essential libssl-dev libffi-dev nginx
- 在服务器上安装了flask和uwsgi pip的Python 3.6虚拟环境
- uWSGI和NGINX安装在服务器上的配置上
- Flask应用程序作为系统服务运行
- 附加到域名和NGINX实例的SSL
硬件
以下是用于构建基于ESP8266的WiFi气象站的硬件列表。
请注意,作为该项目的一部分,我使用了两个ESP8266微控制器。 一个ESP8266连接到BMP280温度传感器,另一个ESP8266连接到MCP9808温度传感器。 这两个温度传感器是以前项目中遗留下来的(我本来应该使用两个相同的传感器,但是我所用的是一个BMP280和一个MCP9808)。
一个ESP8266-温度传感器组合用于测量外部温度,另一个ESP8266-温度传感器组合用于测量内部温度。
起点
我构建的flask应用程序是相对基本的,主要包含2个文件:flaskapp.py和index.html。 Digital Ocean服务器上的文件结构如下所示:
~/ └── flaskapp ├── flaskapp.ini ├── flaskapp.py ├── flaskapp.sock ├── flaskappenv ├── templates │ ├── index.html └── wsgi.py |
运行flask应用程序的主要文件是flaskapp.py
flask应用程序用于构建网页的唯一jinja模板位于/ templates目录中,名为index.html:
创建了flask应用程序并配置了域名,nginx,uWSGI,systemd和SSL后,该应用程序将在服务器上启动,并具有以下功能:
生成的网页如下所示:

Web API
我们已经在Digital Ocean上托管了一个工作Flask应用程序。现在,我们需要将Web API添加到flask应用程序的功能中。
什么是web API
Web API是基于Web的应用程序编程接口。这是一种幻想的说法,即服务器根据服务器从Web浏览器接收的URL保存输入或生成输出。
Web API的一个示例是ThingSpeak.com Web API。当网络浏览器(例如chrome)定向到以下URL时:
https://api.thingspeak.com/channels/266256/fields/2/last.txt
响应是存储在ThingSpeak.com上的数据条目,其对应于:
- channel 266256
- field 2
- last entry
- .txt format
如果粘贴到Web浏览器中的URL不同,则ThingSpeak.com的响应也将不同。
https://api.thingspeak.com/channels/9/fields/1/last.json
对上述URL的响应是存储在ThingSpeak.com上的数据条目,其对应于:
- channel 9
- field 1
- last entry
- .json format
大多数Web API允许您从其服务器中提取数据,但是许多Web API也使您能够在其服务器上放置数据。
如果粘贴到Web浏览器中的URL的格式如下,ThingSpeak.com将存储一个新的数据点。
https://api.thingspeak.com/update?api_key=THECLASSAPIKEY&field1=87
ThingSpeak.com收到GET请求时存储的数据点为:
- user with an API Key = THECLASSAPIKEY
- field = 1
- data = 87
我们将使用flask构建的Web API需要完全满足ThingSpeak.com Web API实现的两个基本功能:
- 根据Flask IoT服务器接收的特定URL输出数据点
- 根据Flask IoT服务器接收的特定URL存储数据点
Web API设计
我们将在Flask IoT服务器Web API中模仿ThingSpeak.com Web API的一部分。 为了基于服务器接收的URL存储数据点,我们需要指定URL的结构。 我们基于ESP8266的WiFi气象站需要知道URL格式,才能将数据点发布到Flask IoT服务器上。
数据将基于服务器从基于ESP8266的WiFi气象站接收的URL存储在我们的Flask IoT服务器上。
每个基于ESP8266的WiFi气象站都有几个独特的方面:
- 用户:每个WiFi气象站都有一个用户。在这种情况下,用户就是我。
- mac地址:mac地址是分配给每个硬件的唯一地址。每个基于ESP8266的WiFi气象站都有一个不同的mac地址。
- 字段:基于ESP8266的WiFi气象站具有输出温度和湿度的功能。 现在,我们将要处理温度,但是最好为同一设备的多个数据流(例如温度和湿度)提供一个额外的字段。
- 数据:每个基于ESP8266的WiFi气象站的温度测量值。
如果我们将这4个标识符作为Web API URL的一部分,我们的IoT服务器将提供WiFi气象站所需的功能。
我们的Web API URL的一般形式如下:
https://mydomain.com/update/API_key=ASCIISTR/mac=6c:rf:7f:2b:0e:g8/field=1/data=72.3
在上面的网址中,我们提供了:
- update(告诉IoT服务器保存数据点,而不仅仅是提供网页)
- API_key=ASCIISTR(识别用户)
- mac=6c:rf:7f:2b:0e:g8(识别基于ESP8255的WiFi气象站)
- field=1(指定数据是温度,而不是湿度)
- data=72.3(指定温度为72.3度)
现在,当出现类似于我们上面指定的URL时,我们需要使Flask IoT服务器保存数据点。
构造一个Flask Web API
由于flask可以选择在路由中包含变量,因此在flask中构建Web API非常容易。通用语法如下:
@app.route("/update/key=<route_var>", methods=['GET']) def update(route_var): # code to run return render_template("index.html") |
在上面的代码中,路由"/update/key=<route_var>"中包含变量<route_var>。 大于/小于符号<>告诉flask路径中有一个变量。 在第二行中,变量route_var(来自@ app.route()行)作为参数传递给update()函数。 最后,update()函数返回一个名为index.html的模板。
建立新路由
在Flask Web API的@ pp.route()URL中分配了四个变量:
parameter | purpose | route variable |
API key | 每个用户的唯一标识符 | <api_key> |
mac address | 每个ESP8266设备的唯一标识符 | <mac> |
field | 表示温度或湿度 | <field> |
data | 数据点保存在服务器上 | <data> |
我们的Web API的完整@ pp.route()URL如下所示:
"/update/API_key=<api_key>/mac=<mac>/field=<int:field>/data=<data>" |
现在,我们可以为此URL构建一个新的@ app.route()函数对。请注意,我们返回了一个名为update.html的新模板,并将数据点数据传递给该模板。
@app.route("/update/API_key=<api_key>/mac=<mac>/field=<int:field>/data=<data>", methods=['GET']) def update(api_key, mac, field, data): return render_template("update.html", data=data) |
建立新模板
由于我们正在调用新模板,因此我们需要构造一个新模板。模板update.html可以具有与index.html相同的形式。让我们创建一个新文件,并将其复制到以下html代码和jinja字段中。
$ cd ~ $ cd flaskapp $ cd templates $ nano update.html |
新的update.html模板的代码如下:
测试Web API
现在,在我们的服务器上,我们可以重新启动flask应用程序,并查看我们的Web API是否有效。
如果浏览到服务器的网址,我们仍然应该看到相同的索引模板。
但是现在,如果我们转到网址:
https://yourdomain.com//update/API_key=APGLMD/mac=a3:45:b5:c9/field=1/data=98.6
您会看到显示的温度为98.6时更新的温度。
验证和时间戳记
现在我们有了Web API,我们可以使用Web浏览器发出GET请求,并参阅Flask返回的网页中的输出。添加到由我们的Web API指定的URL中的任何字符串都可以通过。 我们不希望任何WiFi气象站都上传数据。 我们需要的是一些数据验证。
什么是数据验证
Web API是基于Web的应用程序编程接口。这是说服务器保存输入或根据某人在Web浏览器中键入的URL生成输出的一种奇特的方式。我们的Flask IoT Web服务器接受的示例URL是:
https://mydomain.com/update/API_key=ASCIISTR/mac=6c:rf:7f:2b:0e:g8/field=1/data=72.3
在上面的网址中,我们提供了:
- update(告诉IoT服务器保存数据点,而不仅仅是提供网页)
- API_key = ASCIISTR(识别用户)
- mac = 6c:rf:7f:2b:0e:g8(确定基于ESP8255的WiFi气象站)
- field = 1(指定这是温度数据点,而不是湿度数据点)
- data = 72.3(指定温度为72.3度)
现在,可以将API_key =和mac =之后的任何字符串插入URL。 这允许具有Internet连接的任何人将数据上传到服务器,并可能将恶意代码上传到服务器。 由于可以将任意字符串作为API_key或mac插入,因此我们的flask IoT服务器将读取该任意字符串。
为了在我们的服务器中增加一点安全性,我们将使用一种数据验证形式。这意味着我们的Flask IoT服务器将仅接受某些API_key =和mac =字符串作为Web API URL的一部分。
验证传入的URL
进入我们的物联网服务器的数据仅来自基于ESP8266的WiFi气象站的两个特定数据。每个基于ESP8266的WiFi气象站都有几个独特的方面。
- 用户:每个WiFi气象站都有一个用户。在这种情况下,用户就是我。我可以设置一个只有我知道的唯一API密钥。
- Mac地址:Mac地址是分配给每个硬件的唯一地址。每个基于ESP8266的WiFi气象站都有一个不同的mac地址。
- 字段:基于ESP8266的WiFi气象站具有输出温度和湿度的功能。 现在,我们将要处理温度,但是很高兴有一个额外的字段可用于来自同一设备的多个数据输出。 一台设备可以发送的字段数将受到限制。 我们将字段限制为1-9
- 数据:每个基于ESP8266的WiFi气象站的温度数据。这是一个浮点数,但可以受长度限制。
如果我们将这4个标识符作为URL的一部分,我们的IoT服务器将提供WiFi气象站所需的功能。
以下是有效的Web API网址的一般形式:
https://mydomain.com/update/API_key=GTW89NF3/mac=6c:rf:7f:2b:0e:g8/field=1/data=72.3
在上面的网址中,我们提供了:
- API_key = GTW89NF3
- mac = mac=6c:rf:7f:2b:0e:g8
- field = 1
- data = 72.3
现在,我们需要对Flask IoT服务器进行编程,使其仅接受URL,例如上述URL,并具有一组唯一的API_key,mac地址以及URL的字段和数据部分的限制。
在服务器上,创建一个名为config.py的新文件。 该文件将包含可接受的API_key和mac地址。 将这些类型的私钥保存在单独的文件中并且不受版本控制是一个好习惯。 如果使用git,请确保将config.py添加到.gitignore文件。
#config.py API_key = GTW89NF3 mac = mac=6c:rf:7f:2b:0e:g8 |
现在,在服务器上,我们将修改flaskapp.py文件的imports部分,以从config.py中导入API密钥和mac地址字符串。我们可以从单独的.py文件中导入变量以及函数和类。
现在,我们将修改flaskapp.py的@ app.route部分:
需要在模板目录中创建一个显示错误消息的新模板403.html。
添加时间戳
我们的Web API接受数据点,但是很高兴看到温度测量的时间戳。通过将时间戳记连接到每个数据点,我们可以显示带有温度和测量时间的网页。
用Python处理时间可能会有些混乱。 这似乎是一个简单的概念,但时间和日期实际上却非常复杂。 哪个时区? 保存秒或分钟? 时间将是哪种格式? 在构建用于记录时间戳的代码之前,我们需要回答这些问题。 我希望时间和日期显示在我们的Flask IoT服务器生成的网站上的方式是:
05:18:48 PM Aug 17, 2018
其中05:18:48是太平洋时区的小时:分钟:秒,AM或PM(12小时格式),日期2018年8月17日采用月日,年格式。
showrecent.html模板如下:
<!-- /templates/showrecent.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>read temp</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<!-- Optional theme --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css>
<!-- Latest compiled and minified JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5">
</head>
<body>
<div class="container-fluid"> <div class="jumbotron"> <hr class="my-4"> <h1 class="display-4"> {{ data }}</h1> <p class="lead">Most Recent Data Point</p> <hr class="my-4"> <h1 class="display-4"> {{ time_stamp }}</h1> <p class="lead">Most Recent Time Time Zone: PST</p> <hr class="my-4> <p class="lead"> <a class="btn btn-primary btn-lg" href="/showrecent.html" role="button">REFRESH</a> </p> </div>
</div>
</body> </html> |
重新启动flask应用程序并查看更改
现在,我们可以重新启动flask应用程序,并查看修改后的Web API是否有效。 我们想测试一下,当输入无效的URL时是否呈现403.html模板。 我们还希望看到使用有效URL时showrecent.html呈现的时间戳。
$ sudo systemctl start flaskapp $ sudo systemctl status flaskapp # [ctrl-c] to exit. |
现在,如果我们转到网址:
https://yourdomain.com//update/API_key=APGLMD/mac=a3:45:b5:c9/field=1/data=98.6
您应该看到呈现的403错误模板。因为上面的URL是无效的URL。它不包含有效的API密钥或mac地址。
但是,如果您访问网址:
https://mydomain.com/update/API_key=GTW89NF3/mac=6c:rf:7f:2b:0e:g8/field=1/data=22.0
您应该看到显示了showrecent.html模板。该URL包含有效的API和mac地址。
添加数据库
将代码上传到基于ESP8266的WiFi气象站
阅读完整文档