Sumit Kumar
Published © GPL3+

Farmium- Helium Farm Companion

Harnessing people's network to save the world from another pandemic by improving farm health.

IntermediateFull instructions provided15 hours1,972
Farmium- Helium Farm Companion

Things used in this project

Hardware components

Helium Developer Kit
Helium Developer Kit
×1
SparkFun Environmental Combo Breakout - CCS811/BME280 (Qwiic)
SparkFun Environmental Combo Breakout - CCS811/BME280 (Qwiic)
A good sensor having multiple capabilities including temperature, humidity and CO2 along with TVOC or air quality detection with a greater accuracy.
×1
Ultrasonic Sensor - HC-SR04
SparkFun Ultrasonic Sensor - HC-SR04
×1
Gravity:SHT1x Humidity and Temperature Sensor
DFRobot Gravity:SHT1x Humidity and Temperature Sensor
×1
SparkFun RedBoard Artemis ATP
SparkFun RedBoard Artemis ATP
Apollo3 powered board having turbo execution mode which elevates its ML based usage. It has on board microphone which can be easily integrated with Tensorflow for voice learning capabilities.
×1
SparkFun Proximity Sensor Breakout - 20cm, VCNL4040 (Qwiic)
SparkFun Proximity Sensor Breakout - 20cm, VCNL4040 (Qwiic)
It can not only measure distance but also measure ambient light levels along with white light levels.
×1

Software apps and online services

Helium Console
TensorFlow
TensorFlow
PipeDream

Story

Read more

Schematics

img_20200807_010849_7NMaVIAayD.jpg

img_20200807_014804_lqOLYdZChw.jpg

img_20200807_014953_JHhpQQrfEv.jpg

Code

Farmium.ino

Arduino
#include "plantHeight.cpp"
#include "LoRaWAN.h"
#include <Wire.h>  // Include Wire if you're using I2C
#include <SFE_MicroOLED.h>  // Include the SFE_MicroOLED library
#include "SparkFun_VCNL4040_Arduino_Library.h"
#include "SparkFunCCS811.h" //Click here to get the library: http://librarymanager/All#SparkFun_CCS811
#include <HTS221Sensor.h>
#include <SHT1x.h>

#ifdef ARDUINO_SAM_DUE
#define DEV_I2C Wire1
#elif defined(ARDUINO_ARCH_STM32)
#define DEV_I2C Wire
#elif defined(ARDUINO_ARCH_AVR)
#define DEV_I2C Wire
#else
#define DEV_I2C Wire
#endif
#define SerialPort Serial

#define CCS811_ADDR 0x5B //Default I2C Address
//#define CCS811_ADDR 0x5A //Alternate I2C Address
#define PIN_RESET 9
#define DC_JUMPER 1

#define TRIG_PIN 8
#define ECHO_PIN 9
#define dataPin  10
#define clockPin 11

SHT1x sht1x(dataPin, clockPin);
HTS221Sensor *HumTemp;
MicroOLED oled(PIN_RESET, DC_JUMPER);    // I2C declaration
CCS811 airQualitySensor(CCS811_ADDR);
VCNL4040 lightSensor;

PlantHeight plant(TRIG_PIN, ECHO_PIN);

const char *devEui = "FILL_ME_IN";
const char *appEui = "FILL_ME_IN";
const char *appKey = "FILL_ME_IN";

// Max Payload 53 Bytes for DR1
uint8_t payload[50];

void setup()
{
  Wire.begin();
  Serial.begin(115200);
  Serial1.begin(4800);

  DEV_I2C.begin();
  HumTemp = new HTS221Sensor (&DEV_I2C);
  HumTemp->Enable();

  oled.begin();    // Initialize the OLED
  oled.clear(ALL); // Clear the display's internal memory
  oled.display();  // Display what's in the buffer (splashscreen)
  delay(1000);     // Delay 1000 ms
  oled.clear(PAGE); // Clear the buffer.

  lightSensor.begin();
  lightSensor.powerOffProximity(); //Power down the proximity portion of the sensor
  lightSensor.powerOnAmbient(); //Power up the ambient sensor

  if (airQualitySensor.begin() == false)
  {
    Serial.print("CCS811 error. Please check wiring. Freezing...");
    while (1);
  }
  plant.set_initial_height();

  while (!Serial) { }

  // US Region
  LoRaWAN.begin(US915);
  // Helium SubBand
  LoRaWAN.setSubBand(2);
  // Disable Adaptive Data Rate
  LoRaWAN.setADR(false);
  // Set Data Rate 1 - Max Payload 53 Bytes
  LoRaWAN.setDataRate(1);
  // Device IDs and Key
  LoRaWAN.joinOTAA(appEui, appKey, devEui);

  Serial.println("JOIN( )");
}

void loop( void )
{
  //Read humidity, temperature and pressure.
  float humidity = 0, temperature = 0;
  HumTemp->GetHumidity(&humidity);
  HumTemp->GetTemperature(&temperature);
  payload[0] = temperature * 100; //removing decimal values so that we can sent //only numbers and later decode in console
  payload[1] = round(humidity); // round off to whole number, this value will be //between 0-100

  float soil_temp;
  float soil_humidity;

  // Read values from the sensor
  soil_temp = sht1x.readTemperatureC();
  soil_humidity = sht1x.readHumidity();
  payload[2] = round(soil_humidity);
  payload[3] = soil_temp * 100;

  if (airQualitySensor.dataAvailable())
  {
    //If so, have the sensor read and calculate the results. Get them later
    airQualitySensor.readAlgorithmResults();
  }
  unsigned int co2 = airQualitySensor.getCO2();
  payload[4] = sqrt(co2 / 10);// a decoding factor to reduce the byte size
  unsigned int tvoc = airQualitySensor.getTVOC();
  payload[5] = sqrt(tvoc / 10);// a decoding factor to reduce the byte size
  payload[6] = plant.relativeHeight() / 10;// a decoding factor to reduce the //byte size
  unsigned int ambientValue = lightSensor.getAmbient();
  payload[7] = sqrt(ambientValue);// a decoding factor to reduce the byte size

  if (Serial1.available())
  {
    int c = Serial1.read(); //get readings from Sparkfun board in case any audio //classification is performed
    payload[8] = c;
  }
  for ( int i = 0; i < 9; i++)
  {
    oled.clear(PAGE);     // Clear the screen
    oled.setFontType(0);  // Set font to type 0
    oled.setCursor(0, 0); // Set cursor to top-left
    oled.print(payload[i]);
    delay(1000);
  }
  oled.clear(PAGE);     // Clear the screen
  oled.setFontType(0);  // Set font to type 0
  oled.setCursor(0, 0); // Set cursor to top-left
  oled.print("Sending data over lora");
  delay(1000);
  if (LoRaWAN.joined() && !LoRaWAN.busy())
  {
    Serial.print("TRANSMIT( ");
    Serial.print("TimeOnAir: ");
    Serial.print(LoRaWAN.getTimeOnAir());
    Serial.print(", NextTxTime: ");
    Serial.print(LoRaWAN.getNextTxTime());
    Serial.print(", MaxPayloadSize: ");
    Serial.print(LoRaWAN.getMaxPayloadSize());
    Serial.print(", DR: ");
    Serial.print(LoRaWAN.getDataRate());
    Serial.print(", TxPower: ");
    Serial.print(LoRaWAN.getTxPower(), 1);
    Serial.print("dbm, UpLinkCounter: ");
    Serial.print(LoRaWAN.getUpLinkCounter());
    Serial.print(", DownLinkCounter: ");
    Serial.print(LoRaWAN.getDownLinkCounter());
    Serial.println(" )");

    // Send Packet
    LoRaWAN.sendPacket(1, payload, sizeof(payload));
  }

  delay(20000); //20 Seconds
}

SparkfunAudioML_to_Helium

Arduino
/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// Undefine predefined Arduino max/min defs so they don't conflict with std methods
#if defined(min)
#undef min
#endif

#if defined(max)
#undef max
#endif


#include <TensorFlowLite.h>

#include "main_functions.h"

#include "audio_provider.h"
#include "command_responder.h"
#include "feature_provider.h"
#include "micro_features_micro_model_settings.h"
#include "micro_features_tiny_conv_micro_features_model_data.h"
#include "recognize_commands.h"
#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h"
#include "tensorflow/lite/experimental/micro/micro_error_reporter.h"
#include "tensorflow/lite/experimental/micro/micro_interpreter.h"
#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"

// Globals, used for compatibility with Arduino-style sketches.
namespace {
tflite::ErrorReporter* error_reporter = nullptr;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* model_input = nullptr;
FeatureProvider* feature_provider = nullptr;
RecognizeCommands* recognizer = nullptr;
int32_t previous_time = 0;

// Create an area of memory to use for input, output, and intermediate arrays.
// The size of this will depend on the model you're using, and may need to be
// determined by experimentation.
constexpr int kTensorArenaSize = 10 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
}  // namespace

// The name of this function is important for Arduino compatibility.
void setup() {
  // Set up logging. Google style is to avoid globals or statics because of
  // lifetime uncertainty, but since this has a trivial destructor it's okay.
  // NOLINTNEXTLINE(runtime-global-variables)
  Serial1.begin(4800);
  static tflite::MicroErrorReporter micro_error_reporter;
  error_reporter = &micro_error_reporter;

  // Map the model into a usable data structure. This doesn't involve any
  // copying or parsing, it's a very lightweight operation.
  model = tflite::GetModel(g_tiny_conv_micro_features_model_data);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    error_reporter->Report(
        "Model provided is schema version %d not equal "
        "to supported version %d.",
        model->version(), TFLITE_SCHEMA_VERSION);
    return;
  }

  // Pull in only the operation implementations we need.
  // This relies on a complete list of all the ops needed by this graph.
  // An easier approach is to just use the AllOpsResolver, but this will
  // incur some penalty in code space for op implementations that are not
  // needed by this graph.
  //
  // tflite::ops::micro::AllOpsResolver resolver;
  // NOLINTNEXTLINE(runtime-global-variables)
  static tflite::MicroMutableOpResolver micro_mutable_op_resolver;
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
      tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
  micro_mutable_op_resolver.AddBuiltin(
      tflite::BuiltinOperator_FULLY_CONNECTED,
      tflite::ops::micro::Register_FULLY_CONNECTED());
  micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_SOFTMAX,
                                       tflite::ops::micro::Register_SOFTMAX());

  // Build an interpreter to run the model with.
  static tflite::MicroInterpreter static_interpreter(
      model, micro_mutable_op_resolver, tensor_arena, kTensorArenaSize,
      error_reporter);
  interpreter = &static_interpreter;

  // Allocate memory from the tensor_arena for the model's tensors.
  TfLiteStatus allocate_status = interpreter->AllocateTensors();
  if (allocate_status != kTfLiteOk) {
    error_reporter->Report("AllocateTensors() failed");
    return;
  }

  // Get information about the memory area to use for the model's input.
  model_input = interpreter->input(0);
  if ((model_input->dims->size != 4) || (model_input->dims->data[0] != 1) ||
      (model_input->dims->data[1] != kFeatureSliceCount) ||
      (model_input->dims->data[2] != kFeatureSliceSize) ||
      (model_input->type != kTfLiteUInt8)) {
    error_reporter->Report("Bad input tensor parameters in model");
    return;
  }

  // Prepare to access the audio spectrograms from a microphone or other source
  // that will provide the inputs to the neural network.
  // NOLINTNEXTLINE(runtime-global-variables)
  static FeatureProvider static_feature_provider(kFeatureElementCount,
                                                 model_input->data.uint8);
  feature_provider = &static_feature_provider;

  static RecognizeCommands static_recognizer(error_reporter);
  recognizer = &static_recognizer;

  previous_time = 0;
}

// The name of this function is important for Arduino compatibility.
void loop() {
  // Fetch the spectrogram for the current time.
  const int32_t current_time = LatestAudioTimestamp();
  int how_many_new_slices = 0;
  TfLiteStatus feature_status = feature_provider->PopulateFeatureData(
      error_reporter, previous_time, current_time, &how_many_new_slices);
  if (feature_status != kTfLiteOk) {
    error_reporter->Report("Feature generation failed");
    return;
  }
  previous_time = current_time;
  // If no new audio samples have been received since last time, don't bother
  // running the network model.
  if (how_many_new_slices == 0) {
    return;
  }

  // Run the model on the spectrogram input and make sure it succeeds.
  TfLiteStatus invoke_status = interpreter->Invoke();
  if (invoke_status != kTfLiteOk) {
    error_reporter->Report("Invoke failed");
    return;
  }

  // Obtain a pointer to the output tensor
  TfLiteTensor* output = interpreter->output(0);
  // Determine whether a command was recognized based on the output of inference
  const char* found_command = nullptr;
  uint8_t score = 0;
  bool is_new_command = false;
  TfLiteStatus process_status = recognizer->ProcessLatestResults(
      output, current_time, &found_command, &score, &is_new_command);
  if (process_status != kTfLiteOk) {
    error_reporter->Report("RecognizeCommands::ProcessLatestResults() failed");
    return;
  }
  // Do something based on the recognized command. The default implementation
  // just prints to the error console, but you should replace this with your
  // own function for a real application.
  goToCommandOther(error_reporter, current_time, found_command, score,is_new_command);
 
}

void goToCommandOther(tflite::ErrorReporter* error_reporter,int32_t current_time, const char* found_command,uint8_t score, bool is_new_command)
{
  static bool is_initialized = false;
  if (!is_initialized) {
    // Setup LED's as outputs
    // EAC am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_RED, g_AM_HAL_GPIO_OUTPUT_12);
    am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_BLUE, g_AM_HAL_GPIO_OUTPUT_12);
    // EAC am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_GREEN, g_AM_HAL_GPIO_OUTPUT_12);
    // EAC am_hal_gpio_pinconfig(AM_BSP_GPIO_LED_YELLOW, g_AM_HAL_GPIO_OUTPUT_12);
    is_initialized = true;
  }
  static int count = 0;

  // Toggle the blue LED every time an inference is performed.
  ++count;
  if (count & 1) {
    am_hal_gpio_output_set(AM_BSP_GPIO_LED_BLUE);
  } else {
    am_hal_gpio_output_clear(AM_BSP_GPIO_LED_BLUE);
  }

  // Turn on the yellow LED if 'yes' was heard.
  // EAC am_hal_gpio_output_clear(AM_BSP_GPIO_LED_RED);
  // EAC am_hal_gpio_output_clear(AM_BSP_GPIO_LED_YELLOW);
  // EAC am_hal_gpio_output_clear(AM_BSP_GPIO_LED_GREEN);
  if (is_new_command) {
    error_reporter->Report("Heard %s (%d) @%dms", found_command, score,
                           current_time);
    if (found_command[0] == 'c') {
      error_reporter->Report("\nChainsaw");
       if (Serial1.available()) {
        Serial1.write("1");
       }
      // EAC am_hal_gpio_output_set(AM_BSP_GPIO_LED_YELLOW);
    }
    if (found_command[1] == 'b') {
      error_reporter->Report("\nBee");
       if (Serial1.available()) {
        Serial1.write("2");
       }
      // EAC am_hal_gpio_output_set(AM_BSP_GPIO_LED_RED);
    }
    if (found_command[2] == 'm') {
      error_reporter->Report("\nMosquito");
      // EAC am_hal_gpio_output_set(AM_BSP_GPIO_LED_GREEN);
      Serial1.write("3");
    }
    if(found_command[3] == 'u')
    {
       error_reporter->Report("\nUnknown");
      // EAC am_hal_gpio_output_set(AM_BSP_GPIO_LED_GREEN);
      Serial1.write("0");
    }
  }        
}

PlantHeight.cpp

C/C++
#include "Arduino.h"

class PlantHeight
{
  private:
    long duration;
    int deviceHeight;
    byte trigPin, echoPin;

    //Constructor to initialize HCS04 ultrasonic sensor pin and
  public: PlantHeight(byte t, byte e)
    {
      trigPin = t;
      echoPin = e;
    }
    //set the height read for the first time as a control or test value height
    //or device height from the ground for comparisons in further readings
  public: void set_initial_height()
    {
      // Running a 20 round loop and get an average to make sure that I get a correct value,
      // I found while testing the ultrasonic sensor that it gives 0 values for first 2 times(it might be due to no delay when I start the sensor)
      delay(1000);
      for (int i = 1; i <= 20; i++)
      {
        pinMode(trigPin, OUTPUT);
        digitalWrite(trigPin, LOW);
        delayMicroseconds(2);
        digitalWrite(trigPin, HIGH);
        delayMicroseconds(10);
        digitalWrite(trigPin, LOW);
        pinMode(echoPin, INPUT);
        duration = pulseIn(echoPin, HIGH);
        deviceHeight += microsecondsToCentimeters(duration); //height of device installed from ground
        delay(1000);
      }
      deviceHeight = deviceHeight / 20;
    }
    // Calculating plant relative height from the ground
  public: int relativeHeight()
    {
      int readings;
      for (int i = 1; i <= 20; i++)
      {
        pinMode(trigPin, OUTPUT);
        digitalWrite(trigPin, LOW);
        delayMicroseconds(2);
        digitalWrite(trigPin, HIGH);
        delayMicroseconds(10);
        digitalWrite(trigPin, LOW);
        pinMode(echoPin, INPUT);

        duration = pulseIn(echoPin, HIGH);
        readings += microsecondsToCentimeters(duration);
        delay(1000);
      }
      //the updated value of sensor reading is going to be sent
      readings = readings / 20;
      return deviceHeight - readings ;
    }

  private: long microsecondsToCentimeters(long microseconds)
    {
      return microseconds / 29 / 2;
    }
};

Helium_Decoder_Function

JavaScript
function Decoder(bytes)
{
//LSB (least significant byte first) and sign-extension to get 4 
//bytes for proper decoding of negative values:
  var tempAir = bytes[1]<<24>>16 | bytes[0];  
  var humidity = bytes[2];
  var SHT10_Moisture = bytes[3];
  var SHT10_Temperature = bytes[5]<<24>>16 | bytes[4];  
  var eCO = bytes[6];
  var tVOC = bytes[7];
  var height = bytes[8];
  var lightIntensity = bytes[9];
  var serialData = bytes[10];

  temp_data = tempAir / 100;
  soil_temperature = SHT10_Temperature / 100; 
  
  return {
    airTemperature: temp_data.toFixed(2),
    airHumidity: humidity,
    soilHumidity: SHT10_Moisture,
    soilTemperaure: soil_temperature.toFixed(2),
    CO2: Math.pow(eCO * 10, 2),
    tVOC: Math.pow(tVOC * 10, 2),
    plantHeight: height * 10,
    lux: Math.pow(lightIntensity, 2),
    audioAnalysis: serialData
  }
}

Sparkfun AudioML Codes for Helium

Credits

Sumit Kumar

Sumit Kumar

32 projects • 97 followers
19 y/o. My daily routine involves dealing with electronics, code, distributed storage and cloud APIs.

Comments