使用 InfluxDB Go 库学习 Go 语言
作者:David G. Simmons / 产品, 开发者
2018 年 8 月 22 日
导航至
我不是 Golang 开发者。让我们一开始就把话说清楚。我用 Go 开发了一些东西,但我不是 Go 开发者。我有点需要成为,但这并不是必要的。我决定现在真的是时候深入学习 Go 了。说真的,仅仅通过阅读互联网你能学到的东西是有限的。
为此,我采取了 4 个行动
- 下周我将去丹佛参加 Gophercon 大会。
- 我将一个库从 C 移植到 Go。
- 我用 Go 编写了一个新的库。
- 我开始使用这些库,以及 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 库将一些传感器的小项目整合在一起。
当然,这只是一个更大项目的一部分,我将在不久的将来揭晓,敬请期待!