将 Things Network 连接到 InfluxDB

导航至

在物联网中连接传感器到网络有许多方法。对于短距离连接,有蓝牙低功耗(BLE)、Zigbee、802.15.4 或 ZWave。对于较长的距离(尽管仍然相当短),始终有WiFi。但是当你需要更长的距离时,有时甚至是**非常**长的距离,就有 LoRaWAN。这是一组亚千兆赫的频率,可用于传输少量数据。这些数据通常只有几字节,但可以发送到更长的距离——在某些情况下甚至可达2公里或更远!它们功耗非常低,因此非常适合远程传感应用。

为了测试 LoRaWAN 数据传输,并看看如何将数据导入 InfluxDB 可能有多困难,我决定将我的一个传感器——一个温度/湿度/压力传感器——转移到 The Things Network(TTN),一个基于社区的 LoRaWAN 提供商。我不确定这次过渡可能会多么困难或简单,但我能在一天之内完成它!所以这就是你可以这样做的方式。

硬件

首先,你需要确保你有一个 TTN 网关,或者你所在地区有一个网关。正如你所看到的,网关非常多。

没有一个网关足够近,所以我建立了自己的(提示:这些网关并不便宜——我的成本超过200美元),你现在可以在地图上看到我

关于如何设置网关有许多教程,所以在这里我就不详细说明了。

接下来,你需要为你的传感器购买一个 LoRa 无线电。我恰好有一个 Adafruit Feather M0 板(我有很多随机硬件只是“躺在那里”),所以我为它购买了一个 Adafruit LoRa Featherwing 并安装了它。最后,我使用了一个 BME280 开发板(同样来自 Adafruit。他们真的应该赞助我!)来收集数据,我就可以出发了。

连接所有部件只需要一分钟,所以我将详细介绍我是如何连接的。首先要注意的是,使用LoRaWAN Featherwing时,你必须进行额外的焊接。下面你可以看到,我不得不在IRQCSRSTDIO1DIO2上焊接跳线。这些跳线对应M0 Feather上的引脚,我们将在软件部分看到。如果你以不同的方式焊接这些跳线,你将需要相应地调整软件中的引脚设置。

你还可以看到一些从屏幕外进入的小红色电线(我喜欢这种陶瓷涂层的电线,尽管焊接起来很麻烦)。这些电线来自BME280 Breakout板,连接到I2C引脚以及板上的3v/地,为传感器供电。一旦所有这些都连接好了,就只剩下软件了!

软件

为这个项目编写软件花了我一点时间,但大部分时间都花在如何通过LoRaWAN发送数据的不同之处。我习惯了使用BLE或WiFi,所以数据包的大小并不是很重要。在LoRaWAN中,数据包的大小至关重要。

你首先需要做的是为你的Arduino安装正确的库。我使用了MCCI LoRaWAN LMIC库。它似乎与TTN集成起来最容易。这个库的一些文档不是很清晰(至少对我来说是这样),所以我将告诉你我是如何让这个库在M0 Feather上运行的。从那里开始,我使用了ttn-otaa-feather-us915 示例程序。现在,让我们填充这些部分。你需要去你的TTN控制台创建一个新的应用程序。

一旦你注册了该应用程序,你将需要获取应用程序EUIS(ID),并将其粘贴到你的Arduino Sketch中。需要注意的是,默认情况下,TTN控制台提供的EUIS是以最高有效位(大端)开始的,而Arduino Sketch期望的是小端。幸运的是,TTN控制台使所有这些变得非常简单

如你所见,它甚至使将其复制到字节数组中变得简单。

static const u1_t PROGMEM APPEUI[8] = { 0xB2, 0x38, 0x02, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 };
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

所以,这是APP ID。接下来,你将为你的设备ID和应用程序密钥做同样的事情。点击注册新设备,你可以从那里获取所有所需的信息

// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8] = { <insert Dev Key };
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from the TTN console can be copied as-is.
static const u1_t PROGMEM APPKEY[16] = { <insert Program Key };
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

不用担心我发布这些ID。我创建了这个虚拟应用程序和设备只是为了这篇博客文章,现在它们都已经消失了。但作为提醒,永远不要像这样发布你的ID或密钥。

你需要调整这个数据缓冲区以适应你的数据,但我使用的是:unsigned char mydata[11]; 记住,我之前说过,传输的数据有意保持得很低,所以我把大量数据打包到这11个字节中!我们稍后会看到我是如何做到这一点的。

接下来是引脚。还记得硬件部分吗?如果你将LoRaWAN feather连接得和我一样,这应该适用于你。

#if defined(ARDUINO_SAMD_FEATHER_M0) || defined(ADAFRUIT_FEATHER_M0)
// Pin mapping for Adafruit Feather M0 LoRa, etc.
const lmic_pinmap lmic_pins = {
  .nss = 5,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 6,
  .dio = {9, 10, 11},
  .rxtx_rx_active = 0,
  .rssi_cal = 8,              // LBT cal for the Adafruit Feather M0 LoRa, in dB
  .spi_freq = 8000000,
};

这些有点难弄清楚,在示例代码中,它将一些dio引脚留为LMIC_UNUSED_PIN ,但我的设备直到我定义了所有这些引脚才工作。

对于我的其他代码,我使用了一些为BME280准备的模板。

Adafruit_BME280 bme;
double temperature = 0.00;
double pressure = 0.00;
double altitude = 0.00;
double humidity = 0.00;
bool bme_config = true;

// this goes in the setup() function:
int tryInit = 0;
  while (!bme.begin()) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    delay(3000);
    if (++tryInit > 9) {
      bme_config = false;
      break;
    }
  }

// a function to get readings:
void getReadings() {
  if (bme_config) {
    temperature = bme.readTemperature();
    pressure = bme.readPressure() / 100.0F;
    altitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
    humidity = bme.readHumidity();
    Serial.print("Temp:     "); Serial.println(temperature);
    Serial.print("Humidity: "); Serial.println(humidity);
    Serial.print("Pressure: "); Serial.println(pressure);
    Serial.print("Altitude: "); Serial.println(altitude);
  }
}

现在,要把数据填充到11个字节中!你会注意到在我的BME280代码模板中,我将所有测量值都定义为double,这对于高带宽应用来说没问题,但是对于LoRaWAN来说就不行了。所以我将它们缩减到每个2字节(压力测量除外,它将保持4字节)。

getReadings();
    uint16_t ft = (uint16_t)(temperature * 100);
    uint16_t fh = (uint16_t)(humidity * 100);
    uint32_t fp = (uint32_t)(pressure * 100);
    uint16_t fa = (uint16_t)(altitude * 100);

如果我有以下读数

温度:25.04 湿度:54.60 压力:1006.38 海拔:57.34

那么最终我将得到

温度:2502 湿度:5460 压力:100638 海拔:5734

所有都是16位和32位整数。现在,要将它们全部填充到我的数据数组中

mydata[0] = ft >> 8;
    mydata[1] = ft & 0xFF;
    mydata[2] = fh >> 8;
    mydata[3] = fh & 0xFF;
    mydata[4] = fp & 0xFF; 
    mydata[5] = (fp >> 8) & 0xFF;
    mydata[6] = (fp >> 16) & 0xFF;
    mydata[7] = (fp >> 24) & 0xFF;
    mydata[8] = fa >> 8;
    mydata[9] = fa & 0xFF;

如果你不熟悉位操作数据,基本上我只是将每个数字的每个字节移动到我的字节数组中的某个位置。由于压力值是一个4字节值,我必须进行额外的移位。然后我可以通过LoRaWAN发送到TTN,我的数据传输就完成了。

然而,我们还没有完成!TTN会礼貌地将我发送给它的所有数据发送到一个MQTT代理,这样我就可以订阅它并随意处理它。(剧透警告:我将它放入InfluxDB!)

获取数据

我的数据现在通过LoRaWAN进入TTN,并被写入MQTT代理,但如何获取它呢?首先要做的事情是订阅!我使用一个名为MQTT Box的应用程序在我的Mac上订阅各种MQTT代理以查看不同输入的数据。它允许我定义多个代理,并从这些代理订阅任意数量的主题以查看我的数据。要订阅代理,你需要3个信息:代理的名称/地址、连接的用户名和密码。对于在美国的我们,代理的地址是us-west.thethings.network。对于用户名,你将使用你的应用程序名称。在上面的示例中,我们将使用my-temp-app作为用户名。对于密码,你将进入TTN控制台上的你的应用程序,并查找页面底部的Application Key。将其复制/粘贴到代理的密码字段中,你应该可以连接。

如果我看我的数据输出到我的MQTT代理,我会立即注意到一个问题:它只是一串看似随机的字符。实际上它根本不是随机的——它是你的数据缓冲区,经过base64编码。

{ "app_id":"my-temp-app",
  "dev_id":"my-device",
  "hardware_serial":"009E9BA30C869232",
  "port":1,
  "counter":17,
  "payload_raw":"CdgVLRGJAQAWzg==",
}

这不是很有帮助。但我发现了一个非常有用的网站,可以帮助我将它转换为更有意义的东西。访问Crypti.com并将你的原始base64编码数据粘贴进去,它将……将其转换为十六进制。嗯……还不是我想看到的样子。实际上,为了得到可用的数据,你必须回到你的TTN控制台,并点击Payload Formats标签。从这里,我们将十六进制解码成我们可以实际使用的格式。

记住,我们发送的是一个字节数组。我将base64编码的消息粘贴到上面的网站上,得到了以下内容

它将其解码为一系列字节。很好!现在来解码这些字节!(我们快完成了,我保证!)

在你的TTN控制台Payload Formats标签上,我们将输入以下函数(它是JavaScript!)

function Decoder(bytes, port) {
  var decoded = {};

  var cInt = (bytes[0] << 8) | bytes[1]; // temperature ºC
  var rem =(bytes[2] << 8) | bytes[3]; // humidity %
  var pre = (bytes[4]) + // pressure is a 4-byte value
  ((bytes[5]) << 8) 
      + ((bytes[6]) << 16) 
  + ((bytes[7]) << 24) ;
  var alt = (bytes[8] << 8) + bytes[9];
  
  

  // Decode integer to float
  decoded.temp_c = cInt / 100;
  decoded.humidity = rem / 100;
  decoded.pressure = pre / 100;
  decoded.altitude = alt / 100;

  return decoded;
}

我将逐步解释我们在做什么。如果我们回顾一下我们发送的数据缓冲区,你会记得前两个字节是温度。所以我们将这2个字节剥离并存储在温度变量中。然后我们剥离下一个2个字节,这是我们湿度。然后我们需要抓取压力的4个字节,最后抓取海拔的最后2个字节。最后,我们将它们解码回它们原始的浮点状态,我们就完成了!现在如果我们查看我们MQTT代理输出的内容,我们将看到

{ "app_id":"my-temp-app",
  "dev_id":"my-device",
  "hardware_serial":"009E9BA30C869232",
  "port":1,
  "counter":28,
  "payload_raw":"CeEVJg6JAQAW7A==",
  "payload_fields":{
      "altitude":58.68,
      "humidity":54.14,
      "pressure":1006.22,
      "temp_c":25.29
  },
}

这是一个合适的JSON对象,我们的数据以可用的形式呈现!现在来说说最后一点:将所有数据导入InfluxDB!

导入InfluxDB

幸运的是,这整个过程中最容易的部分得益于Telegraf!在你的Telegraf主机上,编辑你的telegraf.conf文件。找到名为“从MQTT主题读取指标”的部分,并添加以下内容

[[inputs.mqtt_consumer]]
  servers = ["tcp://us-west.thethings.network:1883"]
  qos = 0
  connection_timeout = "30s"
  topics = [ "+/devices/+/up" ]
  client_id = "ttn"
  username = "APP_NAMEr"
  password = "APPKEY"
  data_format = "json"

然后重新启动Telegraf,就像魔法一样,你应该可以看到数据被导入InfluxDB!如果我在Chronograf的数据探索器中查看,我应该会看到一个名为mqtt_consumer的新测量值,在那里……哇!!有很多数据字段!原来TTN提供了一组关于设备如何连接并发送数据的额外信息,这些都是Telegraf插件所保留的。

你的传感器数据将带有payload_fields_前缀。其余的都是关于你的数据的数据。

和往常一样,几乎所有部署中我最容易的部分是InfluxDB部分。一旦MQTT代理中的数据以正确的格式输出,将其存储在InfluxDB中只需几行配置即可。我现在可以在Chronograf中构建我的温度、湿度、压力和海拔数据的仪表板。