illuzn / esphome-april-soil-moisture-sensor

ESPHome Configuration for April Soil Moisture Sensor
GNU Affero General Public License v3.0
6 stars 2 forks source link

Battery #3

Closed peterbarbosa closed 3 months ago

peterbarbosa commented 3 months ago

Hello,

I have received 2 April Soil Moisture sensors. I have successfully programmed them and they seem to be reporting to HA/MQTT great.

When I use only the battery, the unit does not stay powered on.

Seperately, if Im plugging the USB cable into the board to charge the battery, the unit only stays on for a few minutes then powers off (red LED turns off and MQTT does not get updates).

I have removed the board from the case to ensure it has proper connection for both battery and USB cable. When I have the battery unplugged and just the USB cable, it always reports to MQTT and red LED stays on.

Any ideas on what can be happening?

illuzn commented 3 months ago

Can you post your yaml here? I have a suspicion but I just want to confirm.

peterbarbosa commented 3 months ago
esphome:
  name: ourdoor-plant-soil-1
  includes:
      - april_soil_sensor.h
  libraries:
      - Wire

esp32:
  board: esp32-s2-saola-1

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none

mqtt:
  broker: XXX.XXX.XX.XXX
  username: {hidden}
  password: !secret mqtt_password

logger:
  level: DEBUG

sensor:
  - platform: adc
    pin: 7
    attenuation: 11dB
    raw: true
    id: "vbatt"
    name: "Battery Voltage"
    update_interval: 30s
    disabled_by_default: true
    device_class: "voltage"
    state_class: "measurement"
    icon: "mdi:battery-outline"
    accuracy_decimals: 2
    entity_category: "diagnostic"

# derive battery percentage 3300mV=0%, 3950mV=100%
  - platform: template
    lambda: |-
      float x = id(vbatt).state;
      if (x < 3300) {
        x = 3300;
      }
      if (x > 3950) {
        x = 3950;
      }
      float y = (x - 3300) / 650 * 100;
      return y;
    name: "Battery Percentage"
    update_interval: 5s
    unit_of_measurement: "%"
    device_class: "battery"
    state_class: "measurement"
    icon: "mdi:battery"
    accuracy_decimals: 0

  - platform: adc
    pin: 9
    raw: true
    id: rMoisture
    name: "Raw Moisture reading"
    update_interval: never
    unit_of_measurement: raw
    disabled_by_default: true
    state_class: "measurement"
    icon: "mdi:water-alert"
    entity_category: "diagnostic"

#derive moisture level 3800=100% 6800=0%
  - platform: template
    name: "Moisture Level (Instant)"
    id: "iMoisture"
    unit_of_measurement: "%"
    update_interval: never
    state_class: "measurement"
    disabled_by_default: true
    icon: "mdi:water-percent"
    accuracy_decimals: 0
    lambda: |-
      float x = id(rMoisture).state;
      if (x < 3800) {
        x = 3800;
      }
      if (x > 6800) {
        x = 6800;
      } 
      float y = (x - 3800) / 3000 * 100;
      y = 100 - y;
      return y;

# average over 5 readings to smooth
  - platform: template
    name: "Moisture Level"
    id: "aMoisture"
    unit_of_measurement: "%"
    update_interval: 5s
    state_class: "Measurement"
    icon: "mdi:water-percent"
    accuracy_decimals: 0
    lambda: |-
      return id(iMoisture).state;
    filters:
    - quantile:
        window_size: 5
        send_every: 5
        send_first_at: 5
        quantile: 0.9

output:
  - platform: ledc
    pin: 17
    frequency: 1500000Hz
    id: moisture_gen

interval:
  - interval: 1500ms
    then:
      - output.turn_on: moisture_gen
      - output.set_level:
          id: moisture_gen
          level: 34%
      - delay: 500ms
      - component.update: rMoisture
      - component.update: iMoisture
      - component.update: aMoisture
      - output.turn_off: moisture_gen

deep_sleep:
  run_duration:
    default: 1min
  sleep_duration: 30min
  wakeup_pin: 2
peterbarbosa commented 3 months ago

@illuzn here is the code for april_soil_sensor.h

#include "esphome.h"
#include "driver/adc.h"
#include "driver/ledc.h"
#include <esp_adc_cal.h>
#include <Wire.h>

// I2C address for temperature sensor
const int TMP_ADDR  = 0x48;

const adc_channel_t      MOISTURE_CHANNEL    = ADC_CHANNEL_8;     // GPIO9
const adc_bits_width_t   WIDTH               = ADC_WIDTH_BIT_13;
const adc_atten_t        MOISTURE_ATTEN      = ADC_ATTEN_DB_2_5;

#define LEDC_LS_TIMER          LEDC_TIMER_1
#define LEDC_LS_MODE           LEDC_LOW_SPEED_MODE
#define LEDC_LS_DUTY_RES       LEDC_TIMER_3_BIT

#define LEDC_LS_CH0_GPIO       (17)
#define LEDC_LS_CH0_CHANNEL    LEDC_CHANNEL_0

// %50 duty: 2 ^ (LEDC_LS_DUTY_RES - 1) 
#define LEDC_TEST_DUTY          (4)

class AprilSoilSensor : public PollingComponent, public Sensor {

    private:

    float readTemp_() {
        float temp;

        Wire.beginTransmission(TMP_ADDR);
        // Select Data Registers
        Wire.write(0X00);
        Wire.endTransmission();

        // Request 2 bytes , Msb first
        Wire.requestFrom(TMP_ADDR, 2);

        // Read temperature as Celsius (the default)
        while(Wire.available()) {  
            int msb = Wire.read();
            int lsb = Wire.read();

            int rawtmp = msb << 8 |lsb;
            int value = rawtmp >> 4;
            temp = value * 0.0625;

            return temp;
        }

        return 0;
    }

    public:

    Sensor *temperature_sensor = new Sensor();
    Sensor *soil_sensor = new Sensor();

    AprilSoilSensor() : PollingComponent(3000) {}

    void setup() override {
        ledc_timer_config_t ledc_timer = {
            .speed_mode = LEDC_LS_MODE,           // timer mode
            .duty_resolution = LEDC_TIMER_3_BIT, // resolution of PWM duty
            .timer_num = LEDC_LS_TIMER,            // timer index
            .freq_hz = 8000000,                      // frequency of PWM signal
            .clk_cfg = LEDC_AUTO_CLK,              // Auto select the source clock
        };
        ledc_timer_config(&ledc_timer);

        ledc_channel_config_t ledc_channel = {
            .gpio_num   = LEDC_LS_CH0_GPIO,
            .speed_mode = LEDC_LS_MODE,
            .channel    = LEDC_LS_CH0_CHANNEL,
            .timer_sel  = LEDC_LS_TIMER,
            .duty       = LEDC_TEST_DUTY,
            .hpoint     = 0,
        };
        ledc_channel_config(&ledc_channel);

        //Configure ADC
        adc1_config_width(WIDTH);
        adc1_config_channel_atten((adc1_channel_t)MOISTURE_CHANNEL, MOISTURE_ATTEN);

        const int sdaPin = 8;
        const int sclPin = 10;
        Wire.begin(sdaPin, sclPin);

        delay(2000);
    }

    void update() override {
        float temp = readTemp_();
        temperature_sensor->publish_state(temp);
        uint32_t adc_reading = adc1_get_raw((adc1_channel_t)MOISTURE_CHANNEL);
        soil_sensor->publish_state(adc_reading);
    }

};
peterbarbosa commented 3 months ago

Ive also tried your code and cant get the device to appear in MQTT integration on HA (why I originally found the other code).

illuzn commented 3 months ago

Please read the instructions again:

1. Install ESPHome e.g. docker pull esphome/esphome (Tested and working upto v2022.11.5 - there are some issues with v2022.12.* and v2022.2.* bootlooping)
2. Copy default-template.yaml, testunit.yaml and secrets.yaml to your ESPHome folder e.g. on HomeAssistant this is /config/esphome/
3. Configure your secrets.yaml
4. Connect device via USB to your ESPHome host. Install firmware. Click the dropdown next to testunit and install. N.B. Do not install the default-template it will fail (it is only the template configuration). N.B. The device should automatically reboot into CDC mode to receive the firmware however if it does not you may need to hold the IO0 button down while pushing the EN button (the two black buttons on the right of the PCB).
5. Within around 30s logs should become available in ESPHome (and a new device should appear in HomeAssistant if you are using that).
6. (Optional) Depending on the type of soil you should calibrate your soil sensor. See the theory of operation section.
You can duplicate testunit.yaml as many times as you wish just remember to change the name of your device as well e.g. soil-sensor-1.yaml->device-name: "soil-sensor-1"

You are using this in a completely unsupported manner.

This works via the esphome integration (which uses direct HomeAssistant API access and is much faster/ more power efficient): image

Seperately, if Im plugging the USB cable into the board to charge the battery, the unit only stays on for a few minutes then powers off (red LED turns off and MQTT does not get updates).

The red LED is only lit when the device is charging. This is internal behaviour on the PCB and cannot be altered in software.

When I use only the battery, the unit does not stay powered on.

This is by design, if you have it connected 24/7 your device will drain its battery in ~1 day. In fact, using your configuration, the battery will drain very quickly:

deep_sleep:
  run_duration:
    default: 1min
  sleep_duration: 30min
  wakeup_pin: 2

This runs the device for 1 minute every 30 minutes. For comparison, I run my device for 1 minute every 6 hours (this is plenty for soil moisture levels unless you have your plants next to a furnace). If you want it to be constantly powered you can set the

I have removed the board from the case to ensure it has proper connection for both battery and USB cable. When I have the battery unplugged and just the USB cable, it always reports to MQTT and red LED stays on.

With the battery unplugged the charging light will remain lit because it detects a battery voltage of 0V (and hence tries to constantly charge the battery).

I have no idea why the ESP32 remains running - it shouldn't do so, but I suspect you just have not hit the 1 min run_duration.

peterbarbosa commented 3 months ago

Amazing - I got this working! Thanks for the support!