智能室内空气质量与垃圾监测系统
组件与物料
| Raspberry Pi 3 Model B | ×1 |
| Arduino UNO | ×1 |
| NodeMCU ESP8266 开发板 | ×1 |
| DHT11 温湿度传感器 (4针) | ×1 |
| Seeed Grove - 气体传感器 (MQ2) | ×1 |
| MQ-7 一氧化碳气体传感器 | ×1 |
| 超声波测距模块 HC-SR04 | ×1 |
| 树莓派电源适配器 | ×1 |
| HDMI 线 | ×1 |
| USB-A 转 B 线 | ×1 |
| 杜邦线 (若干) | ×1 |
| 5V 电池 | ×1 |
应用与在线服务
- IOTA Tangle (分布式账本)
- Node.js
- Raspberry Pi Raspbian 系统
- Arduino IDE
- MQTT 消息协议
项目简介
1. 简介
楼宇自动化 是通过楼宇管理系统 (BAS) 对供暖、通风、空调、照明等系统进行集中自动控制,目标是提升居住舒适度、提高系统运行效率、降低能耗和运营成本、延长设备寿命。
IOTA 是面向物联网的开源分布式记账技术,旨在实现安全的数据与价值交换。其核心创新是用有向无环图 (DAG) 架构的 Tangle 替代传统区块链,实现零手续费、低延迟、更好扩展性。IOTA 正在开发中,预计 2019 年大规模落地。
树莓派 3B 规格: 四核 64 位 ARM Cortex-A53 @1.2GHz,GPU VideoCore IV @400MHz,1GB LPDDR2 内存,4 个 USB、HDMI、复合视频输出,百兆以太网 + 802.11n WiFi。
Arduino UNO 特性: ATmega328 主控,5V 工作电压,建议 7-12V 输入,14 个数字 IO (6 路 PWM),6 个模拟输入,IO 口最大 40mA,3.3V 引脚 50mA。
NodeMCU (ESP8266) 特性: 可编程 WiFi 模块,Arduino 风格硬件 IO,支持 Arduino IDE 开发,板载 USB-TTL,10 个 GPIO (D0-D10),支持 PWM、I2C、SPI、1-Wire、ADC 等。
本项目分三个版本,章节如下:
- 简介
- 软件安装
- 室内空气质量监测系统 v1
- 室内空气质量监测系统 v2
- 垃圾监测系统
- 总结
2. 软件安装
a) 在树莓派安装 Raspbian
使用 NOOBS 安装,下载地址:官网
b) 安装 Node.js
$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
$ sudo apt-get install -y nodejs
c) 下载并编译 BCM2835 库 (用于驱动 DHT11)
$ wget https://www.airspayce.com/mikem/bcm2835/bcm2835-1.56.tar.gz
$ tar zxvf bcm2835-1.56.tar.gz
$ cd bcm2835-1.56
$ ./configure
$ make
$ sudo make check
$ sudo make install
d) 下载并安装 indoor-air-quality-iota 项目
$ cd ~
$ git clone https://github.com/guillengap/indoor-air-quality-and-garbage-monitoring-system.git
$ cd indoor-air-quality-and-garbage-monitoring-system
$ npm install
e) 安装 serialport 模块
$ npm install serialport
f) 安装 Arduino IDE
3. 室内空气质量监测系统 v1
读取 DHT11 数据并通过 MAM (Masked Authenticated Messaging) 发布到 IOTA Tangle。
工作原理
a) 项目包含 3 个 JS 文件:
- sensor.js:读取并显示 DHT11 温湿度数据
- mam_sensor.js:读取 DHT11 数据并通过 MAM 发布到 Tangle
- mam_receive.js:从 Tangle 读取并显示数据
b) 全节点端点和侧链密钥硬编码在 JS 文件中。按 CTRL+Z 停止脚本。
c) 连接 Arduino UNO 与 DHT11 传感器
d) 运行 node sensor.js 测试树莓派能否读取 DHT11 数据
$ node sensor.js
若正常显示温湿度,进入下一步。
e) 运行 node mam_sensor.js 将数据存入 Tangle
$ node mam_sensor.js
测试节点:https://potato.iotasalad.org:14265 (更多节点见 iotasalad.org/nodes)
发送数据示例:
- 城市:MEXICO
- 楼宇:65
- 时间 (UTC):年/月/日 时:分:秒
- 数据:温度 (℃/℉) 和相对湿度 (%)
f) 另开终端运行 node mam_receive.js <your_root> 查看存储数据
$ node mam_receive.js AMBTIKZKEVEEQFCUGIDUOLDOXL9OZQ9GOMNBFYHVRAUCAMHDQQJBSNAMFZYRLTUVAHFDINHTTFKIPELIG
测试图片
4. 室内空气质量监测系统 v2
读取 DHT11、MQ-2 (液化气)、MQ-7 (一氧化碳) 三传感器数据,通过 MAM 发布到 Tangle。
工作原理
a) 项目包含 5 个文件:
- sensorArduino.ino:采集三传感器数据
- listportsArduino.ino:列出 Arduino 可用串口
- sensorArduino.js:读取并显示三传感器数据
- mam_sensorArduino.js:读取数据并通过 MAM 发布到 Tangle
- mam_receiveArduino.js:从 Tangle 读取并显示数据
b) 节点端点和侧链密钥硬编码。按 CTRL+Z 停止脚本。
c) 连接 Arduino UNO 与 DHT11、MQ-2、MQ-7 传感器
d) 安装传感器库 (DHT11、MQ-2、MQ7) 到 Arduino IDE
e) 上传 sensorArduino.ino 到 Arduino UNO,选择正确串口 (如 /dev/ttyACM0)
f) 运行 node listportsArduino.js 验证串口
$ node listportsArduino.js
g) 运行 node mam_sensorArduino.js 存储数据到 Tangle
$ node mam_sensorArduino.js
发送数据示例:
- 城市:NY
- 楼宇:13
- 时间 (UTC)
- 数据:LPG 浓度 (ppm)、CO 浓度 (ppm)、温度 (℃)、相对湿度 (%)
h) 另开终端运行 node mam_receiveArduino.js <your_root> 查看数据
测试图片
5. 垃圾监测系统
用 NodeMCU 读取 SRF05 超声波传感器数据,通过 MQTT 发送到树莓派,再经 MAM 存入 IOTA Tangle。
工作原理
a) 项目包含 3 个文件:
- trashcan.ino:计算垃圾桶填充百分比,通过 MQTT 发送到树莓派
- nodemcu_mqtt_mam.js:读取数据并通过 MAM 发布到 Tangle
- nodemcu_mam_receive.js:从 Tangle 读取并显示数据
b) 节点端点和侧链密钥硬编码。按 CTRL+Z 停止脚本。
c) 连接 NodeMCU 与 SRF05 超声波传感器
d) 在 Arduino IDE 安装 ESP8266 开发板包
附加开发板管理器网址: https://arduino.esp8266.com/stable/package_esp8266com_index.json
工具 -> 开发板 -> 开发板管理器 -> 搜索 esp8266 -> 安装最新版
e) 安装 PubSubClient 库 (MQTT 客户端)
- 草图 -> 包含库 -> 管理库 -> 搜索 PubSub -> 安装 PubSubClient (Nick O'Leary)
f) 在树莓派安装 Mosquitto (MQTT Broker)
$ cd ~
$ wget https://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
$ apt-key add mosquitto-repo.gpg.key
$ cd /etc/apt/sources.list.d/
$ wget https://repo.mosquitto.org/debian/mosquitto-stretch.list
$ cd ~
$ wget https://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u11_armhf.deb
$ sudo dpkg -i libssl1.0.0_1.0.1t-1+deb8u11_armhf.deb
$ wget https://ftp.nz.debian.org/debian/pool/main/libw/libwebsockets/libwebsockets3_1.2.2-1_armhf.deb
$ sudo dpkg -i libwebsockets3_1.2.2-1_armhf.deb
$ sudo apt-get install mosquitto mosquitto-clients
# 若出错可尝试: npm install mqtt --save
$ sudo reboot
$ mosquitto -v # 验证版本
$ sudo /etc/init.d/mosquitto start/stop # 启动/停止
$ ifconfig # 查看树莓派 IP
g) 上传 trashcan.ino 到 NodeMCU,配置关键参数:
- 开发板:NodeMCU 1.0
- 串口波特率:115200
- WiFi SSID 和密码
- MQTT 服务器 IP (树莓派 IP)
- 垃圾桶高度 (如 25cm)
h) 运行 node nodemcu_mqtt_mam.js 存储数据到 Tangle
$ node nodemcu_mqtt_mam.js
发送数据示例:
- 城市:BERLIN
- 楼宇:7
- 时间 (UTC)
- 数据:垃圾桶填充百分比
i) 另开终端运行 node mam_receiveArduino.js <your_root> 查看数据
可用节点示例:https://durian.iotasalad.org:14265 (更多见 iotasalad.org/nodes)
测试图片
6. 总结
室内空气质量监测系统 v1: 运行良好,在墨西哥城某楼宇监测温湿度,使用树莓派 3B + DHT11 + JavaScript + IOTA 协议完成基础测试。
室内空气质量监测系统 v2: 增加 MQ-2 (LPG) 和 MQ-7 (CO) 传感器,模拟纽约某楼宇数据,通过 Arduino UNO + 树莓派经 MAM 上传 Tangle。适用于使用液化气场所及火灾产生一氧化碳的工业环境。
垃圾监测系统: 目标是监控柏林等智能建筑的垃圾堆积情况,通过超声波测量填充率,及时清运避免异味和卫生隐患。
代码文件
- sensor.js
- mam_sensor.js
- mam_receive.js
- mam_sensorArduino.js
- mam_receiveArduino.js
sensor.js (JavaScript) - v1 测试 DHT11
const sensor = require('node-dht-sensor');
const TIMEINTERVAL = 10; // 秒
const SENSORTYPE = 11; // DHT11
const GPIOPIN = 4; // 树莓派 GPIO 引脚
function readSensor() {
sensor.read(SENSORTYPE, GPIOPIN, function(err, temperature, humidity) {
if (!err) {
console.log('temp: ' + temperature.toFixed(1) + 'C, ' + 'humidity: ' + humidity.toFixed(1) + '%');
} else {
console.log(err);
}
});
}
readSensor();
setInterval(readSensor, TIMEINTERVAL * 1000);
mam_sensor.js (JavaScript) - v1 发布到 Tangle
const sensor = require('node-dht-sensor');
const Mam = require('./lib/mam.client.js');
const IOTA = require('iota.lib.js');
const moment = require('moment');
const iota = new IOTA({ provider: 'https://potato.iotasalad.org:14265' });
const MODE = 'restricted';
const SIDEKEY = 'mysecret';
const SECURITYLEVEL = 3;
const TIMEINTERVAL = 30;
const SENSORTYPE = 11;
const GPIOPIN = 14;
let mamState = Mam.init(iota, undefined, SECURITYLEVEL);
if (MODE == 'restricted') {
const key = iota.utils.toTrytes(SIDEKEY);
mamState = Mam.changeMode(mamState, MODE, key);
} else {
mamState = Mam.changeMode(mamState, MODE);
}
const publish = async function(packet) {
const trytes = iota.utils.toTrytes(JSON.stringify(packet));
const message = Mam.create(mamState, trytes);
mamState = message.state;
console.log('Root: ', message.root);
console.log('Address: ', message.address);
await Mam.attach(message.payload, message.address);
return message.root;
}
function readSensor() {
sensor.read(SENSORTYPE, GPIOPIN, async function(err, temperature, humidity) {
if (!err) {
const city = 'MEXICO';
const building = '65';
const dateTime = moment().utc().format('YYYY/MM/DD hh:mm:ss');
const data = `{Temperature: ${temperature.toFixed(1)}°C (${(temperature.toFixed(1)*1.8)+32}°F), Humidity: ${humidity.toFixed(1)}%}`;
const json = {"data": data, "dateTime": dateTime, "building": building, "city": city};
const root = await publish(json);
console.log(`City: ${json.city}, Building: ${json.building}, Time: ${json.dateTime} UTC, Data: ${json.data}, root: ${root}`);
} else {
console.log(err);
}
});
}
readSensor();
setInterval(readSensor, TIMEINTERVAL * 1000);
mam_receive.js (JavaScript) - v1 接收数据
const Mam = require('./lib/mam.client.js');
const IOTA = require('iota.lib.js');
const iota = new IOTA({ provider: 'https://potato.iotasalad.org:14265' });
const MODE = 'restricted';
const SIDEKEY = 'mysecret';
let root, key;
const args = process.argv;
if (args.length != 3) {
console.log('Missing root as argument: node mam_receive.js ');
process.exit();
} else if (!iota.valid.isAddress(args[2])) {
console.log('You have entered an invalid root: ' + args[2]);
process.exit();
} else {
root = args[2];
}
let mamState = Mam.init(iota);
if (MODE == 'restricted') {
key = iota.utils.toTrytes(SIDEKEY);
mamState = Mam.changeMode(mamState, MODE, key);
} else {
mamState = Mam.changeMode(mamState, MODE);
}
const executeDataRetrieval = async function(rootVal, keyVal) {
let resp = await Mam.fetch(rootVal, MODE, keyVal, function(data) {
let json = JSON.parse(iota.utils.fromTrytes(data));
console.log(`City: ${json.city}, Building: ${json.building}, Time: ${json.dateTime} UTC, Data: ${json.data}`);
});
}
executeDataRetrieval(root, key);
mam_sensorArduino.js (JavaScript) - v2 发布多传感器数据
const SerialPort = require('serialport');
const moment = require('moment');
const IOTA = require('iota.lib.js');
const Mam = require('./lib/mam.client.js');
const iota = new IOTA({ provider: 'https://potato.iotasalad.org:14265' });
const MODE = 'restricted';
const SIDEKEY = 'mysecret';
const SECURITYLEVEL = 3;
const PORTNAME = '/dev/ttyACM1';
const port = new SerialPort(PORTNAME, { baudRate: 9600, autoOpen: true });
const Readline = SerialPort.parsers.Readline;
const parser = port.pipe(new Readline({ delimiter: '\r\n' }));
let mamState = Mam.init(iota, undefined, SECURITYLEVEL);
if (MODE == 'restricted') {
const key = iota.utils.toTrytes(SIDEKEY);
mamState = Mam.changeMode(mamState, MODE, key);
} else {
mamState = Mam.changeMode(mamState, MODE);
}
const publish = async function(packet) {
const trytes = iota.utils.toTrytes(JSON.stringify(packet));
const message = Mam.create(mamState, trytes);
mamState = message.state;
console.log('Root: ', message.root);
console.log('Address: ', message.address);
await Mam.attach(message.payload, message.address);
return message.root;
}
port.on('open', showPortOpen);
parser.on('data', readSerialData);
port.on('close', showPortClose);
port.on('error', showError);
function showPortOpen() {
console.log('Serial port open. Data rate: ' + port.baudRate);
}
async function readSerialData(data) {
let json = {};
const time = moment().utc().format('YYYY/MM/DD hh:mm:ss');
const city = 'NY';
const building = '13';
json['time'] = time;
json['city'] = city;
json['building'] = building;
json['data'] = `{${data}}`;
console.log('json = ', json);
const root = await publish(json);
}
function showPortClose() { console.log('Serial port closed.'); }
function showError(error) { console.log('Serial port error: ' + error); }
mam_receiveArduino.js (JavaScript) - v2 接收数据
const Mam = require('./lib/mam.client.js');
const IOTA = require('iota.lib.js');
const iota = new IOTA({ provider: 'https://potato.iotasalad.org:14265' });
const MODE = 'restricted';
const SIDEKEY = 'mysecret';
let root, key;
const args = process.argv;
if (args.length != 3) {
console.log('Missing root as argument: node mam_receive.js ');
process.exit();
} else if (!iota.valid.isAddress(args[2])) {
console.log('You have entered an invalid root: ' + args[2]);
process.exit();
} else {
root = args[2];
}
let mamState = Mam.init(iota);
if (MODE == 'restricted') {
key = iota.utils.toTrytes(SIDEKEY);
mamState = Mam.changeMode(mamState, MODE, key);
} else {
mamState = Mam.changeMode(mamState, MODE);
}
const executeDataRetrieval = async function(rootVal, keyVal) {
let resp = await Mam.fetch(rootVal, MODE, keyVal, function(data) {
let json = JSON.parse(iota.utils.fromTrytes(data));
console.log(`City: ${json.city}, Building: ${json.building}, Time: ${json.time} UTC, Data: ${json.data}`);
});
executeDataRetrieval(resp.nextRoot, keyVal);
}
executeDataRetrieval(root, key);
原理图
原理图
原理图
原理图
制造工艺
- 树莓派打造空气质量与气象站:InfluxDB+Grafana可视化
- 树莓派+Nagios高效监控机房温度
- 树莓派与 Python 构建远程温度传感器:分步实战指南
- 家庭能源中心:树莓派与 Moteino 监测光伏、光热与室温
- 树莓派 DIY 红外运动传感器:从原理到代码的完整指南
- 树莓派光敏电阻日志与报警系统:低功耗光照传感器 Python 实现
- 树莓派多传感器温度光照监测系统:DS18B20 实战构建
- 基于树莓派的卫生间占用传感器:通过 Twilio 实现语音与短信提醒
- 树莓派传感器与执行器控制实战:加速度计、舵机与数据流
- 树莓派自动化气雾栽培系统:湿度传感器智能控制
- 树莓派家庭安防系统:PIR 运动检测 + 摄像头邮件报警
- 基于 Helium 网络的太阳能供电空气质量监测站:设计与部署全指南