Thursday, July 5, 2018

PZEM-016: Another Chinese Power Monitor

I really like the PZEM-004 that I picked up just to see what it could do <link>, in fact I built the monitor and control system for my water heater with it <link>. This thing has really taught me a lot about water heaters and how much money my solar water heater actually saves me.

That led me to look at other devices available from China that could actually help out around the house. Well, the same manufacturer makes a different model, the PZEM-016 that does much the same, but even better for my purposes. This one does the measurements for me, but also has an RS485 serial interface so I can watch more than one of them.


OF COURSE I took it apart:


It's built extremely similarly to the 004 model I already wrote about. The big difference, of course, is that this one doesn't have any display. That's OK, I'll take care of that part. But first I have to actually talk to the darn thing. I've already worked with RS485 using an arduino when I put together my pool controller <link>, so it isn't too strange, but it still intimidates me a bit.

I dug around in my boxes of left over pieces and found an adapter to go from TTL serial to RS485 and combined it with an arduino and started working on getting it going.

Naturally, it was a pain in the bottom to talk to the device. I emailed the manufacturer when I first ordered the devices (yes, I got five of them) for as much documentation as they could provide. They sent me a manual that was actually pretty easy to read and understand. In it they said that the device was Modbus compatible, and that really impressed me. If you look up Modbus, it is an industrial protocol for machines. It can control a large number of devices in an industrial setting and should have code that I can leverage to get this working.

Right ! Things never ever work out that easily. I did find protocol libraries that I could use on the arduino, but have you ever looked at Modbus? I thought the documents for ZigBee were obtuse, these are where ZigBee learned how to do it.

Frankly, I chucked the idea of using a Modbus library down the tubes pretty quickly in favor of a much simpler approach. When I looked at the messages that the PZEM 016 actually used, there were only a few of them and the responses were pretty much canned and easy to work with. I just sat down and put together a message to read the data from the device and sent it to see what happened.

No, it didn't work first try. No, it didn't answer on the second or third try either. One has to understand that if you don't get the message exactly right, you'll never get a response from the device. In my case, I was messing up the checksum. Fortunately, in the last couple of years there have been many sample checksum implementations and online calculators implemented. I tried a couple with my data and hard coded the actual message I needed to send, that actually got me a response.

Then I spend a couple of afternoons working the kinks out of getting the response and using the checksum to validate the message. Once I could send and receive a single message reliably, I was ready to start adding code. Naturally, it encountered problems. It seems that short messages would cause checksum problems ... sometimes. So much for the idea that a computer does the same thing each time. I worked at this for quite a while without resolution. Here's a couple of screenshots of the arduino serial interface. The first one is using a message that requires a short response and the second is a longer message. The interesting thing is the accumulators I stuck in the code to count the checksum errors.



The short response has 34 checksum errors out of 100 tries while the second longer response has only one out of 100. Same code and timing in both cases...sigh.

For the rest of my experiments I used messages that required a long response. Eventually, I implemented code to handle reading the values from the device, changing the address of the device, resetting the energy (kWh) accumulator on the device, etc. I actually had it working pretty well.

Then I outfoxed myself and decided to modify the code to handle more than one device on a single pair of wires. This was actually easier than I thought it would be. The idea is that each device on an RS485 line has a different address, and you address the one you want to control or receive values from. In theory I could have several of these being read by a single arduino and monitor a lot of things around the house.

But, that would mean unique addresses and unusual delays and strange things happening. Gritting my teeth to the point of pain, I dug into it.

One of the initial problems I ran into was not knowing what would be coming after I sent a request out on the line. Sure, it should be predictable, but it never works out that way. When one does a serial read, you can get back something that is expected and just follow the bytes until you reach the end. We've all seen this: a protocol has a leading byte to tell you the beginning of the response, then a length to tell you how many bytes are to follow. You simply get the length and then read until the rest of them come in.

Suppose that is the last byte you see though. Or suppose there's a burst of noise on the twisted pair and you get about a thousand more? Obviously you can't rely on a length in the incoming characters until you can verify the integrity of the message by reading the checksum way out there at the end of the message. Let's make this problem even nastier, RS485 lines can ring. That means that you can get strange interference on the line that will mess up any message that is running around on it. You have to allow for settling times and such after messages fly around.

The problems are not insurmountable though, industry uses these protocols and devices every day. If they can make them work, I can get them to work well enough for my place. And, I think I did. Here's the code I came up with to read a message coming in:

int getit(){
  memset(rxbuf, 0, sizeof(rxbuf));
  int i = 0;
  if (digitalRead(debugPin)==LOW)
    Serial.println(F("Data from port:"));
  unsigned long startTime = millis();
  unsigned long lastChar;
  boolean startchecking = false;

  while(millis() - startTime < readTimeout){
    if(pMon.available() > 0){
      rxbuf[i++]=pMon.read();
      if (digitalRead(debugPin) == LOW){
        print8Bits(rxbuf[i-1]);
      }
      delay(1);
      lastChar = millis();
      startchecking = true;
    }
    if (startchecking && millis() - lastChar > 4)
      break;
  }
  if(i == 0){
    noResponse++;
    if (digitalRead(debugPin == LOW))
      Serial.println(F("NONE"));
    return(0);
  }
  if (digitalRead(debugPin) == LOW)
    Serial.println();
  uint16_t calcCrc = makeCrc(rxbuf, i-2);
  uint16_t rxcrc = word(rxbuf[i-2], rxbuf[i-1]);
  
  if (rxcrc != calcCrc){
    Serial.println(F("Checksum error"));
    if (digitalRead(debugPin) == LOW){
      Serial.print(F("Calculated "));
      print8Bits(highByte(calcCrc));
      print8Bits(lowByte(calcCrc));
      Serial.println();
      Serial.print(F("Received   "));
      print8Bits(highByte(rxcrc));
      print8Bits(lowByte(rxcrc));
      Serial.println();
    }
    checkSumErrors++;
    return(0);
  }
  return(i);
}


What I do is set a one second timer around the entire message and when a single character comes in, I set a intercharacter timer of four milliseconds for the next character. This way the most I can wait for a message is a second and then if it just stops mid message, I only waste four milliseconds before I give up and try again from the beginning. This works really well to cut the necessary time to read a message down as well as notice a failure quickly. I was pretty proud of this piece of code until a little later.

When I tried to send messages quickly, there were problems. One response would pile up on top of another from a different device. This required a delay between devices so things could quiesce a bit. Long painful experience has shown me that setting delays in code is just programming around a problem rather than solving it, but sometimes you just have to wait for other devices to stabilize before moving on. This is one of those cases because the devices on the line don't send you a ready message.

One other thing you'll notice in the code above is that I found a new debugging tool, an input pin. I use pin 3 on the arduino as a digital input pin and check to see if it is grounded before putting debugging messages out. If it's running and I see something I don't understand, just ground pin 3 and the debugging messages come out to the screen. I really wish I had thought of this about eight years ago.

The other pin I use for a special purpose is pin 2. If it's grounded I go into a special piece of code that allows me to change the address of a device. All the devices come addressed as one initially and I have to change them to something else to actually use them. So, if I add a device to the line, and boot the arduino, the first thing it does is check for a device at address one, and when it finds one, it tells me to change the address and hangs up in a hard loop.

I plug in a wire to pin 2 and then boot the arduino again. It senses the pin and goes into special code to allow me to readdress the device. This is also a good time to recompile and add another device to the device table. Yes, I took the cheap way out. I add a device in the code by changing a number and entering the default values as well. It just wasn't worth the time to come up with a more elegant solution for something that will happen six or seven times ... ever.

Basically, I'm done with being able to control and read this device, but that is the beginning of a greater project I've been thinking about for a long time. I'm going to put several of these in an enclosure and measure the power usage of my major appliances. The 2 AC air handlers and the 2 AC compressors are big users of power and I want to track their operation. The stinking dryer that has cost me so much money because people keep using it is another one <link>. I messed up though and only ordered five of the devices. I need one more for the kitchen stove. It'll be on order tomorrow probably.

Before the more astute of my readers comment on how an appliance that uses both 110 and 220 like a dryer or kitchen stove can't be measured with a single current transformer because one leg is referenced to neutral, go look at this post from quite a while back where I found a way <link>. Yes, Dorothy, there is a way to do it.

Here's a little sample of two of the devices monitoring my light that has two bulbs in it. I used the exact same setup when I worked on the 004 version.


I have the debugging pin grounded (that is so cool) and I'm reading the light with one bulb turned on. Notice the difference in the 'Energy' value? One of them has been recording usage longer than the other. They both read basically the same thing for the other measurements because they are hooked to the same thing.

Adding another monitor to the stuff is simple, Wire it in, ground pin 2, reboot, change the address to 4, change a value in he code, recompile and away it goes. Since this is one of those devices that will just run for a long time without changes, that should be fine.

Here's a picture of the setup I used to get it going.


The board in the upper left is the RS485 converter and the CTs are off on the right hand side.

Next, mount them in something, wire them up and hook the CTs around the power lines in the mains panel. I will add an XBee to the arduino and send messages back to the house monitor just like I did for the water heater. I fully expect to add at least one solid state relay (SSR) to the project to make sure the dryer is under my complete control. No more running the dryer during peak period for me.

Have fun.

21 comments:

  1. Great article. Nice to see you did the hard work already to create the software for the pzem-016 modbus connection to Arduino. Received a pzem-016 today. Appreciate link to your software to have a good start in making my own application (ESP/Arduino). Or make available as Github project?

    ReplyDelete
  2. great job, I immediately buy one ...

    ReplyDelete
  3. Replies
    1. I don't want to put it up on github yet. It may be too full of bugs for most people to use. I'll email it to you if you drop me a note.

      Delete
  4. Hi Dave,

    Any chance that you could help me out in the code here? Still struggling with the send/receive cmds and their CRC... If you "broke stone" already I would appreciate help. I have 3 PZEM-016 at hand and would like to monitor tri-phase here at home.
    Many thks in advance!

    ReplyDelete
    Replies
    1. Yes, I got that working. Here's the crc code I used"

      // Compute the modbus crc (hex, not ascii)
      // it's important that the incoming is an unsigned 8 bit integer
      uint16_t makeCrc(uint8_t *buf, int len){
      uint16_t crc = 0xFFFF;

      for (int pos = 0; pos < len; pos++) {
      crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc

      for (int i = 8; i != 0; i--) { // Loop over each bit
      if ((crc & 0x0001) != 0) { // If the LSB is set
      crc >>= 1; // Shift right and XOR 0xA001
      crc ^= 0xA001;
      }
      else // Else LSB is not set
      crc >>= 1; // Just shift right
      }
      }
      // now reverse the bytes so it's easy to use.
      return (crc << 8) | (crc >> 8 );
      }


      Have at it.

      Delete
    2. Crap, sorry about the tabs. You'll get it though.

      Delete
    3. Thks Dave I've ended up doing things quite easier instead of writing all the code. I've noticed that the module runs on Modbus-RTU so installing this in the Arduino and making a few tweaks made it work at 1st attempt! with only a few lines of code I can get all the readings. Moving now on to make this whole thing over an ESP. Thks for the reply

      Delete
    4. I briefly played with one version of Modbus-RTU, but it turned out to be a pain. Which one did you use? I'd like to take a look.

      Delete
    5. I am wondering why the paper that came with the monito says High byte then Low byte for the CRC. I guess the Arduino UART must be INTEL order.

      Delete
    6. I've seen this so often I just ignore it anymore. It's easy to code around and you always have to do something anyway.

      Delete
  5. Got it all running on a Esp32 with multiply slaves: http://evertdekker.com/?p=1307

    ReplyDelete
  6. Hey Dave,

    Yes the libraries have a lot in there but you just want what's important here for the PZEM016... you don't have to worry about CRCs etc... just send the msg and process the result. I'm not at home but the code is simple, just as per what I'm pasting below... I?ll double check if there's no issue when I get home... if I found any error I'll drop another message here:

    #include

    ModbusMaster node;

    void setup()
    {
    Serial.begin(9600);
    node.begin(1, Serial);
    }


    void loop()
    {
    uint8_t result;

    result = node.readHoldingRegisters(0, 10);

    if (result == node.ku8MBSuccess)
    {
    voltage = result[0] / 10;
    current = (result[1] + result[2] * 65536) / 1000;
    power = (result[3] + result[4] * 65536) / 10;
    energy = (result[5] + result[6] * 65536);
    freq = result[7] / 10;
    powfac = result[8] / 100;
    alrm_stat = result[9];
    }
    }

    ReplyDelete
  7. sorry Dave only this weekend I had a bit of time to come back… There were a few mistakes above… here's my 2 cent of code as mentioned… (now the running one.. no mistakes ;)…):

    #include

    ModbusMaster node_ph1;

    void setup()
    {
    Serial.begin(9600);
    node_ph1.begin(0x01, Serial);
    }

    void loop() {
    uint8_t result;

    result = node_ph1.readInputRegisters(0, 10);

    if (result == node_ph1.ku8MBSuccess)
    {
    float voltage = node_ph1.getResponseBuffer(0) * 0.1;
    float current = (node_ph1.getResponseBuffer(2) * 65536 + node_ph1.getResponseBuffer(1)) * 0.001;
    float power = (node_ph1.getResponseBuffer(4) * 65536 + node_ph1.getResponseBuffer(3)) * 0.1;
    uint16_t energy = (node_ph1.getResponseBuffer(6) * 65536 + node_ph1.getResponseBuffer(5));
    float frequency = node_ph1.getResponseBuffer(7) * 0.1;
    float power_fact = node_ph1.getResponseBuffer(8) * 0.01;
    uint16_t alarm_status = node_ph1.getResponseBuffer(9);
    }
    }

    ReplyDelete
  8. Hello, I want to do almost the same as you, but I was asking if it works with more than 2 phases if the power supply of my 3 PZEM-016 are supplied by the same 230V?!?? I see the sensor is routed to the Neutral ?!

    ReplyDelete
    Replies
    1. The power that runs the device is converted to DC, so it should work fine. I haven't tried it that way, but there doesn't seem to be any reason it won't work.

      Delete
    2. I ordered my PCB with 3 PZEM-016, My prototype on my desktop is functionnal. Wait and see :P
      If someone is interested here is link to hackaday.io :
      https://hackaday.io/project/169037-home-power-monitoring

      Delete
    3. I see you converted it to serial and got rid of the 485. Not a bad idea. Does make it a bit harder to talk to multiple devices, but avoids all the timing stuff of the protocol.

      These devices are nice and I'm considering getting a couple to replace some monitors I currently use. The non-invasive part is nice because it really simplifies the installation. The price ain't bad either.

      Delete
  9. Hi. Maybe some one now how can i reset energy on this power meter ?

    ReplyDelete
    Replies
    1. It comes with instructions on how to do that.

      Delete