Awesome! LoRa Soil sensor

Hello
I am a complete beginner in Arduino. Thanks to you, I got the software installed on the soil sensor. From the Arduino IDE serial port monitor, I can see that data is sent at frequency 868.

As gateway I use LILYGO® TTGO Meshtastic T-Beam V1.1 ESP32 LoRa Development Board 868MHz. I installed OpenMQTTGateway using the web installer. Using “ttgo-lora32-v1-868”

I can see the information sent by the gateway both in home assistant and in MQTT Explorer.

But there is no soil sensor information.
Any idea how to get it to work?




Maybe post your Arduino code. Would be good to see what the LORA settings are.
(Caveat, I haven’t used these sensors yet (but will soon). I just finished up another LORA sensor project and will next start on these sensors; so anyway I thought I could possibly help).

For sure what you should be seeing in OMG is it publishing an mqtt message containing the subtopic “LORAtoMQTT” which occurs when OMG has received a message from a sensor. I don’t see this in your logs, so yes, need to see if there are LORA setting issues.

1 Like

This code is basically a copypaste from the code posted by @thebang2. I only changed NODENAME and node_id

#include <SPI.h>
#include <Wire.h>
#include <RadioLib.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include "I2C_AHT10.h"
//#include <LoRa.h>
#include <ArduinoJson.h>

//#define NODENAME "LORA_KUSLAPUU_1"
String node_id = String("ID") + "010204";

//Set sleep time, when value is 1 almost sleep 20s,when value is 450, almost 1 hour.
//#define SLEEP_CYCLE 450
#define SLEEP_CYCLE 38    //5 Minuten

//Lora set
//Set Lora frequency
// #define FREQUENCY 434.0
#define FREQUENCY 868.0
//#define FREQUENCY 915.0

#define BANDWIDTH 125.0
#define SPREADING_FACTOR 9
#define CODING_RATE 7
#define OUTPUT_POWER 10
#define PREAMBLE_LEN 8
#define GAIN 0

//328p
#define DIO0 2
#define DIO1 6

#define LORA_RST 4
#define LORA_CS 10

#define SPI_MOSI 11
#define SPI_MISO 12
#define SPI_SCK 13

//pin set
#define VOLTAGE_PIN A3
#define PWM_OUT_PIN 9
#define SENSOR_POWER_PIN 5
#define ADC_PIN A2

#define DEBUG_OUT_ENABLE 1

SX1276 radio = new Module(LORA_CS, DIO0, LORA_RST, DIO1);
AHT10 humiditySensor;

String jsonoutput = "";  // string for json transfer
bool readSensorStatus = false;
int sensorValue = 0; // variable to store the value coming from the sensor
int batValue = 0;    // the voltage of battery
int count = 0;
int ADC_O_1;           // ADC Output First 8 bits
int ADC_O_2;           // ADC Output Next 2 bits
int16_t packetnum = 0; // packet counter, we increment per xmission
float temperature = 0.0;
float humidity = 0.0;

bool AHT_init()
{
  bool ret = false;
  Wire.begin();
  if (humiditySensor.begin() == false)
  {
#if DEBUG_OUT_ENABLE
    Serial.println("AHT10 not detected. Please check wiring. Freezing.");
#endif
  }

  if (humiditySensor.available() == true)
  {
    temperature = humiditySensor.getTemperature();
    humidity = humiditySensor.getHumidity();
    ret = true;
  }
  if (isnan(humidity) || isnan(temperature))
  {
#if DEBUG_OUT_ENABLE
    Serial.println(F("Failed to read from AHT sensor!"));
#endif
  }
  return ret;
}
void Lora_init()
{
  int state = radio.begin(FREQUENCY, BANDWIDTH, SPREADING_FACTOR, CODING_RATE, SX127X_SYNC_WORD, OUTPUT_POWER, PREAMBLE_LEN, GAIN);
  Serial.println(state);
  if (state == ERR_NONE)
  {
#if DEBUG_OUT_ENABLE
    Serial.println(F("success!"));
#endif
  }
  else
  {
#if DEBUG_OUT_ENABLE
    Serial.print(F("failed, code "));
    Serial.println(state);
#endif
    // while (true)
    //     ;
  }
}
void setup()
{
#if DEBUG_OUT_ENABLE
  Serial.begin(115200);
  Serial.println("Soil start.");
  Serial.print(FREQUENCY);
  Serial.println(" Mhz");

#endif
  delay(100);

  // set up Timer 1
  pinMode(PWM_OUT_PIN, OUTPUT);

  TCCR1A = bit(COM1A0);            // toggle OC1A on Compare Match
  TCCR1B = bit(WGM12) | bit(CS10); // CTC, scale to clock
  OCR1A = 1;

  pinMode(LORA_RST, OUTPUT);
  digitalWrite(LORA_RST, HIGH);
  delay(100);

  pinMode(SENSOR_POWER_PIN, OUTPUT);
  digitalWrite(SENSOR_POWER_PIN, HIGH); //Sensor power on
  delay(100);

  Lora_init();

  Wire.begin();
  if (humiditySensor.begin() == false)
  {

#if DEBUG_OUT_ENABLE
    Serial.println("AHT10 not detected. Please check wiring. Freezing.");
#endif
  }
#if DEBUG_OUT_ENABLE
  else
    Serial.println("AHT10 acknowledged.");
#endif

  do_some_work();
  //setup over
#if DEBUG_OUT_ENABLE
  Serial.println("[Set]Sleep Mode Set");
#endif
  low_power_set();
}

void loop()
{
  wdt_disable();

  if (count > SLEEP_CYCLE) //(7+1) x 8S  450
  {
#if DEBUG_OUT_ENABLE
    //code start
    Serial.println("Code start>>");
#endif

    do_some_work();
    all_pins_low();

#if DEBUG_OUT_ENABLE
    //code end
    Serial.println("Code end<<");
#endif
    //count init
    count = 0;
  }

  low_power_set();
}

ISR(WDT_vect)
{
#if DEBUG_OUT_ENABLE
  Serial.print("[Watch dog]");
  Serial.println(count);
#endif
  delay(100);
  count++;
  //wdt_reset();
  wdt_disable(); // disable watchdog
}

//Set low power mode and into sleep
void low_power_set()
{
  all_pins_low();
  delay(10);
  // disable ADC
  ADCSRA = 0;

  sleep_enable();
  watchdog_init();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  delay(10);
  noInterrupts();
  sleep_enable();

  // turn off brown-out enable in software
  MCUCR = bit(BODS) | bit(BODSE);
  MCUCR = bit(BODS);
  interrupts();

  sleep_cpu();
  sleep_disable();
}

//Enable watch dog
void watchdog_init()
{
  // clear various "reset" flags
  MCUSR = 0;
  // allow changes, disable reset
  WDTCSR = bit(WDCE) | bit(WDE);
  WDTCSR = bit(WDIE) | bit(WDP3) | bit(WDP0); // set WDIE, and 8 seconds delay
  wdt_reset();                                // pat the dog
}

void do_some_work()
{

  digitalWrite(SENSOR_POWER_PIN, HIGH); // Sensor/RF95 power on
  digitalWrite(LORA_RST, HIGH);
  delay(5);
  pinMode(PWM_OUT_PIN, OUTPUT);    //digitalWrite(PWM_OUT_PIN, LOW);
  TCCR1A = bit(COM1A0);            // toggle OC1A on Compare Match
  TCCR1B = bit(WGM12) | bit(CS10); // CTC, scale to clock
  OCR1A = 1;                       // compare A register value (5000 * clock speed / 1024).When OCR1A == 1, PWM is 2MHz

  Lora_init();
  delay(50);

  //ADC2  AVCC as reference voltage
  ADMUX = _BV(REFS0) | _BV(MUX1);

  //ADC2  internal 1.1V as ADC reference voltage
  //ADMUX = _BV(REFS1) |_BV(REFS0) | _BV(MUX1);

  // 8  分频
  ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0);
  delay(50);
  for (int i = 0; i < 3; i++)
  {
    //start ADC conversion
    ADCSRA |= (1 << ADSC);

    delay(10);

    if ((ADCSRA & 0x40) == 0)
    {
      ADC_O_1 = ADCL;
      ADC_O_2 = ADCH;

      sensorValue = (ADC_O_2 << 8) + ADC_O_1;
      ADCSRA |= 0x40;

      //Mod for value from in 0 to 100
      // 1000 = Air dry, 500 ultrawet
      sensorValue = (sensorValue - 500) / 5;
      // End

#if DEBUG_OUT_ENABLE
      Serial.print("ADC:");
      Serial.println(sensorValue);
#endif

      if (readSensorStatus == false)
        readSensorStatus = AHT_init();
    }
    ADCSRA |= (1 << ADIF); //reset as required
    delay(50);
  }

  //ADC3  internal 1.1V as ADC reference voltage
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX1) | _BV(MUX0);

  delay(50);
  for (int i = 0; i < 3; i++)
  {
    //start ADC conversion
    ADCSRA |= (1 << ADSC);

    delay(10);

    if ((ADCSRA & 0x40) == 0)
    {
      ADC_O_1 = ADCL;
      ADC_O_2 = ADCH;

      batValue = (ADC_O_2 << 8) + ADC_O_1;
      ADCSRA |= 0x40;

#if DEBUG_OUT_ENABLE
      Serial.print("BAT:");
      Serial.println(batValue);
      float bat = (float)batValue * 3.3;
      bat = bat / 1024.0;
      Serial.print(bat);
      Serial.println("V");
#endif
    }
    ADCSRA |= (1 << ADIF); //reset as required
    delay(50);
  }
  send_lora();
  delay(1000);
  radio.sleep();

  packetnum++;
  readSensorStatus = false;
  digitalWrite(SENSOR_POWER_PIN, LOW); // Sensor/RF95 power off
  delay(100);
}

void all_pins_low()
{
  pinMode(PWM_OUT_PIN, INPUT);
  pinMode(A4, INPUT_PULLUP);
  pinMode(A5, INPUT_PULLUP);

  delay(50);
}

void send_lora()
{

  // Create json object for transfer
  DynamicJsonDocument root(256);
  root["node_id"] = node_id;
  root["hum"] = (String)humidity;
  root["temp"] = (String)temperature;
  root["adc"] = (String)sensorValue;
  root["bat"] = (String)batValue;

  serializeJson(root, jsonoutput);
#if DEBUG_OUT_ENABLE
  serializeJson(root, Serial);
#endif
  radio.transmit(jsonoutput);
jsonoutput = "";
}

I2C_AHT10.cpp

/****************************************************************

 ***************************************************************/

#include "I2C_AHT10.h"

/*--------------------------- Device Status ------------------------------*/
bool AHT10::begin(TwoWire &wirePort)
{
    _i2cPort = &wirePort; //Grab the port the user wants to communicate on

    _deviceAddress = AHT10_DEFAULT_ADDRESS; //We had hoped the AHT10 would support two addresses but it doesn't seem to

    if (isConnected() == false)
        return false;

    //Wait 40 ms after power-on before reading temp or humidity. Datasheet pg 8
    delay(40);

    //Check if the calibrated bit is set. If not, init the sensor.
    if (isCalibrated() == false)
    {
        //Send 0xBE0800
        initialize();

        //Immediately trigger a measurement. Send 0xAC3300
        triggerMeasurement();

        delay(75); //Wait for measurement to complete

        uint8_t counter = 0;
        while (isBusy())
        {
            delay(1);
            if (counter++ > 100)
                return (false); //Give up after 100ms
        }

        //This calibration sequence is not completely proven. It's not clear how and when the cal bit clears
        //This seems to work but it's not easily testable
        if (isCalibrated() == false)
        {
            return (false);
        }
    }

    //Check that the cal bit has been set
    if (isCalibrated() == false)
        return false;

    //Mark all datums as fresh (not read before)
    sensorQueried.temperature = true;
    sensorQueried.humidity = true;

    return true;
}

//Ping the AHT10's I2C address
//If we get a response, we are correctly communicating with the AHT10
bool AHT10::isConnected()
{
    _i2cPort->beginTransmission(_deviceAddress);
    if (_i2cPort->endTransmission() == 0)
        return true;

    //If IC failed to respond, give it 20ms more for Power On Startup
    //Datasheet pg 7
    delay(20);

    _i2cPort->beginTransmission(_deviceAddress);
    if (_i2cPort->endTransmission() == 0)
        return true;

    return false;
}

/*------------------------ Measurement Helpers ---------------------------*/

uint8_t AHT10::getStatus()
{
    _i2cPort->requestFrom(_deviceAddress, (uint8_t)1);
    if (_i2cPort->available())
        return (_i2cPort->read());
    return (0);
}

//Returns the state of the cal bit in the status byte
bool AHT10::isCalibrated()
{
    return (getStatus() & (1 << 3));
}

//Returns the state of the busy bit in the status byte
bool AHT10::isBusy()
{
    return (getStatus() & (1 << 7));
}

bool AHT10::initialize()
{
    _i2cPort->beginTransmission(_deviceAddress);
    _i2cPort->write(sfe_aht10_reg_initialize);
    _i2cPort->write(0x80);
    _i2cPort->write(0x00);
    if (_i2cPort->endTransmission() == 0)
        return true;
    return false;
}

bool AHT10::triggerMeasurement()
{
    _i2cPort->beginTransmission(_deviceAddress);
    _i2cPort->write(sfe_aht10_reg_measure);
    _i2cPort->write(0x33);
    _i2cPort->write(0x00);
    if (_i2cPort->endTransmission() == 0)
        return true;
    return false;
}

//Loads the
void AHT10::readData()
{
    //Clear previous data
    sensorData.temperature = 0;
    sensorData.humidity = 0;

    if (_i2cPort->requestFrom(_deviceAddress, (uint8_t)6) > 0)
    {
        uint8_t state = _i2cPort->read();

        uint32_t incoming = 0;
        incoming |= (uint32_t)_i2cPort->read() << (8 * 2);
        incoming |= (uint32_t)_i2cPort->read() << (8 * 1);
        uint8_t midByte = _i2cPort->read();

        incoming |= midByte;
        sensorData.humidity = incoming >> 4;

        sensorData.temperature = (uint32_t)midByte << (8 * 2);
        sensorData.temperature |= (uint32_t)_i2cPort->read() << (8 * 1);
        sensorData.temperature |= (uint32_t)_i2cPort->read() << (8 * 0);

        //Need to get rid of data in bits > 20
        sensorData.temperature = sensorData.temperature & ~(0xFFF00000);

        //Mark data as fresh
        sensorQueried.temperature = false;
        sensorQueried.humidity = false;
    }
}

//Triggers a measurement if one has not been previously started, then returns false
//If measurement has been started, checks to see if complete.
//If not complete, returns false
//If complete, readData(), mark measurement as not started, return true
bool AHT10::available()
{
    if (measurementStarted == false)
    {
        triggerMeasurement();
        measurementStarted = true;
        return (false);
    }

    if (isBusy() == true)
    {
        return (false);
    }

    readData();
    measurementStarted = false;
    return (true);
}

bool AHT10::softReset()
{
    _i2cPort->beginTransmission(_deviceAddress);
    _i2cPort->write(sfe_aht10_reg_reset);
    if (_i2cPort->endTransmission() == 0)
        return true;
    return false;
}

/*------------------------- Make Measurements ----------------------------*/

float AHT10::getTemperature()
{
    if (sensorQueried.temperature == true)
    {
        //We've got old data so trigger new measurement
        triggerMeasurement();

        delay(75); //Wait for measurement to complete

        uint8_t counter = 0;
        while (isBusy())
        {
            delay(1);
            if (counter++ > 100)
                return (false); //Give up after 100ms
        }

        readData();
    }

    //From datasheet pg 8
    float tempCelsius = ((float)sensorData.temperature / 1048576) * 200 - 50;

    //Mark data as old
    sensorQueried.temperature = true;

    return tempCelsius;
}

float AHT10::getHumidity()
{
    if (sensorQueried.humidity == true)
    {
        //We've got old data so trigger new measurement
        triggerMeasurement();

        delay(75); //Wait for measurement to complete

        uint8_t counter = 0;
        while (isBusy())
        {
            delay(1);
            if (counter++ > 100)
                return (false); //Give up after 100ms
        }

        readData();
    }

    //From datasheet pg 8
    float relHumidity = ((float)sensorData.humidity / 1048576) * 100;

    //Mark data as old
    sensorQueried.humidity = true;

    return relHumidity;
}

I2C_AHT10.h

/****************************************************************
 * 
 ******************************************************************/

#ifndef __I2C_AHT10_H__
#define __I2C_AHT10_H__

#include <Arduino.h>
#include <Wire.h>

#define AHT10_DEFAULT_ADDRESS 0x38

enum registers
{
    sfe_aht10_reg_reset = 0xBA,
    sfe_aht10_reg_initialize = 0xBE,
    sfe_aht10_reg_measure = 0xAC,
};

class AHT10
{
private:
    TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware
    uint8_t _deviceAddress;
    bool measurementStarted = false;

    struct
    {
        uint32_t humidity;
        uint32_t temperature;
    } sensorData;

    struct
    {
        uint8_t temperature : 1;
        uint8_t humidity : 1;
    } sensorQueried;

public:
    //Device status
    bool begin(TwoWire &wirePort = Wire); //Sets the address of the device and opens the I2C bus
    bool isConnected();                   //Checks if the AHT10 is connected to the I2C bus
    bool available();                     //Returns true if new data is available

    //Measurement helper functions
    uint8_t getStatus();       //Returns the status byte
    bool isCalibrated();       //Returns true if the cal bit is set, false otherwise
    bool isBusy();             //Returns true if the busy bit is set, false otherwise
    bool initialize();         //Initialize for taking measurement
    bool triggerMeasurement(); //Trigger the AHT10 to take a measurement
    void readData();           //Read and parse the 6 bytes of data into raw humidity and temp
    bool softReset();          //Restart the sensor system without turning power off and on

    //Make measurements
    float getTemperature(); //Goes through the measurement sequence and returns temperature in degrees celcius
    float getHumidity();    //Goes through the measurement sequence and returns humidity in % RH
};
#endif

I haven’t used the radio.h library (I use the lora library), but here is something I noticed.
In OMG 1.0, it uses the following Lora settings:

#define LORA_SIGNAL_BANDWIDTH 125E3
#define LORA_TX_POWER         17
#define LORA_SPREADING_FACTOR 7
#define LORA_CODING_RATE      5
#define LORA_PREAMBLE_LENGTH  8
#define LORA_SYNC_WORD        0x12
#define DEFAULT_CRC           true

These are also the same settings I use in my Lora mailbox sensor I just finished working on.

In your Arduino Soil Sensor code, signal bandwidth and preamble length settings are fine, but maybe change the spreading factor to 7, and coding rate to 5.

For the sync word, I find the settings do not matter in my experiments on the sx1726, but looking at the radio library I think the default is x12 anyway, so you could leave as is or you could add a line #define SX127X_SYNC_WORD 0x12.

As for the default crc settings, we’ll… I can’t find any way to set this in the radio library, so not sure what to tell you about it.

I finally had time to put the fixes into the code and it works

Thank you for taking the time to help me!

Hey everyone! It’s been very interesting reading through this thread and others to solve the puzzle of connecting these sensors to Home Assistant. It’s taken me a couple months but I’m almost there! Just having trouble with the very last step.

I have my sensor loaded with Kaimo123’s code, it’s connected to my OpenMQTTGateway, my Gateway is configured in Home Assistant, and I can see strings of data like these coming in:

‘{“node_id”:“ID846758”,“hum”:“25.08”,“temp”:“32.16”,“adc”:“45”,“bat”:“953”}’

Great!

Now I’d like to break down this string and convert it into individual sensors in my sensor.yaml file. However when I copy and paste any of the examples in this thread into my sensor.yaml file, the entire thing lights up red and I get loads of "String does not match the pattern of “DEPRECATED^” messages.

If I hover over the error message it directs me to this URL: http://schemas.home-assistant.io/integration-sensor

Which seems to be broken? I’m guessing something in Home Assistant has changed, but I’m not sure what to try next. Has anyone run into this issue?

For reference, I’m trying to put the code TheBang2’s code below into my sensor.yaml file. Any clues would be much appreciated!

The following code works for me.

mqtt:
  sensor:
  - name: "Kuslapuu_01_Humidity"
    state_topic: "home/OpenMQTTGateway_ESP32_LORA/LORAtoMQTT"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID010204" %}
          {{ (value_json.message|from_json).hum }}
        {% else %}
          {{ is_state_attr("sensor.kuslapuu_01_Humidity") | round(2) }}
        {% endif %}
    unit_of_measurement: '%'
    icon: mdi:water-percent

  - name: "Kuslapuu_01_Temperature"
    state_topic: "home/OpenMQTTGateway_ESP32_LORA/LORAtoMQTT"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID010204" %}
          {{ (value_json.message|from_json).temp }}
        {% else %}
          {{ is_state_attr("sensor.kuslapuu_01_Temperature") | round(2) }}
        {% endif %}
    unit_of_measurement: '°C'
    icon: mdi:thermometer

  - name: "Kuslapuu_01_ADC"
    state_topic: "home/OpenMQTTGateway_ESP32_LORA/LORAtoMQTT"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID010204" %}
          {{ (value_json.message|from_json).adc }}
        {% else %}
          {{ is_state_attr("sensor.kuslapuu_01_adc") }}
        {% endif %}
    unit_of_measurement: '%'
    icon: mdi:flower

  - name: "Kuslapuu_01_battery"
    state_topic: "home/OpenMQTTGateway_ESP32_LORA/LORAtoMQTT"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID010204" %}
          {{ (value_json.message|from_json).bat }}
        {% else %}
          {{ is_state_attr("sensor.kuslapuu_01_battery") }}
        {% endif %}
    unit_of_measurement: 'V'
    icon: mdi:battery

Thanks for getting back to me!

Where are you putting this code?
If I place it in my sensor.yaml file I get an error on the first line

Missing property “platform”
If I try to make it a platform I get a whole host of new errors.

If I place it directly into my config.yaml file I get no errors, but It also doesn’t create any sensors.
I’ve changed all the variables to line up with my own system, so there shouldn’t be any issues that way.

What is the expected behavior after putting in that code? Should 4 new sensors appear?

I have the code in configuration.yaml. Did you restart home assistant? You can search for new entity from developer tools➡ states

I would have never thought to look in developer tools, but yes they show up there!
However all the “states” are unknown, so I’m still missing something. I’ll have to spend more time on this.

In an unrelated note, my “History” tab has been frozen for a while, through multiple restarts. But clicking the “last updated” link when I searched for an entity in “Developer Tools” has somehow fixed my history tab! A small victory.

After restart, it is normal that state is unknown. The state is updated when the soil sensor sends new data

I’ve looked it over quite a few times now and can’t see what I’m missing. I have the following string coming in through my gateway every 20s. I assume that means things are all good between my sensor and my gateway.

{"topic":"home/OMG_01/LORAtoMQTT/Soil01","node_id":"ID846758","hum":"29.27","temp":"25.95","adc":"76","bat":"949"}

Coupled with the following code in my config.yaml file.

mqtt:
  sensor:
  - name: "Soil01_Humidity"
    state_topic: "home/OMG_01/LORAtoMQTT/Soil01"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID846758" %}
          {{ (value_json.message|from_json).hum }}
        {% else %}
          {{ is_state_attr("sensor.soil01_humidity") | round(2) }}
        {% endif %}
    unit_of_measurement: '%'
    icon: mdi:water-percent

  - name: "Soil01_Temperature"
    state_topic: "home/OMG_01/LORAtoMQTT/Soil01"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID846758" %}
          {{ (value_json.message|from_json).temp }}
        {% else %}
          {{ is_state_attr("sensor.soil01_temperature") | round(2) }}
        {% endif %}
    unit_of_measurement: '°C'
    icon: mdi:thermometer

  - name: "Soil01_ADC"
    state_topic: "home/OMG_01/LORAtoMQTT/Soil01"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID846758" %}
          {{ (value_json.message|from_json).adc }}
        {% else %}
          {{ is_state_attr("sensor.soil01_adc") }}
        {% endif %}
    unit_of_measurement: '%'
    icon: mdi:flower

  - name: "Soil01_Battery"
    state_topic: "home/OMG_01/LORAtoMQTT/Soil01"
    value_template: >-
        {% if (value_json.message|from_json).node_id == "ID846758" %}
          {{ (value_json.message|from_json).bat }}
        {% else %}
          {{ is_state_attr("sensor.soil01_battery") }}
        {% endif %}
    unit_of_measurement: 'V'
    icon: mdi:battery

There must be something I’m missing between my gateway and home assistant.

Edit: I suspect my problem has to do with topics…

Code looks good.
l have one idea that you might try, change state_topic to home/OMG_01/LORAtoMQTT

No effect. I think I am I missing some fundamental understanding of how MQTT topics work. If I get too far into the weeds I’ll make a new thread on this, but I do have a couple more leads to chase down first.

In the sensor code you posted on November 22nd, I notice you don’t have a topic in there. How do you define the topic that this sensor is publishing on?

Where does this path home/OpenMQTTGateway_ESP32_LORA/LORAtoMQTT come from? And how can I change it?

I think debugging this would be much easier if I could use MQTTExplorer, but I am unable to connect to my broker using this software. All I get is a “Disconnected from server” error. This may be another clue to my problem.

What MQTT topic does that get posted to?

1 Like

I don’t know! :smiley:

I was working under the assumption that sticking root["topic"] = "home/OMG_01/LORAtoMQTT/Soil01"; in my Arduino code for the sensor would publish the message to that topic. But I honestly have no understanding of how topics work.

What do I need to put in my code to make the message publish to a topic?

Go to Devices&Services, Mosquitto broker, LORAgateway, MQTT info

1 Like

That’s it! That’s the missing piece! I didn’t know my own topic path.

After putting that path in my config.yaml file all sensors are up, working, and recording.

I would have never found that setting on my own. Thank you so much for walking me through this!

Edit: Debrief. I made three large errors which contributed to my being unable to finish this project without help. I think I have a grasp on them now, so I’ll leave this writeup for anyone else wanders their way through this project and might make similar blunders.

  1. As described in this post, I didn’t know my own topic path or where to find it. I had copied and pasted code without understanding it. This included variables which needed to be unique to my setup.

  2. I assumed the topic was defined in the node/sensor code. It is not. Using the latest iteration of code in this thread, the topic is defined in the gateway, and uses a node_id to separate messages, then breaks them down into separate sensors.

  3. When using MQTTExplorer, I mistakenly tried connecting to my MQTT Gateway instead of my MQTT broker. In my case, my MQTT Broker is running on the same machine as Home Assistant. So I should have been entering the IP for my Home assistant device. If I had figured this out earlier, I would have saved a lot of trouble debugging!

All and all this was very fun. I came into this project having never touched LORA or MQTT, and now I can start experimenting with both!

2 Likes

Hi All,
I finally got around to playing with this and wanted to share a few things:

1 - RadioLib: Don’t try to use a more recent version of RadioLib. I tried version 6.0.0 (which causes a couple of compile errors which I fixed), but this version turns out to be too bloated and after adding the JSON library, the code was too big to fit. So I reverted back to the version 4.6.0 (as mentioned earlier in this thread) and everything compiled and small enough to fit.

2- LoRa Settings: I use LoRa in the 915MHz range and I use Open MQTT G/W 1.0 as the LoRa G/W. To get this device to work, plus boost the Tx Power levels, I used the following:

#define FREQUENCY 915.0

#define BANDWIDTH 125.0
//#define SPREADING_FACTOR 9
#define SPREADING_FACTOR 7
//#define CODING_RATE 7
#define CODING_RATE 5
//#define OUTPUT_POWER 10
#define OUTPUT_POWER 17  //docs say range is 2 to 17 dBm
#define PREAMBLE_LEN 8
#define GAIN 0

3 - Soil Moisture Readings - The Soil moisture sensor itself is “Capacitance” based. The ProMini’s I/O provides a 2MHz square wave to this capacitor along with a resistor which forms an RC time constant and the output of this is further filtered to provide a more smoothed output which is then measured by the ProMIni’s ADC3. However none of this uses a voltage regulator. Since the battery voltage drops over time as the battery drains, it should mean that the PWM output drops over time, and the ADC reference voltage drops over time. I wasn’t sure of what the overall affects would be so I took some measurements. I varied the device’s battery voltage from 3.3 to 2.0 (which is suppose to be within the operating voltage range of the device), and monitored the ADC readings of the soil sensor and found that it varied a lot.

As an example, at 3.3V, the ADC reading after conversion (See Note below) was 22, but at 2.0V it was 35.

Note: I should note here that the original MakerFab code sent LoRa data of the raw reading of the ADC2 (ranging from 500 as very wet, to 1000 as very dry), and thebang2 changed this to JSON but also changed the range to be 0%(very wet) to 100% (very dry). Both of these measure dryness. In my case, I ended up changing this be a moisture reading so in my measurements the range is 0%(very dry) to 100%(very wet): sensorValue = 100 -((sensorValue - 500)/5);
So to conclude, in my measurements, as the battery drains, it will read the same soil conditions as wetter.

4 - Battery Level. The ProMini’s ADC2 is used to measure the battery voltage. The ADC2 is configured to use a bandgap based reference voltage of 1.1V and a voltage divider is used to get the battery voltage down within this 1.1V range. I took some measurements and found the ADC2 reading of the battery level is pretty close to actual.

Best Regards.

2 Likes

I have read this topic up and down a couple of times and I don’t understand if the original firmware on the Makersfab Lora Soil Moisture Sensor V3 are suppose to work directly with openmqttgateway if the gateway is the same Mhz as the sender?