主要内容

本页采用了机器翻译。点击此处可查看英文原文。

通过物联网收集农业数据

此示例展示了如何设置从连接到带有 LoRa® 无线电的微处理器板的三个传感器的数据收集。

这种配置允许在广阔的区域内创建分布式传感器网络。传感器将数据发送到物联网,然后转发到 ThingSpeak™ 进行分析和可视化。在示例中,您构建一个原型设备,连接到物联网,并将数据收集与 ThingSpeak 集成。此处显示的设备收集温度、土壤湿度和 GPS 数据。

概述

该示例包含三个主要步骤。物联网集成进一步分为几个子步骤。最复杂的步骤是构建设备。为了完成该示例,您需要一个 ThingSpeak 帐户和一个 The Things Network 帐户。在 ThingSpeak 上,您创建一个新通道。在物联网上,您可以创建一个应用程序并注册一个设备。然后创建一个有效载荷解码器并添加将数据转发到 ThingSpeak 的集成。

1) 设置 ThingSpeak 通道来收集数据

2) 设置物联网

  • 创建应用程序

  • 注册设备

  • 创建有效载荷格式

  • 添加集成

3) 创建设备

  • 创建传感器节点所用的硬件

  • 原理图和连接

4) 程序装置

  • 编程设置

  • 代码

设置 ThingSpeak 通道来收集数据

1) 创建一个 ThingSpeak 通道,如在新通道中收集数据所示。记录新通道的写入 API 密钥和通道 ID。

2) 导航至通道设置页面。按如下方式设置字段标签。

  • Field 1- Counter

  • Field 2- Soil Moisture

  • Field 3- Temperature F

3) 点击底部的 Save Channel 保存您的设置。

配置物联网应用程序

在 The Things Network 上创建一个帐户,然后登录 The Things Network Console

创建应用程序

1) 选择 Applications

2) 选择 Add Application

3) 创建一个 Application ID,然后添加一个 Description。根据您的位置选择 Handler registration

注册设备

1) 点击 Devices 选项卡并注册一个设备。有关详细信息,请参阅设备注册

2) 创建设备 ID。如果您的设备有设备 EUI,请输入设备 EUI。如果没有,请选择 Device EUI 字段左侧的按钮以自动生成 EUI。

3) 点击Register。浏览器将返回到 Overview 选项卡。

4) 选择 Settings 选项卡。

5) 在设置中,选择 ABP 作为激活方式。为了帮助调试,您可以选择禁用页面底部的 Frame Counter Checks

6) 记录 Device AddressNetwork Session KeyApp Session Key。您的设备代码中需要此信息。

创建有效负载格式化程序

有效载荷格式化程序使用从网关发送到应用程序的字节来组装消息。在此示例中,所需的有效负载消息是发送到 ThingSpeak 的 JSON 编码对象。

1) 使用顶部的导航菜单返回应用程序视图。然后点击 Payload Formats 选项卡。

2) 在 decoder 接口中,创建代码将从您的设备发送的字节转换为 JSON 对象以写入 ThingSpeak。latlon 的条件代码处理正值或负值的可能性。

function Decoder(b, port) {
  
 var counter = b[0] << 8) | b[1];
 var moisture = b[2] | b[3] << 8;
 var temp= ( b[4] | b[5] << 8 )/100;
 var lat = ( b[6] | b[7] << 8 | b[8] << 16 | (b[8] & 0x80 ? 0xFF << 24 : 0)) / 10000;
 var lon = ( b[9] | b[10] << 8 | b[11] << 16 | (b[11] & 0x80 ? 0xFF << 24 : 0)) / 10000;

  return {
    field1: counter,
    field2: moisture,
    field3: temp,
    latitude: lat,
    longitude: lon
  }
}

添加集成

要将数据转发到 ThingSpeak,您必须在物联网上拥有一个带有注册设备和有效载荷格式化程序的应用程序。创建一个 ThingSpeak 集成来转发数据。

1) 登录您的 The Things Network Console

2) 选择 Applications,然后选择您想要将数据转发到 ThingSpeak 的应用程序。

3) 点击 Integrations 选项卡。

4) 选择 ThingSpeak。

5) 在“Process ID”字段中,为您的集成命名。

6) 在“Authorization”字段中,输入您想要存储数据的通道的写入 API 密钥。您可从 ThingSpeak 通道的 API keys 选项卡中获取 API 密钥。

7) 在“Channel ID”字段中,输入您要将数据转发到的 ThingSpeak 通道的通道 ID。通道 ID 可在您的 ThingSpeak 通道页面上找到。

创建设备

创建传感器节点所用的硬件

您可以使用各种支持 LoRaWan 协议的 LoRa 设备连接到物联网。此示例演示了使用以下硬件设置的过程。

原理图和连接

按照示意图所示连接传感器。照片展示了工程箱中传感器的可能配置。在这种配置下,盒子内的温度传感器可能无法准确反映外部温度。您需要为 LoRa 无线电添加天线

1) 连接 GPS 和温度传感器的电源和接地。请勿将电源连接到湿度传感器。

2) 将土壤湿度传感器输出连接到 A0 的模拟输入。

3) 设置系统,使得湿度传感器电源在不使用时关闭。湿度传感器电源引脚连接到羽毛 M0 上的 GPIO 引脚 11。不使用时关闭电源可延长传感器的使用寿命。

4) 将 DH- 22 传感器数据引脚连接到 Feather M0 上的 PA-15(即 Arduino® 草图中的引脚 5)。

5) 对于 GPS 板,将 Feather M0 上的 TX 连接到 RX,将 RX 连接到 TX。

6) 通过将 Feather M0 上的 PA20(引脚 29,GPIO 6) 连接到地面来启用 LoRa 无线电。

7) 通过将开关从 En 引脚连接到地来创建电源开关。

程序设备

编程设置

1) 下载最新的 Arduino IDE。

2) 下载 Adafruit GPS 库或在库管理器中添加 Adafruit_GPS 库。选择 Sketch > Include Library > Manage Libraries。搜索 Adafruit_GPS 并将其添加到您已安装的库中。

3) 下载适用于 Arduino 环境的 LoraWan-in-C 库或在库管理器中添加 lmichal/hal 库。选择 Sketch > Include Library > Manage Libraries。搜索 lmic 并选择 MCCI LoRaWAN LMIC library 将其添加到您已安装的库中。同时添加 MCCI Arduino LoRaWan Library to the library manager

4) 创建应用程序。在 Arduino IDE 中打开一个新窗口并保存文件。添加代码部分提供的代码。

代码

1) 首先包含适当的库并初始化变量。

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include "DHT.h"
#include <Adafruit_GPS.h>

#define DHTPIN 5
#define GPSSerial Serial1
#define SOIL_PIN 14
#define SOIL_POWER_PIN 11
#define GPSECHO false   // Set to 'true' if you want to debug and listen to the raw GPS sentences
#define PAYLOAD_SIZE 13 // Number of bytes sent plus 2


// LoRaWAN NwkSKey, network session key
static const PROGMEM u1_t NWKSKEY[16] = {0x98, 0xEB, 0x1A, 0xC5, 0xF9, 0x20, 0x15, 0xCD, 0x12, 0xE5, 0x72, 0xFF, 0xCD, 0xE2, 0x94, 0x46};
// LoRaWAN AppSKey, application session key
static const u1_t PROGMEM APPSKEY[16] = {0x50, 0x28, 0x4D, 0xAE, 0xEA, 0x41, 0x53, 0x7E, 0xCA, 0x70, 0xD2, 0x26, 0xCC, 0x14, 0x66, 0x19};
// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x26021115;

// Callbacks are only used in over-the-air activation. Leave these variables empty unless you use over the air activation.
void os_getArtEui(u1_t *buf) {}
void os_getDevEui(u1_t *buf) {}
void os_getDevKey(u1_t *buf) {}

// Payload to send to TTN gateway
static uint8_t payload[PAYLOAD_SIZE];
static osjob_t sendjob;

// Schedule TX at least this many seconds
const unsigned TX_INTERVAL = 60; //was 30

// Pin mapping for Adafruit Feather M0 LoRa
const lmic_pinmap lmic_pins = {
    .nss = 8,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 4,
    .dio = {3, 6, LMIC_UNUSED_PIN},
    .rxtx_rx_active = 0,
    .rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB.
    .spi_freq = 8000000,
};

Adafruit_GPS GPS(&GPSSerial); // Connect to the GPS on the hardware port.

DHT dht(DHTPIN, DHT22);  // Connect to the temperature sensor.
uint16_t counter = 0;
int32_t myLatitude = -12345; // Initialize for testing before GPS finds a lock.
int32_t myLongitude = 54321; // Initialize for testing.
int myMoisture = 0; // 10 bit ADC value.
float temperatureF = 1111; 

2) 使用 setup 函数启动温度传感器、GPS 和 LoRa 无线电。

void setup()
{
    Serial.begin(115200);
    dht.begin();
    Serial.println("Start");
    // Set the power pin for the moisture sensor
    pinMode(SOIL_POWER_PIN,OUTPUT);
    digitalWrite(SOIL_POWER_PIN, LOW);

    GPS.begin(9600); // 9600 NMEA is the default baud rate.
    GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
    GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // Set a 1 Hz update rate.

    delay(1000); // Wait for GPS to initialize.

    // Ask for firmware version
    GPSSerial.println(PMTK_Q_RELEASE);
    // Initialize the LMIC.
    os_init();
    // Reset the MAC state. Resetting discards the session and pending data transfers. 
    LMIC_reset();

    // Set static session parameters. 
    uint8_t appskey[sizeof(APPSKEY)];
    uint8_t nwkskey[sizeof(NWKSKEY)];
    memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
    memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
    LMIC_setSession(0x13, DEVADDR, nwkskey, appskey);

    LMIC_selectSubBand(1);
    // Only use the correct The Things Network channels, disable the others.
    for (int c = 0; c < 72; c++)
    {
        if ((c < 8) || (c > 15))
        {
            LMIC_disableChannel(c);
        }
    }
    
    LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);

    // Disable link check validation
    LMIC_setLinkCheckMode(0);

    // TTN uses SF9 for its RX2 window.
    LMIC.dn2Dr = DR_SF9;

    // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
    LMIC_setDrTxpow(DR_SF7, 14);

    // Start job.
    processJob(&sendjob);
}

3) 使用 loop 函数启动 LoRa 进程并解析 GPS 数据。

void loop() // Run over and over again
{
    os_runloop_once();

    char c = GPS.read();
    if (GPSECHO)
     {
        if (c){
            Serial.print(c);
              }
     }
    // If a sentence is received, parse it
    if (GPS.newNMEAreceived())
    {
        if (!GPS.parse(GPS.lastNMEA())) // Also sets the newNMEAreceived() flag to false
            return;                   
    }
}

4) GetSensorData 函数打开湿度传感器的电源并读取其数据,然后关闭电源。它还读取温度传感器并检查来自 GPS 设备的信息。如果有 GPS 定位,此函数将更新位置信息。

void GetSensorData()
{
    digitalWrite(SOIL_POWER_PIN, HIGH);
    delay(1000);
    myMoisture = analogRead(SOIL_PIN);
    digitalWrite(SOIL_POWER_PIN, LOW);
    temperatureF = dht.readTemperature( true );
    Serial.println("moisture " + String( myMoisture ) + " temp " + String( temperatureF ));
     
    if (GPS.fix)
    {
        Serial.print( "Location: " );
        Serial.print( GPS.latitudeDegrees * 100, 4 );
        Serial.print( " break " );
        Serial.print( GPS.lat );
        Serial.print( ", " );
        Serial.print( GPS.longitudeDegrees * 100 , 4 );
        Serial.println( GPS.lon );
        myLatitude = GPS.latitudeDegrees * 10000;
        myLongitude = GPS.longitudeDegrees * 10000;
    }
}

5) 使用 onEvent 函数处理来自 LoRa 无线电的事件。该函数更新串行监视器、安排下一次传输并接收消息。

void onEvent(ev_t ev)
{
    Serial.print(os_getTime());
    Serial.print(": ");

    switch (ev)
    {
    case EV_SCAN_TIMEOUT:
        Serial.println(F("EV_SCAN_TIMEOUT"));
        break;
    case EV_BEACON_FOUND:
        Serial.println(F("EV_BEACON_FOUND"));
        break;
    case EV_BEACON_MISSED:
        Serial.println(F("EV_BEACON_MISSED"));
        break;
    case EV_BEACON_TRACKED:
        Serial.println(F("EV_BEACON_TRACKED"));
        break;
    case EV_JOINING:
        Serial.println(F("EV_JOINING"));
        break;
    case EV_JOINED:
        Serial.println(F("EV_JOINED"));
        break;
    case EV_JOIN_FAILED:
        Serial.println(F("EV_JOIN_FAILED"));
        break;
    case EV_REJOIN_FAILED:
        Serial.println(F("EV_REJOIN_FAILED"));
        break;
    case EV_TXCOMPLETE:
        Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
        if (LMIC.txrxFlags & TXRX_ACK)
            Serial.println(F("Received ack"));
        if (LMIC.dataLen)
        {
            Serial.println(F("Received "));
            Serial.println(LMIC.dataLen);
            Serial.println(F(" bytes of payload"));
        }
        // Schedule next transmission
        os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
        break;
    case EV_LOST_TSYNC:
        Serial.println(F("EV_LOST_TSYNC"));
        break;
    case EV_RESET:
        Serial.println(F("EV_RESET"));
        break;
    case EV_RXCOMPLETE:
        // data received in ping slot
        Serial.println(F("EV_RXCOMPLETE"));
        break;
    case EV_LINK_DEAD:
        Serial.println(F("EV_LINK_DEAD"));
        break;
    case EV_LINK_ALIVE:
        Serial.println(F("EV_LINK_ALIVE"));
        break;

    case EV_TXSTART:
        Serial.println(F("EV_TXSTART"));
        break;
    default:
        Serial.print(F("Unknown event: "));
        Serial.println((unsigned)ev);
        break;
    }
}

6) processJob 函数将传感器数据转换为位,通过 LoRa 无线电发送。

void processJob(osjob_t *j)
{
    getSensorData();
    
    if (LMIC.opmode & OP_TXRXPEND) // Check if there is a current TX/RX job running.
    {
        Serial.println(F("OP_TXRXPEND, not sending"));
    }
    else
    {
        payload[0] = byte(counter);
        payload[1] = counter >>8;

        payload[2] = byte(myMoisture);
        payload[3] = myMoisture >> 8;

        int shiftTemp = int(temperatureF * 100); // Convert temperature float to integer for sending and save two places.
        payload[4] = byte(shiftTemp);
        payload[5] = shiftTemp >> 8;

        payload[6] = byte(myLatitude);
        payload[7] = myLatitude >> 8;
        payload[8] = myLatitude >> 16;

        payload[9] = byte(myLongitude);
        payload[10] = myLongitude >> 8;
        payload[11] = myLongitude >> 16;

        LMIC_setTxData2(1, payload, sizeof(payload) - 1, 0); // Prepare upstream data transmission at the next possible time.

        counter++;
        Serial.println(String(counter));
    }
    // Next TX is scheduled after TX_COMPLETE event.

要了解如何在您的 ThingSpeak 通道中创建详细的可视化仪表板,请参阅创建自定义 ThingSpeak 通道视图