I’m developing a soil moisture sensor project using LoRa communication with Heltec LoRa V3 boards.
I’m doing a single ADC read of the batteries and sending the value to the receiver, then to MQTT, and finally to Home Assistant.
My doubt is about using the multiply parameter.
We should divide the multimeter reading by the raw_adc value to get the multiply value to use in the sensor. But I’m not sure whether I should calculate it using the battery voltage measured while the board is in deep sleep or while it is awake (ON) .
I also had some trouble getting a stable ADC value without noise due to using update_interval: never . I had to use samples: 255 and sampling_mode: avg to improve it.
Here is my code below:
substitutions:
sector: 3
battery_low_level: 0.001
send_interval: 2min
esphome:
name: esp-kiwi-soil-sender
friendly_name: "ESP Kiwi Soil - Sender"
on_boot:
#- priority: -100
then:
# 1. wait to stabilize
- delay: 5s
# 2. read sensors
- script.execute: reading
# 3. wait for readings
- wait_until:
not:
script.is_running: reading
# 4. --- WEAK BATTERY CHECK ---
- if:
condition:
lambda: 'return id(battery_voltage).state <= ${battery_low_level};'
then:
- button.press: shutdown_button
# 4. Envia pacote LoRa
# Format: [sector(1B)] [volt_mV high(1B)] [volt_mV low(1B)] [pct(1B)] [temp_high(1B)] [temp_low(1B)]
- sx126x.send_packet:
data: !lambda |-
uint8_t sector = ${sector};
uint16_t volt_cV = (uint16_t)(id(battery_voltage).state * 100);
uint8_t pct = (uint8_t)(id(battery_percentage).state);
int16_t temp_dev = (int16_t)(id(device_temperature).state * 10);
std::vector<uint8_t> payload;
payload.push_back(sector);
payload.push_back((volt_cV >> 8) & 0xFF);
payload.push_back(volt_cV & 0xFF);
payload.push_back(pct);
payload.push_back((temp_dev >> 8) & 0xFF);
payload.push_back(temp_dev & 0xFF);
ESP_LOGI("lora_tx", "Sent: Sector=%d Volt=%d cV Batt=%d%% DeviceTemp=%d", sector, volt_cV, pct, temp_dev);
return payload;
# 4. Put LoRa radio to sleep
- sx126x.set_mode_sleep
# 5. wait to stabilize
- delay: 50ms
# 6. disable all GPIOs
- lambda: |-
// === LORA SX1262 ===
gpio_set_direction(GPIO_NUM_14, GPIO_MODE_DISABLE); // DIO1
gpio_set_direction(GPIO_NUM_8, GPIO_MODE_DISABLE); // NSS/CS
gpio_set_direction(GPIO_NUM_12, GPIO_MODE_DISABLE); // RESET
gpio_set_direction(GPIO_NUM_13, GPIO_MODE_DISABLE); // BUSY
gpio_set_direction(GPIO_NUM_9, GPIO_MODE_DISABLE); // CLK
gpio_set_direction(GPIO_NUM_11, GPIO_MODE_DISABLE); // MISO
gpio_set_direction(GPIO_NUM_10, GPIO_MODE_DISABLE); // MOSI
// === OLED SSD1306 ===
gpio_set_direction(GPIO_NUM_17, GPIO_MODE_DISABLE); // SDA
gpio_set_direction(GPIO_NUM_18, GPIO_MODE_DISABLE); // SCL
gpio_set_direction(GPIO_NUM_21, GPIO_MODE_DISABLE); // RST
// === VEXT GPIO36 ===
gpio_set_direction(GPIO_NUM_36, GPIO_MODE_DISABLE);
// === LED GPIO35 ===
gpio_set_direction(GPIO_NUM_35, GPIO_MODE_DISABLE);
// === BATTERY ===
gpio_set_direction(GPIO_NUM_37, GPIO_MODE_DISABLE); // VBAT_CTRL
gpio_set_direction(GPIO_NUM_1, GPIO_MODE_DISABLE); // VBAT_ADC
// === SOIL SENSOR GPIO4 ===
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_DISABLE);
// === POWER DOMAINS ===
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RC_FAST, ESP_PD_OPTION_OFF);
# 7. wait to stabilize
- delay: 50ms
# 8. enter deep sleep mode
- deep_sleep.enter: deep_sleep_1
esp32:
variant: esp32s3
flash_size: 8MB
#cpu_frequency: 80MHZ
#cpu_frequency: 160MHZ
cpu_frequency: 240MHZ
framework:
type: esp-idf
logger:
hardware_uart: UART0
level: DEBUG
logs:
component: ERROR
deep_sleep:
id: deep_sleep_1
sleep_duration: ${send_interval}
button:
- platform: shutdown
id: shutdown_button
spi:
clk_pin: GPIO9
mosi_pin: GPIO10
miso_pin: GPIO11
sx126x:
# --- PINS / HARDWARE ---
dio1_pin: GPIO14
cs_pin: GPIO8
busy_pin: GPIO13
rst_pin: GPIO12
hw_version: sx1262
rf_switch: true
# --- BASE CONFIG ---
frequency: 433.0MHz
modulation: LORA
bandwidth: 125_0kHz
crc_enable: true
sync_value: [0x14, 0x24]
preamble_size: 8
rx_start: false
# --- TCXO ---
tcxo_voltage: 1_8V
tcxo_delay: 5ms
# --- TUNING ---
pa_power: -2
spreading_factor: 12
coding_rate: CR_4_8
output:
- platform: gpio
pin: 37
id: vbat_control
inverted: true
- platform: gpio
pin: 36
id: vext_control
inverted: true
- platform: gpio
pin: 4
id: moisture_power
sensor:
- platform: adc
pin: 1
id: battery_voltage
attenuation: 12db
accuracy_decimals: 3
samples: 255
sampling_mode: avg
update_interval: never
filters:
- round: 2
#- multiply: 5.21
#- round: 2
- platform: copy
id: battery_percentage
source_id: battery_voltage
unit_of_measurement: "%"
filters:
- calibrate_linear:
- 3.30 -> 0
- 4.10 -> 100
- clamp:
min_value: 0
max_value: 100
- platform: internal_temperature
id: device_temperature
accuracy_decimals: 1
update_interval: never
script:
- id: reading
mode: single
then:
# BAT VOLT
- output.turn_on: vbat_control
- delay: 250ms
- delay: 5s
- component.update: battery_voltage
- delay: 5s
- output.turn_off: vbat_control
# DEV TEMP
- delay: 50ms
- component.update: device_temperature
Thanks for any help!


