1.2.3 案例:办公室环境监测系统
案例:办公室环境监测系统
更新时间:2018-07-23 11:57:17
一、系统设计
项目方案分为三个模块:
- 在设备端基于树莓派的环境参数收集
- 在 LD 平台对设备数据分析、汇总
- 基于 LD 平台的 Web 应用开发、构建、部署
整体方案结构如下图:
最终效果预览图:
二、具体实现
1. 设备端环境参数收集
硬件准备
- 准备树莓派,DHT11(温湿度传感器),PMS5003S(甲醛、PM2.5传感器)、PMS5003S 转接小板、USB 转串口
- 在树莓派刷入 NOBOS 固件
- 在树莓派上安装 Node.js (版本要求 >=8.9),安装方法
- 在树莓派上安装 pigpio C library:sudo apt-get install pigpio (安装过后,建议重启树莓派)
- 按照以下步骤装好传感器
树莓派链接传感器
- DHT11
DHT11 引脚 | GPIO 引脚 | PIN |
---|---|---|
ACC | 01 | |
GND | 06 | |
DO | 17 | 11 |
- PMS5003S PMS5003S +++++ 转接小板 +++++ USB 转串口 +++++ 树莓派 USB 口
树莓派 GPIO 引脚参考
最终完成的树莓派和传感器的照片:
LD平台准备
按照设备接入文档,在 LD 平台创建好相关信息。关键步骤:
创建产品信息,注意选择环境监测设备
默认该类别里有大部分我们需要的设备信息,我们需额外添加甲醛设备
新增测试设备
代码开发(树莓派上)
创建代码目录,npm install 以下模块
- aliyun-iot-device-sdk 阿里云数据上报模块
- node-dht-sensor 温湿度传感器数据采集模块
- plantower 甲醛、PM2.5传感器采集模块
基本数据上报代码
```javascript
const device = aliyunIot.device({
productKey: 'a1UXftllibt',
deviceName: 'monitorDevice',
deviceSecret: 'P9ocmPYgGPyPbkMcmALXavnaTrseNtNY'
});
(() => {
device.on('connect', async () => {
console.log('connect successfully');
while(true) {
await sleep(5000);
const val = env.getValue(); // 每隔5秒采集一次环境数据
console.log(temperature: ${val.CurrentTemperature}\u2103; humidity: ${val.RelativeHumidity}%; pm2.5: ${val.PM25Value}µg/m^3; pm10: ${val.PM10}µg/m^3; hcho: ${val.HCHO}mg/m^3
);
device.postProps(val, err => {
if (err) {
return console.log('post error:', err);
}
console.log('post sucessfully');
});
}
});
})();
* DHT11数据读取代码
```javascript
const sensor = require('node-dht-sensor');
exports.read = async function () {
return new Promise(function(resolve, reject) {
// 17 为 GPIO 编号
sensor.read(11, 17, function(err, temperature, humidity) {
if (!err) {
resolve({temperature, humidity});
} else {
reject(err);
}
});
});
};
- PMS5003S数据读取代码 ```javascript const Plantower = require('plantower'); // 根据 PMS5003S 连接的 USB 树莓派口决定第二个参数 const plantower = new Plantower('PMS5003S', '/dev/ttyUSB0');
exports.read= async function () {
let content = null;
try {
content = await plantower.read();
}
catch (e) {
throw 'device not ready';
}
return {
atmosPm25: content['concentration_pm2.5_atmos'].value,
atmosPm10: content['concentration_pm10_atmos'].value,
standardPm25: content['concentration_pm2.5_normal'].value,
standardPm10: content['concentration_pm10_normal'].value,
formaldehyde: content.formaldehyde.value
};
};
#### 树莓派上开启持续监控
```shell
sudo pm2 start index.js
2. 设备数据分析、汇总
设备端获取的数据是每 5s 中上报一次,但我们需要在 Web 应用上展示当天的环境变化趋势,因此我们需要对设备端上报的原始数据进一步分析、汇总。
这里使用 LD 平台的数据模型功能,简单的对原始数据中的每一项数据采用每小时取平均值的处理方式,产生一个按小时的汇总数据表,用于 Web 应用的展示。
具体操作步骤请参考LD平台数据开发文档
最终的汇总数据表如下,可以看到设备端每 5s 上报的数据最终处理成维度为小时的汇总数据。
3. Web 应用开发、构建、部署
LD平台准备
按照 Web应用开发 文档,在 LD 平台创建好一个托管Web应用,打开数据表查询服务。 具体步骤
新建Web应用
注意相关数据表查询服务开通情况
代码开发
项目主要依赖模块
- bone-web web开发框架
- bone-web-ui web组件库
- @bone/linkdevelop-sdk ld服务请求库
- bizcharts g2 react图表库
- moment 处理日期库
项目初始化
```javascript
import { createApp } from "@bone/web";
// 引入各个页面组件
import Home from "./app/pages/home";
import DayView from "./app/pages/dayview";
import NotFound from "./app/pages/not-found";
import Layout from "./app/Layout";
// 创建App实例
export default createApp({
layout: Layout,
// 设置App ID
appName: "a120CnYe80ltb8yD",
// 配置路由信息
router: {
routes: [{
// 页面路径
path: "/",
// 页面组件
page: Home
}, {
path: '/dayview',
page: DayView
}],
// routes中路由无法匹配时显示的404页面
notFound: NotFound
}
})
```javascript
// DayView Page
import React from 'react';
import BarChart from '../../components/BarChart';
import { fetchAggregateEnvData } from "../../api";
import styles from './index.css'
import { Form, Input, Button, Field, Grid, DatePicker } from '@bone/bone-web-ui';
import moment from "moment";
const { Row, Col } = Grid;
const FormItem = Form.Item;
export default class DayView extends React.Component {
state = {
temperatureData: [],
humidityData: [],
pm25Data: [],
hchoData: [],
}
field = new Field(this);
handleSubmit(e) {
e.preventDefault();
const values = this.field.getValues();
this.readAvgEnvironment(moment(values.day).format('YYYY-MM-DD'));
}
render() {
const init = this.field.init;
const formItemLayout = {
labelCol: {span: 6},
wrapperCol: {span: 14},
};
const insetLayout = {
labelCol: {fixedSpan: 4}
};
return (
<div className={styles.dashboard}>
<h1>一天室内环境情况汇总</h1>
<div className={styles.filter}>
<Form field={this.field}>
<FormItem
id="control-input"
{...formItemLayout}>
<Row>
<Col>
<FormItem label="查看日期" >
<DatePicker {...init('day', {})}/>
</FormItem>
</Col>
<Col>
<Button type="primary" onClick={this.handleSubmit.bind(this)}>确定</Button>
</Col>
</Row>
</FormItem>
</Form>
</div>
<div>
<Row>
<Col span="12">
<BarChart data={this.state.temperatureData} title="温度(°C)走势"
unit="°C" />
</Col>
<Col span="12">
<BarChart data={this.state.humidityData} title="湿度(%)走势"
unit="%" color="#55e" />
</Col>
</Row>
<Row>
<Col span="12">
<BarChart data={this.state.pm25Data} title="PM2.5走势"
unit="μg/m³" color="#4ee" />
</Col>
<Col span="12">
<BarChart data={this.state.hchoData} title="甲醛走势"
unit="mg/m³" color="#bb2" />
</Col>
</Row>
</div>
</div>
);
}
componentDidMount() {
this.readAvgEnvironment();
}
async readAvgEnvironment (day) {
const envData = await fetchAggregateEnvData(day);
const temperatureData = [];
const humidityData = [];
const pm25Data = [];
const hchoData = [];
envData.forEach((avg) => {
temperatureData.unshift({ hour: avg.gmtCreate.substring(11, 16), value: Math.round(avg.average_temperature) });
humidityData.unshift({ hour: avg.gmtCreate.substring(11, 16), value: Math.round(avg.average_humidity) });
// pm25 和 hcho 的图表在精度上显示上有问题
pm25Data.unshift({ hour: avg.gmtCreate.substring(11, 16), value: +avg.average_pm25.toFixed(2) });
hchoData.unshift({ hour: avg.gmtCreate.substring(11, 16), value: +avg.average_hcho.toFixed(5) });
});
this.setState({
temperatureData,
humidityData,
pm25Data,
hchoData,
})
}
}
- 数据服务请求代码 ```javascript import { APIGateway } from '@bone/linkdevelop-sdk';
export async function fetchDeviceStatus (productKey, deviceName) {
const res = await APIGateway.request(
'https://api.link.aliyun.com/thing/device/status/query',
{
version: '1.0.1',
data: {
ProductKey: productKey,
DeviceName: deviceName,
},
}
)
if (res.code !== 200) {
throw new Error(res.localizedMsg || res.message);
}
const props = {};
res.data.forEach(item => {
props[item.attribute] = item.value;
});
return props;
}
export async function fetchAggregateEnvData (day) {
const res = await APIGateway.request(
'https://api.link.aliyun.com/datacenter/data/query',
{
version: '1.1.0',
data: {
"dataQueryJson": {
"mid": 12167, // 这个id隐藏在数据表详情页的url中,请认真找哦乛◡乛
"expr":{
"op":"and",
"filters": [
{"col": "gmtCreate","comp": ">=","val": day || "2018-07-02"},
{"col": "gmtCreate","comp": "<=","val": day || "2018-07-03"}
]
},
"cols":["gmtCreate","average_pm25","average_temperature","average_humidity","average_hcho"],
"page":{"to":1,"size":24},
"orderby":[{"col":"gmtCreate","type":"desc"}]
},
"userContextDTO": {}
},
}
)
if (res.code !== 200) {
throw new Error(res.localizedMsg || res.message);
}
return res.data.data;
}
* G2图表
```javascript
// BarChart
import React from 'react';
import PropTypes from 'prop-types';
import { Chart, Axis, Geom, Tooltip } from 'bizcharts';
export default class BarChart extends React.PureComponent {
static propTypes = {
data: PropTypes.array.isRequired,
title: PropTypes.string.isRequired,
color: PropTypes.string,
unit: PropTypes.string,
}
static defaultProps = {
unit: ''
}
render() {
const { title, color, unit, value } = this.props;
const cols = {
'value': { formatter: v => `${v}${unit}` },
'hour': { }
};
return (
<div>
<h4>{title}</h4>
{
this.props.data && this.props.data.length ? (
<Chart height={420} data={this.props.data} scale={cols} forceFit>
<Axis name="hour" label={{ textStyle: { rotate: 30 } }} />
<Axis name="value" />
<Tooltip crosshairs={{type : "y"}}/>
<Geom type="interval" position="hour*value" color={color || "red"}/>
</Chart>
) : null
}
</div>
)
}
}
打包发布
在项目目录下执行 bone pack
,在LD平台上按照 部署发布 文档进行云端构建并发布。
三 总结与展望
目前只有 Web 应用可以查看环境监测系统,近期产出一个专门的移动应用。
本环境检测系统是完全基于 LD 平台进行开发、部署的,可以看到这里需要开发部分的有两部分:
- 设备端的开发
- Web 应用的开发
设备端的开发这里使用的是 NodeJs,当然 LD 平台也支持 python、php、java、c 等方式的开发接入;针对 Web 应用的开发,未来 LD 也会支持 Web/移动应用的可视化搭建,针对这部分开发者完全不需要额外开发,只需要拖拖拽拽即可完成一个展示页面或者应用。