监测平台开发文档
智能监测平台 TJAI-Platform
开发文档
平台结构概述
监测平台(下文简称平台)采用 NextJS 作为开发技术栈。基于 NextJS 的前后端一体化和服务器端预渲染(Server Side Rendering) 特性,可以方便地进行设备端传感器数据上传和客户端页面实时刷新。
NextJS 依赖 Node.js 环境和 ReactJS 前端框架,因此开发环境上需要安装好 Node.js 和 npm 包管理工具。在 Ubuntu 20.04 LTS 版本下安装nodejs 方法如下
1 | $ curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - |
Nodejs 环境安装完毕后,就可以直接用 npm 包管理工具创建 NextJS项目了
1 | $ npx create-next-app tjai-platform --use-npm |
平台项目目录大致分为五个部分:
/components 组件目录:前端页面的组件 jsx 文件存放在该目录中,在前端页面可以直接调用。
/lib 库函数目录:组件或前端页中需要多次调用的函数或方法在该目录中定义。
/models 数据库结构目录:平台采用 mongoose 库管理 mongodb 的数据模型结构。该目录用于定义各模型的 Schema 结构。
/pages 前端页面目录:平台网站的页面目录。该目录下的脚本文件通过 nextjs 的编译后可以直接通过 base_url/[pages] 访问。页面内容采用 react 函数和 jsx 语法编写。脚本格式可以使用 .js, .ts, 或 .jsx,非常灵活。
/pages/api 服务器端接口目录:服务器端响应外部或网站内部请求的 http 接口在该目录中定义。
其中的核心是
/pages前端页面和/pages/api服务端接口。客户端页面会调用/component组件。前端和接口都会调用/lib库中定义好的函数。
平台使用 MongoDB 作为处理客户信息和传感器数据的数据库。并使用 Mongoose 库作为数据库管理工具:
1 | # 安装 MongoDB Enterprise Edition |
将/etc/mongod.conf配置好之后,用mongosh设置管理员名称和密码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17$ mongosh
> use admin
> db.createUser({
user: "admin",
pwd: "password",
roles: [
{ role: "userAdminAnyDatabase", db:"admin" },
{ role: "readWriteAnyDatabase", db: "admin" }
]
})
> db.adminCommand( { shutdown: 1 } )
$ sudo nano /etc/mongod.conf
# 修改以下配置
security:
authorization: enabled.env.local文件中设置好环境变量
1
2
3MONGODB_URI=mongodb://admin:password@hostname:port/db
MONGODB_DB=db
DB_NAME=db
前端页面
NextJS 的前端框架是 ReactJS,在创建 NextJS 项目时会自动安装好 React
依赖,不必再手动导入bable或react-dom等脚本库。
前端主要页面有两个:主页和仪表页。主页即/pages/indes.js页是单纯的展示页,除了平台介绍和登录按钮以外没什么别的功能内容。仪表页是客户使用账户密码登录后查看设备、传感器、实时画面和警告信息的综合页面,因此大部分功能组件都在仪表页中。
CSS 设计
前端整体选用 NextJS 推荐的 Tailwind CSS 作为 CSS 工具库。安装方法参考
官方教程: 1
2
3
4$ cd ~/Projects/tjai-platform
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
$ nano tailwind.config.js./tailwind.config.js 1
2
3
4
5
6
7
8
9
10module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
./style/global.css
1 | @tailwind base; |
./pages/_app.js 1
import "../style/global.css"
之后即可在className中直接使用tailwindcss预设样式。
主页组件
<Layout>主页的上半部分,包括顶部菜单栏和正中平台名称。文件路径
/components/layouts/Layout.jsx。<Layout>组件中简单导入了以下两个组件:< Header>顶部菜单栏。目前菜单栏仅作为外观和占位作用,真正可以点击进入的只有
Dashboard和登录验证按钮。Documentation用于将来编写使用教程和文档,配置了相应的下拉菜单。Purchase是预留的在线商店页面。Contact预留用于放置社交账户。<Hero>主页正中平台名称展示。<Tabs>主页下半部分。使用标签展示形式。鼠标指针点击标签时自动切换文本内容。实现方式是定义一个页面 state 变量openTab以及对应的设置函数setOpenTab。每个标签设置onClick()事件处理函数:onClick=( () => { setOpenTab(1/2/3) } )。然后在文本tag的 className 中加入条件判断className = { openTab === 1/2/3? "block":"hidden" }作为文本显示脚本。
仪表页组件
<Admin>仪表页框架组件,包含标题组件<Head>、侧边栏组件<Sidebar>和用户导航栏组件<AdminNavbar>。<AdminNavBar>仪表页顶部导航栏。包含隐藏侧栏按钮和用户名邮箱公司名称的显示,最右边是等处按钮。侧栏隐藏显示按钮是绑定了事件处理函数,并在1
onClick={ () => { props.setOpenSidebar(!props.openSidebar); } }
<Admin>组件中将openSidebar状态变量用于控制侧边栏的显示和隐藏:用户名邮箱公司名则根据登入用户的注册信息在数据库中进行提取。利用 NextJS 的SSR特性,在1
2
3<div className={`relative ${openSidebar ? "md:ml-64" : ""} transition-all` } >
// children
</div>dashboard.js脚本中定义getServerSideProps(context)函数:在1
2
3
4
5
6
7
8
9
10
11export async function getServerSidePrps(context) {
// ...Query mongodb for user info.
return {
props: {
username: session.user.name,
email: session.user.email,
corp: camera[0]?.user.corp,
camera: camera
}
}
}dashboard页中把 props 以{...props}的形式传递给<Admin>控件后即可在<AdminNavbar>中以props.username的形式直接获取用户信息显示在DOM中。<Sidebar>用<ul>标签以列表依次显示「首页」、「设备列表」、「警告通知」、「日志图表」、「危险行为分析」和「Signout」。 这些列表项点击事件并非页面跳转,而是组件隐藏或显示。 列表项的onClick事件处理函数绑定了从dashboard.js传递过来的props.setXXX()等函数,用于控制各控件的显示和隐藏。 在Sidebar.jsx中定义一个linkLabel对象,将各个链接的文本存储在该队对象中。linkLabel.home:地图页链接。点击后通过设置mapshow状态变量控制地图显示。1
2
3
4onClick = {() => {
props.hideAll();
props.setMapshow(true);
}}linkLabel.devices:设备列表。利用CSS实现下拉及隐藏。1
2
3
4
5
6
7
8
9
10
11
12<Link href="/dashboard">
<a
href="#devicesSubmenu"
data-bs-toggle="collapse"
aria-expanded="false"
aria-controls="devicesSubmenu"
className="dropdown-toggle text-blueGray-700 hover:text-blueGray-500 text-xs uppercase py-3 font-bold block"
>
<i className="fas fa-fingerprint text-blueGray-400 mr-2 text-sm"></i>{" "}
{linkLabel.devices + " "} {"(" + props.camera.length + ")"}
</a>
</Link>利用前文提到的1
2
3
4
5
6
7
8
9
10.dropdown-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}dashboardjs中传递来的props.camera获取设备名称并映射成设备名称显示在列表中:其中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{props.camera.map((item) => (
<li className="items-center" key={item._id}>
<a
href="#"
onClick={() => {
props.setCamSource(item.device.deviceid);
props.setDevId(item.device._id);
props.hideAll();
props.setVideoshow(true);
props.setRtdata(true);
props.socket?.emit("leave");
props.socket?.emit("find",item.device.deviceid);
}}
className="text-blueGray-700 hover:text-blueGray-500 text-xs uppercase py-3 font-bold block"
>
<i className="fas fa-fingerprint text-blueGray-400 mr-2 text-sm"></i>
{item.device.deviceid}
</a>
</li>
))}onClick的props.socket?.emit()两行是通过向SocketIO server发送事件来唤起webrtc连接。具体在视频传输方案一节论述。linkLabel.notification:警告通知 调用/lib/useAlert中定义的useAlert("all",userid)方法获取未读警告通知数量,并在「警告通知」按钮右上用红圈数字标识显示。(待补充)<FooterAdmin>页面底部的版权链接和预留的备案号信息。简单地以<ul>标签形式横排列出。<CardLineChart>日志图表页的实时数据图表控件。使用recharts库作为图表绘制工具库(该库专为React框架设计)。<CardRealtimeData>设备页的实时数据图表控件。同样使用recharts库作为图表绘制工具库。<ServoControl>舵机操作按钮控件。<CardAlerts>警告通知页的消息列表控件。<RTCPlayer>WebRTC实时视频显示控件。视频播放器使用react-player库作为wrapper。通过props变量传入props.remoteStream和props.isFull参数。前者作为控件的url属性,url={props.remoteStream}。后者用于判断是否有其他客户端正在查看视频图像。
服务器端接口
按照NextJS的项目目录结构编写以下API接口对公网开放。 -
/api/alert
警告信息查询接口,分为[...alertquery].js,
read.js两个接口。前者查询警告信息的具体内容,后者用于获取警告信息的已读状态。
/api/auth用户登录认证接口,详见 登录认证方案。/api/device设备端用于向平台发送请求获取认证token或上传实时数据到数据库的接口。分为cameraSource,deviceInfo,getToken,realtimeData,sensorInfoUpdate五个接口。/api/getdata平台客户端页面从数据库调用查询历史数据的接口。/api/socketioSocketIO的服务器端接口。用于WebRTC的Signaling和舵机控制。
数据库结构
平台使用Mongodb作为数据库,并且用mongoose库作为数据库处理工具,在/models目录中以json格式定义了
mongoose 模组,即 mongodb collection 的数据格式。 - Alert
警告通知的数据结构。一级键名有alertdata, unit,
sensorinfo, device, user,
isRead,
createdAt。其中alertdata以列表形式存放警告数据,包含气体浓度和警告时间两个二级键名。
- Camera
摄像机信息数据结构。一级键名有cameraid, url,
user, device。
Device探测器设备信息数据结构。一级键名有deviceid,serialnumber,coordinate,user。Realtimedata实时气体浓度数据的数据结构。一级键名有sensordata,device,user,createdAtSensorinfo气体传感器信息的数据结构。一级键名有code,name,type,upperthreshold,bottomthreshold,user,deviceUser用户信息的数据结构。一级键名有name,email,password,corp
登录认证方案
平台认证使用nextauth作为验证框架,bcryptjs作为加密算法库。
视频传输方案
平台使用WebRTC作为实时视频传输方案。配合设备端的aiortc脚本进行设备端摄像头画面传输。
舵机控制方案
设备页舵机控制采用网页客户端发送socketio的servo-control事件到服务器端再转发到设备端,并在rtc.py脚本中使用python-pigpio库对树莓派gpio
pin脚占控比设置的方式进行。