Awesome! LoRa Soil sensor

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?

@kaimo123 Can you please tell what it was that finally made it work? I have the exact same hardware as you have and I can see the OMG is working with mosquito in HA but I can’t get any reading from the Lora Soil Moisture Sensor V3. Please help me !

The original Makerfab firmware does not send data using JSON. OMG could still send this non-JSON data to HA, but it would be difficult to parse.

What most people are using is found in this thread, topic #23 from thebang2. He took the Makerfab firmware and modified it to send the LoRa data using JSON format. OMG gets this JSON formatted data from the soil sensor and marks it as a “message”, and adds some stuff and then formats it all in JSON. So HA gets an MQTT payload in JSON format and inside this is a “message” which itself is JSON formatted. The trick in HA is to configure the mqtt sensor to extract the “message” from the JSON formatted payload, and then tell HA that the value of “message” is itself JSON (using from_json) and now HA can parse the individual parameters (node_id, hum, temp, adc, bat) inside the “message”.

2 Likes

@wmaker gave advice on how to change LORA settings.
The final arduino code that worked for me:

#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") + "ID846758";

//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 Minutit

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

#define BANDWIDTH 125.0
#define SPREADING_FACTOR 7 
#define CODING_RATE 5 
#define OUTPUT_POWER 10
#define PREAMBLE_LEN 8
#define GAIN 0
#define SX127X_SYNC_WORD 0x12 

//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 = "";
}

Thanks !!
I will order a USB to serial adapter then, to be able to change the firmware on the “Lora Soil Moisture Sensor” :slight_smile:
To be continue…

This ADC change is really quite big.
Maybe the arduino code could be changed a bit. So that it takes this change into account?

1 Like

Now it works!!
Well, kind of… I get the json string in HA MQTT broker so that is great news!!
But my “dry” value in air is 70 (water 1)
Is this only to accept and make a template to adjust the sensor?
Thanks for the support @kaimo123 :ok_hand: