使用 Python 读取天气传感器数据
Source: Dev.to
除了构建气象站之外,我长期想做的另一件事是学习 Python。
通过这个气象站项目,我两者都实现了 :-)
在 Codeberg 上查看此项目的 源代码。
学习 Pimoroni 的 WeatherHAT 库
第一步,我在完成所有安装后,学习 Pimoroni 的 WeatherHAT 库的工作原理,以便:
- 从传感器读取天气数据
- 在 WeatherHAT 屏幕上显示天气数据
所以我打开了 SSH 连接并开始尝试!
我对该库的了解
- 在获取天气读数之前,需要调用传感器的
update方法。 - 第一次读取的值是“垃圾”;在获得可靠数值之前,需要进行一次初始更新并稍作等待。
- 有两个温度属性:
device_temperature和temperature。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 分钟一次)。在每一次迭代中,系统会:
- 从传感器读取数据。
- 应用温度偏移。
- 在屏幕上显示数据。
- 将数据发送到外部服务。
我在自己的电脑上开发,因为比直接在树莓派上编码更舒适。为实现这一点,我创建了 mocks 来模拟对 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. 生产)变得混乱。为简化组件的选择,我采用了 抽象工厂模式。只需一个配置设置,我现在就能加载相应的组件集合(Sensor、Display 和 Connector)。
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