一文简述建立简单噪音监测平台搭建

124 阅读6分钟

前言:

本文将简单展示建立一个最基本且简易噪音监测站在软件上面的实践过程,供大家学习参考。虽然攥写本文的我并不懂代码及硬件,写本文的主要目的是为了解决不太可能会被解决的噪音问题,如果问题不能被解决那么最起码我需要记录下关于它的实际情况。

长话短说下面是真实的案例展示求助网址: 夜间民居噪音结果记录

所需要的能力:

  1. 树莓派4B raspberrypi-os-lite-64-debian

  2. 噪音分贝计 GM1356

  3. InfluxDB 时序数据库

  4. 使用远程ssh

开始搭建

将GM1356与RaspberryPi通过USB线缆连接,并完成一切系统配置及初始化操作,下面开始找参考

首先是GM1356的软件安装

github.com/dobra-noc/g…

github.com/pvachon/gm1…

github.com/gdsports/ba…

以上三个是可以在debian-arm-64上正确运行的,还有些就不列了,总之大概这些在这个linux环境中都是可以用的。有一个windows上的使用python,其余的不是ruby就是python。

如果你使用 pvachon 的方案,做的时候不要啊忘记编译它所提供的

github.com/pvachon/tsl…

在此选用了 bafx3608 这个方案 ,实际上他确实和gm1356 兼容,但不要忘记使用pip安装包括但不限于如下

python3-paho-mqtt python3-typeshed python3-libusb1 python3-usb1
在新安装的python3中使用虚拟环境并写入终端的环境变量

#nano /home/raspi/.bashrc
source /<hidden>/bin/activate

复制一份bafx3608目录下的bafx3608.py 为sound_level.py备用

确保你的bafx3608.py是能正常使用的,开始下一步安装数据库(往下看)

influxdata | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

虽然使用apt包管理器也可以但是我觉得使用aptitude 更靠谱

安装完成后使用它的网页面板进一步安装,如果显示404说明装错版本了,换新版本的influxdb2

记得刚刚复制的文件吗?修改它 `

# 修改头文件(文件开头)
import time
import sys
import struct
import getopt
import threading
import usb1
from influxdb_client import InfluxDBClient, Point, WritePrecision, WriteOptions

# 增加下面的在合适位置(开头也行)
# InfluxDB配置
bucket = "sensor"
org = "raspi"
token = "<hidden>"
url = "http://localhost:8086"  # InfluxDB的URL

# 在def reading_callback_str(decibels, _fast, maxmode, weight_c, db_range):中加入
        # 在这里将数据写入InfluxDB
        point = (
            Point("sound_level")
            .tag("mode", weight_c)
            .field("decibels", decibels_float)
            .tag("max_mode", maxmode)
            .tag("range", db_range)
            .time(time.time_ns(), WritePrecision.NS)
        )
        write_api.write(bucket=bucket, org=org, record=point)
        print(decibels, "dB" + weight_c, maxmode, db_range)`

看看数据库中是否出现了数据,再做面板设置看看是否正常

将sound_level.py设置为开机自启

# sudo nano /etc/systemd/system/sound_level.service 
[Unit]
Description=My Python Script Service
After=network.target

[Service]
ExecStart=/home/raspi/py/venv/bin/python /home/raspi/bafx3608/sound_level.py
Restart=always
User=raspi
Group=raspi
Environment=PATH=/usr/bin:/usr/local/bin
WorkingDirectory=/usr/local/bin

[Install]
WantedBy=multi-user.target

cdn+ bucket建图床供静态页面展示

Blog 图床方案:Backblaze B2 (私密桶) + Cloudflare Workers + PicGo | Standat's

设置图片上传Bucket 参考下文

github.com/Backblaze/B…

b2-command-line-tool.readthedocs.io/en/master/q…

确保连接正确将结果截图并发布上传的脚本

# coding: utf-8
import time
import os
import pickle
import schedule
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from contextlib import contextmanager
from datetime import datetime, timedelta
from PIL import Image
from io import BytesIO
import signal
import sys
import subprocess

# 全局变量用于截图目录
screenshot_dir = "/home/raspi/bafx3608/screenshots"
dashboard_url = "http://localhost:8086/orgs/<hidden>/dashboards/<hidden>?lower=now%28%29+-+12h"

@contextmanager
def get_driver():
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-dev-shm-usage")
    chrome_options.add_argument("--ignore-certificate-errors")

    driver = None
    try:
        print("启动 WebDriver 实例...")
        driver = webdriver.Chrome(service=Service("/usr/lib/chromium-browser/chromedriver"), options=chrome_options)
        driver.set_window_size(2560, 1440)
        yield driver
    finally:
        if driver:
            time.sleep(2)  # 截图后等待 2 秒再关闭 WebDriver
            driver.quit()
            print("WebDriver 实例已清理。")

def login(driver):
    print("打开登录页面...")
    driver.get("http://localhost:8086/signin?returnTo=/orgs/<hidden>dashboards/<hidden>")
    print(f"当前页面URL: {driver.current_url}")

    # 等待页面加载并确保登录表单元素存在
    print("等待登录表单加载...")
    WebDriverWait(driver, 4).until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, 'input[name="username"]'))
    )
    
    # 填写用户名和密码
    print("填写用户名和密码...")
    username_field = driver.find_element(By.CSS_SELECTOR, 'input[name="username"]')
    password_field = driver.find_element(By.CSS_SELECTOR, 'input[name="password"]')
    submit_button = driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
    
    username_field.send_keys("<hidden>")  # 替换为实际的用户名
    password_field.send_keys("<hidden>")  # 替换为实际的密码
    submit_button.click()
    # 等待重定向到仪表盘页面
    print("等待重定向到仪表盘页面...")
    time.sleep(2)
    print(f"当前页面URL: {driver.current_url}")

def capture_screenshot():
    with get_driver() as driver:
        # 加载目标仪表盘页面
        driver.get(dashboard_url)

        # 检查是否已经登录,如果未登录则执行登录操作
        if "signin" in driver.current_url:
            print("检测到未登录状态,重新登录...")
            login(driver)
            driver.get(dashboard_url)
        else:
            print("检测到登录状态,再次重新登录...")
            login(driver)
            driver.get(dashboard_url)
        # 等待页面完全加载
        time.sleep(4)

        # 打印截图前的页面URL
        print(f"截图前的页面URL: {driver.current_url}")

        # 等待目标div元素加载并可见
        try:
            element = WebDriverWait(driver, 10).until(
                EC.visibility_of_element_located((By.CSS_SELECTOR, 'div[data-testid="cell dbA"]')) #替换为实际设置面板数据的名称
            )
        except TimeoutException:
            print("目标元素在指定时间内没有出现。")
            return
        
        # 截取整个页面
        png = driver.get_screenshot_as_png()
        im = Image.open(BytesIO(png))

        # 获取目标 div 的位置和大小
        location = element.location
        size = element.size
        left = location['x']
        top = location['y']
        right = location['x'] + size['width']
        bottom = location['y'] + size['height']

        # 裁剪图像
        im = im.crop((left, top, right, bottom))

        # 生成截图文件路径
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        screenshot_path = os.path.join(screenshot_dir, f"sound_{timestamp}.png")

        # 保存裁剪后的截图
        im.save(screenshot_path)
        
        # 打印截图后的页面URL
        print(f"截图后的页面URL: {driver.current_url}")
        print(f"截图已保存到 {screenshot_path}")

def cleanup_old_screenshots():
    now = datetime.now()
    cutoff = now - timedelta(days=365)

    for filename in os.listdir(screenshot_dir):
        if filename.endswith(".png"):
            file_path = os.path.join(screenshot_dir, filename)
            file_mtime = datetime.fromtimestamp(os.path.getmtime(file_path))
            if file_mtime < cutoff:
                os.remove(file_path)
                print(f"删除过期截图: {file_path}")

def generate_html():
    print("生成 HTML 页面...")
    html_file_path = os.path.join(screenshot_dir, "index.html")
    
    # 如果存在旧的 index.html 文件,先删除
    if os.path.exists(html_file_path):
        os.remove(html_file_path)
        print("旧的 index.html 已删除。")
    
    # 创建新的 index.html 文件(自己修改下面的部分)
    with open(html_file_path, "w") as f:
        f.write("<html lang='zh-CN'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no'><title>夜间民居噪音结果记录</title></head><body>\n")
        f.write("<h1>夜间民居噪音结果记录</h1>\n")
        # 使用 reverse=True 使得新文件排在前面
        for filename in sorted(os.listdir(screenshot_dir), reverse=True):
            if filename.endswith(".png"):
                relative_path = os.path.join("./", filename)
                f.write(f'<div><img src="{relative_path}" alt="{filename}" style="max-width: 100%;"><p>{filename}</p></div>\n')
        f.write("</body></html>")
    
    print(f"HTML 页面已生成: {html_file_path}")

def sync_to_b2(local_dir="/home/raspi/bafx3608/screenshots", b2_bucket_url="b2://<bucket-name>/sound"):
    """
    将本地目录同步到 Backblaze B2 存储桶。
    如果同步过程中出现错误,将及时退出程序。

    :param local_dir: 本地目录路径
    :param b2_bucket_url: B2 存储桶 URL
    """
    command = ["b2", "sync", local_dir, b2_bucket_url]

    try:
        print(f"开始同步:{local_dir} -> {b2_bucket_url}")
        result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        print(f"同步完成:{result.stdout.decode('utf-8')}")
    except subprocess.CalledProcessError as e:
        print(f"同步失败:{e.stderr.decode('utf-8')}", file=sys.stderr)
        sys.exit(1)

def daily_job():
    try:
        print("开始截图任务...")
        capture_screenshot()
        cleanup_old_screenshots()
        generate_html()
        sync_to_b2()
        print("截图任务完成。")
    except Exception as e:
        print(f"任务执行时发生错误: {e}")
        sys.exit(1)  # 退出并返回错误代码

def signal_handler(sig, frame):
    print('捕获到退出信号,正在退出...')
    sys.exit(0)

if __name__ == "__main__":
    # 注册退出信号处理器
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # 设置定时任务
    schedule.every().day.at("05:00").do(daily_job)
    print("启动定时任务...")
    daily_job()

    while True:
        schedule.run_pending()
        time.sleep(1)

给它也设置个开机自启,如果一切正常就搭建完成