树莓派-家庭健康监测-空气篇

树莓派-家庭健康监测-空气篇

最后编辑日期: 2023.7.16

1、背景和目标

室内空气中高浓度的二氧化碳会损害人类的认知能力和健康,良好室内空气质量的重要性和价值不言而喻。家庭健康监测-空气篇应运而生。

最后效果总览:
请添加图片描述

细节:
请添加图片描述

手机预览:
请添加图片描述

实现功能:

  • 二氧化碳浓度监测
  • 空气温湿度监测
  • 根据Co2浓度, 指示灯给出提示, 绿色: Co2浓度很低; 蓝色: 良好; 红色: 差。
  • 局域网内手机预览结果
1、主要硬件型号
  • 二氧化碳浓度+温湿度传感器: SCD41
  • 树莓派4b Raspberry Pi 4B
  • WS2812彩色LED
  • 0.96寸OLED显示屏
2、软件环境

树莓派: Raspberry Pi OS Lite (64-bit)
请添加图片描述

开发环境: node.js(v18.16.1)Python 3.9.2
主要是 javascript 和少量的 python.

node -v
python3 --version
3、软件结构

请添加图片描述

  • 一共3个进程,通过 WebSocket 和 HTTP 通信。
  • App作为 WebSocket 服务端,当有客户端连接后,就会定时推送数据
  • Web Server 作为 WebSocket 客户端,从App获得数据后存在自己本地,相当于数据中转站,用于数据分发。

遇到的问题:

  • LED的控制也想用js控制,但是 node-gyp 编译失败了。
  • led.py 也想用 WebSocket 通信, 也失败了😭。
4、代码实现
  • 因为serve.js是 WebSocket 的客户端,所以要等App(WebSocket 服务端)上线,有个轮训过程。
  • led.py 进程是在获取到传感器数据后才启动的,这里发现过早使用led 会有问题,突然控制不了led灯了。
4.1、serve.js
console.error("开始运行serve...");
const Koa = require('koa');
const cors = require('@koa/cors');
const Router = require("koa-router");
const BodyParser = require('koa-bodyparser');
const Static = require("koa-static");
const WebSocket = require('ws');
const { exec } = require('child_process');

function getSocketServerIsOnline(cb) {
  exec("netstat -tln | grep ':8888'", (error, stdout, stderr) => {
    if (error) {
      cb(false);
      return;
    }
    if (stdout.length === 0) {
      cb(false);
      return;
    }

    cb(true);
  });
}

let hasStartLed = false;
function startLed() {
  console.error("启动 led service...");

  if(hasStartLed) return;

  hasStartLed = true;
  exec("sudo systemctl start led.service", (error, stdout, stderr) => {
    if (error) {
      // cb(false);
      console.error(error);

      return;
    }
    if (stdout.length !== 0) {
      console.error(stdout);

      return;
    }

  });
}

let currentData = "";
let co2Data = 0;
function initSocketClient() {
  let ws = null;

  try {
    ws = new WebSocket('ws://localhost:8888');
  } catch (error) {
    console.error('连接失败:', error);
  }

  if (!ws) return;
  ws.on('open', () => {
    console.log('WebSocket connected');
  });

  let updateTimer = -1;
  ws.on('message', (message) => {
    currentData = message;

    const data = message.toString("utf-8")
    co2Data = data.split('&')[0];

    if(co2Data > 0) {
      startLed()
    }
    clearTimeout(updateTimer);
    updateTimer = setTimeout(() => {
      co2Data = 0;
    }, 10000);
  });

  ws.on('close', () => {
    console.log('WebSocket closed');
  });
}

let checkOnlineTimer = setInterval(() => {
  getSocketServerIsOnline(isOnline => {
    if (isOnline) {
      console.error("8888 is online...");

      clearInterval(checkOnlineTimer);
      initSocketClient();
    }
  })
}, 1000);

const app = new Koa();

console.error("创建 koa...");
// 启用CORS
app.use(cors({
  origin: '*',
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization']
}));


// 这个koa-bodyparser必须在router之前被注册到app对象上
app.use(BodyParser());
const router = new Router();

router.get('/getSensorData', async ctx => {
  try {
    ctx.body = currentData
  }
  catch (error) {
    console.log('get_config.error', error)
  }
})

router.get('/getCo2Data', async ctx => {
  ctx.body = co2Data
  console.log('getCo2Data...', co2Data)
})


app.use(Static(__dirname));
app.use(router.routes());
app.listen(8080)
4.2、app.js
const { exec } = require('child_process');

const OledDevice = require("./sensor/oled")
const SCD4XDevice = require('./sensor/scd4x.js');
const WebSocket = require('ws');

const { getIP, getCPUTemp } = require("./device.js")

class App {
  constructor() {
    this.data = {
      co2: 0,
      temperature: 0,
      humidity: 0,
      ip: null,
      cpuTemp: null
    };

    this.oled = new OledDevice()
    this.scd4x = new SCD4XDevice()

    let hasInit = false;
    let timer = -1;
    const checkNetOk = (ip)=>{
      if(hasInit === false && ip && ip.indexOf("192.168.124.") !== -1) {
        hasInit = true;
        this.initWebSocketServe();

        // 启动 webserver 和 websocket client
        exec('node ../serve/serve.js')

        this.loopPiData()
        this.initSensor();
        clearInterval(timer)
      }
    }

    timer = setInterval(() => {
      getIP(ip => {
        this.data.ip = ip;
        checkNetOk(ip)
      })
    }, 500);


  }

  oledShowData() {
    this.oled.writeString({
      size: 2,
      x: 1,
      y: 1,
      text: this.data.co2 + " ppm"
    })

    this.oled.writeString({
      size: 2,
      x: 1,
      y: 24,
      text: this.data.temperature + " 'c"
    })

    this.oled.writeString({
      size: 2,
      x: 1,
      y: 48,
      text: this.data.humidity + " %"
    })
  }

  sendDataBySocket(data) {
    if (!this.ws) { return }
    this.ws.send(data)
  }

  sendData() {
    let sendDataContext = `${this.data.co2}&${this.data.temperature}&${this.data.humidity}`;
    if (this.data.ip && this.data.cpuTemp) {
      sendDataContext += `&${this.data.ip}&${this.data.cpuTemp}`
    }
    this.sendDataBySocket(sendDataContext)
  }

  loopPiData() {
    setInterval(() => {
      getCPUTemp(temp => {
        this.data.cpuTemp = temp.match(/\d+\.\d+/)[0];
        this.sendData()
      })
    }, 1000);
  }

  loopSensorData() {
    setInterval(() => {
      this.scd4x.readSensorData()
        .then(async data => {
          this.data.co2 = data.co2;
          this.data.temperature = data.temperature.toFixed(2);
          this.data.humidity = data.humidity.toFixed(2);
          this.oledShowData()
          this.sendData()
        });
    }, 5000);
  }

  initWebSocketServe() {
    const wss = new WebSocket.Server({ port: 8888 });

    wss.on('connection', (ws) => {
      console.log('WebSocket connected');

      // ws.on('message', (message) => {
      //   console.log(`Received message: ${message}`);
      //   ws.send(`Server received message: ${message}`);
      // })
      ws.on('close', () => {
        this.ws = null;
        console.log('WebSocket closed')
      })
      this.ws = ws;
    })
  }

  async initSensor() {
    this.oled.writeString({
      size: 2,
      x: 1,
      y: 1,
      text: "scd4x init..."
    })
    await this.scd4x.initialize();
    await this.scd4x.startPeriodicMeasurement();

    this.oled.writeString({
      size: 2,
      x: 1,
      y: 1,
      text: "scd4x ready..."
    })

    this.oled.clear();
    this.loopSensorData()
  }
}

new App()
4.3、led.py

当data大于0才会启动该进程,不然led失去控制当自动运行的时候。

import time
import MyHttp
import MyLed

data = 0

def getData(_data):
  global data  # 声明data为全局变量
  data = int(_data)

def breathLed():
    MyLed.set_brightness(20)
    MyLed.set_pixel_color([255,0,0])
    start_time = time.time()
    while (time.time() - start_time) < 5:
        MyLed.breathe()

while True:
    MyLed.set_brightness(20)
    MyHttp.get_request("http://192.168.124.17:8080/getCo2Data", getData)

    if(data == 0):
        breathLed()
        print("等待数据...")
    else :
        time.sleep(2)
        print(data)

        if data < 500:
            MyLed.set_pixel_color([0,255,0])
        elif 500 <= data < 800:
            MyLed.set_pixel_color([0,0,255])
        elif 800 <= data < 2000:
            MyLed.set_pixel_color([255,0,0])
        else:
            # 异常值
            MyLed.set_pixel_color([255,255,0])

5、自动运行

使用守护进程, /etc/systemd/system 目录下:

sudo nano /etc/systemd/system/ web.service
sudo nano /etc/systemd/system/ app.service
sudo nano /etc/systemd/system/ led.service

web.service:

[Unit]
Description=Run my scripts on startup

[Service]
ExecStart=/bin/bash -c "node /home/pi/Documents/home/app.js"
WorkingDirectory=/home/pi/Documents/home
Restart=always
User=pi

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable web.service
sudo systemctl start web.service
资料

raspberry pi
python lib

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值