esp8266时钟+天气+提醒(五)云服务篇二

esp8266时钟+天气+提醒(五)云服务篇二

本篇主要讨论后端服务FastAPI如何在Linux上部署,同时使用ESP8266访问它。

我的服务器地址为47.102.199.118,FastAPI服务目前是开放状态,不需要身份校验,预计开放一周,欢迎萌新朋友们调试(但真的不要再玩SSH了)。

七、后端服务

1. pip

pip是Python的包管理工具,提供了一种简便的方式来安装、升级和删除Python包。这些包通常从Python包索引(PyPI,Python Package Index)获取,PyPI是一个存储Python程序员广泛使用的软件库和对应版本信息的数据库。

source venv/bin/activate
pip -V

输入以上命令可以激活虚拟环境并且查看pip的版本:

可以看到是虚拟环境中的pip,版本为23.0.1,这说明我们的操作都是正确的。

下面提供几个常用的指令:

pip install package_name
pip install --upgrade package_name
pip uninstall package_name
pip list
pip show package_name

它们分别表示安装、升级、卸载、列出已安装的包、展示包的信息。

2. 库安装

我们需要通过pip安装依赖,方法如下:

pip install fastapi uvicorn gunicorn

我们安装了三个库,分别是fastapi、uvicorn、gunicorn,下面简要介绍一下它们:

2.1 FastAPI

FastAPI是一个现代、快速(高性能)的Web框架,用于构建APIs与Web应用程序。它基于Python 3.6+类型提示,旨在快速开发,同时提供自动化的数据模型验证、序列化和文档(基于Swagger和ReDoc)。FastAPI支持异步编程,使其能够处理大量并发请求,非常适合高性能应用场景。

2.2 Uvicorn

Uvicorn是一个轻量级、超快的ASGI(Asynchronous Server Gateway Interface)服务器,用于Python。ASGI是WSGI(Web Server Gateway Interface)的异步版本,旨在提供一种标准方式来构建异步Web应用。Uvicorn基于uvloop和httptools,旨在提供高效的异步能力。作为ASGI服务器,Uvicorn可以运行任何基于ASGI的应用,如Starlette或FastAPI,提供了快速异步处理能力。

2.3 Gunicorn

Gunicorn(“Green Unicorn”)是一个Python WSGI HTTP服务器,用于UNIX系统。它是一个预分叉的工作模式的服务器,用于部署Python Web应用。Gunicorn被设计为易于使用和部署,提供了简单的命令行界面来启动Web应用。虽然Gunicorn本身不支持异步处理,但它可以与Uvicorn一起作为工作程序(worker)使用,从而结合Uvicorn的异步能力和Gunicorn的管理功能。

安装好后,使用pip list应当如下图所示:

3. 编写代码

3.1 Python代码

代码如下所示:

from fastapi import FastAPI
import uvicorn

from schemas.schemas import Data

app = FastAPI()

data_list = [
        {"id": 1, "time": "9:00", "content": "洗脸刷牙1"},
        {"id": 2, "time": "10:00", "content": "洗脸刷牙2"},
        {"id": 3, "time": "19:00", "content": "洗脸刷牙3"},
        {"id": 4, "time": "20:00", "content": "洗脸刷牙4"},
    ]


@app.get("/")
async def root():
    return {"data": data_list}


@app.delete("/")
async def delete_data(id: int):
    global data_list
    data_list = [item for item in data_list if item['id'] != id]
    return {"data": data_list}


@app.patch("/")
async def patch_data(data: Data):
    global data_list
    data_key = data.id
    for i, record in enumerate(data_list):
        if record["id"] == data_key:
            # 为更新的记录创建一个新的字典
            updated_record = data.model_dump()
            updated_record["time"] = updated_record["time"].strftime("%H:%M")
            # 在data_list中更新记录
            data_list[i] = updated_record
            break
    return data_list


@app.post("/")
async def post_data(data: Data):
    global data_list
    data_list.append(data.model_dump())
    return data_list


if __name__ == "__main__":
    uvicorn.run("main:app", port=8000, host="0.0.0.0")

这是一个python程序,我们将其保存在main.py中(它与venv目录是同级的)。

在这里我们仿造一个数据库,用data_list来反映数据库中查询的结果(主要是服务器太拉胯,所以这样对付对付)。

此外我们又新建了一个目录schemas,其下有一个文件schemas.py。

mkdir schemas
cd schemas/
vi schemas.py

vi对萌新来说比较不友好,但这里不详细讲述了。但如果使用Xshell则会比较简单,直接右键->粘贴->粘贴到终端中即可,然后按ESC,输入:wq即可保存并退出(这是vi的要求)。

文件内容如下所示:

import datetime

from pydantic import BaseModel


class Data(BaseModel):
    id: int
    content: str
    time: datetime.time

我们使用python开发,具体原理不再讲述了(可参看《Python语言与程序设计》),有兴趣的读者们可以自己做一个,或者直接魔改我的。

3.2 Gunicorn配置

刚才介绍了gunicorn,令人摸不着头脑。简单来说,我们要使用它为我们的fastapi应用保驾护航。

在当前目录下我们新建一个文件gunicorn.py,在里面输入以下内容:

loglevel = 'info'
accesslog = 'access.log'
errorlog = 'error.log'
workers = 4
worker_class = 'uvicorn.workers.UvicornWorker'

之后,我们使用以下命令就可以启动服务了:

gunicorn -c gunicorn.py main:app

接下来讲解一下这个命令:

该命令是在使用Gunicorn作为HTTP服务器来启动一个Python web应用时所使用的命令行语句,具体来说:

  • gunicorn:这是命令的主体,指调用Gunicorn服务器,它可以和Uvicorn结合使用
  • -c gunicorn.py:这个选项告诉Gunicorn使用一个预先定义的配置文件gunicorn.py。在这个配置文件中,你可以指定工作进程的数量、日志配置、绑定的IP地址和端口号等配置信息(比如错误日志就是error.log)。这是一个自定义的Python脚本,里面定义了Gunicorn服务器运行时的配置参数。
  • main:app:这指定了Gunicorn应当寻找并运行的应用实例,我们的fastapi服务是卸载main.py里面的,所以这里是main:app。

启动服务后,我们会看到:

好像什么都没有,其实这是因为程序正在运行,如果它被访问则会打印出访问日志,但现在没人去访问,所以下面一片空白。

3.3 后台启动与结束进程

我们先输入Ctrl+C结束当前程序,看看这个目录里现在有什么:

可以看到多出来了两个log文件,它们是gunicorn.py中设置的日志,分别是访问日志和错误日志。

此外还有一个文件夹__pycache__,这是缓存文件夹,使用python都会有的。

如果像刚才一样启动服务,我们就会发现无法继续输入命令,所以我们需要让服务在后台运行,不占用我们的终端,方法如下:

gunicorn -c gunicorn.py main:app &

我们可以看到,gunicorn进程在后台启动了,8000端口的就是:

那么如何结束进程呢?我们之前是直接按下Ctrl+C来结束的,而现在我们的服务在后台运行,Ctrl+C不起作用,这该怎么办呢?

我通常采用这种方法来关闭进程:

pkill gunicorn

pkill命令用于关闭进程,上面这行则是说关闭所有gunicorn进程。而我们的fastapi服务是通过gunicorn运行的,这样就可以停止我们的服务。

4. 反向代理

如果在安全组中放行了8000端口,那么就可以通过浏览器直接访问了,不过一般还是使用HTTP默认的80端口比较好,这就需要反向代理

反向代理的原理不在这里讲述了,有兴趣的可以自行了解。

观察上图,可知在/usr/local/nginx下有一个叫做conf的文件夹,里面是nginx的各种配置。

nginx的主配置文件是nginx.conf,我们可以编辑它。

我们在conf文件夹下又新建了一个文件夹conf,这个是我们的子配置文件夹,接着我们在其中新建配置文件:

cd /usr/local/nginx/conf
mkdir conf
cd conf/
vi main.conf

新建了一个main.conf的配置文件,我们用它来管理80端口。

在这个文件里我们写入以下内容:

server {
    listen 80;
    
    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

它的意思是监听80端口,同时把对80的访问转发给8000(或者说是代理)。

接下来修改主配置文件nginx.conf,让它引入我们的main.conf。

操作如下:

cd ../
vi nginx.conf

可以看到主配置文件里已经有内容了,我们需要修改其中的一部分:

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
    include ./conf/*.conf;
        #charset koi8-r;
}

把http部分修改为上面的内容。

最后的include表示导入conf文件夹下一切后缀名为conf的配置文件。

完成后,我们检查一下nginx配置是否正确:

cd /usr/local/nginx/sbin
./nginx -t

nginx -t表示检查nginx配置是否正确,如果正确,结果应当如下图所示:

接下来我们重启nginx即可,重启方法为:

./nginx -s reopen

重启后稍微等一会,就可以在浏览器里观察到效果了:

5. 测试效果

以上步骤都成功之后,我们就使用esp8266试图访问,只需要模仿天气的代码段即可(并非最终版,但可用):

const char* reminder_host = "47.102.199.118";
void reminder() {
  String reqRes = "/";
  String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" + "Host: " + reminder_host + "\r\n" + "Connection: close\r\n\r\n";
  Serial.println("");
  Serial.print("Connecting to ");
  Serial.print(host);
  WiFiClient client;
  // 尝试连接服务器
  if (client.connect(reminder_host, 80)) {
    Serial.println(" Success!");

    // 向服务器发送http请求信息
    client.print(httpRequest);
    Serial.println("Sending request: ");
    Serial.println(httpRequest);

    // 获取并显示服务器响应状态行
    String status_response = client.readStringUntil('\n');
    Serial.print("status_response: ");
    Serial.println(status_response);
    // 使用find跳过HTTP响应头
    if (client.find("\r\n\r\n")) {
      Serial.println("Found Header End. Start Parsing.");
    }
    const size_t capacity = JSON_ARRAY_SIZE(4) + JSON_OBJECT_SIZE(1) + 4*JSON_OBJECT_SIZE(3) + 120;
    DynamicJsonDocument doc(capacity);

    deserializeJson(doc, client);
    for (JsonObject data_item : doc["data"].as<JsonArray>()) {

      int data_item_id = data_item["id"];                    // 1, 2, 3, 4
      const char* data_item_time = data_item["time"];        // "9:00", "10:00", "19:00", "20:00"
      const char* data_item_content = data_item["content"];  // "洗脸刷牙", "洗脸刷牙", "洗脸刷牙2", "洗脸刷牙2"
      Serial.print(data_item_content);
    }
  }
}

这里我们定义了一个函数reminder,它的作用主要是在串口监视器中输出提醒的内容,至于容量的配置,是通过第四章第三节中所述的方法,使用Assistant自动计算,考虑到裕量的字符串容量设为120。

此外,注意reminder_host是云服务器的公网IP,即第六章中的。

我们再修改一下loop函数:

void loop() {
  if (mode == 0) {
    clock_display(prevDisplay);
  } else if (mode == 1) {
    mode = 0;
    reminder();
  }
}

只是把获取天气的替换为获取提醒,在添加第二个按钮的时候我们会设置的。

运行程序后,按下按钮,触发中断,执行reminder函数,观察串口监视器:

如果如上图所示,说明我们的后端服务没有问题,esp8266也能很好地与它通信,说明成功了。

6. 其他的想法

观察原作者的代码(参见本专栏第二篇),可以发现获取的天气都是杭州的,是程序中固定好的,这就带来了比较大的局限性,比如我们批量生产、售往全国各地的时候,必然不能只查看杭州天气,为了使我们的项目具备普适性,解决方案主要有:

  1. 使用定位模块,获取当前地理位置。
  2. 通过IP地址解析出实际地理位置。

方案一的优势在于精度相较于原方案更高,原方案是城市级天气,使用定位模块可以获得更精确的位置,例如区县甚至乡镇街道,这样就能获得更准确的天气,虽然使用这种网格级天气可能要额外付费。。。而且定位模块本身也需要购买。。

心知天气手册

方案二依赖服务器,因为对IP地址的解析是需要借助专业的解析工具或者解析网站的。我们可以在自己的后端服务中实现这个功能,获取城市信息后直接对心知天气发请求,将获得的数据再传回ESP8266。

未完待续

接下来我们要将获得的提醒数据按一定格式显示在屏幕上,此外提醒应当是可以修改的。

局域网通信的功能也将在后面实现。

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值