使用 InfluxDB Go 库学习 Go 语言

导航至

我不是 Golang 开发者。让我们一开始就把话说清楚。我用 Go 开发了一些东西,但我不是 Go 开发者。我有点需要成为,但这并不是必要的。我决定现在真的是时候深入学习 Go 了。说真的,仅仅通过阅读互联网你能学到的东西是有限的。

为此,我采取了 4 个行动

  1. 下周我将去丹佛参加 Gophercon 大会。
  2. 我将一个库从 C 移植到 Go。
  3. 我用 Go 编写了一个新的库。
  4. 我开始使用这些库,以及 InfluxDB Go 库,将数据存储在 InfluxDB 中。

我今天要在这里写的正是最后 3 点,并不是因为我认为我做得特别好,而是因为这对其他人可能有用。我希望您觉得它有用。

背景

我一直在做一个小的物联网项目(duh),它使用 Raspberry Pi。它还使用了来自 Adafruit 的 Bosch BME280 breakout 板和一个 SenseAir k30 CO2 传感器。如果我在 Arduino 上运行它们,这些东西会非常容易处理——事实上我一直在这样做。但我现在没有。是的,我知道有一些方法可以在 Raspberry Pi 上运行 Arduino sketches,但我真的不是 Arduino sketches 的粉丝,所以我决定用另一种方法来做。

移植库

BME 280 传感器是 I2C,当然,就是这样。在 Raspberry Pi 上访问 I2C 总线很容易(只需运行 raspi-config,一切就搞定了),但处理 I2C 设备有点困难。我尝试了几个基于 C 的 I2C 库,但它们大多数都给出了……意想不到的结果。我发现最接近的一个是由 GitHub 用户 “BitBank2”  (https://github.com/bitbank2/bme280) 编写的,所以我决定使用他的。我能够编译它并运行示例程序,至少得到了合理的结果。但是,我仍然需要从一些用户空间程序中调用它才能获得结果。我应该闻到一丝不对劲,但当然我没有。

我就把它移植到 Go 吧!当时听起来很合理。

实际上,这比我想象的要容易得多。首先,@rakyll 有一个很棒的 Go I2C 库,它工作得很好。我曾用它来访问 SenseAir K30 CO2 传感器(我接下来要讨论的库),所以我认为我会从那里开始。

由于我开始使用的库可以工作,我认为最简单的方法就是做一个半直接的翻译。我会复制几行 C 代码,然后将其转换为 Go。当然,有些东西不太好用。例如,I2C 库希望处理字节和字节切片,所以我不太可能直接使用 C 库使用的整数。此外,C 库使用了一堆静态全局变量,这在 Go 中也不太好用。所以我做了一些调整

static int calT1,calT2,calT3;
static int calP1, calP2, calP3, calP4, calP5, calP6, calP7, calP8, calP9;
static int calH1, calH2, calH3, calH4, calH5, calH6;

变成了

type BME280 struct {
 Dev *i2c.Device
 tConfig []int
 pConfig []int
 hConfig []int
}

device.tConfig = make([]int, 3)
device.pConfig = make([]int, 9)
device.hConfig = make([]int, 6)

几乎其余的部分都是将 C 语言结构简单地翻译成 Golang 结构。

// Prepare temperature calibration data
calT1 = ucCal[0] + (ucCal[1] << 8);
calT2 = ucCal[2] + (ucCal[3] << 8);
if (calT2 > 32767) calT2 -= 65536; // negative value
calT3 = ucCal[4] + (ucCal[5] << 8);
if (calT3 > 32767) calT3 -= 65536;

变成了

// time to set up the calibration
 device.tConfig[0] = int(ucCal[0]) + (int(ucCal[1]) << 8)
 device.tConfig[1] = int(ucCal[2]) + (int(ucCal[3]) << 8)
 if device.tConfig[1] > 32767 {
     device.tConfig[1] -= 65536
 }
 device.tConfig[2] = int(ucCal[4]) + (int(ucCal[5]) << 8)
 if device.tConfig[2] > 32767 {
     device.tConfig[2] -= 65536
 }

等等。

现在任何 Go 程序都可以简单地执行以下操作

package main
import (
  "fmt"
  "github.com/davidgs/bme280_go"
  "time"
)
func main() {
  dev := "/dev/i2c-1"
  bme := bme280_go.BME280{}
  r := bme.BME280Init(dev)
  if r < 0 {
    fmt.Println("Error")
  }
  rets := bme.BME280ReadValues()
  f := 0.00
  f = float64(rets[0]) / 100.00
  fmt.Println("Temp: ", f)
  f = float64(rets[2]) / 1024.00
  fmt.Println("Humidity: ", f)
  bme.Dev.Close()
}

因为调用 BME280ReadValues 会返回一个简单的 ints 切片,其中包含温度、压力和湿度,顺序就是这样。 注意:压力计算目前已损坏,因此我不建议使用它。

正如我所说,让所有东西都工作起来出奇地容易!我现在有一个主要功能齐全的 Adafruit BME280 Breakout Board 的 GoLang 库! 如果您有兴趣使用此库,可以在 我的 GitHub 上免费获得。

编写我自己的库

接下来是为 SenseAir K30 传感器编写类似的库。我已经成功地通过 Go 与这个传感器一起工作了一段时间,所以我已经有了可以工作的 Go 代码。我所要做的就是将其转换为其他人可以使用的库。

这甚至比移植练习更容易。我只需要添加一些东西,比如 K30 对象

type K30 struct {
	Dev     *i2c.Device
	co2Read []byte
}

以及设备的地址

const (
	CO2_ADDR = 0x68
)

然后对 Init() 和 ReadValue() 函数进行一些更改。由于传感器只返回一个值——CO2 在百万分之几的浓度,因此返回值也更容易。与 BME280 库一样, 这个库也可以在 我的 GitHub 上免费获得。

在 InfluxDB 中使用这些值

太棒了!所以现在我有这 2 个用 Go 编写的设备驱动程序,我可以在我的 Raspberry Pi 上使用它们,而 Raspberry Pi 当然也在运行 InfluxDB。我所要做的就是将这些值放入 InfluxDB。事实证明这也很简单。您所需要的只是 InfluxDB Go 客户端库!

首先,您需要导入所需的 3 个库

import (
    "fmt"
    "github.com/davidgs/SenseAir_K30_go"
    "github.com/davidgs/bme280_go"
    "github.com/influxdata/influxdb/client/v2"
    "time"
)

然后您只需初始化所有内容

   c, err := client.NewHTTPClient(client.HTTPConfig{
    Addr: "http://localhost:8086",
    })
    if err != nil {
        fmt.Println("Error creating InfluxDB Client: ", err.Error())
    }
    defer c.Close()
    dev := "/dev/i2c-1"
    bme := bme280_go.BME280{}
    r := bme.BME280Init(dev)
    if r < 0 {
        fmt.Println("Error")
    }
    defer bme.Dev.Close()
    k30 := SenseAir_K30_go.K30{}
    r = k30.K30Init(dev)
    if r < 0 {
        fmt.Println("Error")
    }
    defer k30.Dev.Close()
    temp_tags := map[string]string{"sensor": "bme_280"}
    c_tags := map[string]string{"sensor": "k30_co2"}

您会注意到我在那里创建了几个映射,其中包含我将用于每个测量的标签。您很快就会看到这些是如何使用的。

CO2 传感器实际上只能(最多)每 2 秒读取一次,实际上,3.5 - 4 秒左右更合理,但我希望更频繁地读取温度和湿度。我所做的是每秒读取一次温度和湿度,并重复 4 次,然后再读取 CO2

for 1 > 0 {
    count := 0
// Create a new point batch
    bp, _ := client.NewBatchPoints(client.BatchPointsConfig{
        Database:  "telegraf",
        Precision: "s",
    })
    for count < 4 {
        rets := bme.BME280ReadValues()
        t := float64(float64(rets[0]) / 100.00)
        fmt.Println("Temp: ", t)
        h := float64(rets[2]) / 1024.00
        fmt.Println("Humidity: ", h)
        if rets[0] != -1 && t > -100 {
            temp_fields := map[string]interface{}{
                "temp_c":   t,
                "humidity": h,
            }
	// Create a point and add to batch
            pt, err := client.NewPoint("bme_280", temp_tags, temp_fields, time.Now())
            if err != nil {
                fmt.Println("Error: ", err.Error())
            }
            bp.AddPoint(pt)
        }
        time.Sleep(1000 * time.Millisecond)
        count += 1
    }

其中发生了一些需要解释的事情。首先是批处理点。这是一个最终将写入 InfluxDB 实例的点。在创建点时,我需要定义它将进入的数据库以及精度。在这种情况下,我使用的是秒级精度,因为我只是每秒读取一次。接下来,我添加字段及其值——即温度和湿度——然后使用标签和字段创建一个新点。最后,我将这个点添加到批处理点。

现在,我可以在创建每个点时立即插入它,但这在数据库写入和客户端资源利用率方面都将非常低效,所以我将继续向批处理点添加点一段时间——事实上,我会在继续之前添加所有 4 个温度和湿度读数。

接下来,我将读取 CO2 读数,并在将其全部写入数据库之前将其添加到批处理点

    co2_value := k30.K30ReadValue()
    if co2_value > 0 {
    fmt.Println("CO2: ", co2_value)
    // Create a point and add to batch
    c_fields := map[string]interface{}{
        "co2": co2_value,
    }
    pt, err := client.NewPoint("k30_reader", c_tags, c_fields, time.Now())
    if err != nil {
        fmt.Println("Error: ", err.Error())
    }
    bp.AddPoint(pt)
}
// Write the batch
c.Write(bp)

这样就完成了循环。每次循环时,我都会创建一个新的批处理点,进行 4 次温度/湿度读取,并将它们添加到批处理点。然后我将读取一次 CO2 读数,并将其添加到批处理点,然后再将所有 5 个点写入数据库。

然后我将看到每 4 秒(左右)向数据库写入 5 个点。为了提高效率,我可以维护一个单独的计数器,并在写入之前向批处理中添加几十个点,但对于本地数据库来说,这并没有做太多其他事情——这工作得很好。

结论

对于我来说,这是一个非常有趣的项目,它扩展了我对 Go 的了解,并使用现有的 InfluxDB Go 库将一些传感器的小项目整合在一起。

当然,这只是一个更大项目的一部分,我将在不久的将来揭晓,敬请期待!