使用 InfluxDB Go 库学习 Go 语言

导航至

我不是一个 Golang 开发者。让我们先把这个前提说清楚。我使用 Go 语言开发过一些东西,但并非 Golang 开发者。我某种程度上需要成为,但这并非必需。我决定是时候采取行动,认真对待 Go 语言。真的,通过阅读互联网,你能学到的只有这么多。

为此,我采取了以下 4 项行动

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

今天我将重点介绍最后 3 项,不是因为我认为我做得特别出色,而是因为这对其他人可能有用。希望你会发现它有用。

背景

我一直在做一个小型物联网项目(嗯),该项目使用 Raspberry Pi。它还使用来自 Adafruit 的 Bosch BME280 扩展板和 SenseAir k30 CO2 传感器。如果我在 Arduino 上运行它们,这将非常容易处理——事实上,我曾经这样做过。但现在我不再这样做。是的,我知道有方法可以直接在 Raspberry Pi 上运行 Arduino 脚本,但我真的不是 Arduino 脚本的粉丝,所以我决定用另一种方式来做。

移植库

BME 280 传感器当然是 I2C,所以这是第一个问题。在 Raspberry Pi 上使 I2C 总线可用很简单(只需运行 raspi-config,然后 Bob 就是你的叔叔),但处理 I2C 设备稍微困难一些。我尝试了几个基于 C 语言的 I2C 库,但大多数都给出了意外的结果。我发现最接近的是 GitHub 用户“BitBank2”编写的库(https://github.com/bitbank2/bme280),所以我决定使用它。我能够编译它并运行示例程序,至少结果是合理的。但然后我仍然需要从用户空间程序中调用它才能获得结果。我应该已经闻到了一个陷阱,但当然我没有。

我把它移植到 Go 语言!当时听起来是合理的。

实际上比我想的容易得多。首先,有一个来自 @rakyll 的出色的 Go I2C 库,效果很好。我已经使用它来访问 SenseAir K30 CO2 传感器(我将在下一段中讨论的库),所以我从那里开始。

由于我从该库开始工作,我觉得最简单的方法就是进行半直译。我会复制几行C代码,然后将其转换为Go。当然,有些事情做起来并不顺利。例如,I2C库希望以字节和字节切片的形式进行操作,所以我不可能只使用C库中使用的int类型。此外,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的调用返回一个简单的整数切片,按顺序为温度、压力和湿度。注意:目前压力计算是错误的,所以我建议不要使用它。

正如我所说,让它全部运行起来非常简单!现在我有一个在Go中几乎完全功能的Adafruit BME280 Breakout Board库!如果你对这个库感兴趣,它在我的GitHub上免费提供。我的GitHub

编写自己的库

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

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

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

以及设备的地址

const (
	CO2_ADDR = 0x68
)

然后对Init()ReadValue()函数进行一些修改。由于传感器只返回一个值——二氧化碳的百万分率,因此返回值也更容易。就像BME280库一样,这个库也免费提供在我的GitHub上。我的GitHub

使用InfluxDB的值

太棒了!现在我有这两个设备驱动程序,我可以在运行InfluxDB的Raspberry Pi上使用它们。我需要做的只是将值放入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: "https://127.0.0.1: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"}

你会在那里看到我创建了一些包含我将用于每个测量的标签的映射。你很快就会看到它们是如何使用的。

二氧化碳传感器只能每2秒读取一次(最多),但实际上,3.5-4秒更合理,但我希望温度和湿度读取更频繁。我这样做是每秒读取一次温度和湿度,然后重复4次,然后读取二氧化碳。

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_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库来结合使用一些传感器完成一个小型项目。

当然,这只是一个即将在不久的将来揭晓的更大项目的组成部分,所以请继续关注!