用Grafana监控一条宠物蟒蛇

258 阅读7分钟

有趣的故事:在2020年的夏天,我的女儿Astri决定她想要一只宠物。她对Minecraft的喜爱意味着斧头蛇(在游戏的一些生物群落中发现)是第一选择,但作为第一个宠物,它并不理想(见:pH值,水温,喂食)。

选项2是一条蛇。在允许她养蛇之前,我的要求是她要对蛇进行研究。最终,她的选择范围缩小到了玉米蛇(很好的入门蛇,但精力相当旺盛)、皇家蟒蛇或球蟒。作为一个Python开发人员,我倾向于选择蟒蛇。我担心的是,我们住在英国,那里比它在西非的家要冷得多。蟒蛇需要的湿度在50-70%之间,活体箱中的温度梯度在25±1°C (77°F) 和31±1°C (87.8°F)之间

(在我继续之前,有两件事需要注意:英国的大多数皇家蟒蛇都是人工饲养的,很少有非法交易;为了以防万一,我们是在一家有信誉的商店购买的。第二件事是,所有类型的蟒蛇都不能忍受寒冷,所以它们对当地的三个物种没有威胁。不过它们的价值相当高,所以有很多饲养者)。

一旦我们确定了一条宠物蟒蛇--阿斯特里将其命名为 "椒盐"--我们需要一个监控系统,以支持她照顾蛇的能力。这意味着它必须是移动的,并且易于使用。额外的好处是:这让我有机会在我的 "我需要在某个时候学习一下这个 "的清单上勾选一些技术。

在这篇文章中,我将带你了解我们如何使用Grafana、InfluxDB、MosQuiTTo和NodeRed来构建它。就她而言,Astri帮助建造了这个生活馆,把至少一半的传感器焊接到电路板上,并建立了一些工作流程。

开始工作

我们需要相当多的部件来建立这个系统:

我们使用Kubernetes作为解决方案的计算层,因为使用一个容错平台是有意义的。此外,它使测试和更换解决方案的层变得非常简单。

高层架构

高层架构需要几个部分来运作。核心要素是自动化。数据通过Node Red从传感器推送到InfluxDB。Grafana设置存储在MySQL中,用户界面通过身份感知代理访问。我想,谷歌可以建立一个比我更好的认证系统。我之前写过这个,所以你可以看到Grafana使用JSON网络令牌的强大集成能力

控制系统

我需要建立一个自定义的容器,因为我希望在容器被加载到集群上之前,预先加载必要的库。当容器被配置为加载库时,它增加了几分钟的启动时间,所以我选择了预建的容器图像。

诀窍是在Google Cloud Build上为Raspberry Pi构建ARM容器,所以这里是我使用的代码。

构建命令:(为ARM64容器使用Google Cloud Build)

gcloud builds submit 
--substitutions="_DOCKER_BUILDX_PLATFORMS"="linux/arm64",_REGISTRY_LOCATION
="europe-west1-docker.pkg.dev",_REGISTRY="pi-cluster",_REGISTRY_APPLICATION
="nodered",_BUILD_FILE="Dockerfile.NodeRedBase",_BUILD_CONTEXT="." 
--config="cloudbuild.yaml"

Cloudbuild.yaml

steps:
    - name: 'gcr.io/cloud-builders/docker'
      args: ['run', '--privileged', 'linuxkit/binfmt:v0.7']
      id: 'initialize-qemu'
    - name: 'gcr.io/cloud-builders/docker'
      args: ['buildx', 'create', '--name', 'mybuilder']
      id: 'create-builder' 
    - name: 'gcr.io/cloud-builders/docker'
      args: ['buildx', 'use', 'mybuilder']
      id: 'select-builder'
    - name: 'gcr.io/cloud-builders/docker'
      args: ['buildx', 'inspect', '--bootstrap']
      id: 'show-target-build-platforms'
    - name: 'gcr.io/cloud-builders/docker'
      entrypoint: 'bash'
      timeout: '2400s'
      args:
      - '-c'
      - |
        docker buildx build --platform $_DOCKER_BUILDX_PLATFORMS --tag $_REGISTRY_LOCATION/$PROJECT_ID/$_REGISTRY/$_REGISTRY_APPLICATION:latest \
        --tag $_REGISTRY_LOCATION/$PROJECT_ID/$_REGISTRY/$_REGISTRY_APPLICATION:$(date +%Y%m%d%H%M%S) --push -f $_BUILD_FILE $_BUILD_CONTEXT
timeout: '2400s'
options:
    env:
        - 'DOCKER_CLI_EXPERIMENTAL=enabled'
substitutions:
    _DOCKER_BUILDX_PLATFORMS: 'linux/amd64,linux/arm64'
    _REGISTRY_LOCATION: 'europe-west1-docker.pkg.dev'
    _REGISTRY: 'fixme'
    _REGISTRY_APPLICATION: 'fixme'
    _BUILD_FILE: 'Dockerfile'
    _BUILD_CONTEXT: '.'

Dockerfile.nodered

FROM nodered/node-red

# Copy package.json to the WORKDIR so npm builds all
# of your added nodes modules for Node-RED
# COPY package.json .
RUN npm install --unsafe-perm --no-update-notifier --no-fund --only=production
RUN npm install --no-audit --no-update-notifier --no-fund --save --save-prefix=~ --production

# You should add extra nodes via your package.json file but you can also add them here:
#WORKDIR /usr/src/node-red
RUN npm install node-red-node-smooth node-red-contrib-amqp node-red-contrib-bigtimer node-red-contrib-cron-plus node-red-contrib-filter \
node-red-contrib-google-cloud node-red-contrib-google-iot-core node-red-contrib-huemagic node-red-contrib-iot-in-gcp node-red-contrib-redis \
node-red-node-mysql node-red-node-base64 node-red-node-google node-red-node-mysql node-red-node-rbe node-red-node-tail node-red-node-stomp \
node-red-contrib-kubernetes-client node-red-node-feedparser node-red-contrib-influxdb node-red-node-irc node-red-contrib-slack \
node-red-contrib-slackbot node-red-contrib-sun-position node-red-node-rbe node-red-node-tail node-red-node-email node-red-node-irc \
node-red-node-twitter node-red-contrib-auth node-red-node-email node-red-node-feedparser node-red-node-twitter node-red-contrib-neo4j-bolt \
node-red-contrib-neo4j node-red-contrib-prometheus-exporter node-red-contrib-counter node-red-contrib-msg-speed node-red-contrib-web-worldmap \
node-red-contrib-telegrambot node-red-contrib-sendgrid node-red-contrib-proj4 node-red-dashboard node-red-node-geofence \
node-red-contrib-discord node-red-contrib-postgresql node-red-contrib-uuid node-red-node-snmp node-red-contrib-discord \
node-red-contrib-kafka-client \
node-red-contrib-smartthings \
node-red-contrib-zigbee2mqtt-devices \
node-red-node-mongodb google-auth-library \
# googleapis\
#node-red-contrib-zigbee

[

从传感器MQTT输入到InfluxDB的Node Red流程

](grafana.com/static/asse…)

数据采集

来自Node Red的数据被推送到InfluxDB。我把InfluxDB作为一个容器运行--有一个相当可靠的启动清单。在我女儿的要求下,我创建了一个叫 "Snek "的桶。它仍然在那里。Influx中的数据是带有八个地点和类型、湿度和温度的原始时间序列。值只是一个浮点数,湿度是0-100,温度是摄氏度。

还有三个我想跟踪的数据点:两个灯的状态,以及一个来自Node Red的测试点。测试点给了我一个提醒的方法,如果它丢失了,那么我可以检测到Node Red是否失败了。如果influx失败了,我将在Node Red中得到一个来源警报。

在下面的Influx桶视图中,测试点是最上面的蓝线;湿度范围在35-80之间;温度是24-32之间的紧密集群;底部的两条零线和一条线是光传感器的读数。

[

涌流桶视图

](grafana.com/static/asse…)

警报

这方面的挑战是围绕正确的条件发送正确的警报。温度是一个直接的控制问题:如果温度变化太大,即使是很短的时间,也会对蛇的健康造成损害。自动控制系统在这方面是足够的。然而,在我目前的设置中,湿度没有直接的控制方式,所以提醒人类是解决这个问题的关键。

在下图的仪表板上,我们设置了关键的面板,显示了活体箱中的湿度以及热侧、冷侧的温度(是的,蛇需要一个温度梯度,以便它可以移动到它感觉舒适的地方),以及围绕中间区域的预测(那里没有传感器)。

这些图表跟踪了用于警报的超时分析的湿度和温度。底部的图表显示了输入(和数据的差距)以及Influx测试点(它告诉我是否有来自传感器或Node Red的数据丢失)。

瞬时值不需要在很长一段时间内进行汇总。Influx似乎在大型查询时速度会变慢(SD卡和树莓Pis的好处之一),所以限制关键图形的数据是非常好的。这里是这些图表的代码(过滤器可以为任何传感器调整)。

from(bucket: "snek")
  |> range(start: -5m, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] =~ /^vivarium\/sensor\/.*\/temperature$/ and r["_measurement"] =~ /left\/top\/front/)
  |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
  |> yield(name: "mean")

监测其他作品是相当有趣的,因为当日光灯关闭时,会影响到温度和湿度。另外,如果蛇被激活或门被打开,这也会严重影响湿度和温度。在这里,你可以看到冷面不够冷,但这是热浪期间外部温度的副产品。

左侧顶部的前置传感器给出了最接近地面上最热点的读数,所以这被用于趋势图,每个区都有不同的过滤器。

from(bucket: "snek")
 |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
 |> filter(fn: (r) => r["_measurement"] =~ /^vivarium\/sensor\/left\/top\/front\/temperature$/ )
 |> map(fn: (r) => ({ r with _measurement: "temperature", _value: r._value}))
 |> aggregateWindow(every: 5m, fn: mean, column: "_value", createEmpty: true)

移动客户端

使用Node Red UI,我们能够创建一个简单的视图和控制系统,以防温度太高,需要关闭加热器。它还能让我知道,当Pretzel的喂食时间在日落之后时,应该把灯打开。如果蛇被放置几天,来自互联网的控制是至关重要的。

对于用户界面客户端,它只是一个流程的延伸,在用户界面上显示温度和湿度。然而,我使用了来自开关的输入,而不是来自流程的输出来指示灯是开还是关。这意味着,在发出开启或关闭的信号后,流量将等待从ESP32获得确认,以验证控制信号是否已经发送。这将使一个未知数脱离循环(灯是否真的打开/关闭?)