ESP32 + LoRa - remote 12V battery monitoring

26/07/2025



LoRa is a technology for transmitting data over long distances, which also excels in its resistance to interference or data transmission even through obstacles, where other technologies would fail. LoRa is excellent for use where we do not have WiFi or another form of internet connection and we want to transmit data. Today we will show a LoRa system using PPP communication (point-to-point) in the application of monitoring the voltage of a traction battery that powers an electric fence. The battery is standard lead-acid with a nominal voltage of 12V. This project is suitable for agricultural cooperatives, independent farmers, livestock and animal breeders who need to measure the battery voltage of their fence. It will also find use in other industries for a similar purpose.


The described LoRa devices work in pairs. One works as a transmitter and measures the battery voltage through a voltage divider, and the other works as a receiver, which after receiving the data can forward the data to the database to its own web application, which was created for the project - Batmonitor, or can also transfer data via the MQTT protocol to any IoT cloud service. Both devices have identical hardware and are mounted on the 433_LoRa_Lolin32 interconnect board designed by me.

The traction battery, the voltage of which is measured, is permanently powered by an electric fence, or rather a source (generator) of pulses, which generates short high-voltage pulses (usually from 3 to 12 thousand volts) into the fence, which discourage animals from crossing the fence and thus protect the crop from possible damage, or the animals from escaping from the enclosure, or from animals from outside that can get to them and cause great damage.

In this case, the traction battery is charged by a solar panel through a separate solar controller, which controls the entire charging process. The controller has configurable operating modes, threshold voltages. It even has a relay output, where it is possible to connect a load, either to the terminal blocks or to the USB-A outputs (only 5V). There is also a timer function for starting the load at the required time and more... This is only an additional functionality that is not necessary for the application of collecting data on the battery voltage. However, since the battery is being charged, it is possible to monitor the voltage at the battery terminals during charging and check whether the battery was charged during the day and at what voltages it moves. In other words, we will see the battery voltage even above 12.6V, which is the voltage of a fully charged battery in a quiescent state. When operating without recharging the battery, we would only see a proportionally decreasing voltage.

It used to happen in the past years that wild animals (deer, wild boars) would tear down the fence when trying to cross the fence, which resulted in the fence being driven into the ground, which drastically increased the consumption. The second factor was that if the grass grew tall enough and touched the fence, it was a problem especially during and after rain, when the grass was wet and the fence was also driven into the ground. Within 24 to 48 hours, this could discharge the battery to below 8V from a fully charged state.


In the long term, this also has a negative impact on the battery capacity due to sulfation and the like. Therefore, the battery had to be measured regularly, at least once every 2 to 3 days. In order to make life easier and automate this functionality, we will use a sensor node (LoRa transmitter based on ESP32 microcontroller) for this purpose, which will perform battery voltage measurements at regular intervals and send this data further.


Originally, I wanted to create the solution on the XIAO_LoRa board, but I didn't really like soldering the battery directly to the pads from the bottom of the XIAO board, especially since I didn't have a JST XH connector (in SMD version) with a pitch of 2.54 mm, i.e. in a similar design to the PH 2.0 version on the Lolin32 devkit. I didn't want to hard-solder the battery without the possibility of disconnection, since I always assembled the device on site. That's why I decided on the 433_LoRa_Lolin32 PCB (on the right).

Assembled XIAO_LoRa PCB and the second 433_LoRa_Lolin32 PCB (excluding OneWire and ADC1_CH5 connectors) and comparison of their sizes with a 2€ coin


The 433_LoRa_Lolin32 board includes options to connect:

  • ADC input (3V3, GND, ADC_IN)
  • OneWire sensor (3V3, GND, DATA)
  • I2C peripheral (3V3, GND, SDA, SCL) - hardware I2C
  • Solar panel (5V_IN, GND)
  • RA-02 (Semtech SX1278 - SPI bus + DIO0)
  • Lolin32 (Wemos V1.0.0)

Compared to the XIAO_LoRa board, the 433_LoRa_Lolin32 does not have a place to solder resistors for the voltage divider directly to the PCB. For this reason, it was necessary to solder them directly to the cable that goes to the battery on the transmitter side. I used the resistors that the drawer provided. When choosing, I made sure to cover the entire possible voltage of the battery that is being charged. By default, it can have up to 14.8 V (for easier calculations, let's round to 15V).


Finally, I decided on a combination of a 470K resistor for the divider input and a 100K resistor to the divider ground. This resistor ratio resulted in the divider output voltage being 5.7 times smaller than the input. The entire 3V3 range of the ESP32 and its ADC pin will not be used, with a battery voltage of 15V the divider output will be 2.63V, so there is still more than 0.6V of reserve to increase the input voltage by more than 3V, so the limit is somewhere around (above) the 18V input.

The divider's consumption itself is low tens of uA, which are drawn from the battery, in the operating range it is from 18 uA to 26 uA. At 12.6V it is a little over 20 uA, which is a negligible current that practically does not affect the durability of the traction battery, since the daily consumption (in 24 hours) will be somewhere at the level of 480 uA to 600 uA depending on the input voltage of the measured traction battery, which changes because the battery is recharged by the charging circuit during the day and sunlight, so at a given time the voltage is higher compared to, for example, at night, when the solar panel does not generate sufficient voltage for charging.


This formula is also used by the sensor node (LoRa transmitter) after measuring mV on the ADC pin to calculate the input voltage. The transmitter sends the ADC voltage and also the calculated input voltage of the divider in a predefined structure, which is also known by the receiver in order to be able to decode the data. When measuring with the ADC pin of the ESP32 microcontroller, one test measurement is performed, which has the task of eliminating noise or induced voltage, and then 5 averaged measurements are performed, the result of which is taken as the final value of the divider voltage. The input voltage of the divider is also calculated from this value.



As we can see in the block diagram of the solution, the transmitter does not send data directly to the database, but only to the receiver via LoRa technology. The receiver has a WiFi AP (access point) within range, which provides it with connectivity to connect to the internet and transfer decoded data via HTTPS protocol using the POST method to the database of its own web application or cloud IoT service (alternatively, the MQTT protocol can also be used). The placement of the receiver relative to the transmitter will therefore have priority for reliable and especially periodic data reception.

The transmitter and receiver were placed in electrical boxes with dimensions of 100x100x38 mm, including the power lithium cell. I fitted a 100x100 mm solar panel to the front cover of the boxes. This panel has a 5V output and can deliver peak power up to 3W, standard 1.2W, which is sufficient. The transmitter operates in deep sleep mode, which also puts the LoRa module RA-02 (AI-Thinker) to sleep to save energy in an inactive state. It wakes up at regular intervals only for the purpose of measuring voltage and sending data to the receiver once every 10 minutes. Given the Spreading factor of 12 and the bandwidth of 125 kHz, the airtime is somewhere around 1.2 to 1.3 seconds, which represents a duty cycle of 0.2%. The system can thus transmit up to 50 times more often to reach the duty cycle limit, which is 10%.

The total uptime of the transmitter in the active state is under 2 seconds, since the measurement takes practically a few nanoseconds and the data transfer itself takes about 1.2 seconds. The consumption in deep sleep mode for the transmitter will be somewhere above 2 mA. This is mainly due to the integrated LEDs, the permanently powered USB-UART converter CH340, powered by a step-down converter. Considering the battery charging, this consumption, which may seem high for a LoRa device, is not a problem either. If we switched to XIAO, the consumption would be somewhere around 50 uA, so maybe next time :-).


However, the receiver must be permanently powered, so it has a higher requirement for battery operation. For this reason, I reached for a 2000 mAh Li-Ion battery here because the consumption can be from 17 mA to 80 mA depending on the settings of the Lolin32 devkit, or. firmware. The Devkit can be in an active state and consume about 50 mA to check in the program loop if any data has arrived.


The second option is that the Devkit can sleep, but it has to do a few things first. The RA-02 must be set to listen (RX) mode to actively receive packets, which results in its constant consumption of 15 mA. The interrupt pin DIO0 on the RA-02 (SX1278) module can change its state to HIGH when a packet is received and can be used as an external interrupt to wake up the ESP32 microcontroller from sleep mode, which will then process the data.

But there are some pitfalls... The receiver can be in light sleep mode at most, which means higher consumption compared to the deep sleep mode of the transmitter, although it is not that dramatic and the main component of consumption will be only the LoRa module. This is precisely because when waking up from light sleep, the microcontroller continues the program where it left off (from the point where it was put to sleep) in order to immediately read the data. If it were deep sleep, the microcontroller would have to be restarted and so it would have to set up the LoRa module again, so the data would be lost or missed.



Real consumption:

I managed to calculate the consumption of the receiver for the last 3 days, during which I disconnected the solar panel, so that it would be possible to calculate the approximate consumption from the voltage drop on the battery. It comes out to 13.62 mAh, which is slightly less than was assumed according to the table values (17 to 20 mAh). This consumption includes not only the "low-power" mode, but also numerous data transmissions via WiFi to the webserver. Lower consumption compared to the estimated one will certainly please and extend the device's runtime. With such consumption, the receiver could operate for 6 days in an active state (for a 2000 mAh battery) without recharging with a solar panel. So it will easily survive several days when the sun is not shining and it is rainy or cloudy.


I did not measure the consumption of the transmitter. I just tried to attach a voltmeter a few times, mostly in the early evening, when the sun was already setting and the battery was not being recharged by the panel. I still measured 4.18 to 4.22V, so the battery was still fully charged. My modest estimate is that the consumption will be up to 5 mAh and the solar panel can compensate for this quite easily even with this smaller battery, which has 1000 mAh.

The transmitter looks identical to the receiver with two differences: in addition to the antenna, it also has wires with crimped lugs coming out of the box, which connect to the traction battery terminals under the screw. The second difference is the use of a Li-pol battery instead of a Li-Ion, which is connected identically, but also has a protection circuit. The circuit should work mainly against short circuits and overcharging.

It does not have protection against battery discharge, as I have seen for myself before, it discharged the battery to 0V. After reaching a low voltage, when the ESP32 could no longer start, it did an endless restart (brownout) and drained the battery completely in one night (maybe in just a few hours, I did not measure when this happened). The good news was that even in such a state, the electronics of the Lolin32 were able to recharge the battery and maintain its capacity.

The critical point was the location of the receiver relative to the transmitter. The transmitter was already stationary and there was a problem with indirect visibility during the first planned installation of the receiver. The transmitter and receiver were about 662 meters apart. There was no direct visibility, which is confirmed by the HeyWhatsThat Path Profiler, or UISP Design Center, calculation model. The second model is more accurate, as it allows you to adjust the height of the antennas above the ground surface relative to each other, which allows you to get a more accurate output. However, neither model takes into account buildings or trees in the view. Their calculation is relevant only to the surface as such for verification of "direct visibility".


Such placement of the transmitter and receiver resulted in inconsistency in received packets and significant variation in the signal-to-noise ratio (lower value is worse). RSSI can be evaluated as quite bad, almost insufficient (although LoRa has a margin of up to -137 dBm, maybe up to -141 dBm) with such data transmission conditions without direct visibility. Also, sometimes the receiver was not able to receive a packet, or decode it, as a result of which it was discarded.


The signal in such a placement of the transmitter and receiver passes practically almost one third of the way through the hill and near the receiver even through about 3 roofs of buildings and a large deciduous tree. In the future, it will be necessary to adjust the position of the receiver, or increase the height of the antenna on the transmitter. In the current constellation, there is a bigger problem with the SNR parameter, which represents the signal-to-noise ratio. The values should be from +20 dB to -5, which is already considered a poor-quality signal. In our case, we have SNR values that fluctuate and are close to -20 dB, which represents a very poor quality signal, practically full of noise.


When the ratio exceeds -20 dB, it is practically undecodeable signal. But -20 is only for Spreading Factor 12, for other spreading factors it is closer to 0 dB. As we can also see the SNR variation over the last hour, it changes quite a bit, which is mainly due to the fact that the signal is not caught by the receiver directly, but only from reflections. It happens that sometimes the receiver is not able to decode the packet, other times it is silent for X minutes before the receiver manages to receive data again.


As we can see from the line chart (ApexCharts), the voltage of the traction battery is logged and sent to the SQL database. I also call this web application Batmonitor. We can see that before six in the morning the solar panel starts to be active, which charges this battery and takes care of its power management. After four in the afternoon the current from the panel decreases, which reduces the voltage on the battery, only later the battery is not charged at all and during the night the voltage reaches somewhere around 12.6 to 12.5V, which means that the battery is almost fully charged even during this period.


The system can also send an email with information about the voltage status when the battery voltage is low, so it is not necessary to constantly monitor the web visualization with the graph. To prevent emails from being annoying, a maximum of 1 email will be sent in 6 hours and only if the battery voltage has dropped below 11V, which already indicates a significant discharge, when the maximum permissible discharge of a lead-acid battery is up to 10.5V and it should not drop below this value, so the notification is timely and with measurements in XX minute intervals it can warn so that the operator can inspect or measure the battery.


As for LoRa communication, the transmitter and receiver use the same preset carrier (center) frequency, bandwidth, the same spreading factor, coding rate, or most importantly, a common sync word, thanks to which I can have multiple PPP LoRa communications, which will only be logically separated by a sync word that filters the packets. Thanks to this, the receiver will only respond to packets from a specific transmitter that uses an identical sync word and ignores (or filters out) others.


In practice, it is great for example for a production version of a LoRa application and a test version, when you don't have to worry that the receiver in production would also capture your test data, even if you are working close to it. A different sync word is used, although the center frequency and bandwidth are the same. All devices will use the same frequency, bandwidth, i.e. channel, but they will be divided only logically based on a different sync word (we can also call it a different subnet). There are also specific sync words that you should avoid, for example 0x34, which is reserved for public LoRaWAN networks. In this case, you will find not a LoRaWAN network, but your own LoRa PPP (Point-to-Point) application with data that is practically public and if someone sets up the receiver appropriately and can decode the content that is being transmitted, they can see what is being sent there.


For this specific application, I use a "strict" transmitter and receiver. Of course, it is also possible to switch devices between transmitting or receiving data modes. However, they cannot receive and transmit in real time, it is always necessary to select a mode. From this point of view, we refer to such communication as half-duplex. In a similar application, reception on the transmitter could be used to receive configuration data, change parameters, e.g. the transmission frequency timer, etc. However, the receiving mode on the transmitter would have to be active, e.g. after transmitting for a certain period of time, and of course the module would consume 15 mA only on the RA-02 module. In the pursuit of a low power end-node, this is just a strict transmitter that is inactive and wakes up only at a predefined time to send information briefly, after which it goes into deep sleep mode again. Even if something is sent after its sync word, it will not receive anything because it is "deaf".


In the video below you can see the visual of the Batmonitor web application and the moment when new data is received (simulated through Postman), which automatically appear in the upper part, which points to the last value of the battery voltage and the time of receipt of this data. The data is dynamically written to a graph with a visualization of the battery voltage over time, as well as to a table below, which also lists technical data about RSSI, SNR...


Since the simulated received data contains a battery voltage below 11V, an email notification is also sent with information about a drop in battery voltage. This will help inform the operator about the need to inspect the fence, its integrity, or the need to replace the lead-acid battery. This will also reduce the chance that the battery will discharge to dangerous levels in the event of a fallen fence. The information is timely. If a different voltage divider with a different ratio were used, the identical hardware can also be used to measure voltage for 24V or 48V systems.

Also possible to use 3rd party IoT platforms where LoRa receiver can push data via HTTPS or MQTT-

You can find a project at: https://hladinomer.eu/LoRa/


© 2025 Arduino Blog - . All rights reserved - martinius96@gmail.com
Powered by Webnode Cookies
Create your website for free! This website was made with Webnode. Create your own for free today! Get started