使用 Seeed 的 LoRa-E 5模块, 为 LoRaWAN 网络开发风速/风向传感器

描述

该项目涵盖了开发基于Seeed LoRa-E5的Arduino风速/方向传感器的过程,并将其安装在能够连接到DigiKey公司总部大楼屋顶气象平台的室外外壳中。该项目还包括将传感器节点添加到现有的基于ChirpStack的私有LoRaWAN网络和Machinechat的JEDI Pro IoT软件平台。LoRa传感器使用Seeeduino Xiao, Grove LoRa-E5无线电板,以及来自Sparkfun天气仪表套件的风速计和风向标硬件。LoRaWAN网络使用Seeed的IP67等级工业LoRaWAN网关将LoRa传感器数据包转发到Seeed的ReServer上运行的专用ChirpStack LoRaWAN网络服务器(安装了Ubuntu linux)。Machinechat的JEDI Pro IoT平台运行在同一个ReServer上。

硬件

软件

  • JEDI Pro或JEDI Pro SSE
    适用于物联网数据收集、可视化、监控和数据存储的软件,可集成到物联网解决方案中。功能包括:收集来自传感器、设备和机器的数据;构建直观的实时和历史数据以及系统视图仪表板;创建规则,自动监控和响应数据情况;通过电子邮件和短信接收警报通知。JEDI Pro SSE是JEDI Pro的Seeed工作室版版本,为Seeed的SenseCAP LoRaWAN传感器系列增加了一个数据收集器
  • ChirpStack
    ChirpStack开源LoRaWAN网络服务器堆栈为LoRaWAN网络提供开源组件。模块化的架构使得在现有的基础设施内集成成为可能。
  • Arduino
    Arduino是一个基于易于使用的硬件和软件的开源电子平台。

背景

这篇文章是一个后续项目,建立在相关的技术论坛文章的基础上,使用 Machinechat 和 Seeed SenseCAP 建立一个私有的 LoRaWAN 传感器网络,其中详细介绍了使用DigiKey上现成的硬件和软件建立一个私有的LoRaWAN物联网传感器网络。相关项目中使用的软件包括Machinechat的JEDI Pro应用软件和ChirpStack的LoRaWAN网络服务器应用程序。项目中使用的硬件包括Seeed reServer x86服务器和SenseCAP室外LoRaWAN网关。对于这篇文章,Seeeduino Xiao, Grove LoRa-E5板和Sparkfun气象仪套件被设置为在LoRaWAN网络上报告风速和风向。
Sparkfun气象仪表套件风速计和风向标
风速是由杯式风速计测量,旋转磁铁导致接触关闭一次每转。触点每秒关闭一次,对应的风速为1.492 MPH (2.4 km/h)。触点闭合可以通过风向标电缆上RJ-11连接器(引脚2和3)的两个内导体进行监测。
风向由风向标上的8个开关决定,每个开关都有自己的电阻器。随着风向标方向的改变,风向标上的磁铁会使各个开关闭合。外部电阻器用来设置分压器,这样就可以监测电压输出来确定风向标的位置。RJ-11连接器的两个外部导体(引脚1和引脚4)用于风向标。(注:详细信息请参阅Sparkfun气象仪连接指南)

实现

对于该项目,外部USB 5V电源为Seeeduino Xiao供电,Xiao的3V3输出为Grove LoRa-E5板和天气传感器硬件供电。Xiao UART连接到Grove LoRa-E5的TX/RX引脚,Xiao输入引脚1和2连接到风传感器风速计和风向标。
Xiao和LoRa-E5板安装在室外外壳中(Hammod 型号#RL6225), +5V电源和风传感器风速计/风向标使用M8面板安装连接器和电缆组件连接。
Arduino应用代码在Seeeduino Xiao上运行,向LoRa-E5发送AT命令,并监控输入引脚1和2。应用程序加入LoRa网络,监控输入引脚以计算风速/风向数据,以CayenneLPP格式编码数据,通过LoRa发送数据,延迟并循环返回下一次读取。下面的原理图说明了电路是如何连接和实现的(参见Scheme-it 项目: LoRaE5_Xiao_WindSensorM8cables)。

模块与风传感器连接器J1的电气连接如下图所示:

Seeeduino Xiao pin Grove LoRa-E5 连接器 风传感器连接器 J1
GND GND 1
3 v3 VCC 2
1 4(风向标)
2 3(风速计)
6 RX
7 TX

设置Seeeduino Xiao, Grove LoRa-E5和风速计/风向标

1 - 在Seeeduino Xiao上设置Arduino参见链接Seeeduino Xiao入门

2 - 安装应用程序所需的库。通过Arduino的库管理器添加这些库:

CayenneLPP

3 - 代码演练(文名:Xiao_LoraE5CayenneLPP_WindRev1.ino)

初始设置

// below code is based on Seeed LoRaE5 example code and modified to NOT use the display on the Seeeduino Xiao expansion board (just uses a Xiao connected to a LoRaE5 
// Grove expansion board. Seeed example code at https://wiki.seeedstudio.com/Grove_LoRa_E5_New_Version/
//
//note: all Seeed LoRaE5 Grove boards have example code App key of "2B7E151628AED2A6ABF7158809CF4F3C" so needs to be changed
// this version changes out the DHT11 sensor for Sparkfun weather meter kit for wind speed and direction

// Anemometer measuring code based on below:
//More Information at: https://www.aeq-web.com/
//Version 2.0 | 11-NOV-2020
//
//SBR modifications
//SensorPin is connected to RC pulldown circuit on anemometer switch
//Wind vane pin is connected to voltage divider circuit for vane resistors


#include <Arduino.h>
#include <CayenneLPP.h>


// anemometer and direction parameters
const int RecordTime = 3; //Define Measuring Time (Seconds)
const int SensorPin = 2;     // the number of the sensorpin
const int VanePin = 1;      // pin# connected to wind vane
const int ledPin =  13;      // the number of the LED pin
int InterruptCounter;
float WindSpeed;
float DirWind;   //wind direction voltage
float angleWind; //wind vane angle
  

CayenneLPP lpp(51);   //setup Cayenne LPP (low power payload) buffer - per documentation 51 bytes is safe to send
 
static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;
static int led = 0;

int buf_size;  //Cayenne LPP buffer payload size
int Pointer;   //pointer used in Cayenne LPP buffer
int Offset = 12;    //offset to where Cayenne LPP payload data starts
int Loop1;       //loop counter in LoRa payload builder
int Loop2;       //loop counter in LoRa payload builder
int Loop3 = 0;       //loop counter in LoRa parameter send

 
static int at_send_check_response(char *p_ack, int timeout_ms, char *p_cmd, ...)
{
    int ch;
    int num = 0;
    int index = 0;
    int startMillis = 0;
    va_list args;
    memset(recv_buf, 0, sizeof(recv_buf));
    va_start(args, p_cmd);
    Serial1.printf(p_cmd, args);
    Serial.printf(p_cmd, args);
    va_end(args);
    delay(200);
    startMillis = millis();
 
    if (p_ack == NULL)
    {
        return 0;
    }
 
    do
    {
        while (Serial1.available() > 0)
        {
            ch = Serial1.read();
            recv_buf[index++] = ch;
            Serial.print((char)ch);
            delay(2);
        }
 
        if (strstr(recv_buf, p_ack) != NULL)
        {
            return 1;
        }
 
    } while (millis() - startMillis < timeout_ms);
    return 0;
}
 
static void recv_prase(char *p_msg)
{  
    if (p_msg == NULL)
    {
        return;
    }
    char *p_start = NULL;
    int data = 0;
    int rssi = 0;
    int snr = 0;
 
    p_start = strstr(p_msg, "RX");
    if (p_start && (1 == sscanf(p_start, "RX: \"%d\"\r\n", &data)))
    {
        Serial.println(data);
        led = !!data;
        if (led)
        {
            digitalWrite(LED_BUILTIN, LOW);
        }
        else
        {
            digitalWrite(LED_BUILTIN, HIGH);
        }
    }
 
    p_start = strstr(p_msg, "RSSI");
    if (p_start && (1 == sscanf(p_start, "RSSI %d,", &rssi)))
    {
         Serial.println(rssi);
    }
    p_start = strstr(p_msg, "SNR");
    if (p_start && (1 == sscanf(p_start, "SNR %d", &snr)))
    {
        Serial.println(snr);
    }
}

 
void setup(void)
{
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, HIGH);

    // anemeometer and wind vane setup
    pinMode(SensorPin, INPUT); //use input to count interrupts of anemometer
    pinMode(VanePin, INPUT); //use input to read analog voltage of wind vane output
 
    Serial1.begin(9600);                  //UART serial1 connection to LoRaE5
    Serial.print("E5 LORAWAN TEST\r\n");
 
    if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
    {
        is_exist = true;
        at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
        at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
        //at_send_check_response("+DR: EU868", 1000, "AT+DR=EU868\r\n");  //original
        at_send_check_response("+DR: US915", 1000, "AT+DR=US915\r\n"); 
        //at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,0-2\r\n");  //original
        at_send_check_response("+CH: NUM", 1000, "AT+CH=NUM,8-15,65\r\n"); // configure channels to match chirpstack
        at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY,\"2B7E151628AED2A6ABF7158809CF4F3C\"\r\n");
        at_send_check_response("+CLASS: C", 1000, "AT+CLASS=A\r\n");
        at_send_check_response("+PORT: 8", 1000, "AT+PORT=8\r\n");
        delay(200);
        is_join = true;
    }
    else
    {
        is_exist = false;
        Serial.print("No E5 module found.\r\n");
    }
 
}

主循环——确定风速和风向,以CayenneLPP格式编码,并作为LoRa载荷数据发送

void loop(void)
//void loop()
{
     
    // measure windspeed
    measure();
    // measure wind vane voltage
    DirWind = analogRead(VanePin)*3.33;
    // calculate wind direction in degrees
    vaneAngle();
    
    
    Serial.print("Windspeed: ");
    Serial.print(WindSpeed);
    Serial.print(" mph ");
    Serial.print("Wind Direction: ");
    Serial.print(angleWind);
    Serial.println(" degrees");


//***************** start of Cayenne LPP code **************************
    // due to character byte limitatations in LoRa payload buffer, need to alternate between 
    // the two wind parameters when sending out parameter payload
    
    Serial.print("Loop3 = ");  //debug code
    Serial.println(Loop3);    //debug code

    if (Loop3 == 0)
    {
      lpp.reset();
      lpp.addAnalogOutput(1, WindSpeed);  //channel 1, windspeed value
      Loop3 = 1;
    }
    else
    {
      lpp.reset();
      lpp.addAnalogOutput(2, angleWind);  //channel 2, wind direction value
      Loop3 = 0;
    }
   

    buf_size = lpp.getSize();
    Serial.print("Cayenne LPP buffer size = ");
    Serial.println(buf_size);

    uint8_t *payload = lpp.getBuffer();
    char cmd[128];
    
    // Lmsg is just test code that probably can be deleted?
    char Lmsg[4];
    for (unsigned char i = 0; i < lpp.getSize(); i++)
    {
      sprintf(Lmsg, "%02X", (char)payload[i]);
      Serial.print(Lmsg);
    }

    Serial.println(" payload"); 

//**************end of payload builder code**************************



    // start of LoRa communication code 
    if (is_exist)
    {
        int ret = 0;
        if (is_join)
        {
 
            ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
            if (ret)
            {
                is_join = false;   //resets join check?
            }
            else
            {
                at_send_check_response("+ID: AppEui", 1000, "AT+ID\r\n");
                Serial.print("JOIN failed!\r\n\r\n");
                delay(5000);
            }
        }
        else
        {
            char cmd[128]; 
            //sprintf(cmd, "AT+CMSGHEX=\"%04X%04X\"\r\n", (int)temp, (int)humi); //original
            //****** add in CayenneLPP ecoding ******
            sprintf(cmd, "AT+CMSGHEX=\"%02X\"\r\n", (char)payload[0]);  //first part of confirmed message
            Serial.println("debug - print cmd at start of loop");
            Serial.println(cmd);
            // add data payload to LoRa message by looping thru Cayenne LPP data buffer
            char TestMsg[2];   
            for (Loop1 = 0; Loop1 < buf_size; Loop1++)
              {
                Pointer = (Loop1*2) + Offset;
                sprintf(TestMsg, "%02X", (char)payload[Loop1]);
                // write data buffer character to LoRa message
                for (Loop2 = 0; Loop2 < 2; Loop2++)
                  {
                     cmd[Loop2 + Pointer] = TestMsg[Loop2];
                  }
              }            
            // create end of message characters for LoRa message (need ",return,null characters)
            char EndMsg[20];
            sprintf(EndMsg, "test\"%02X\"\r\n", (char)payload[0]);
            // add ", return and null characters to end of LoRa message
            for (unsigned char i = 2; i < 7; i++)  
              {
                cmd[Pointer + i] = EndMsg[5 + i];
              }            
            Serial.println("debug - print cmd at end of loop");
            Serial.println(cmd);
            //******** end of CayenneLPP encodeding *********
            ret = at_send_check_response("Done", 12000, cmd);        //sends cmd command over LoRa - increase time from 5000 to 12000
            if (ret)
            {
                recv_prase(recv_buf);
            }
            else
            {
                Serial.print("Send failed!\r\n\r\n");
            }
            delay(5000); 
        }
    Serial.println(" in main loop checking LoRa then wait 5 minutes");   
    delay(300000); //main delay 300 seconds 
    }
    else
    {
        delay(1000);
    }
}

传感器子例程


//count anemometer pulses and calculate windspeed
void measure() {
  InterruptCounter = 0;
  attachInterrupt(digitalPinToInterrupt(SensorPin), countup, RISING);
  delay(1000 * RecordTime);
  detachInterrupt(digitalPinToInterrupt(SensorPin));
  WindSpeed = (float)InterruptCounter / (float)RecordTime * 2.4;
}

void countup() {
  InterruptCounter++;
}

//determine wind direction angle based on measured wind vane voltage
void vaneAngle() {
  angleWind = 99.9;
  if (DirWind < 270)
    {
      angleWind = 112.5;
    }
  else if ((DirWind >= 270) and (DirWind <= 310))
    {
      angleWind = 67.5  ;
    } 
   else if ((DirWind > 310) and (DirWind <= 400))
    {
      angleWind = 90.0  ;
    }    
  else if ((DirWind >= 400) and (DirWind < 600))
    {
      angleWind = 157.5  ;
    }    
  else if ((DirWind >= 600) and (DirWind < 750))
    {
      angleWind = 135.0  ;
    }    
  else if ((DirWind >= 750) and (DirWind < 850))
    {
      angleWind = 202.5  ;
    }    
  else if ((DirWind >= 850) and (DirWind < 1150))
    {
      angleWind = 180.0  ;
    }    
   else if ((DirWind >= 1150) and (DirWind < 1450))
    {
      angleWind = 22.5  ;
    }    
   else if ((DirWind >= 1450) and (DirWind < 1900))
    {
      angleWind = 45.0  ;
    }    
  else if ((DirWind >= 1900) and (DirWind < 2050))
    {
      angleWind = 247.5  ;
    }    
   else if ((DirWind >= 2050) and (DirWind < 2250))
    {
      angleWind = 225.0  ;
    }    
  else if ((DirWind >= 2250) and (DirWind < 2500))
    {
      angleWind = 337.5;
    }
  else if ((DirWind >= 2500) and (DirWind < 2700))
    {
      angleWind = 0.0;
    }
  else if ((DirWind >= 2700) and (DirWind < 2850))
    {
      angleWind = 292.5;
    }
  else if ((DirWind >= 2850) and (DirWind < 3000))
    {
      angleWind = 315.0;
    }
  else if (DirWind >= 3000)
    {
      angleWind = 270.0;
    }
}

eewiki/machinechat/blob/master/Seeeduino Xiao/Xiao_LoraE5CayenneLPP_WindRev1.ino

确定Grove LoRa-E5板设备EUI

在Arduino中,编译并上传Xiao_LoraE5CayenneLPP_WindRev1。进入Seeeduino Xiao并启用串行监视器。检查串行监视器输出以确定Grove LoRa-E5设备的EUI。

将LoRa-E5传感器节点添加到ChirpStack LoRaWAN网络服务器中

(注意:这个项目和下面的步骤假设一个基于ChirpStack的私有LoRaWAN网络是活跃的,并且在LoRa-E5传感器节点的范围内,如果没有参考TechForum帖子
使用 Machinechat 和 Seeed SenseCAP 建立一个私有的 LoRaWAN 传感器网络)

1 - 在ChirpStack中,选择设备配置文件并创建。名称设备配置文件“Seeed LoRaE5”,LoRaWAN MAC版本选择“1.0.2”,LoRaWAN区域参数版本选择“A”,ADR算法选择“Default ADR algorithm”,上行间隔输入“3600”。在JOIN(OTAA/ABP) 选项卡中,选中“设备支持OTAA”。在CODEC 选项卡中,在CODEC下拉列表中选择“Cayenne LPP”。

2 - 在ChirpStack中,选择应用程序,然后选择“FarmTest”,然后选择创建。设备名称输入“LoRaE5wind”,设备描述输入“description”,Grove LoRa-E5板输入“Device EUI”(由上一步*“*Determine Grove LoRa-E5 board device EUI 确定),设备配置文件输入“STM32WL Sensors”,选择CREATE Device。(注意:对于初始测试和演示,您可能需要勾选“ Disable frame-counter validation”框)

3 - 添加设备的应用密钥。输入应用键“2B7E151628AED2A6ABF7158809CF4F3C”(注意:这是LoRa-E5中的默认键,更改请参见LoRa-E5 AT命令规范的key部分)并选择SET DEVICE-KEYS。

设置和测试ChirpStack HTTP集成与JEDI Pro通用LoRaWAN自定义数据收集器

修改ChirpStack,增加HTTP集成,用于将LoRaWAN元数据和传感器数据转发到指定IP地址。Machinechat的通用LoRaWAN自定义数据收集器插件用于监听指定的IP地址并解析LoRaWAN数据以供审查(启用调试时)并在JEDI Pro平台中使用。
(注意:自定义数据收集器包含两个文件:lorawan-linux.bin和config。可从Machinechat获取: https://support.machinechat.io/hc/en-us/articles/6046199010327-Generic-LoRaWAN-Custom-Data-Collector-Beta-for-JEDI-PRO-Linux )

1 - 在ChirpStack中启用HTTP集成。
在ChirpStack集成界面中选择“Add”。

2 - 配置HTTP集成
选择“JSON”为有效载荷封送器,添加IP地址(使用相同的IP配置。为Endpoint URL 添加ip地址,然后选择ADD INTEGRATION

3 - 拷贝“lorawan-linux.bin 和“config.yml 。到 Ubuntu Linux Mini-PC上安装JEDI Pro的~/jedi/plugins目录下。修改配置。yml文件使能调试,并指定IP监听地址。
(注意:如果你之前已经安装了lorawan-linux.bin config. yml 文件。yml 文件对于不同的传感器,你所需要做的就是编辑config.yml 如步骤5所示,添加风速和风角度参数的信息)

4 - 在命令行中使用*“**./lorawan-linux.bin ./config.yml* 运行自定义插件。在 Ubuntu Linux Mini-PC 的终端上运行 yml ,并监控输出数据。数据应该类似于下面(注意:记得让lorawan-linux.bin文件可执行):

5 - 编辑config.yml文件。将LoRaWAN数据映射到JEDI Pro数据参数并关闭调试。对于这个项目示例,编辑propertyNames以便LoRaWAN cSproperty: " anslogOutput.1 对应mcProperty: WindSpeed 和LoRaWAN cSproperty:**“ analogOutput.2 对应mcProperty: WindAngle 。通过设置“setDebug:”为false来关闭调试并保存文件。

设置JEDI Pro自定义数据收集器和数据仪表板

在JEDI Pro中,选择“设置”选项卡,然后选择“数据收集器”,然后选择“添加收集器”。(注意:如果您之前已经将LoRaWAN自定义数据收集器添加到JEDI应用程序中,则无需执行此步骤)

如下图所示配置Collector。将数据收集器命名为“LoRaWAN”(或您喜欢的任何名称),选择“收集器类型”为“自定义插件”,选择“LoRaWAN -linux.bin”作为插件可执行文件,输入config.yml 文件(例如:“/home/scottr/jedi/plugins/config.yml”作为Plug-in选项,选中“Run As Background Process and Monitor”复选框,然后选择“VALIDATE PLUG-IN”以验证功能。

在JEDI Pro中,选择“Data Dashboards”,然后选择“+”来添加一个新图表。

image

为风速和风角度配置数据图表,并选择“Add”以包含在数据仪表板中(参见下面的示例)

结论

Arduino、Seeeduino Xiao、LoRa-E5无线电和Sparkfun的气象仪套件的组合提供了一个灵活的风传感器平台,可以通过LoRa提供风速和风向数据。然后配置ChirpStack的HTTP集成和Machinechat的通用LoRaWAN自定义数据采集器,将风传感器数据导入JEDI Pro,用于物联网数据收集、可视化、监控和数据存储。可以根据需要轻松修改示例代码以用于其他传感器。

参考文献