1.2.3 案例:办公室环境监测系统

优质
小牛编辑
167浏览
2023-12-01

案例:办公室环境监测系统

更新时间:2018-07-23 11:57:17

一、系统设计

项目方案分为三个模块:

  1. 在设备端基于树莓派的环境参数收集
  2. 在 LD 平台对设备数据分析、汇总
  3. 基于 LD 平台的 Web 应用开发、构建、部署

整体方案结构如下图:
结构图

最终效果预览图:

预览图

查看项目 Demo

二、具体实现

1. 设备端环境参数收集

硬件准备

  1. 准备树莓派,DHT11(温湿度传感器),PMS5003S(甲醛、PM2.5传感器)、PMS5003S 转接小板、USB 转串口
  2. 在树莓派刷入 NOBOS 固件
  3. 在树莓派上安装 Node.js (版本要求 >=8.9),安装方法
  4. 在树莓派上安装 pigpio C library:sudo apt-get install pigpio (安装过后,建议重启树莓派)
  5. 按照以下步骤装好传感器
树莓派链接传感器
  1. DHT11
DHT11 引脚GPIO 引脚PIN
ACC01
GND06
DO1711
  1. PMS5003S PMS5003S +++++ 转接小板 +++++ USB 转串口 +++++ 树莓派 USB 口
树莓派 GPIO 引脚参考

GPIO

最终完成的树莓派和传感器的照片:
树莓派

LD平台准备

按照设备接入文档,在 LD 平台创建好相关信息。关键步骤:

  1. 创建产品信息,注意选择环境监测设备
    创建产品

  2. 默认该类别里有大部分我们需要的设备信息,我们需额外添加甲醛设备
    新增甲醛设备

  3. 新增测试设备
    新增测试设备

测试设备信息

代码开发(树莓派上)

  • 创建代码目录,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应用,打开数据表查询服务。 具体步骤

  1. 新建Web应用
    新增 Web 应用

  2. 注意相关数据表查询服务开通情况

代码开发

  • 项目主要依赖模块

    • 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 平台进行开发、部署的,可以看到这里需要开发部分的有两部分:

  1. 设备端的开发
  2. Web 应用的开发

设备端的开发这里使用的是 NodeJs,当然 LD 平台也支持 python、php、java、c 等方式的开发接入;针对 Web 应用的开发,未来 LD 也会支持 Web/移动应用的可视化搭建,针对这部分开发者完全不需要额外开发,只需要拖拖拽拽即可完成一个展示页面或者应用。