0
点赞
收藏
分享

微信扫一扫

大一上--数科作业(连载完结篇)

雷亚荣 2022-02-05 阅读 28

说明(read!me!please!!!):

本篇目旨在讲解,所附代码为片段截取式,每一个片段侧重于展现一项功能,不同片段各有省略和重合部分,完整项目代码详见GitHub哦👌

②注意看代码注释,可以说是 相 当 之 详 细(不吹🙅)


目录:

①功能要求

②流程概况

③代码简介


序:一些准备工作

python基本知识补充(附代码说明)

flask官方参考文档:Welcome to Flask — Flask Documentation (2.0.x)

websocket官方参考文档:websocket.org - Powered by Kaazing

redis官方参考文档:Welcome to redis-py’s documentation! — redis-py dev documentation


PART1:功能要求

制作一个老年求助机,设备实现当用户按下求助按键时,将不同的求助信息通过企业微信转发,显示到用户微信消息,用户通过微信回复消息,该消息可由求助机收到并显示

实现用户端(求助机)---用户端(手机微信)的双向通信


PART2:流程概况

前三步完成的功能逻辑:

第一步:开通企业微信,新建自定义应用用于消息转接 

第二步:调试

①发送HTTP请求,获取调用企业微信发送消息API的凭证

②使用API,发送HTTP请求,验证API有效性

③用户若成功接收到自定义应用发来的消息则证明发送消息接口可用,可进一步编写入代码中

第三步:验证

验证URL有效性,使自定义应用的后台(服务器)能接收到来自客户端的回复

\Rightarrow前三步整合:企业微信自定义应用的收发消息---实际实现了HTTP服务器与用户之间的通信

第四步:

①在服务器中部署写入redis的代码实现服务器间通信

②在websocket服务器中部署写入websocket的代码实现服务器到单片机设备的双向通信


PART3:代码简介

1.HTTP服务器端代码:

基础配置:

from flask import Flask, request, jsonify, make_response
import api
from WXBizMsgCrypt import WXBizMsgCrypt, ET
import redis
# 导入模块

app = Flask(__name__)
# 实例化flask类,创建项目对象

token = api.get_token()
my_crypt = WXBizMsgCrypt(api.receive_token, api.AESKey, api.corpid)
# 调用企业微信api接口,获取token、AESkey、corpid等信息

r = redis.Redis(host='redis', port=6379, db=0)
p = r.pubsub()
# 创建redis pubsub对象
# redis 提供两个类 Redis 和 StrictRedis, StrictRedis 用于实现大部分官方的命令
# Redis 是StrictRedis 的子类,用于向后兼用旧版本。

功能:

①验证URL有效性---回调请求

@app.route('/', methods=["GET"])
# 注册路由:当访问域名根目录且使用GET请求方法时,执行以下函数

def check():
    
# 企业微信验证SSL接口

    if request.method == 'GET':
        msg_signature = request.args.get('msg_signature')
        timestamp = request.args.get('timestamp')
        nonce = request.args.get('nonce')
        echostr = request.args.get('echostr')
        content = my_crypt.VerifyURL(msg_signature, timestamp, nonce, echostr)
        response = make_response(content)
        return response  # 将请求回调所需信息打包回复,以通过验证

②接收用户消息

@app.route('/', methods=["POST"])  # 注册路由
def receive():
   
# 企业微信用户发送消息的处理接口
    
    if request.method == 'POST':
        msg_signature = request.args.get('msg_signature')
        timestamp = request.args.get('timestamp')
        nonce = request.args.get('nonce')
        _, xml_content = my_crypt.DecryptMsg(
            request.get_data(), msg_signature, timestamp, nonce)
        content = ET.fromstring(xml_content).find("Content")
        user = ET.fromstring(xml_content).find("FromUserName")
# 使用企业微信封装api,解码出消息内容和用户名
        response = make_response(jsonify({'message': 'OK'}, 200))
# 返回消息,表示成功接收
        
        pass
# ...
        return response

③redis订阅/广播消息

@app.route('/', methods=["POST"])
def receive():
    pass
# ...
        r.publish('w2d-channel', user.text + ": " + content.text)
# http服务器端需要向websocket服务器端发送消息
# 即:向 ① wechat to desk 频道广播消息
        return response


def message_handler(message):
    api.send_message_all(message['data'].decode('utf-8'), token)
# @message: String --从websocket服务器端publish来的消息对象
# 将收到的订阅消息显示在自定义应用后台


if __name__ == "__main__":
    app.config['SERVER_NAME'] = 'chichibomm.com:6666'
    
    p.subscribe(**{'d2w-channel': message_handler})
# http服务器需要接收来自websocket服务器端的消息
# 订阅 ② desk to wechat 频道至 message_handler 函数
    
    p.run_in_thread(sleep_time=0.001)
# 新开一个进程来保证订阅消息被处理
# 多线程
 
    app.run('0.0.0.0', debug=True, port=6666, ssl_context=(
        "/ssl/chichibomm.com.pem", "/ssl/chichibomm.com.key"))
# 配置flask运行的服务器端口、ssl证书等

2.websocket服务器端代码:

基础配置:

from websocket_server import WebsocketServer
import redis
# 导入redis和websocket模块

r = redis.Redis(host='redis', port=6379, db=0)
p = r.pubsub()
# 连接到redis

server = WebsocketServer(host="0.0.0.0", port=2333)
# 创建websocket对象

功能:

①对是否连接的判断及反应:

# 定义连接成功和断开连接两个功能函数
def new_client(client, server):
    """
    新的求助机连接的事件处理函数
    @client: websocket client对象
    @server: websocket server对象
    """

    print("当新的客户端连接时会提示:%s" % client['id'])
    r.publish('d2w-channel',"%s号求助机已连接" % client['id'])
    # 广播消息到d2w频道

 
def client_left(client, server):
    """
    求助机断开连接的事件处理函数
    @client: websocket client对象
    @server: websocket server对象
    """

    r.publish('d2w-channel',"%s号求助机断开连接" % client['id'])
    # 广播消息到d2w频道

 ②发送消息到服务器及处理接收到的消息

def message_received(client, server, message):
    """
    求助机向服务器端发送消息的事件处理函数
    @client: websocket client对象
    @server: websocket server对象
    @message: 发送的消息对象
    """

    r.publish('d2w-channel',"%s号求助机发送消息:" % client['id'] + message)
    # 向 d2w 频道广播消息

def message_handler(message):
    """
    订阅消息的处理函数
    @message: String //从HTTP服务器端publish来的消息对象
    """
    
    server.send_message_to_all(message['data'].decode('utf-8'))
    # 将收到的消息广播至全体求助机(websocket发送消息到所有连接)

程序运行:

if __name__ == '__main__':
    p.subscribe(**{'w2d-channel': message_handler})
    # 订阅 wechat to desk 频道至 message_handler 函数

    server.set_fn_new_client(new_client)
    # 有新设备连接上(()内为定义的设备连接函数)
    server.set_fn_client_left(client_left)
    # 有设备断开连接(()内为定义的设备断开函数)
    server.set_fn_message_received(message_received)
    # 开始监听消息
    
    thread = p.run_in_thread(sleep_time=0.001)
    # 新开一个进程来保证订阅消息被处理
    
    server.run_forever()
    # 持续运行

附:服务器端 docker的使用

Docker作为一个软件集装箱化平台,可以让开发者构建应用程序时,将它与其依赖环境一起打包到一个容器中,然后很容易地发布和应用到任意平台中。

Docker广泛应用于Web应用的自动化打包和发布

什么是Docker,它可干什么? - 听海漫步 - 博客园

version: '2'
services:
  http_server:
    build: ./http_server/.
    links:
      - redis
    ports:
      - 6666:6666
    networks:
      - helpdesk_network
    depends_on:
      - redis
    volumes:
      - /root/ssl:/ssl
  websocket_server:
    build: ./websocket_server/.
    links:
      - redis
    ports:
      - 2333:2333
    networks:
      - helpdesk_network
    depends_on:
      - redis
  redis:
    image: redis
    networks:
      - helpdesk_network
    expose:
      - 6379
networks:
  helpdesk_network: {}

3.MCU端代码:

MCU大部分功能在上一部分已经实现,以下主要展开websocket服务相关部分代码

基础配置:

①接线方式:

/*接线
 * SSD1309/OLED128X64---NODE MCU32/arduino iic
 * OLED VCC----------------3.3v
 * GND---------------------GND
 * OLED SCL----------------5
 * OLED SDA----------------18
 * Other-------------------NODE MCU32/arduino iic
 * Beep--------------------19
 * BUTTON_A----------------12
 * BUTTON_B----------------14
 * BUTTON_C----------------27
 * BUTTON_D----------------26
 */

②导入库:

// Arduino架构库
#include <Arduino.h>
// 基于Arduino架构的显示屏驱动库
#include <U8g2lib.h>
// 基于Arduino架构的多按钮驱动库
#include <Button2.h>
// 基于Arduino架构的蜂鸣器驱动库
#include <EasyBuzzer.h>
// 基于Arduino架构的WIFI连接库
#include <WiFi.h>
// 基于Arduino架构的Websocket协议库
#include <WebSocketsClient.h>

③封装websocket服务:

// 基于Arduino架构的Websocket协议库
#include <WebSocketsClient.h>

// 定义Websocket服务端口
#define WEBSOCKET_PORT 2333
// 定义Websocket方法路由
#define WEBSOCKET_PATH "/"
// websocket事件逻辑定义
// 定义事件处理函数
// 参数WStype_t type判断websocket与服务器连接状态
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length)
{
	// C语言switch语句(不同并列条件对应不同处理方式)
    switch(type)
    {
        // 情况1:websocket未成功连接		
        case WStype_DISCONNECTED:
			Serial.printf("Websocket断开连接!\n");
			message = "服务器断开连接";
            // 调用封装好的蜂鸣器函数
			ez_beep(2);
			break;
		// 情况2:websocket成功连接
        case WStype_CONNECTED:
			Serial.printf("Websocket已连接至路由:%s\n", payload);
			message = "服务器已连接";
			break;
        // 情况3:websocket收到来自服务器的消息
		case WStype_TEXT:
			Serial.printf("Websocket收到信息: %s\n", payload);
			message = String((const char*)payload);
			ez_beep(1);
			break;
	}

}
// 尝试连接服务器
webSocket.begin(WEBSOCKET_SERVER, WEBSOCKET_PORT, WEBSOCKET_PATH);
webSocket.setReconnectInterval(5000);
webSocket.onEvent(webSocketEvent);

主程序展现:

// 按钮按下时,定义触发逻辑
void click(Button2 &btn)
{
	if (btn == buttonA)
	{
        // 单片机显示
		Serial.println("按键A按下");
        // 传入显示函数的message参数
		message = "我生病了";
        // 调用
		ez_beep(1);
        // 使用websocket发送消息"我生病了"到websocket服务器端
		webSocket.sendTXT("我生病了");
	}
    // ...
    pass
}
举报

相关推荐

0 条评论