<audio id="audio" title="22 | 掌控数据：家里的数据可以怎么利用？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6c/25/6c052c71c91c469360456c3cd9d68025.mp3"></audio>

你好，我是郭朝斌。

到目前为止，我们已经完成智能电灯、光照传感器、智能音箱和自动浇花器的实战训练，在这个过程中，我们主要关注的是设备功能和远程控制的实现。

其实，物联网设备会生成大量的数据。如果我们能把这些数据存储到物联网系统的数据库中，并且好好应用这些数据，比如提供查询和分析功能，就能够产出更大的价值。

这一讲，我就基于自动浇花器来讲一讲数据的应用方法，主要包括以下两种：

1. 基于腾讯云物联网平台提供的**数据流**功能，介绍一个**设备消息推送应用**的配置方法。
1. 基于腾讯云的**HTTP方式的数据同步**功能，开发一个**Web数据应用系统**。因为需要购买云服务器，所以你可以酌情选择是否实际部署。

## 方法一：基于数据流的设备消息推送应用

腾讯云物联网平台已经为我们提供了一种简便的数据应用方法。我们可以使用它的可视化编辑界面来完成数据流的创建工作。

你可以登录腾讯云物联网平台的[控制台](https://console.cloud.tencent.com/iotexplorer)，然后进入我们之前创建的“智能家居”项目，点击左边菜单栏中的“数据开发”。

<img src="https://uploader.shimo.im/f/gQZPGbwpSJRbg0Xa.png!thumbnail" alt="">

然后，你需要新建一个数据流，名称可以是“自动浇花器”。点击数据流列表中的“自动浇花器”项目，你就可以进入可视化的编辑界面。

<img src="https://uploader.shimo.im/f/YHiZxGoEO2MmIeAX.png!thumbnail" alt="">

在可视化编辑界面，我们可以看到，一个数据流包括“输入”“处理”和“输出”三个部分。

1. 输入，包括设备数据、设备事件和设备状态三种，其中设备数据和设备事件与物模型中的定义是一致的。设备状态是设备的上线、下线的状态变化。
1. 处理，可以编写基本的判断逻辑来过滤输入数据。
1. 输出，可以作为消息将数据推送到App或者小程序中。你可以对消息的内容模板进行定义。

自动浇花器设备会上报环境的温度、湿度信息，那么我们可以定义一个温湿度不适宜的消息提醒。

你可以拖拽“设备数据”到编辑区域，然后点击这个模块，在右边的选项中定义设备数据，产品选择“自动浇花器”，属性选择“环境温度”和“环境湿度”。

<img src="https://uploader.shimo.im/f/cMStN80VDhRiCo8p.png!thumbnail" alt="">

接着，你可以添加“数据过滤”模块，并且将这个模块与“设备数据”模块相连，然后点击这个模块，在右边编辑过滤条件。

<img src="https://uploader.shimo.im/f/ZWokFm4h3v20SEXW.png!thumbnail" alt="">

<img src="https://uploader.shimo.im/f/XINTSH99JV0v0WPX.png!thumbnail" alt="">

完成数据过滤的定义后，你需要继续添加“公众号推送”模块，并且编辑消息推送的模板。具体的消息模板定义，你可以参考下面内容：

```
环境温度、湿度不适宜：
当前温度是$env_temp 
当前湿度是$env_hum 
当前时间是$timeStamp 

```

<img src="https://uploader.shimo.im/f/fdGE9qFP81xoVLtG.png!thumbnail" alt="">

最后，点击页面上方的“保存”和“启用”，你就完成了数据流的定义。

如果你的自动浇花器设备是在线状态，那么当环境的温度或者湿度过高、过低时，你就会在腾讯连连小程序的消息列表中收到“告警”消息。

<img src="https://uploader.shimo.im/f/uKGvPeQ6L5TvqA0R.jpg!thumbnail" alt="">

## 方法二：基于HTTP数据同步的Web数据应用

物联网平台除了提供数据流的方式，还可以基于HTTP协议把数据推送到你指定的网址，比如你开发的Web服务器的网址。所以你也可以使用这种方式，更加灵活地利用物联网设备的数据。

### 数据同步体验

我们借助在线的Webhook服务，测试一下数据同步功能。

首先，打开[webhook.site](https://webhook.site/)网站，并且记录页面中显示的专属URL地址。

然后，你需要登录腾讯云物联网平台，进入我们之前创建的“智能家居”项目，再点击左边菜单栏中的“数据同步”，选择“HTTPS”标签。

<img src="https://uploader.shimo.im/f/WJGxUfUpJLgXvUeO.png!thumbnail" alt="">

接着，点击“自动浇花器”对应的“设置”链接，在设置窗口，将webhook网站获取到的URL地址，粘贴在输入框。

<img src="https://uploader.shimo.im/f/rybXFS7FwK2oVD85.png!thumbnail" alt="">

完成设置后，打开“生效状态”。

<img src="https://uploader.shimo.im/f/SUKwtfVaGUWeaoz4.png!thumbnail" alt="">

在保证自动浇花器设备正常运行的情况下，你就可以在webhook网站的页面中看到设备上报的数据了。

<img src="https://uploader.shimo.im/f/rtdqJo4NFSnghZq1.png!thumbnail" alt="">

下面我来介绍一下用Python语言来开发Web服务器的方法。

因为购买云服务器需要费用，所以这个实战任务是选学内容，你可以在自己的电脑上实践一下Web服务器的开发过程，然后酌情考虑要不要部署到云服务器上。

### 准备Django开发环境

Django是一个流行的基于Python语言的Web开发框架。它提供了强大的功能，同时也简单易用。接下来，我们就基于Django来实现一个Web应用程序。

首先是在电脑上配置Django的开发环境，在终端上运行下面的命令安装Django，准备好需要用到的工具。

```
$ pip3 install django

```

安装完成后，你可以在终端上切换到一个代码开发目录，比如：

```
$ cd ~/study/iot/geektime

```

然后，直接使用Django提供的脚手架工具django-admin来创建一个项目：

```
$ django-admin startproject watering_web

```

这时，命令会创建一个名称为watering_web的目录。目录中包含manage.py、settings.py、urls.py、wsgi.py和asgi.py等几个项目“骨架”文件，其中：

- manage.py是项目管理脚本，比如创建子应用、运行开发服务器等都可以通过它实现。
- settings.py是项目的整体配置文件。比如子应用的配置、数据库的配置等。
- urls.py是项目的全局路由声明文件。
- wsgi.py是WSGI（Python Web Server Gateway Interface的缩写）服务接口文件，是整个Django应用的调用入口。
- asgi.py是ASGI（Asynchronous Server Gateway Interface的缩写）服务接口文件。ASGI是WSGI的替代者，它增加了异步应用的能力。除了支持WSGI协议，同时对Websocket和HTTP2.0这些长连接方式的协议提供了支持。

现在，我们进入watering_web目录，运行下面的命令，就可以在电脑上启动这个项目的开发服务器了。

```
$ python manage.py runserver

```

<img src="https://uploader.shimo.im/f/unz2fDt0y6oOS9qB.png!thumbnail" alt="">

这非常有利于你的开发调试工作。比如，你可以在浏览器输入 [http://127.0.0.1:8000/](http://127.0.0.1:8000/) 随时访问Web应用的开发效果。

<img src="https://uploader.shimo.im/f/mb5wW8VRe8ddoe78.png!thumbnail" alt="">

当你在浏览器上看到这个界面时，就说明Django应用的开发环境已经准备好了。下面，我们就来开发Web应用。

### Django应用开发

#### 创建子应用watering

首先，我们通过运行下面的命令，创建watering_web项目的子应用watering：

```
$ python manage.py startapp watering

```

这时，你应该可以看到项目目录下，新增加了watering的目录。目录中包含了watering子应用的基本代码模块。

- admin.py是后台管理应用的配置文件，我们可以在其中增加数据库模型对象，让Django应用管理员能通过后台管理页面进行编辑。你在浏览器输入[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)可以访问到后台管理应用。
- apps.py是子应用的配置文件。
- migrations是数据库迁移的文件目录，它下面会保存每次数据库迁移的中间文件。
- models.py是定义子应用的数据库数据模型文件。
- tests.py是子应用的单元测试代码文件。
- views.py是子应用的视图代码文件。

#### 理解Django的视图调用

在Django里面，网页或者说HTTP的响应都是视图生成的，每个视图对应views.py中的一个函数。Django会根据HTTP请求的URL域名地址来选择相应的视图，而URL和视图函数的映射关系是在urls.py文件中定义的。

比如，你可以打开watering目录下的views.py文件，添加下面的函数：

```
from django.http import HttpResponse

def demo(request):
    return HttpResponse('Hello World!')

```

然后，在watering目录下，新建一个urls.py文件，文件内容如下：

```
from django.urls import path

from . import views

urlpatterns = [
    path('demo', views.demo, name='demo'),
]

```

最后，在watering_web目录下的项目全局路由文件中，将urlpatterns的内容替换为下面的内容：

```
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('watering.urls')),
]

```

现在，你在浏览器输入地址[http://127.0.0.1:8000/demo](http://127.0.0.1:8000/demo)，就可以看看我们刚刚定义的demo视图的内容。

<img src="https://uploader.shimo.im/f/KNyQrpXeaWABnhn2.png!thumbnail" alt="">

#### 应用代码开发

接下来，我们正式开始子应用的开发。

首先，我们需要定义数据库的数据模型。Django框架实现了ORM（Object-Relational Mapping，对象关系映射器）技术。你可以直接在Python代码中定义数据库的表结构。

基于这个定义，Django的migration工具能自动在数据库中创建相应的数据库表。在后面的部署阶段，我会讲解到具体的migration命令。

我把models.py的代码贴在文稿中，供你参考：

```
from django.db import models

# Create your models here.
class Watering(models.Model):
    seq_no = models.IntegerField(blank=False, null=False)
    device_name = models.CharField(max_length=64, blank=False)
    product_id = models.CharField(max_length=64, blank=False)
    power_switch = models.IntegerField(default=0)
    humidity = models.IntegerField(default=0)
    env_temp = models.FloatField(default=0.0)
    env_hum = models.IntegerField(default=0)
    env_illum = models.IntegerField(default=0)
    timestamp = models.DateTimeField(auto_now=False)

    def __str__(self):
        return self.device_name + '_' + str(self.seq_no)

```

有了数据模型之后，你就可以在views.py文件中开发视图，接收腾讯云物联网平台推送的HTTP请求，并且将数据存储到数据库中。

文稿中是我的示例代码，供你参考：

```
from django.shortcuts import render

# Create your views here.
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
import json
from datetime import datetime
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.iotexplorer.v20190423 import iotexplorer_client, models

SECRET_ID = &quot;你的Secret ID&quot;
SECRET_KEY = &quot;你的Secret Key&quot;
PRODUCT_ID = &quot;你的ProductID&quot;

from .models import Watering

def build_response(resp, msg):
    dict = {
        &quot;resp&quot;: resp,
        &quot;msg&quot;: msg,
    }
    return HttpResponse(json.dumps(dict), content_type='application/json')

def demo(request):
    return HttpResponse('Hello World!')

@csrf_exempt
def data_sync(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')
    
    if 'seq' in json_data:
        _seq = json_data['seq']
    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    if 'productid' in json_data:
        _product_id = json_data['productid']
    if 'timestamp' in json_data:
        _timestamp = json_data['timestamp']
    if 'payload' in json_data:
        _payload = json_data['payload']
        if 'method' in _payload and 'report' == _payload['method']:
            _params = _payload['params']
            _env_temp = _params['env_temp']
            _env_hum = _params['env_hum']
            _env_illum = _params['env_illum']
            _humidity = _params['humidity']
            _power_status = _params['power_switch']
        
            try:
                Watering.objects.create(
                    seq_no=_seq, device_name=_device_name, product_id=_product_id,
                    power_switch=_power_status, humidity=_humidity,
                    env_temp = _env_temp, env_hum = _env_hum, env_illum = _env_illum,
                    timestamp=datetime.fromtimestamp(_timestamp) )
                
            except:
                return HttpResponse('Insert Failed!')

    return HttpResponse('OK')

@csrf_exempt
def latest_data(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')
    
    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    else:
        _device_name = None
    
    if _device_name is not None:
        try:
            data = Watering.objects.filter(device_name=_device_name).latest('timestamp')
            data_dict = model_to_dict(data, fields=['seq_no','device_name','product_id','power_switch','humidity','env_temp','env_hum','env_illum'])
            data_dict['timestamp'] = str(data.timestamp)
            dict = {
                &quot;resp&quot;: 0,
                &quot;msg&quot;: &quot;OK&quot;,
                &quot;data&quot;: data_dict
            }
            return HttpResponse(json.dumps(dict), content_type='application/json')
        except Exception as e:
            return build_response(1, str(e))
    else:
        return build_response(1, 'Parameter invalid.')

```

为了能够控制自动浇花器的水泵打开和关闭，你也可以增加发送控制命令的视图。同样，你可以参考下面的代码：

```
@csrf_exempt
def control_device(request):
    if request.method == 'POST':
        json_data = json.loads(request.body.decode())
    else:
        return HttpResponseBadRequest('Bad Request')
    
    if 'devicename' in json_data:
        _device_name = json_data['devicename']
    else:
        _device_name = None
    if 'status' in json_data:
        _status = json_data['status']
    else:
        _status = None

    if _device_name is not None and _status is not None:
        try: 
            cred = credential.Credential(SECRET_ID, SECRET_KEY) 
            httpProfile = HttpProfile()
            httpProfile.endpoint = &quot;iotexplorer.tencentcloudapi.com&quot;

            clientProfile = ClientProfile()
            clientProfile.httpProfile = httpProfile
            client = iotexplorer_client.IotexplorerClient(cred, &quot;ap-guangzhou&quot;, clientProfile) 

            req = models.ControlDeviceDataRequest()
            data = {
                &quot;power_switch&quot;: _status
            }
            data_str = json.dumps(data)

            params = {
                &quot;DeviceName&quot;: _device_name,
                &quot;ProductId&quot;: PRODUCT_ID,
                &quot;Data&quot;: data_str
            }
            req.from_json_string(json.dumps(params))

            resp = client.ControlDeviceData(req)

            return build_response(0, resp.to_json_string())

        except TencentCloudSDKException as err: 
            return build_response(1, str(err))

    else:
        return build_response(1, 'Parameter invalid.')

```

完成视图的开发后，你需要更新urls.py文件中的urlpatterns，为新增视图添加映射关系。

```
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('geektime/', include('watering.urls')),
]

```

同时，在watering子应用的目录增加一个urls.py文件，内容如下：

```
from django.urls import path

from . import views

urlpatterns = [
    path('demo', views.demo, name='demo'),
    path('data', views.data_sync, name='data_sync'),
    path('control', views.control_device, name='control_device'),
    path('fetch', views.latest_data, name='latest_data'),
]

```

然后，我们来开发一个简单的网页，实现设备信息的显示和控制。为了便于理解，我这里只使用最基础的方法。当熟悉基本原理之后，你可以尝试使用VUE、React等前端应用框架来开发功能更丰富的Web前端应用。

我把代码贴在文稿中，供你参考：

```
&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot;&gt;
&lt;meta content=&quot;webkit&quot; name=&quot;renderer&quot;&gt;
&lt;meta content=&quot;width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0&quot; name=&quot;viewport&quot;&gt;
&lt;meta content=&quot;IE=Edge,chrome=1&quot; http-equiv=&quot;X-UA-Compatible&quot;&gt;
&lt;style type=&quot;text/css&quot;&gt;
    th {
    text-align: left;
    }
&lt;/style&gt;

&lt;title&gt;Plant Watering&lt;/title&gt;
&lt;script src=&quot;jquery-3.1.1.min.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;h1&gt;Plant Watering&lt;/h1&gt;

    &lt;table&gt;
        &lt;tr&gt;
            &lt;th&gt;Device Name: &lt;/th&gt;&lt;th id='devicename'&gt;&lt;/th&gt;
        &lt;/tr&gt;

        &lt;tr&gt;
            &lt;th&gt;Date: &lt;/th&gt;&lt;th id='datetime'&gt;&lt;/th&gt;
        &lt;/tr&gt;

        &lt;tr&gt;
            &lt;th&gt;Soil Moisture (&amp;#37;): &lt;/th&gt;&lt;th id='moisture'&gt;&lt;/th&gt;
        &lt;/tr&gt;

        &lt;tr&gt;
            &lt;th&gt;Environment Temperature (℃): &lt;/th&gt;&lt;th id='envtemp'&gt;&lt;/th&gt;
        &lt;/tr&gt;

        &lt;tr&gt;
            &lt;th&gt;Environment Humidity (&amp;#37;): &lt;/th&gt;&lt;th id='envhum'&gt;&lt;/th&gt;
        &lt;/tr&gt;

        &lt;tr&gt;
            &lt;th&gt;Environment Illumination (Lux): &lt;/th&gt;&lt;th id='envillum'&gt;&lt;/th&gt;
        &lt;/tr&gt;

    &lt;/table&gt;

    &lt;button id='on'&gt;Open&lt;/button&gt;
    &lt;button id='off'&gt;Close&lt;/button&gt;

    &lt;script type='text/javascript'&gt;
        var timer = false
        var interval = 5*1000   //5 seconds
        var device_name = 'Watering_1'

        $(&quot;#on&quot;).on('click',function(){
            control(1)
        })
        $(&quot;#off&quot;).on('click',function(){
            control(0)
        })

        function control(state){

            var payload = {
                &quot;devicename&quot;:device_name,
                &quot;status&quot;:state
            }

            $.ajax({
                url : &quot;http://159.75.214.14/geektime/control&quot;,
                contentType: &quot;application/json; charset=utf-8&quot;,
                method : 'POST',
                dataType: &quot;json&quot;,
                data: JSON.stringify(payload),
                
                success:function (obj) {

                    if(obj.resp === 0){
                        console.log(&quot;pump on&quot;);
                    }
                    else{
                        console.log(obj.msg);
                    }
                },
                error:function(e){
                    console.log(&quot;control device post failed.&quot;);
                }
            })
        }

        function update_data(){
            var payload = {
                &quot;devicename&quot;:device_name,
            }

            $.ajax({
                url : &quot;http://159.75.214.14/geektime/fetch&quot;,
                contentType: &quot;application/json; charset=utf-8&quot;,
                method : 'POST',
                dataType: &quot;json&quot;,
                data: JSON.stringify(payload),
                
                success:function (obj) {

                    if(obj.resp === 0){
                        console.log(&quot;fetch success&quot;);
                        $(&quot;#devicename&quot;).text(device_name)
                        $(&quot;#datetime&quot;).text(obj.data.timestamp)
                        $(&quot;#moisture&quot;).text(obj.data.humidity)
                        $(&quot;#envtemp&quot;).text(obj.data.env_temp)
                        $(&quot;#envhum&quot;).text(obj.data.env_hum)
                        $(&quot;#envillum&quot;).text(obj.data.env_illum)
                    }
                    else{
                        console.log(obj.msg);
                    }
                },
                error:function(e){
                    console.log(&quot;fetch latest data failed.&quot;);
                }
            })
        }

        window.onload = function(){
            update_data()   //Fetch data to get devicename first.
            timer = setInterval(function(){
                update_data()
            }, interval)
        }
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

```

对于这些前端代码文件，你可以在watering_web项目的根目录下创建一个web目录，然后把它们放入这个目录下。

关于Django项目的settings.py文件，你需要在ALLOWED_HOSTS中增加自己服务器的IP地址。

```
ALLOWED_HOSTS = [
    '159.75.214.14',    #替换自己的云服务器IP
]

```

同时，在数据库的配置中，修改为MySQL的配置内容：

```
DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': BASE_DIR / 'db.sqlite3',
    # }
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'geektime',
        'USER': 'root',
        'PASSWORD': 'geektime',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}

```

### Django应用部署

完成Django应用的开发，我们就可以把它部署到云服务器中。

首先，你需要先登录到[腾讯云控制台](https://console.cloud.tencent.com/)，从“云产品”中点击选择“云服务器”，进入云服务的配置页面。

<img src="https://uploader.shimo.im/f/Ljni1rDHy0cPE46B.png!thumbnail" alt="">

在云服务器页面，点击“新建”，购买一台服务器。

<img src="https://uploader.shimo.im/f/bh1F82gO1yMlG9tW.png!thumbnail" alt="">

在新建的页面，你可以根据自己的需求选择服务器的硬件配置。如果只是用来练习，选择最低的配置就行了。

服务器的操作系统选择“Ubuntu Server 16.04.1”，其他选项保持默认值。

<img src="https://uploader.shimo.im/f/nDMcg5fmle2sSMQQ.png!thumbnail" alt="">

<img src="https://uploader.shimo.im/f/n5WjGhdUjk3KQYYS.png!thumbnail" alt="">

点击“立即购买”，完成支付后，我们重新进入云服务器控制台，就可以看到我们的服务器新实例了。

<img src="https://uploader.shimo.im/f/mmy44RHTSD6zNId2.png!thumbnail" alt="">

准备好云服务器后，我再介绍一下Django应用在Ubuntu服务器上的具体部署操作。

以下的介绍都是针对单台服务器部署展开的。如果多台机子部署，你需要做相应的调整，比如使用腾讯云的CDB（云数据库服务），你就需要对Django应用的设置文件（settings.py）中数据库部分作修改。

#### Ubuntu准备

首先，通过SSH登录到Ubuntu服务器，像连接树莓派一样，你仍然可以使用Putty或者SecureCRT这样的终端软件。

服务器的IP地址可以从腾讯云的云服务器控制台获取。关于用户名和密码，“腾讯云助手”公众号会在购买云服务器时发送消息通知。如果没有收到，你可以在控制台重置密码。

登录后，首先需要更新apt，你可以运行下面的命令：

```
$ sudo apt-get update

```

#### 代码上传

接着，你需要将Django应用的代码上传到服务器某个目录下，比如 /home/ubuntu/iot/ 这个目录下。这涉及到下面介绍的Nginx和uWSGI的配置文件中的路径，因此，**如果代码的路径不是这个，你需要相应地修改这些配置中的路径。**

上传的工具，你还是可以使用第19讲中提到的FileZilla等软件。

#### 数据库

我们使用的MySQL数据库，可以通过以下命令来安装：

```
$ sudo apt-get install mysql-server mysql-client libmysqlclient-dev

```

说明一下，如果没有安装libmysqlclient-dev的话，接下来安装mysql-python的步骤可能会报错。

安装过程中，终端也提示你输入MySQL数据库的密码，你可以像我一样，输入“geektime”。

<img src="https://uploader.shimo.im/f/82e0vPFxvGbqlsp6.png!thumbnail" alt="">

安装完成后，你可以通过下面的命令，连接上数据库。

```
$ mysql -p -u root

```

然后，在数据库的交互命令行中，你需要输入下面的命令，创建 geektime 数据库。

```
create database geektime; 
exit;

```

#### Python环境

接下来，我们需要配置Python语言环境，因为应用程序和Django应用框架都是基于Python的。

首先，我们修改一下系统的默认python，将它修改为python3版本。运行下面的命令：

```
$ vim ~/.bashrc

```

在文件中，增加下面的内容：

```
alias python='/usr/bin/python3'

```

添加完成后，退出vim。在终端输入下面的命令，使其生效：

```
$ source ~/.bashrc

```

接着，我们需要安装 pip ，Python包管理器（Python package manager）。你可以运行下面的命令：

```
$ sudo apt-get install python3-pip
$ sudo pip3 install --upgrade pip

```

接着，你还需要安装 python-dev ，一些软件包的安装需要依赖它。命令如下：

```
$ sudo apt-get install python3-dev 

```

后面，你需要进入Django应用目录（我们这里是 /home/ubuntu/iot/watering_web），然后运行下面的命令，安装所有依赖的软件包。：

```
pip3 install -r requirements.txt 

```

如果出现 locale.Error: unsupported locale setting 的错误，请在命令行输入下面的命令：

```
export LC_ALL=&quot;en_US.UTF-8&quot;

```

requirements.txt文件的内容如下：

```
tencentcloud-sdk-python==3.0.313
mysqlclient
Django

```

现在，环境已经准备就绪，下面我来讲解一下数据库和uWSGI、Nginx的配置。

#### 数据库Migrate

在Django中，数据库的创建已经处理得非常简单，框架本身做了很多的工作。

首先，在manage.py文件所在的目录，运行下面的命令：

```
$ python manage.py makemigrations 

```

接着，只需要运行下面的命令，就可以完成所有的数据库表创建工作。

```
$ python manage.py migrate

```

#### 确认测试

现在，你可以执行下面的命令，启动Django应用。然后，你可以通过浏览器确认Django 应用是否可以正常运行。

```
$ python manage.py runserver 0.0.0.0:8080

```

下面，我们就可以安装uWSGI和Nginx了。

#### uWSGI安装

uWSGI相当于是Django应用和Nginx之间的桥梁，它使用标准的WSGI接口与应用通信。你需要运行命令，安装uWSGI：

```
$ sudo apt-get install -y uwsgi
$ sudo apt-get install uwsgi-plugin-python3
$ pip3 install uwsgi

```

然后，在 /home/ubuntu/iot/config 目录下为uWSGI增加配置文件，文件内容如下:

```
[uwsgi]
socket = 127.0.0.1:3031 
chdir = /home/ubuntu/iot/watering_web
wsgi-file = watering_web/wsgi.py 
processes = 4
plugins = python3
threads = 2
stats = 127.0.0.1:9191

```

接着，运行下面的命令，查看uWSGI是否可以正常运行。

```
$ sudo uwsgi --ini ./config/uwsgi.ini 

```

之后，我们还需要配置基于systemd的uWSGI的自启动流程。这需要创建systemd的unit文件。我们还是在 /home/ubuntu/config 目录下增加 uwsgi.service文件，内容如下：

```
[Unit]
Description=uWSGI
After=syslog.target  

[Service]
User=ubuntu
ExecStart=/usr/bin/uwsgi --ini /home/ubuntu/iot/config/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

```

这时，你还不需要添加这个service，在后面我们会通过命令添加、执行这个配置文件。

#### 安装Nginx

首先，你需要执行下面的命令，安装Nginx软件。

```
$ sudo apt-get install nginx 

```

然后，我们需要配置它。我们通过新添加文件，来配置我们的服务，在 /etc/nginx/conf.d/ 目录下，增加 iot.conf 文件，文件的内容如下:

```
server {
    listen 80;
    #listen [::]:80 default_server; 
    server_name 159.75.214.14; #替换为自己服务器IP地址 

    location / {
        root /home/ubuntu/iot/watering_web/web/;
        index index.html; 
    }

    location ^~ /geektime/ {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:3031; 
    }

    error_page 404 /404.html; 
    location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html; 
    location = /50x.html {
    }
}

```

并且，将/etc/nginx/nginx.conf文件中的“user www-data;” 修改为“user ubuntu;”。

现在，你需要执行下面的命令检查Nginx配置文件的语法：

```
$ sudo nginx -t

```

如果没有错误，就可以重启Nginx，来加载新的配置文件：

```
$ sudo service nginx restart 

```

然后，把uWSGI的service配置文件拷贝到systemd配置目录下：

```
$ sudo cp uwsgi.service /etc/systemd/system/ 

```

现在，你可以执行下面的命令，启动uWSGI服务：

```
$ sudo systemctl start uwsgi

```

如果一切正常，我们就可以把uWSGI添加到开机自启动中：

```
$ sudo systemctl enable uwsgi

```

到这里，服务就部署完毕了。

### 云平台数据同步URL更新

云服务器上的Web应用运行正常后，你可以对腾讯云物联网平台上的HTTP数据同步进行更新。修改配置中的URL地址为自己云服务器的视图地址。

<img src="https://uploader.shimo.im/f/SDBx7kqGp5LVzkdt.png!thumbnail" alt="">

更新完成后，你就可以在浏览器上访问自己的服务器地址，查看自动浇花器的实时数据，并且控制它进行浇水。

<img src="https://uploader.shimo.im/f/lQQUwhToOtUKDL9r.png!thumbnail" alt="">

## 小结

总结一下，在这一讲中，我围绕自动浇花器讲解了智能家居系统中设备数据的应用系统开发方法。主要的内容有：

1. 腾讯云物联网平台为我们提供了简单易用的数据开发方法。基于可视化界面，我们编辑“输入”，“处理”和“输出”就可以完成一个数据流的创建，实现数据的过滤和消息推送。
1. 我们可以基于物联网平台的数据同步能力，比如HTTP推送，实现物联网平台和应用服务器的对接。在应用服务器上，我们可以灵活地开发数据应用系统。
1. Django是非常流行的，基于Python语言的Web应用开发框架。工作中，你可以使用Django比较快速地实现一个Web应用系统。

在这一讲中，我们的数据应用系统只包含了自动浇花器的数据，你在时间允许的情况下，也可以尝试在这个系统中增加一下智能电灯和光照传感器的数据。

另外，物联网平台的数据同步也提供了 CKafka（Cloud Kafka）的数据转发方式。通过订阅Topic主题消息，我们可以消费Kafka的消息，也可以直接将Kafka消息转储到MySQL云数据库中。

不过，Kafka的费用比云服务器要贵不少，这里就不介绍了。如果你的条件允许，也可以动手实践一下这种方式。

这里，我整理了一个思维导图，供你参考：

<img src="https://static001.geekbang.org/resource/image/76/21/76ce53374f7e4c35bf71c8836fff4521.jpg" alt="">

## 思考题

最后，我给你留一个思考题吧。

在Django应用开发的介绍部分，我提到了admin.py是后台管理应用的配置文件，而且访问[http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)地址，就会出现管理员登录页面。你知道在Django项目中如果创建管理员账号吗？另外，如何在admin.py文件中增加Watering数据库模型对象，实现对自动浇花器监测数据的查询呢？

欢迎你在留言区写一写自己的思考，同时，也欢迎你将这一讲分享给对Web应用开发感兴趣的朋友，大家一起交流学习。
