Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
SpencerYvonne
Published © GPL3+

Sensing Environmental Conditions in My Garden with XIAO

Utilizing XIAO, Wio-E5(LoRa), let's navigate through the LoRaWAN architecture to enhance my agricultural practices and experiences.

IntermediateWork in progress2 hours464
Sensing Environmental Conditions in My Garden with XIAO

Things used in this project

Hardware components

Seeed Studio XIAO ESP32S3
×1
Grove extension board for Seeed Studio XIAO
×1
Seeed Studio Grove - SEN54
×1
Grove - Soil Moisture Sensor
Seeed Studio Grove - Soil Moisture Sensor
×1
Grove - Wio-E5 (STM32WLE5JC)
Seeed Studio Grove - Wio-E5 (STM32WLE5JC)
×1

Software apps and online services

PlatformIO IDE
PlatformIO IDE
The platofmrio on VSCode
The Things Stack
The Things Industries The Things Stack
Datacake
Datacake

Story

Read more

Code

other.cpp

C/C++
Those related functions.
#include "main.h"
/**
 * @brief
 * Wet: Lower
 * Dry: Higher
 */
int moisture_read(payload_moisture &data)
{
	data.moisture = analogRead(PIN_SENSOR_MOISTURE);
	return data.moisture;
}

void SEN5x_Read(payload_SEN5x &data)
{
  uint16_t error;
  char errorMessage[256];

  delay(500);

  error = sen5x.readMeasuredValuesAsIntegers(
      data.massConcentrationPm1p0, data.massConcentrationPm2p5, data.massConcentrationPm4p0,
      data.massConcentrationPm10p0, data.ambientHumidity, data.ambientTemperature, data.vocIndex,
      data.noxIndex);

  if (error)
  {
    SERIAL_DEBUG.print("Error trying to execute readMeasuredValues(): ");
    errorToString(error, errorMessage, 256);
    SERIAL_DEBUG.println(errorMessage);
  }
  else
  {
    SERIAL_DEBUG.print("MassConcentrationPm1p0:");
    SERIAL_DEBUG.print(data.massConcentrationPm1p0);
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("MassConcentrationPm2p5:");
    SERIAL_DEBUG.print(data.massConcentrationPm2p5);
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("MassConcentrationPm4p0:");
    SERIAL_DEBUG.print(data.massConcentrationPm4p0);
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("MassConcentrationPm10p0:");
    SERIAL_DEBUG.print(data.massConcentrationPm10p0);
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("AmbientHumidity:");
    if (isnan(data.ambientHumidity))
    {
      SERIAL_DEBUG.print("n/a");
    }
    else
    {
      SERIAL_DEBUG.print(data.ambientHumidity);
    }
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("AmbientTemperature:");
    if (isnan(data.ambientTemperature))
    {
      SERIAL_DEBUG.print("n/a");
    }
    else
    {
      SERIAL_DEBUG.print(data.ambientTemperature);
    }
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("VocIndex:");
    if (isnan(data.vocIndex))
    {
      SERIAL_DEBUG.print("n/a");
    }
    else
    {
      SERIAL_DEBUG.print(data.vocIndex);
    }
    SERIAL_DEBUG.print("\t");
    SERIAL_DEBUG.print("NoxIndex:");
    if (isnan(data.noxIndex))
    {
      SERIAL_DEBUG.println("n/a");
    }
    else
    {
      SERIAL_DEBUG.println(data.noxIndex);
    }
  }
}
void I2C_scan(void)
{
      uint8_t error, i2cAddress, devCount, unCount;

  SERIAL_DEBUG.println("Scanning...");

  devCount = 0;
  unCount = 0;
  for (i2cAddress = 1; i2cAddress < 127; i2cAddress++)
  {
    WIRE.beginTransmission(i2cAddress);
    error = WIRE.endTransmission();

    if (error == 0)
    {
      SERIAL_DEBUG.print("I2C device found at 0x");
      if (i2cAddress < 16)
        SERIAL_DEBUG.print("0");
      SERIAL_DEBUG.println(i2cAddress, HEX);
      devCount++;
    }
    else if (error == 4)
    {
      SERIAL_DEBUG.print("Unknow error at 0x");
      if (i2cAddress < 16)
        SERIAL_DEBUG.print("0");
      SERIAL_DEBUG.println(i2cAddress, HEX);
      unCount++;
    }
  }

  if (devCount + unCount == 0)
    SERIAL_DEBUG.println("No I2C devices found\n");
  else
  {
    SERIAL_DEBUG.print(devCount);
    SERIAL_DEBUG.print(" device(s) found");
    if (unCount > 0)
    {
      SERIAL_DEBUG.print(", and unknown error in ");
      SERIAL_DEBUG.print(unCount);
      SERIAL_DEBUG.print(" address");
    }
    SERIAL_DEBUG.println();
  }
  SERIAL_DEBUG.println();
  // delay(3000);
  // SERIAL_DEBUG.println("END");

}

void wait_serial(void)
{
  while (!SERIAL_DEBUG)
  {
    // Led_Blink(500);
    ;
  }
}

main.cpp

C/C++
the main funcitons to run Wio-E5 and collect data from sensors.
#include "main.h"
#include "TCA9548A.h"
#include "encoder.h"
#include "channel.h"

SoftwareSerial Serial_LoRa(D2, D3); 
TCA9548A<TwoWire> TCA;
SensirionI2CSen5x sen5x;
Capture_data data;
/* Please modify them as your configuration from TTN*/
// #define AppEUI "526973696E67xxxx"
// #define DevAddr "260Bxxxx"
// #define DevEui "2CF7F1204200xxxx"
// #define APPKEY "E2CDBCF60535BA0D0C791454F67xxxx"

static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;
// #define Europe
#ifdef Europe
#define DR "EU868" // Data Rate
#define Channel "NUM,0-2"
#else
#define DR "US915" // Data Rate
#define Channel "NUM,8-15"
#endif
/**
 * @param p_ack: The string to be checked
 * @param timeout_ms: Timeout in milliseconds
 * @param p_cmd: The command to be sent
 * @example
 * ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
			if (ret){
			  is_join = false;
			}
  sprintf(cmd, "AT+CMSGHEX=%08X %08X\r\n", classification_flag);
			ret = at_send_check_response("Done", 10000, cmd);
			if (ret){
			  Serial.print("classification_flag:");
			  Serial.print(classification_flag);
			  Serial.print("\t");
			  recv_prase(recv_buf);
			}
*/
static int at_send_check_response(const char *p_ack, int timeout_ms, const char *p_cmd, ...)
{
	int ch = 0;
	int index = 0;
	int startMillis = 0;
	va_list args;
	memset(recv_buf, 0, sizeof(recv_buf));
	va_start(args, p_cmd);
	SERIAL_E5.printf(p_cmd, args);
	SERIAL_DEBUG.printf(p_cmd, args);
	va_end(args);
	delay(200);
	startMillis = millis();

	if (p_ack == NULL)
	{
		return 0;
	}

	do
	{
		while (SERIAL_E5.available() > 0)
		{
			ch = SERIAL_E5.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_DEBUG.println(data);
	}

	p_start = strstr(p_msg, "RSSI");
	if (p_start && (1 == sscanf(p_start, "RSSI %d,", &rssi)))
	{
		SERIAL_DEBUG.println(rssi);
	}

	p_start = strstr(p_msg, "SNR");
	if (p_start && (1 == sscanf(p_start, "SNR %d", &snr)))
	{
		SERIAL_DEBUG.println(snr);
	}
}

void LoRa_Setup()
{
	int ret;
	SERIAL_E5.begin(9600);
	SERIAL_DEBUG.println("LoRa Setup");
	if (at_send_check_response("+AT: OK", 100, "AT\r\n"))
	{
		is_exist = true;
		at_send_check_response("+ID: DevEui", 1000, "AT+ID=DevEui," TOSTRING(DevEui) "\r\n");
		at_send_check_response("+ID: AppEui", 1000, "AT+ID=AppEui," TOSTRING(AppEUI) "\r\n");
		at_send_check_response("+KEY: APPKEY", 1000, "AT+KEY=APPKEY," TOSTRING(APPKEY) "\r\n");
		at_send_check_response("+MODE: LWOTAA", 1000, "AT+MODE=LWOTAA\r\n");
		at_send_check_response("+DR: ", 1000, "AT+DR=" TOSTRING(DR) "\r\n");
		at_send_check_response("+CH: NUM", 1000, "AT+CH=" Channel "\r\n");
		at_send_check_response("+CLASS: A", 1000, "AT+CLASS=A\r\n");
		at_send_check_response("+PORT: 8", 1000, "AT+PORT=8\r\n");
		delay(200);
	}
	else
	{
		is_exist = false;
		SERIAL_DEBUG.print("No E5 module found.\r\n");
	}
}

template <typename T>
void LoRa_Send_Payload(T data) //
{
	static char *cmd;
	if (is_exist)
	{
		int ret = 0;
		if (is_join == true)
		{
			cmd = (char *)calloc(1, MAX_CMD_LEN);
			Get_Payload(data, cmd); // Get_Payload(data, cmd
			ret = at_send_check_response("Done", 10000, cmd);
			if (ret)
			{
				recv_prase(recv_buf);
			}
			else
			{
				SERIAL_DEBUG.print("Send failed!\r\n\r\n");
			}
			free(cmd);
			delay(5000);
		}
		else
		{
			ret = at_send_check_response("+JOIN: Network joined", 12000, "AT+JOIN\r\n");
			if (ret)
			{
				is_join = true;
			}
			else
			{
				SERIAL_DEBUG.println("");
				SERIAL_DEBUG.print("JOIN failed!\r\n\r\n");
				delay(5000);
				while (!SERIAL_DEBUG.available())
				{
					;
				}
			}
		}
	}
	else
	{
		delay(500);
	}
}
void setup()
{
	SERIAL_DEBUG.begin(9600);
	wait_serial();
	delay(1000);
	SERIAL_DEBUG.println("Starting...");

	TCA.begin(WIRE);
	TCA.openAll();

	sen5x.begin(WIRE);
#pragma region SEN5x Setup
	uint16_t error;
	char errorMessage[256];
	error = sen5x.deviceReset();
	if (error)
	{
		SERIAL_DEBUG.print("Error trying to execute deviceReset(): ");
		errorToString(error, errorMessage, 256);
		SERIAL_DEBUG.println(errorMessage);
	}
	float tempOffset = 0.0;
	error = sen5x.setTemperatureOffsetSimple(tempOffset);
	if (error)
	{
		SERIAL_DEBUG.print("Error trying to execute setTemperatureOffsetSimple(): ");
		errorToString(error, errorMessage, 256);
		SERIAL_DEBUG.println(errorMessage);
	}
	else
	{
		SERIAL_DEBUG.print("Temperature Offset set to ");
		SERIAL_DEBUG.print(tempOffset);
		SERIAL_DEBUG.println(" deg. Celsius (SEN54/SEN55 only)");
	}

	// Start Measurement
	error = sen5x.startMeasurement();
	if (error)
	{
		SERIAL_DEBUG.print("Error trying to execute startMeasurement(): ");
		errorToString(error, errorMessage, 256);
		SERIAL_DEBUG.println(errorMessage);
	}
#pragma endregion
	
	SERIAL_E5.begin(9600);
	LoRa_Setup();
}

void loop()
{
	
	// I2C_scan();
	SEN5x_Read(data.sen5x_data);
	moisture_read(data.moi_data);
	LoRa_Send_Payload(data.sen5x_data);
	LoRa_Send_Payload(data.moi_data);
	delay(3000);
}

main.h

C/C++
those specific functions
#ifndef _MAIN_H
#define _MAIN_H

#include <Arduino.h>
#include "pins_arduino.h"
#include <SoftwareSerial.h>
#include <Wire.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define PIN_SENSOR_MOISTURE 2
#include "channel.h"

typedef struct
{
    payload_SEN5x sen5x_data;
    payload_moisture moi_data;
} Capture_data;

#define WIRE Wire // IIC
#define SERIAL_DEBUG Serial
#define SERIAL_E5 Serial_LoRa
extern SoftwareSerial SERIAL_E5;

#include <SensirionI2CSen5x.h>
// The used commands use up to 48 bytes. On some Arduino's the default buffer
// space is not large enough
#define MAXBUF_REQUIREMENT 48

#if (defined(I2C_BUFFER_LENGTH) &&                 \
     (I2C_BUFFER_LENGTH >= MAXBUF_REQUIREMENT)) || \
    (defined(BUFFER_LENGTH) && BUFFER_LENGTH >= MAXBUF_REQUIREMENT)
#define USE_PRODUCT_INFO
#endif
extern SensirionI2CSen5x sen5x;

void SEN5x_Read(payload_SEN5x &data);
int moisture_read(payload_moisture &data);
void LoRa_Setup();
void LoRa_Send_Payload(char *cmd);
void I2C_scan(void);
void wait_serial(void);

#endif

channel.h

C/C++
The data structure based on what I need.
#ifndef _CHANNEL_
#define _CHANNEL_
#include <stdio.h>
#include <stdint.h>
typedef struct
{
    uint8_t id = 6;
    union
    {
        struct
        {
            uint16_t massConcentrationPm1p0;
            uint16_t massConcentrationPm2p5;
            uint16_t massConcentrationPm4p0;
            uint16_t massConcentrationPm10p0;
            int16_t ambientHumidity;
            int16_t ambientTemperature;
            int16_t vocIndex;
            int16_t noxIndex;
        };
        uint16_t element[8] = {0}; // 8
    };
    uint16_t crc;
} payload_SEN5x;

typedef struct{
    uint8_t id = 7;
    union
    {
        uint16_t moisture;
        uint16_t element[1] = {0}; // 1
    };
}payload_moisture;

uint16_t crc16_ccitt(const char *data, size_t length);
#endif

Credits

Spencer
4 projects • 3 followers
Embrace life.
Contact
Yvonne
3 projects • 2 followers
Contact

Comments

Please log in or sign up to comment.