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

news2024/12/24 9:48:30

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

最后编辑日期: 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/758527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis数据类型的常用命令和应用场景

1. Redis数据类型概述 Redis 常见的数据类型有10 种&#xff0c;其中包括 5 种基本数据类型&#xff1a;Strings(字符串)、Lists(列表)、Sets(集合)、Hashes(散列)、Sorted sets(Zset有序集合)。除了基本数据类型外&#xff0c;还有 5 种常用数据类型&#xff1a;Bitmaps 、Hy…

《二叉搜索树OJ》

文章目录 1、 [根据二叉树创建字符串](https://leetcode.cn/problems/construct-string-from-binary-tree/)2、 [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)3、 [二叉树的层序遍历 II](https://leetcode.cn/problems/binary-tree-le…

5.3 Python高级特性之-列表生成式、生成器、迭代器

一、 列表生成式 是Python内置的非常简单却强大的可以用来创建list的生成式 具体可根据如下案例理解&#xff0c;且代码也是可用的""" 1、 生成[0,1,2,3,4,5,6]这样列表 """ print(list(range(0, 7))) """ 2、 生成[0&#xff0…

MyBatis入门案列

MyBatis入门案列 开发Mybatis程序的步骤&#xff1a; 1、配置mybatis conf.xml: 配置数据库信息 和 需要加载的映射文件 &#xff12;、表 - 类 映射文件xxMapper.xml: 增删改查标签<select> &#xff13;、测试类: session.selectOne(“需要查询的SQL的namespace.id”…

jenkins 采用ssh方式连接gitlab连接不上

一、gitlab 添加jenkins服务器的公钥 jenkins 生成秘钥命令 ssh-keygen -t rsa2.jenkins 秘钥地址&#xff1a; cd /root/.ssh3.复制公钥 到gitlab 添加 cat id_rsa_pub4.添加私钥到jenkins cat id_rsa5.绑定&#xff08;顺利的话到这里就结束了&#xff09; &#xff0…

oc基本控件2

// // ViewController.m // OcDemoTest // // Created by Mac on 2023/7/14. //#import "ViewController.h"interface ViewController () // label property (weak, nonatomic) IBOutlet UIImageView *imageView; // Use of undeclared identifier // 全局propert…

基于Open3D的点云处理11-三维点云表面重建

点云表面重建 &#xff08;1&#xff09;显式建模方法&#xff1a; Explicit reconstruction 例如&#xff1a;Delaunay 三角网、Alpha shapes &#xff08;2&#xff09;隐式建模方法&#xff1a; Implicit reconstruction 例如&#xff1a;径向基函数法、移动最小二乘法、泊…

【QT】——Base64加解密

介绍 用 记事本 打开 exe、jpg、pdf 这些文件时&#xff0c;我们都会看到一大堆乱码&#xff0c;因为二进制文件包含很多无法显示和打印的字符。如果要让记事本这样的文本处理软件 能 处理二进制数据&#xff0c;如使用 json 保存二进制信息&#xff0c;需要先把数据先做一个 …

微服务 云原生:搭建 K8S 集群

为节约时间和成本&#xff0c;仅供学习使用&#xff0c;直接在两台虚拟机上模拟 K8S 集群搭建 踩坑之旅 系统环境&#xff1a;CentOS-7-x86_64-Minimal-2009 镜像&#xff0c;为方便起见&#xff0c;直接在 root 账户下操作&#xff0c;现实情况最好不要这样做。 基础准备 关…

数据在内存中的存储1(C语言进阶)

数据在内存中的存储 1.数据类型介绍1.1类型的基本归类&#xff1a;整形家族浮点数家族构造类型指针类型空类型 2.整形在内存中的存储2.1 原码、反码、补码2.2 大小端介绍为什么有大端和小端&#xff1a; 我们今天来学习数据在内存中的存储 1.数据类型介绍 前面我们已经学习了基…

TCP三次握手四次挥手

文章目录 TCP 三次握手和四次挥手1、三次握手过程image-202307100943094972、为什么要三次握手3、第 2 次握手传回了 ACK&#xff0c;为什么还要传回 SYN&#xff1f;4、断开连接-TCP 四次挥手5、为什么要四次挥手6、丢包问题7、为什么不能把服务器发送的 ACK 和 FIN 合并起来&…

qt开发技巧之嵌入式linux qt按钮点击触发两次

1.问题 移植qt5.12.9到嵌入式linux系统&#xff0c;tslib作为触摸输入&#xff0c;开发平台是imx6ull&#xff0c;点击pushbutton按钮会出现触发两次点击的情况。 2.解决 vi /etc/profile&#xff0c;在 /etc/profile里添加环境变量&#xff0c;禁止QT自带输入检测&#xff0…

vue-watch监听器

1. 概述 watch是 vue 中常用的监听器&#xff0c;它主要用于侦听数据的变化&#xff0c;在数据发生变化的时候执行一些操作。 Vue官网很明确的建议我们这样使用watch侦听属性&#xff1a;当需要在数据变化时执行异步或开销较大的操作时&#xff0c;这个方式是最有用的。 2. 用…

【通览一百个大模型】XLNet(Google)

XLNet 欢迎订阅阅读【大模型&NLP&算法】。 作者&#xff1a;王嘉宁本文章内容为转载或整理&#xff1b;仓库链接&#xff1a;https://github.com/wjn1996/LLMs-NLP-Algo 【通览一百个大模型】XLNet&#xff08;Google&#xff09; 作者&#xff1a;王嘉宁&#xff0…

lua脚本语言学习笔记

Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。 因为我们使用redis的时候一般要写lua脚本&#xff0c;这篇文章就介绍一下lua脚本语言的基础用…

Python 算法基础篇:时间复杂度和空间复杂度简介

Python 算法基础篇&#xff1a;时间复杂度和空间复杂度简介 引言 1. 时间复杂度 a ) 常见的时间复杂度 b ) 时间复杂度示例 2. 空间复杂度 a ) 常见的空间复杂度 b ) 空间复杂度示例 结论 引言 在学习和分析算法时&#xff0c;时间复杂度和空间复杂度是两个关键概念。它们帮助…

【Python】类型注解 ④ ( 函数类型注解 | 函数形参类型注解语法 | 函数返回值类型注解 )

文章目录 一、函数形参类型注解1、函数中由于类型缺省导致的提示问题2、函数形参类型注解语法3、代码示例 - 函数形参类型注解 二、函数返回值类型注解1、函数返回值类型注解语法2、代码示例 - 函数返回值类型注解 一、函数形参类型注解 1、函数中由于类型缺省导致的提示问题 由…

LSTM对比Bi-LSTM的电力负荷时间序列预测(Matlab)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

小程序api的promise化

小程序根目录cmd运行安装命令 npm install --save miniprogram-api-promise1.0.4 安装完成之后先到根目录中删除miniprogram_npm文件夹(不删除构建npm时可能会出现问题) 删除之后再在工具中点击构建npm 构建成功之后会看到根目录中重新出现了miniprogram_npm文件夹 在app.j…

P1747 好奇怪的游戏

好奇怪的游戏 题目背景 《爱与愁的故事第三弹shopping》娱乐章。 调调口味来道水题。 题目描述 爱与愁大神坐在公交车上无聊&#xff0c;于是玩起了手机。一款奇怪的游戏进入了爱与愁大神的眼帘&#xff1a;***&#xff08;游戏名被打上了马赛克&#xff09;。这个游戏类似…