将 The Things Network 连接到 InfluxDB

导航至

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

为了测试一些 LoRaWAN 数据传输,并了解将数据导入 InfluxDB 可能有多难,我决定将我的一个传感器(温度/湿度/压力传感器)迁移到 The Things Network (TTN),这是一个基于社区的 LoRaWAN 提供商。我不确定这种转变有多难或多容易,但我能够在不到一天的时间内完成它!所以这是您也可以这样做的方法。

硬件

首先,您需要确保自己拥有 TTN 网关,或者您所在地区有网关。正如您所见,有很多网关可用。

附近没有网关,所以我自己搭建了一个(提示:这些网关便宜——我的花费超过 200 美元),所以您现在可以在地图上看到我

有很多关于如何设置网关的教程,所以这里不再赘述。

接下来,您的传感器需要一个 LoRA 无线电。我碰巧有一个 Adafruit Feather M0 板子闲置(我有很多随机硬件“闲置”),所以我买了一个 Adafruit LorRa Featherwing 并装上它。最后,我使用了一个 BME280 breakout board(同样来自 Adafruit。他们真的应该赞助我!)来收集数据,我准备好了。

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

您还可以看到一些小的红色导线从屏幕外进入(我喜欢这种陶瓷涂层导线,即使它很难焊接)。这些导线来自 BME280 Breakout 板,并连接到板上的 I2C 引脚和 3v/接地以给传感器供电。一旦所有这些都连接好,就轮到软件了!

软件

软件方面我花了一分钟才使其工作,但其中大部分与如何通过 LoRaWAN 发送数据有关。我习惯使用 BLE 或 WiFi,因此数据包的大小实际上并不那么重要。对于 LoRaWAN,数据包的大小至关重要。

您要做的第一件事是为您的 Arduino 安装正确的库。我使用了 MCCI LoRaWAN LMIC Library。它似乎最容易与 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 和您的 App Key 执行相同的操作。单击以注册新设备,您可以从那里访问所有需要的信息

// 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 代理,但我如何获取它呢?好吧,要做的第一件事是订阅!我在我的 Mac 上使用一个名为 MQTT Box 的应用程序来订阅各种 MQTT 代理,以查看来自不同输入的数据。它允许我定义多个代理,并订阅来自这些代理的任意数量的主题以查看我的数据。要订阅代理,您需要 3 条信息:代理的名称/地址、用户名和密码才能连接。对于我们这些在美国的人来说,代理的地址是 us-west.thethings.network。对于您的用户名,您将使用您的应用程序的名称。在上面的示例中,我们将使用 my-temp-app 作为用户名。对于密码,您需要转到 TTN 控制台上的应用程序,并在页面底部查找 Application Key。将其复制/粘贴到代理的密码字段中,您应该可以连接。

如果我查看我的数据输出到我的 MQTT 代理,我立即注意到一个问题:它只是一个看起来随机的字符串。它实际上一点也不随机——它是您的数据缓冲区,经过 base-64 编码。

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

不是很实用。我确实找到了一个超级有用的网站,可以帮助我将其翻译成更有意义的东西。转到 Crypti.com 并粘贴您的原始 base-64 编码数据,它将……将其翻译为十六进制。嗯……仍然不是我想看到的。事实证明,为了以可用的形式获取数据,您必须返回到您的 TTN 控制台并单击 Payload Formats 选项卡。从这里,我们将十六进制解码为我们可以实际使用的东西。

请记住,我们发送一个字节数组。我将 base-64 编码的消息粘贴到上面的网站中,并得到了以下结果

它将其解码为一系列字节。酷!现在解码这些字节!(我们快到了,我保证!)

在您的 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 个字节并将它们存储在一个温度变量中。我们剥离接下来的 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 中构建我的温度、湿度、压力和海拔数据的仪表板。