智慧卫生间

Hypergryph 智能卫生间

系统结构图

F1-male门锁-0
网关F1-male
F1-male门锁-1
F1-male门锁-2
F1-female门锁-0
网关F1-female
F1-female门锁-1
F1-female门锁-2
EMQX-Broker
用户终端1
用户终端2
用户终端3

系统硬件概述

门锁

技术参数:

名称参数值
产品尺寸75mm x 53 mm
产品材质304不锈钢
信号输出干接点开关量输出
引脚GND/OUT
适用场景内开门
用途开关信号输出

产品图:

在这里插入图片描述

在这里插入图片描述

网关

技术参数:

名称参数值
输入输出通道6路DI开关量
通讯接口1路RJ45网口
工作电压DC9~30V
用途数据发布者(publisher)

在这里插入图片描述

物联网平台-Broker

技术参数:

名称参数值
云平台阿里云
区域上海
规格1000连接/1000TPS(5,000 连接 / 10,000 TPS,10,000 连接 / 20,000 TPS等可选)
用途消息订阅与转发的代理

在这里插入图片描述

软件架构与系统集成

软件架构概述

名词说明:

名称功能概述
用户终端Subcriber, 订阅者,飞书app,web页面,显示每个厕所的实时状态
API服务器阿里云平台,用作接收认证请求与返回EMQX平台的的认证信息
物联网平台Broker, EMQX物联网平台,作消息订阅与发布的代理

流程:

  1. 认证请求 :飞书app与API服务器通信,请求临时认证
  2. web页面访问:当用户终端第一步获取到认证以后,将参数填入web页面的url后访问
  3. 物联网平台数据获取:web页面作为订阅者向物联网平台订阅数据

认证请求

飞书app在打开网页链接时,需要传入三个参数,gender、username、pw,其中username、pw为物联网平台认证,需要从API服务器获取,所有认证有效期按天算,每天0点清空所有认证,参数说明如下:

名称类型
gender取值为male和female, 用来直接跳转到当前性别的网页string
username物联网平台认证,用户名称, 需要飞书app 向API服务器发送post请求 (每天0点失效)string
pw物联网平台认证,对应的用户密码, 需要飞书app 向API服务器发送post请求 (每天0点失效)string

加密请求:

考虑到数据安全性,物联网平台认证均在飞书app内进行,并采用rsa加密方式:

  1. 事先将公钥文件.pem 保存到飞书app内

  2. 读取pem文件,获取当前系统时间,格式为"%Y-%m-%d %H:%M:%S",采用PKCS#1 v1.5加密当前系统时间为encrypted_time

  3. 向api服务器地址发送post请求:

名称
url“http://139.196.xxx.xxx/api/decrypt”(临时演示)
data{“encrypted_time” : “加密后的系统时间”}
header{“Content-Type”: “application/json”}
  1. 返回值说明:
字段类型
createat当前日期,例:2023-12-18string
username一段随机的字符串(每天0点失效)string
password一段随机的字符串(每天0点失效)string

web页面访问

访问流程与示例代码:

  1. 解析认证请求步骤返回的json,提取username 和 password 并组合成网页 url = f"http://139.196.xxx.xxx/?gender={gender}&username={username}&pw={password}" 后打开浏览器访问 用户终端显示的网页

​ 示例:http://139.196.xxx.xxx/?gender=male&username=kF91WFKve3&pw=LLOcTxHGeb

  1. 示例脚本:
python
import base64
import time
import requests
import json
from datetime import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from concurrent.futures import ThreadPoolExecutor
import statistics
# 加载公钥
def load_public_key():
    with open("public_key.pem", "rb") as key_file:
        return serialization.load_pem_public_key(key_file.read(), backend=default_backend())

# RSA加密字符串
def encrypt_string(data, public_key):
    return public_key.encrypt(
        data.encode(),
        padding.PKCS1v15()
    )

# 发送POST请求
def send_post_request(url, encrypted_data):
    headers = {'Content-Type': 'application/json'}
    data = json.dumps({"encryptedString": base64.b64encode(encrypted_data).decode('utf-8')})
    try:
        response = requests.post(url, headers=headers, data=data)
        if response.status_code == 200:
            return response.json(), None
        else:
            return None, f"Error {response.status_code}: {response.text}"
    except requests.RequestException as e:
        return None, str(e)

# 获取当前时间并格式化
def get_current_time():
    now = datetime.now()
    return now.strftime("%Y-%m-%d %H:%M:%S")

def test_request(url, encrypted_time):
    start_time = time.time()
    response_data, error = send_post_request(url, encrypted_time)
    end_time = time.time()
    # 转化为毫秒
    response_time = (end_time - start_time) * 1000

    if error:
        print(f"Request failed: {error}")
        return False, response_time

    # 提取 username 和 password
    username = response_data.get("username")
    password = response_data.get("password")
    gender = 'male'
    url = f"http://139.196.xxx.xxx/?gender={gender}&username={username}&pw={password}"
    webbrowser.open(url)


    print(response_data)
    return True, response_time

def main():
    public_key = load_public_key()
    current_time = get_current_time()
    encrypted_time = encrypt_string(current_time, public_key)
    test_request("http://139.196.xxx.xxx/api/decrypt", encrypted_time)


if __name__ == "__main__":
    main()


Unity3d & C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Networking;
using UnityEngine.UI;

public class ApiManager : MonoBehaviour
{
    public Text debugText;

    private string publicKeyXML;

    [System.Serializable]
    public class EncryptedData
    {
        public string encryptedString;
    }

    [System.Serializable]
    public class User
    {
        public string username;
        public string password;
    }

    private string username;
    private string password;
    private string txt;

    private string apiUrl;
    private string htmlUrl;

    private string gender;
    //获取系统时间,请求时,发送加密后的系统时间
    private string GetDateTime()
    {
        // 获取当前的系统时间
        DateTime now = DateTime.Now;

        // 格式化时间。例如:"yyyy-MM-dd HH:mm:ss" 表示 "年-月-日 时:分:秒"
        string formattedTime = now.ToString("yyyy-MM-dd HH:mm:ss");

        // 打印格式化后的时间
        Debug.Log("Current Time: " + formattedTime);

        return formattedTime;
    }


    private IEnumerator SendPostRequest(string url, string encryptedString)
    {

        // 创建JSON数据
        string jsonData = JsonUtility.ToJson(new EncryptedData { encryptedString = encryptedString });

        // 创建UnityWebRequest对象
        using (UnityWebRequest www = new UnityWebRequest(url, "POST"))
        {
            byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
            www.uploadHandler = new UploadHandlerRaw(bodyRaw);
            www.downloadHandler = new DownloadHandlerBuffer();
            www.SetRequestHeader("Content-Type", "application/json");

            // 发送请求并等待返回
            yield return www.SendWebRequest();

            if (www.result != UnityWebRequest.Result.Success)
            {
                string responseText = www.downloadHandler.text;
                debugText.text = "Response: " + responseText + "\n-------------------------------\n"; ;

            }
            else
            {
                debugText.text += "Response: " + www.downloadHandler.text + "\n-------------------------------\n"; ;
                string recjsonData = www.downloadHandler.text;
                // 将 JSON 字符串解析为 User 类的实例
                User user = JsonUtility.FromJson<User>(recjsonData);
                txt = debugText.text;
                // 访问解析后的数据
                username = user.username;
                password = user.password;
                // 打开终端网页
                htmlUrl = "http://139.196.xxx.xxx/?gender=" + gender + "&username=" + username + "&pw=" + password;
                Application.OpenURL(htmlUrl);
                debugText.text = txt + "打开链接: " + htmlUrl + "\n-------------------------------\n"; ;

            }
        }
    }
    
    //利用公钥加密系统时间
    public string EncryptString(string textToEncrypt, string publicKeyXML)
    {
        byte[] bytesToEncrypt = Encoding.UTF8.GetBytes(textToEncrypt);

        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            rsa.FromXmlString(publicKeyXML);
            byte[] encryptedData = rsa.Encrypt(bytesToEncrypt, false);
            return Convert.ToBase64String(encryptedData);
        }
    }
    //请入账户密并打开网页
    public void OpenHtml()
    { 
        string encryptedString = EncryptString(GetDateTime(), publicKeyXML);
        debugText.text += encryptedString + "\n-------------------------------\n"; ;
        StartCoroutine(SendPostRequest(apiUrl, encryptedString));
    }

   //绑定按钮,点击触发请求时间 btn.name 为 male 和 female
    public void BtnClick()
    {
        GameObject btn = EventSystem.current.currentSelectedGameObject;
        if (btn != null)
        {
            debugText.text = publicKeyXML + "\n-------------------------------\n";
            gender = btn.name;
            OpenHtml();
        }
    }
    
    
    public void Start()
    {
        apiUrl = "http://139.196.xxx.xxx/api/decrypt";
        publicKeyXML = Resources.Load<TextAsset>("public_key").text;
        debugText.text = publicKeyXML+"\n-------------------------------\n";
    }

}

web页面说明:

在这里插入图片描述

绿色代表未被占用的

橙色代表被占用

红色代表网关失联或者未设置网关

下方有两个按钮,Male 和 Female, 用来切换界面

物联网平台数据获取

数据获取流程:

  1. 门锁数据采集:

    所有门锁采用干接点方式集中接入到网关DI口上

  2. 数据发布:

    网关采用RJ45接口有线接入公网,或者采用ap模式,无线接入公网

    采用MQTT TCP协议,将门锁数据发布到物联网平台,发布模式分两种:

    主动上报:当有门锁数据变化时,立马发布数据,或者 设置 10s 间隔自动循环发布

    按需上报:web页面发送订阅请求,无论是否有门锁数据变化,或者是否到达10s间隔,网关都会立马发布一条数据

  3. 数据订阅:

    web页面根据网关的TopicName向物联网平台订阅, 此时物联网平台则会向网关订阅,如果网关发布有数据,物联网平台则将获取到的数据发布给web页面,物联网平台在其中扮演一个Broker的角色

负载测试

web页面服务器负载测试

名称规格
测试工具Apache JMeter
测试服务器规格最低配 2核CPU 2GB运行内存 带宽 3Mbps
总线程数100
总线程启动时间1s 1s内启动100个线程

测试结果如下:

在这里插入图片描述

WebPageTest.csv

Label# SamplesAverageMedian90% Line95% Line99% LineMinMaxError %ThroughputReceived KB/secSent KB/sec
HTTP Request100393843567432780.00%97.08738764.3715.55
TOTAL100393843567432780.00%97.08738764.3715.55

字段说明:

名称说明
Samples100总共进行了100次请求
Average39平均响应时间为39ms
Median38响应时间的中位数为38ms
90% Line4390%的请求都在43ms内响应
95% Line5695%的请求都在56ms内响应
99% Line7499%的请求都在74ms内响应
Min32最小的响应时间为32ms
Max78最大的响应时间为78ms
Error %0.00%错误率为0%
Throughput97.08738吞吐量,服务器每秒可以处理97.08738个请求
Received KB/sec764.37每秒接收764.37KB
Sent KB/sec15.55每秒发送15.55KB

API服务器负载测试

第一组测试:

名称规格
测试工具自定义python脚本
测试服务器规格最低配 2核CPU 2GB运行内存 带宽 3Mbps
总线程数100
线程池10

测试结果如下:

在这里插入图片描述

字段说明:

名称说明
Total Time0.584909 seconds100个请求的总耗时0.584909 秒
Total requests100总共测试了100个请求
Concurrent requests10同时发送10个请求
Successful100100个请求都成功了
Failed00个请求失败
Successful Rate100.0错误率为0%
Average response time55.6159平均响应时间为55ms
Median response time54.273响应时间中位数为54ms

第二组测试:

名称规格
测试工具自定义python脚本
测试服务器规格2核CPU 2GB运行内存 带宽 3Mbps
总线程数1000
线程池100

测试结果如下:

在这里插入图片描述

物联网平台负载测试

名称规格
测试工具XMeter
物联网平台规格基础款最低配,1000 连接/1000TPS ,最多支持1000个客户端连接和1000个数据每秒的通信
发布者数量20 也就是有20个网关
客户端数量500 500个订阅
测试时长1分钟
测试类型连接及消息吞吐测试

测试结果如下:

在这里插入图片描述

page运行次数最大响应时间(ms)最小响应时间(ms)平均响应时间(ms)平均成功响应时间(ms)平均吞吐量(/s)平均成功吞吐量(/s)平均请求大小(KiB)响应码成功率验证点成功率验证点错误率平均标准差90分位响应时间(ms)90%平均响应时间(ms)
MQTT Connect - pub2079233232220.0107100.00%100.00%0.00%14.32793329.6
MQTT Connect - sub5001112031.431.450500.0107100.00%100.00%0.00%16.8855328.5
MQTT Pub Sampler60100.10.11.21.20.0195100.00%100.00%0.00%0.261300
MQTT Sub Sampler2695216462114396.44396.4414.6462414.64621.0234100.00%100.00%0.00%4868.8762134633996.2

核心性能指标说明:

名称说明
吞吐量每秒完成的操作总数。如 MQTT 连接测试中的吞吐量,指每秒新建的连接数。MQTT 消息吞吐测试中的消息吞吐量,指每秒发布和订阅的总数。
响应时间一次操作从发起到完成的时间
90% 平均响应时间所有操作的响应时间中前 90% 数据的平均值。90% 平均响应时间排除了部分波动数据对整体响应时间可能造成的影响
虚拟用户数每一个虚拟用户代表一个模拟客户端。如 MQTT 连接测试中的虚拟用户数,指模拟的连接数。MQTT 消息吞吐测试中的虚拟用户数,指发布模拟客户端或订阅模拟客户端的数量
响应码成功率所有操作中成功操作所占的比例。如 MQTT 连接测试中的响应成功率,指成功连接数占所有连接的比例。

演示视频

演示视频

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值