Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Place code snippets and demo code here
Post Reply
lyizb
Posts: 259
Joined: Fri Feb 12, 2021 8:23 pm
Has thanked: 128 times
Been thanked: 98 times

Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by lyizb »

It took a great deal of head-scratching, but I got the ESP32-C3Supermini running Annex to talk through the RS485 RTU module to a PZEM-017 to monitor Volts, Amps, Watts, and Watt Hours--with the wiring suggested by the Annex32 manual, and about 90% of the code from the example in the manual.
PZEM-017.jpg

Code: [Local Link Removed for Guests]

Hardware: 

8-AA nominal 12V battery pack, several years old and only starting with a little over 10V
48-LED 12V-48V automobile interior light
PZEM-017
50A shunt
RS485 RTU module (as shown in the Annex manual)
ESP32-C3Supermini
Following the wiring on the back of the PZEM-017 and in the Annex32 manual, I connected battery, LED, shunt, PZEM-017, RS485RTU module, and C3Supermini (using pins 3,4, and 2).
PZEM-017 project2.jpg
Here are the registers the PZEM-017 provides: Volts (16-bit), Amps (16-bit), Watts (32 bits), Watt Hours (32 bits)
PZEM-017 registers.jpg
Here is the code, mostly from the manual. I changed the modbus.requestRTU command to read 6 16-bit words starting from register 0.

Code: [Local Link Removed for Guests]

' RS485RTU.bas ' with PZEM-017, use RS485 to get voltage/current readings
  
READ_INPUT_REGISTER = 4
SERVER_ID = 1
onWgetAsync modbus_received 'set the event handler function
modbus.setupRTU 4,3,2 ' pins; by default is 9600,N,8,1

do
  modbus.requestRTU 1234, SERVER_ID, READ_INPUT_REGISTER, 0, 6 ' 6 bytes from reg 0
  pause 3000
loop

end

modbus_received:
  r$ = WGETRESULT$
'  wlog r$,ramfree(1)
  token$ = word$(r$, 1) 'extract the token (first word)
  if (token$ ="1234") then  ' if the token correspond to the read request
    if (word$(r$, 2) <> "Error") then  'if is not an error
      'assemble the 2 bytes in one 16 bits word
      Volts$ = "&h"+ word$(r$, 5) + word$(r$, 6) 
      Amps$ = "&h"+ word$(r$, 7) + word$(r$, 8)
      Watts$ = "&h"+ word$(r$, 11) + word$(r$, 12)+ word$(r$, 9) + word$(r$, 10)
      WattHours$ = "&h"+ word$(r$, 15) + word$(r$, 16)+ word$(r$, 13) + word$(r$, 14)
      '     volts           amps            watts          Watt hours
      wlog val(Volts$)/100,val(Amps$)/100,val(Watts$)/10,val(WattHours$)
    else
      wlog r$
    end if
  endif
  return
The result: every 5 seconds the program logs volts, amps, watts, and watt hours. It's now been running for about an hour and the LED has used 4 watt hours.

This is just a test to understand the wiring and to get the code working. Next I plan to hook it up to a 12V 50A LiFePO4 battery and run a 300W inverter and 200W 120V heater off of it, running until the battery shuts off. Then, with a bigger shunt, a 48V 100A LiFePO4 battery running a 2K 120V inverter and a 9000BTU mini-split heat pump.

And I plan to add a web page, and also to log readings to my house data accumulator (a Raspberry Pi 5).

Fun to make progress, and thanks ever so much for Annex, which ultimately made easy what I thought would be hard.
You do not have the required permissions to view the files attached to this post.
Last edited by lyizb on Mon Mar 10, 2025 2:04 pm, edited 1 time in total.
User avatar
PeterN
Posts: 704
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 316 times
Been thanked: 391 times
Contact:

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by PeterN »

I had a problem with wgetasync not freeing the memory used.
This seems to be specific to the ESP32-C3 single core processor when using Annex32 versions 1.70.3 to .5.
CiccioCB is working on this problem and pointed out a workaround.
He suggested using the command line a=PING(" ") within the callback routine.
This seems to clean up the memory.

Good luck
Helmut_number_one
Posts: 187
Joined: Fri Dec 09, 2022 10:03 am
Location: Flensburg Deutschland
Has thanked: 132 times
Been thanked: 28 times

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by Helmut_number_one »

Thank you Peter,
a=ping(192.168.1.123)
In the code above: where does this line have to go?
And what would a return? true or false
Do you then have to do something with a?
User avatar
cicciocb
Site Admin
Posts: 2871
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 603 times
Been thanked: 2054 times
Contact:

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by cicciocb »

[Local Link Removed for Guests] wrote: [Local Link Removed for Guests]Mon Mar 10, 2025 11:18 am Thank you Peter,
a=ping(192.168.1.123)
In the code above: where does this line have to go?
And what would a return? true or false
Do you then have to do something with a?
This is a little trick I discovered when trying to fix this issue.
It Is valid only for the esp32-c3 and must be put inside the event OnWgetAsync, like in the example shown below (taken from the help):

Code: [Local Link Removed for Guests]

ONWGETASYNC answer_done
WGETASYNC("www.fakeresponse.com/api/?sleep=5", 80)
For i = 0 to 10000
  ' a kind of sleep just to demonstrate that the code continue to run
  Print i
Next i
Wait
answer_done:
a=ping(" ") 'this is a dummy function, just permit to clean the memory allocated by WGETASYNC; the result must be ignored
Print WGETRESULT$
Return
lyizb
Posts: 259
Joined: Fri Feb 12, 2021 8:23 pm
Has thanked: 128 times
Been thanked: 98 times

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by lyizb »

I have a question from the manual:

Code: [Local Link Removed for Guests]

Here's a received message corresponding to the request MODBUS.REQUEST 4567, 1, 6, 3, 1:

4567 01 06 00 03 00 01

As in the previous example, the message starts with the token used in the corresponding request (4567) 
followed by the serverID (01), the functionCode (06) and the data (00 03 00 01).
This suggests that the data begins at the 4th word ("00"). But the sample code in the manual has the data beginning at the 5th word, and this works for what the PZEM-017 is returning.

For instance, when I power the circuit but don't turn on the LED, I get this return:

1234 01 04 0C 04 2F 00 00 00 00 00 00 00 00 00 00

The code calculates the voltage as 10.71 volts, which is 4*256=1024+47=1071/100 yields 10.71 volts (the sum of hex word #5 times 256 and hex word #6 divided by 100).

So is the manual right in that instance (for that particular modbus request #6), or should it be said that the data begins in word #5--or does it depend on which modbus request is used--in my case, request #4 (READ_INPUT_REGISTER)?
User avatar
cicciocb
Site Admin
Posts: 2871
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 603 times
Been thanked: 2054 times
Contact:

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by cicciocb »

[Local Link Removed for Guests] wrote: [Local Link Removed for Guests]Mon Mar 10, 2025 2:26 pm I have a question from the manual:

Code: [Local Link Removed for Guests]

Here's a received message corresponding to the request MODBUS.REQUEST 4567, 1, 6, 3, 1:

4567 01 06 00 03 00 01

As in the previous example, the message starts with the token used in the corresponding request (4567) 
followed by the serverID (01), the functionCode (06) and the data (00 03 00 01).
This suggests that the data begins at the 4th word ("00"). But the sample code in the manual has the data beginning at the 5th word, and this works for what the PZEM-017 is returning.

For instance, when I power the circuit but don't turn on the LED, I get this return:

1234 01 04 0C 04 2F 00 00 00 00 00 00 00 00 00 00

The code calculates the voltage as 10.71 volts, which is 4*256=1024+47=1071/100 yields 10.71 volts (the sum of hex word #5 times 256 and hex word #6 divided by 100).

So is the manual right in that instance (for that particular modbus request #6), or should it be said that the data begins in word #5--or does it depend on which modbus request is used--in my case, request #4 (READ_INPUT_REGISTER)?
The result depends on the module used so the example in the help chapter refers to the module I used at that time for testing.
So this is very module specific, even if "globally" it are very similar between different modules.

In your case, looking at the spec that I found here , looking at the answer described in the document :
image.png
Considering that you asked for 6 registers (and not 6 bytes in this case)

Code: [Local Link Removed for Guests]

modbus.requestRTU 1234, SERVER_ID, READ_INPUT_REGISTER, 0, 6
the result seems coherent as you have

Code: [Local Link Removed for Guests]

1234 01 04 0C 04 2F 00 00 00 00 00 00 00 00 00 00
where 0C means 12 bytes = 6*2 then 6 couples of bytes representing each unit (voltage, current, ..)
You do not have the required permissions to view the files attached to this post.
lyizb
Posts: 259
Joined: Fri Feb 12, 2021 8:23 pm
Has thanked: 128 times
Been thanked: 98 times

Re: Monitor 12V(+) battery with PZEM-017 and C3SuperMini

Post by lyizb »

A pretty trivial example, but here's a web page:
PZEM-017 battery status.jpg

Code: [Local Link Removed for Guests]

' RS485RTU.bas ' with PZEM-017, use RS485 to get voltage/current readings
  
READ_INPUT_REGISTER = 4
SERVER_ID = 1
onWgetAsync modbus_received 'set the event handler function
modbus.setupRTU 4,3,2 ' pins; by default is 9600,N,8,1
pin.mode 1,output     ' switch mosfet to power circuit
pin(1)=1
flagOn=1
oldVolts$=""
oldAmps$=""
oldWatts$=""
oldWattHours$=""
sp$ = string$(5, "&nbsp;")
pause 2000

do
  modbus.requestRTU 1234, SERVER_ID, READ_INPUT_REGISTER, 0, 6 ' 6 bytes from reg 0
  pause 5000
loop

end

modbus_received:
  aa=ping(" ") 'this is a dummy function, just permit to clean the memory allocated by WGETASYNC; the result must be ignored
  r$ = WGETRESULT$
'  wlog r$,ramfree(1)
  token$ = word$(r$, 1) 'extract the token (first word)
  if (token$ ="1234") then  ' if the token correspond to the read request
    if (word$(r$, 2) <> "Error") then  'if is not an error
      'assemble the 2 bytes in one 16 bits word
      Volts$ = "&h"+ word$(r$, 5) + word$(r$, 6) 
      Amps$ = "&h"+ word$(r$, 7) + word$(r$, 8)
      Watts$ = "&h"+ word$(r$, 11) + word$(r$, 12)+ word$(r$, 9) + word$(r$, 10)
      WattHours$ = "&h"+ word$(r$, 15) + word$(r$, 16)+ word$(r$, 13) + word$(r$, 14)
      '     volts           amps            watts          Watt hours
      if flagOn or val(Amps$)>0 then
        wlog val(Volts$)/100,val(Amps$)/100,val(Watts$)/10,val(WattHours$)
      endif
      if val(Amps$)=0 then ' if no current, log only once
        flagOn=0
      else
        flagOn=1
      endif
      flagChange=0
      if oldVolts$<>Volts$ then
        oldVolts$=Volts$
        flagChange=1
      endif
      if oldAmps$<>Amps$ then
        oldAmps$=Amps$
        flagChange=1
      endif
      if oldWatts$<>Watts$ then
        oldWatts$=Watts$
        flagChange=1
      endif
      if oldWattHours$<>WattHours$ then
        oldWattHours$=WattHours$
        flagChange=1
      endif
      if flagChange then
        cls
        a$ = ""
        A$ = A$ + |<!DOCTYPE html><html><body>|
        A$ = A$ + |<center>Battery Status<br><br>|
        A$=A$+|<table border=1 style="background-color:#d0d0d0"><tr>|
        j1$=str$(val(Volts$)/100)
        a$ = a$ + "<td>Voltage</td><td>&nbsp;" + j1$ + "&nbsp;</td></tr><tr>"
        j2$=str$(val(Amps$)/100)
        a$ = a$ + "<td>Amps</td><td>&nbsp;" + j2$ + "&nbsp;</td></tr><tr>"
        j3$=str$(val(Watts$)/10)
        a$ = a$ + "<td>Watts</td><td>&nbsp;" + j3$ + "&nbsp;</td></tr><tr>"
        j4$=str$(val(WattHours$))
        a$ = a$ + "<td>Watt Hours&nbsp;</td><td>&nbsp;" + j4$ + "&nbsp;</td></tr>"
        A$ = A$ + |</table></center></body></html>|
        html a$
        autorefresh 10000
      endif
    else
      wlog r$
    end if
  endif
  return
You do not have the required permissions to view the files attached to this post.
Post Reply