使用 Python 读取天气传感器数据

发布: (2026年3月30日 GMT+8 02:18)
7 分钟阅读
原文: Dev.to

Source: Dev.to

除了构建气象站之外,我长期想做的另一件事是学习 Python。
通过这个气象站项目,我两者都实现了 :-)

Codeberg 上查看此项目的 源代码

学习 Pimoroni 的 WeatherHAT 库

第一步,我在完成所有安装后,学习 Pimoroni 的 WeatherHAT 库的工作原理,以便:

  • 从传感器读取天气数据
  • 在 WeatherHAT 屏幕上显示天气数据

所以我打开了 SSH 连接并开始尝试!

我对该库的了解

  • 在获取天气读数之前,需要调用传感器的 update 方法。
  • 第一次读取的值是“垃圾”;在获得可靠数值之前,需要进行一次初始更新并稍作等待。
  • 有两个温度属性:device_temperaturetemperature
    • temperature 是在 device_temperature 读取值上加上默认偏移 7.5 °C 后计算得到的。

校准温度

我首先关注的是正确校准温度。为此,我花了几天时间从 WeatherHAT 传感器和附近的气象站收集温度数据,每小时读取一次。

我注意到,两者传感器之间的差异并不是一个固定值。WeatherHAT 的读数始终比附近站点高 12 °C–16 °C

我没有使用固定偏移,而是尝试了线性回归。我创建了一个类,根据给定的 device_temperature 值计算偏移。将来我可能会找到更好的计算偏移的方法,这也是我引入 TemperatureOffsetInterface 的原因。

class LinearRegressionOffset(TemperatureOffsetInterface):
    def __init__(self, config):
        self.a = 0
        self.b = 0
        self.config = config
        self.doLinearRegression()

    def getOffset(self, x):
        """Return the offset for a given device temperature."""
        offset = (self.b * x) + self.a
        return offset

    def doLinearRegression(self):
        """Calculate the linear regression coefficients a (intercept) and b (slope)."""
        x = self.config['data']['x'].split(",")
        y = self.config['data']['y'].split(",")

        x = list(map(float, x))
        y = list(map(float, y))

        medianX = statistics.median(x)
        medianY = statistics.median(y)
        numSum = 0
        denSum = 0

        for index, xValue in enumerate(x):
            numSum += (xValue - medianX) * (y[index] - medianY)
            denSum += (xValue - medianX) ** 2

        self.b = numSum / denSum
        self.a = medianY - (self.b * medianX)

我本可以使用 Python 库来执行回归,但请记住——这个项目的目标是 学习 Python

与硬件解耦

在解决了温度校准问题后,接下来就该开始编写读取系统的代码了。

该过程应定期运行(例如,每 5 分钟一次)。在每一次迭代中,系统会:

  1. 从传感器读取数据。
  2. 应用温度偏移。
  3. 在屏幕上显示数据。
  4. 将数据发送到外部服务。

我在自己的电脑上开发,因为比直接在树莓派上编码更舒适。为实现这一点,我创建了 mocks 来模拟对 WeatherHAT 硬件的访问。

用于模拟访问 WeatherHAT 硬件的接口和实现

# Example of using the abstract factory to obtain concrete implementations
sensor   = factory.createSensor()
display  = factory.createDisplay()

主算法完成后,我只需实现硬件特定的部分。借助 Pimoroni 提供的示例,从传感器获取数据并在 WeatherHAT 屏幕上显示变得非常直接。

将数据发送到外部服务

我希望能够随时随地查看当前温度,并将读取的数据存储起来以便以后进行统计。

下一步是将数据发送到外部服务。由于我自己的天气数据 Web 应用尚未准备好,我先把数据发送到 Adafruit IO。我创建了一个仪表板,显示当前数据,并提供一张图表,让我可以将 WeatherHAT 的读数与附近站点的读数进行比较——这对于检查线性回归的质量非常有用。

我定义了一个 ConnectorInterface,以便以后可以用发送数据到我自己的 Web 应用的实现来替换 Adafruit 连接器。

同时创建了一个 MockConnector,以避免在我仍在处理算法主流程时发送无效数据。

class ConnectorInterface:
    def send(self, weather_data):
        """Send a WeatherData instance to the configured endpoint."""
        pass

实现抽象工厂

一切运行良好,但在实现之间切换(模拟 vs. 生产)变得混乱。为简化组件的选择,我采用了 抽象工厂模式。只需一个配置设置,我现在就能加载相应的组件集合(SensorDisplayConnector)。

class SystemFactoryInterface:
    def createDisplay(self) -> DisplayInterface:
        pass

    def createConnector(self, config) -> ConnectorInterface:
        pass

    def createSensor(self) -> SensorInterface:
        pass

现在其余代码可以完全不关心是运行在带有模拟的开发机器上,还是运行在配备真实硬件的 Raspberry Pi 上。

sensor   = factory.createSensor()
display  = factory.createDisplay()
connector = factory.createConnector(config)

结论

我非常享受这一部分。学习 Python,从传感器读取数据,校准温度,应用一些设计模式……这让我想起了刚开始学习编程时的感觉。

也许是因为这是一个与我平时更常开发的项目截然不同的项目,也许是因为它融合了不同的东西,或者两者兼而有之 :-)。

在下一篇文章中,我将尝试解释天气数据 Web 应用的实现。这次使用 PHP 和 Symfony,这是一种我更熟悉的语言和框架。

示例接口定义(Python)

class ConnectorInterface:
    def connect(self) -> bool:
        ...

    def disconnect(self) -> bool:
        ...

class SensorInterface:
    def read(self) -> float:
        ...

class Config:
    ...

class MyFactory:
    def createConnector(self, config: Config) -> ConnectorInterface:
        pass

    def createSensor(self, config: Config) -> SensorInterface:
        pass
0 浏览
Back to Blog

相关文章

阅读更多 »

用小工具解决 venv 头疼问题?

Python 虚拟环境的问题:Python 的虚拟环境非常棒——但直到你真的尝试使用它们时才会发现问题。每个项目都有自己的 .venv,但……