Monday, September 25, 2023

Solar shack battery monitor mark 2 - simpler is better

After the failure of my ESP32 web server to monitor battery voltage I reverted back to a simple system with an Arduino Nano reading analog voltage from a 5k-1k resistor divider to bring 15V down to 3V. Every second it takes a reading and sends on serial over USB to a Linux computer.

I love these little nano boards - very cheap and easy to use. The code is very simple, here's a screen shot from my favourite Arduino environment PlatformIO.

The battery (a 105Ah flooded lead acid battery charged from a 100W panel), is charged for some of the day and over night runs the computer and any radios I have on for WSPR. Here's a plot from 5pm until the next 5pm.

It's a deep cycle battery so I think there's a fair bit of voltage drop to go if required.

At the moment I'm taking a sample every second which is clearly way too much and rather hammers the spreadsheet. I'll back it off to every 10 minutes.

I mentioned that I like PlatformIO and one reason is that it's so fast compared to the Arduino IDEs. Here is a screen recording of building and uploading that little sketch above.

This is not sped up! The Arduino IDE v2 is better than v1 but still not a patch on Visual Studio Code with PlatformIO. There's more to set up and learn but it's worth it.

There is one complexity that needs to be solved. Reading ASCII voltage readings from the serial port and writing them to a file is more complex than it should be. I think the problem is that I'm using python 3 which defaults to unicode. I've got it working but it's more complex than it should be.

Writing serial data from Arduino to a file

I ran into a few puzzling issues getting this simple thing to work. Firstly, writing to the file is buffered quite a bit so nothing appears for some time. I thought it wasn't working. In the end I use this command to write the lines written by the voltage logger (now only once a minute) to a file with a prepended timestamp.

nohup tail -f /dev/ttyUSB0 | gawk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0 }' > voltMinutes.txt&

The file looks like this:

2023-09-26 11:29:07 14.46

2023-09-26 11:29:57 14.40

2023-09-26 11:30:57 14.37

2023-09-26 11:31:57 14.51

2023-09-26 11:32:57 14.46

2023-09-26 11:33:57 14.54

2023-09-26 11:34:57 14.49

2023-09-26 11:35:56 14.46

2023-09-26 11:36:56 14.46

2023-09-26 11:37:56 14.49

2023-09-26 11:38:56 14.46

2023-09-26 11:39:56 14.29

2023-09-26 11:40:56 14.49

The nohup and ampersand mean that I can kick it off and disconnect from the terminal and it keeps going.

A problem with this simple approach is that Linux buffers the writes to the file so tailing the output file shows results from hours before. A possible solution I found is to install the expect package and then use the unbuffer command like this:

nohup tail -f /dev/ttyUSB0 | unbuffer -p gawk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0 }' >> voltMinutes.txt &

This isn't quite working as I expect so I'll continue to search for the simplest and working solution.


Here's a neater solution. There's a ts (timestamp) command in the moreutils package. Install that and then:

ts </dev/ttyUSB0 >>received.log &

produces a file like this:

Sep 27 07:49:48 12.26

Sep 27 07:51:48 12.23

Sep 27 07:52:48 12.23

(There are time stamp format options).

Here's a Gnuplot showing several days. The last day has been very dark with lots of rain.


set terminal png size 800, 600
set title "Solar Battery Voltage"
set output "Volts.png"
set xlabel "DATE"
set ylabel "Volts"
set xdata time
# Sep 28 09:19:21
set timefmt "%b %d %H:%M:%S"
set xtics format "%d/%m %H"
set datafile separator ","
plot "volts.log" using 1:2 title 'Volts' with points smooth bezier

Here's the Arduino Nano code. (Note that I use PlatformIO these days). I found that if I sent serial with println() I get an extra timestamp line from ts so I manually add the \n.

#include <Arduino.h>

// divide the ADC by this to get volts (with my divider)
const float kVoltageFactor = 35.0;

void setup() {
// put your setup code here, to run once:
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
// analogReadResolution(10);

void loop() {
// put your main code here, to run repeatedly:
int pin = 0;
int voltageRaw = analogRead(pin);
float voltage = voltageRaw / kVoltageFactor;
String voltageString = String(",") + String(voltage) + String("\n");
// briefly flash the LED at each read
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(50); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(950); // wait for a second
//delay(1000UL * 59UL); // wait for a minute

No comments: