1.A Hardware


1.B Hardware Setup


  1. Plant Box
  2. Power source
  3. Antenna
  4. Seeeduino LoRaWan Microcontroller
  5. Base Sheild v.2
  6. Grove - Capacitive Moisture Sensor
  7. Grove - Sunlight Sensor v.1
  8. Grove - Temperature, Humidity, Pressure and Gas Sensor v.1



1.C Hardware - Source Code, Outputs, Unit of Measures

SOURCE CODE

ComponentsLibrary
Grove - 16x2 LCD (Black on Red)https://github.com/Seeed-Studio/Grove_LCD_RGB_Backlight
Grove - Capacitive Moisture Sensor

Grove -  Sunlight Sensor v.1

https://github.com/Seeed-Studio/Grove_Sunlight_Sensor/tree/Si1151

Grove - Temperature, Humidity, Pressure and Gas Sensor v.1

https://github.com/Seeed-Studio/Seeed_Arduino_BME68x

OUTPUTS & UNITS OF MEASURES

Grove - 16x2 LCD (Black on Red)

Code
#include <Wire.h>
#include "rgb_lcd.h"
#define PIN_GROVE_POWER 38

rgb_lcd lcd;

const int colorR = 255;
const int colorG = 0;
const int colorB = 0;

void setup() {
    // Powerup Seeeduino LoRaWAN Grove connectors
    pinMode(PIN_GROVE_POWER, OUTPUT);
    digitalWrite(PIN_GROVE_POWER, 1);
    // set up the LCD's number of columns and rows:
    lcd.begin(16, 2);

    lcd.setRGB(colorR, colorG, colorB);

    // Print a message to the LCD.
    lcd.print("HelloWorld!");

    delay(10);
}


Grove - Capacitive Moisture Sensor

Code
/*
  AnalogReadSerial

  Reads an analog input on pin 0, prints the result to the Serial Monitor.
  Graphical representation is available using Serial Plotter (Tools > Serial Plotter menu).
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.

  This example code is in the public domain.

  https://arduino.cc/en/Tutorial/AnalogReadSerial
*/

#define PIN_GROVE_POWER 38


// the setup routine runs once when you press reset:
void setup() {
  // Powerup Seeeduino LoRaWAN Grove connectors
  pinMode(PIN_GROVE_POWER, OUTPUT);
  digitalWrite(PIN_GROVE_POWER, 1);

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(A0);
  // print out the value you read:
  Serial.println(sensorValue);
  delay(100);        // delay in between reads for stability
}


Unit of Measure

MeasuresUnit
MoistureAnalog voltage reading


Grove - Sunlight Sensor v.1

Code
#include <Wire.h>

#include "Arduino.h"
#include "SI114X.h"
#define PIN_GROVE_POWER 38

SI114X SI1145 = SI114X();

void setup() {
    // Powerup Seeeduino LoRaWAN Grove connectors
    pinMode(PIN_GROVE_POWER, OUTPUT);
    digitalWrite(PIN_GROVE_POWER, 1);
    Serial.begin(115200);
    Serial.println("Beginning Si1145!");

    while (!SI1145.Begin()) {
        Serial.println("Si1145 is not ready!");
        delay(1000);
    }
    Serial.println("Si1145 is ready!");
}

void loop() {
    Serial.print("//--------------------------------------//\r\n");
    Serial.print("Vis: "); Serial.println(SI1145.ReadVisible());
    Serial.print("IR: "); Serial.println(SI1145.ReadIR());
    //the real UV value must be div 100 from the reg value , datasheet for more information.
    Serial.print("UV: ");  Serial.println((float)SI1145.ReadUV() / 100);
    delay(1000);
}


Unit of Measure

MeasuresUnit
Vis - Visible Lightlm
IR - Infrared Lightlm
UVUN index


Grove - Temperature, Humadity, Pressure and Gas Sensor v.1

Code
#include "seeed_bme680.h"

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define PIN_GROVE_POWER 38


#define IIC_ADDR  uint8_t(0x76)


/**  NOTE!!!!!!!!!!!!  Select the communication protocol correctly **/

Seeed_BME680 bme680(IIC_ADDR); /* IIC PROTOCOL */
//Seeed_BME680 bme680;             /* SPI PROTOCOL */
//Seeed_BME680 bme680(BME_CS, BME_MOSI, BME_MISO,  BME_SCK);/*SPI PROTOCOL*/

void setup() {
    // Powerup Seeeduino LoRaWAN Grove connectors
    pinMode(PIN_GROVE_POWER, OUTPUT);
    digitalWrite(PIN_GROVE_POWER, 1);
    
    Serial.begin(9600);
    while (!Serial);
    Serial.println("Serial start!!!");
    delay(100);
    while (!bme680.init()) {
        Serial.println("bme680 init failed ! can't find device!");
        delay(10000);
    }
}

void loop() {
    if (bme680.read_sensor_data()) {
        Serial.println("Failed to perform reading :(");
        return;
    }
    Serial.print("temperature ===>> ");
    Serial.print(bme680.sensor_result_value.temperature);
    Serial.println(" C");

    Serial.print("pressure ===>> ");
    Serial.print(bme680.sensor_result_value.pressure / 1000.0);
    Serial.println(" KPa");

    Serial.print("humidity ===>> ");
    Serial.print(bme680.sensor_result_value.humidity);
    Serial.println(" %");

    Serial.print("gas ===>> ");
    Serial.print(bme680.sensor_result_value.gas / 1000.0);
    Serial.println(" Kohms");

    Serial.println();
    Serial.println();

    delay(2000);
}



Unit of Measure

MeasuresUnit
TemperatureCelcius
PressureKPa
Humidity%r.H
GasKohms



Sensor Data

NameGroupNoThingDevEUISensorObservedPropertyLppChannelNrFROST Datastream IDs
Iot-Based Urban Gardening11Seeeduino LoRaaWAN

TTN: 8765182202E81E14

8765182202E81E15

Grove - Capacitive Moisture SensorAnalog Voltage Reading81069

SWM: 002A862DA290C679

005FD950519EBC37

Grove -  Sunlight Sensor v.1

Vis (lm)

51066


IR (lm)


61067

UV (UN Index)

71068
Grove - Temperature, Humidity, Pressure and Gas Sensor v.1

Temperature (°C)




11059

Pressure (KPa)

21062

Humidity (%r.H)

41065

Gas (Kohms)

31064


Working progress & Timeline



Codes for data retrieval

Cayenne LPP
// Seeeduino LoRaWAN ------------------------------------------------------------
#define PIN_GROVE_POWER 38
#define SerialUSB Serial
 
// LoRaWAN -----------------------------------------------------------------------
#include <LoRaWan.h>
 
// Put your LoRa keys here
#define DevEUI "002A862DA290C679"
#define AppEUI "70B3D57ED002F952"
#define AppKey "1741A4E69973C2AE31754F88DFEA33AC"
 
// CayenneLPP --------------------------------------------------------------------
#include <CayenneLPP.h>  // Include Cayenne Library
CayenneLPP lpp(51);      // Define the buffer size: Keep as small as possible
 
// Sensor Libraries
// sunlight
#include "SI114X.h" 
// temperature
#include "seeed_bme680.h"
#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

// Sensor instances
SI114X SI1145 = SI114X();
#define IIC_ADDR  uint8_t(0x76)
Seeed_BME680 bme680(IIC_ADDR);


// SETUP -------------------------------------------------------------------------
// vars
char buffer[256];
void setup(void)
{
  // Setup Serial connection
  delay(5000);
  Serial.begin(115200);
 
  // Powerup Seeeduino LoRaWAN Grove connectors
  pinMode(PIN_GROVE_POWER, OUTPUT);
  digitalWrite(PIN_GROVE_POWER, 1);
 
  // Config LoRaWAN
  lora.init();
 
  memset(buffer, 0, 256);
  lora.getVersion(buffer, 256, 1);
  if (Serial) {
    Serial.print(buffer);
  }
 
  memset(buffer, 0, 256);
  lora.getId(buffer, 256, 1);
  if (Serial) {
    Serial.print(buffer);
  }
 
  // void setId(char *DevAddr, char *DevEUI, char *AppEUI);
  // replace the xxxxxx and the yyyyyy below with the DevEUI and the
  // AppEUI obtained from your registered sensor node and application
  // in The Things Network (TTN). The numbers are hexadecimal strings
  // without any leading prefix like "0x" and must have exactly the
  // same number of characters as given below.
  // lora.setId(NULL, "xxxxxxxxxxxxxxxx", "yyyyyyyyyyyyyyyy");
  lora.setId(NULL, DevEUI, AppEUI);
 
  // setKey(char *NwkSKey, char *AppSKey, char *AppKey);
  // replace the zzzzzz below with the AppKey obtained from your registered
  // application in The Things Network (TTN). The numbers are hexadecimal
  // strings without any leading prefix like "0x" and must have exactly
  // the same number of characters as given below.
  // lora.setKey(NULL, NULL, "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
  lora.setKey(NULL, NULL, AppKey);
 
  lora.setDeciveMode(LWOTAA);
  lora.setDataRate(DR0, EU868);      // DR5 = SF7, DR0 = SF 12
  lora.setAdaptiveDataRate(true);
 
  lora.setChannel(0, 868.1);
  lora.setChannel(1, 868.3);
  lora.setChannel(2, 868.5);
  lora.setChannel(3, 867.1);
  lora.setChannel(4, 867.3);
  lora.setChannel(5, 867.5);
  lora.setChannel(6, 867.7);
  lora.setChannel(7, 867.9);
 
  lora.setDutyCycle(false);
  lora.setJoinDutyCycle(false);
 
  lora.setPower(14);
  lora.setPort(33);
 
  unsigned int nretries;
  nretries = 0;
  while (!lora.setOTAAJoin(JOIN, 20)) {
    nretries++;
    if (Serial) {
      Serial.println((String)"Join failed, retry: " + nretries);
    }
  }
  Serial.println("Join successful!");
}
 
// LOOP --------------------------------------------------------------------
unsigned int nloops = 0;
void loop(void) {
  nloops++;
  if (Serial) {
    Serial.println((String)"Loop " + nloops + "...");
  }
 
  bool result = false;
 
  // Read sunlight sensor data
  float visible = SI1145.ReadVisible();
  float IR = SI1145.ReadIR();
  float UV = SI1145.ReadUV();
  float uvIndex = UV / 100.0;  // UV index

  // Read environmental sensor data
  bme680.init();
  bme680.read_sensor_data();
  float temperature = bme680.read_temperature();  // Celsius
  float humidity = bme680.read_humidity();        // %
  float pressure = bme680.read_pressure();        // hPa
  float gas = bme680.read_gas() / 1000.0; // kOhms
  float moisture = analogRead(A0); 

  // Reset CayenneLPP buffer and add sensor data
  lpp.reset();
  lpp.addTemperature(1, temperature);
  lpp.addRelativeHumidity(2, humidity);
  lpp.addBarometricPressure(3, pressure);
  lpp.addAnalogInput(4, gas);  // Assuming gas resistance as analog input
  lpp.addLuminosity(5, visible);
  lpp.addLuminosity(6, IR);
  lpp.addAnalogInput(7, uvIndex);  // UV index as analog input
  lpp.addAnalogInput(8, moisture);
 
  // Transfer LoRa package
  result = lora.transferPacket(lpp.getBuffer(), lpp.getSize(), 5);                  // sends the Cayenne encoded data packet (n bytes) with a default timeout of 5 secs
  // result = lora.transferPacketWithConfirmed(lpp.getBuffer(), lpp.getSize(), 5);  // sends the Cayenne encoded data packet (n bytes) with a default timeout of 5 secs, using confirmed LoRa package
 
  if (result) {
    short length;
    short rssi;
 
    // Receive LoRaWAN package (LoraWAN Class A)
    char rx[256];
    length = lora.receivePacket(rx, 256, &rssi);
 
    // Check, if a package was received
    if (length)
    {
      if (Serial) {
        Serial.print("Length is: ");
        Serial.println(length);
        Serial.print("RSSI is: ");
        Serial.println(rssi);
        Serial.print("Data is: ");
 
        // Print received data as HEX
        for (unsigned char i = 0; i < length; i ++)
        {
          Serial.print("0x");
          Serial.print(rx[i], HEX);
          Serial.print(" ");
        }
 
        // Convert received package to int
        int rx_data_asInteger = atoi(rx);
 
        Serial.println();
        Serial.println("Received data: " + String(rx_data_asInteger));
      }
    }
  }
   
  if (Serial) {
    Serial.println((String)"Loop " + nloops + "...done!\n");
  }
  delay(60000);
 
}


FROST Server
import requests
import json
import time

# FROST server URL
FROST_SERVER_URL = 'https://gi3.gis.lrg.tum.de/frost/v1.1'

# Function to retrieve data from a specific Datastream
def get_latest_observations(datastream_ids, interval=5):
    while True:
        observations_by_datastream = {}
        for datastream_id in datastream_ids:
            url = f"{FROST_SERVER_URL}/Datastreams({datastream_id})/Observations?$orderby=phenomenonTime%20desc&$top=1"
            response = requests.get(url)
            if response.status_code == 200:
                data = response.json()
                observations_by_datastream[datastream_id] = data["value"][0]["result"]
            else:
                print(f"Failed to retrieve data: {response.status_code} {response.reason}")
        #result_value = observations_by_datastream['result']
        yield 'data: {}\n\n'.format(json.dumps(observations_by_datastream))
        time.sleep(interval) 


Weather Forecast
import requests
import time
import json

def get_weather(api_key, city_name, interval=60):
    while True:
        weather_values = {}
        base_url = 'http://api.openweathermap.org/data/2.5/weather?'
        complete_url = f'{base_url}q={city_name}&appid={api_key}&units=metric'
        backoff_time = interval
        
        while True:
            response = requests.get(complete_url)
            if response.status_code == 200:
                data = response.json()
                main = data['main']
                wind = data['wind']
                weather = data['weather'][0]
                weather_values['Temperature'] = main['temp']
                weather_values['Wind Speed'] = wind['speed']
                weather_values['Humidity'] = main['humidity']
                weather_values['Weather description'] = weather['description']
                yield 'data: {}\n\n'.format(json.dumps(weather_values))
                break
            elif response.status_code == 429:
                print(f"Failed to retrieve data: {response.status_code}. Retrying in {backoff_time} seconds.")
                time.sleep(backoff_time)
                backoff_time *= 2  # Exponential backoff
            else:
                print(f"Failed to retrieve data: {response.status_code}")
                break
        
        time.sleep(interval)


def main():
    # Replace 'your_api_key' with your actual OpenWeatherMap API key
    api_key = '4890013cad9b0ee94bf0edd0d861d569'
    city_name = 'Munich'
    
    while True:
        get_weather(api_key, city_name)
        time.sleep(10)  # Wait for 10 seconds before the next update

if __name__ == "__main__":
    main()

Backend to Frontend

Backend application
from flask import Flask, render_template, request, Response, send_from_directory
from data_retrieving_frost import get_latest_observations
from data_retrieving_wf import get_weather

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/dashboard')
def dashboard():
    return render_template('dashboard.html')

@app.route('/css/<path:path>')
def send_css(path):
    return send_from_directory('static/css', path)

@app.route('/js/<path:path>')
def send_js(path):
    return send_from_directory('static/js', path)

@app.route('/frost')
def frost():
    datastream_ids = [1059, 1062, 1064, 1065, 1066, 1067, 1068, 1069]
    return Response(get_latest_observations(datastream_ids, interval=5), mimetype='text/event-stream')

@app.route('/weatherforecast')
def weatherforecast():
    api_key = '4890013cad9b0ee94bf0edd0d861d569'
    city_name = 'Munich'
    return Response(get_weather(api_key, city_name, interval=60), mimetype='text/event-stream')
    
if __name__ == '__main__':
    app.run(debug=True, port=5500)


1.D Dashboard

                         

                                     

  • Keine Stichwörter