Main Content

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

通过物联网收集农业数据

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

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

概述

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

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

2) 设置物联网

  • 创建应用

  • 注册设备

  • 创建有效负载格式

  • 添加集成

3)创建设备

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

  • 原理图和连接

4) 程序设备

  • 编程设置

  • 代码

设置 ThingSpeak 通道来收集数据

1)创建ThingSpeak通道,如 Collect Data in a New Channel 所示。记录新通道的写入 API 密钥和通道ID。

2) 导航至“通道设置”页面。如下设置字段标签。

  • 字段 1 — Counter

  • 字段 2 — Soil Moisture

  • 字段 3 — Temperature F

3) 点击底部的“保存通道”以保存您的设置。

配置物联网应用

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

创建应用

1) 选择应用。

2) 选择“添加应用”。

3) 创建一个“应用ID”,然后添加一个“说明”。根据您的位置选择“处理程序注册”。

注册设备

1) 点击“设备”选项卡并注册设备。有关详细信息,请参阅 Device Registration

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

3) 点击“注册”。浏览器将返回到“概述”选项卡。

4) 选择“设置”选项卡。

5) 在设置中,激活方式选择ABP。为了帮助调试,您可以选择禁用页面底部的“帧计数器检查”。

6) 记录“设备地址”、“网络会话密钥”和“ App 会话密钥”。您的设备代码中需要此信息。

创建有效负载格式化程序

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

1) 使用顶部的导航菜单返回到应用视图。然后点击“有效负载格式”选项卡。

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) 选择应用并选择要将数据转发到 ThingSpeak 的应用。

3) 点击“集成”选项卡。

4) 选择 ThingSpeak。

5) 在进程 ID 字段中,为您的集成命名。

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

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

创建设备

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

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

原理图和连接

如图所示连接传感器。该照片显示了项目盒中传感器的可能配置。在此配置中,盒子内的温度传感器可能无法准确反映外部温度。您需要 add an antenna 到 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 library或在库管理器中添加Adafruit_GPS库。选择Sketch> Include Library> Manage Libraries。搜索 Adafruit_GPS 将其添加到已安装的库中。

3) 下载适用于 Arduino 环境的 LoraWan-in-C library 或在库管理器中添加 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); // Convet 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 通道中创建详细的可视化仪表板,请参阅 Create Customized ThingSpeak Channel View