Resetting accumulated energy on EPIC PM and RIO EMU without PAC Control

For both the groov EPIC power monitoring module (GRV-IVAPM-3) and groov RIO EMU / Energy Monitoring Unit (GRV-R7-I1VAPM-3) there are a total of 64 points of power and energy data, where power is measured as instantaneous usage and energy is accumulated over time.
But sometimes the accumulated energy values need to be cleared and reset; maybe at a specific billing period, at the end of the day, or between shifts. This can be done with the “Read and Clear” command in PAC Control – but what if you’re not running a strategy?

The solution is to use a memory mapped endpoint with either OptoMMP or the RESTful API.
This post will focus on how to use these two options with Node-RED, but this method can be applied with C++, Python, or anything that has HTTP request support like CURL or even Postman.

If you want to read more about the Memory Mapped Protocol, the details are covered in the OptoMMP Protocol Guide (form 1465)
Configuring I/O ChannelsReading and Writing Energy Values in GRV-IVAPM-3 & GRV-R7-I1VAPM-3Read and Clear Energy Value


The first step is to determine the specific hexadecimal memory address for the channel you want to clear using this formula:

Base address for channel = 0xF01C8000 + <module> * 0x200 + <channel> * 0x8

A quick overview of each value:

  • 0xF01C8000 – The Read and Clear Area starting address, where each channel is 8 bytes, with 64 channels per module.
  • 0x200 – The hexadecimal length of one module in bytes. Using a RIO there is only one module with the index of zero, so if you’re using an EMU this can be ignored.
    (This value comes from 8 bytes * 64 channels = 512 bytes, and 512 is 0x200 in hex.)
  • 0x8 – The hexadecimal length of one channel, also in bytes.

This offsets the starting address by the module index then adds the offset of the channel index. Which channel you reset will depend on your application, and you can find a full list of each channel in either your groov Manage I/O menu or the product datasheets; and the channels are the same between both the PM module or RIO EMU. For example “Net Energy” for Phase A is channel 13 on both.

Once you get your address, for example channel 13 on module index 2 is 0xF01C8000 + (2 * 0x200) + (13 * 0x8) = 0xF01C8468, you just need to read / GET that endpoint using either OptoMMP or the REST API endpoint /api/v1/io/{device}/mmp/address/{address}.

Here’s what that would look like in Node-RED:

groov-io read node:
image

HTTP request node:

More details on how to authenticate the HTTP request call can be found in this previous forum post: Authenticating Opto API calls with HTTP request

Also, note that I am using a length of 2 in both cases – this is because the data type is float, and a float is 4 bytes – so getting two floats gets the 8 bytes that make up a single channel. You can also take advantage of this to get and clear multiple consecutive channels at once, if appropriate for your application.
For example to clear the 5 consecutive channels for net energy, positive energy, negative energy, net reactive energy, AND apparent energy with one call, just get a length of 10 and all five channels of accumulated energy will be reset.


Here is an example flow for you to try this out yourself, just remember to put in the MMP endpoint for your specific module / channel and choose your device if using groov-io, or paste in an admin-level API key if using HTTP requests.

[{"id":"77b121fc39325722","type":"inject","z":"996af57394f7db28","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":270,"y":920,"wires":[["6907acd937ffda87"]]},{"id":"6907acd937ffda87","type":"groov-io-read","z":"996af57394f7db28","device":"","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF01C8468","mmpType":"float","mmpLength":"10","mmpEncoding":"ascii","value":"","valueType":"msg.payload","itemName":"","name":"","x":440,"y":920,"wires":[["53a4fd88adef81b8"]]},{"id":"53a4fd88adef81b8","type":"debug","z":"996af57394f7db28","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":920,"wires":[]},{"id":"80d0239e1eb4d49e","type":"inject","z":"996af57394f7db28","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":980,"wires":[["1c99653c2d575fd0"]]},{"id":"1c99653c2d575fd0","type":"change","z":"996af57394f7db28","name":"","rules":[{"t":"set","p":"url","pt":"msg","to":"https://<HOSTNAME_OR_IP>/manage/api/v1/io/local/mmp/address/0xF01C8468?type=float&length=2","tot":"str"},{"t":"set","p":"headers","pt":"msg","to":"{\"apiKey\":\"<YOUR_API_KEY>\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":980,"wires":[["49312fcf77b75107"]]},{"id":"49312fcf77b75107","type":"http request","z":"996af57394f7db28","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"7e4813585b6be2bf","persist":false,"proxy":"","authType":"","x":630,"y":980,"wires":[["ebd55fbbc824a85e"]]},{"id":"ebd55fbbc824a85e","type":"debug","z":"996af57394f7db28","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":790,"y":980,"wires":[]},{"id":"7e4813585b6be2bf","type":"tls-config","name":"localhost","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":""}]

If you have any questions, or if you end up using this in one of your applications just drop a line in the thread below, and as always happy coding!

3 Likes

Bonus round!

What if you want to reset all the accumulated energy channels?
That’s a total of 21 channels; 5 for each of the three A, B, and C phases, plus another 6 for the three-phase totals… That might sound like a lot of API calls – but it’s really easy to do them all dynamically in just a few seconds using a bit of JavaScript.

I’ll have full flow imports for both RIO EMU and EPIC PM modules below; but here’s the main part of the code:

// List of starting channels and the total length of channels to read & clear
channels = [
    [13 , 5], // 13-17 = five channels in phase A
    [31 , 5], // 31-35 = five channels in phase B
    [49 , 5], // 49-53 = five channels in phase C
    [57 , 6]];// 57-62 = six 3-phase totals channels

for(i = 0; i < channels.length; i++) {
    // Calculate the address offset based on the starting channel number stored in channels[i][0], convert to hex string
    myAddress = (0xF01C8000 + (channels[i][0] * 0x08)).toString(16);

    // Determine the URL based on hostname, current address, and length of stored in channels[i][1]
    msg.url = "https://" + hostname + "/manage/api/v1/io/local/mmp/address/" + myAddress + "?type=float&length=" + channels[i][1]*2;

    // Send this HTTPS request with msg.headers and msg.url for this group of channels
    node.send(msg);
}

First I define an array that holds the starting channel number and the number of channels to read & clear. For each of the phases there are five consecutive channels, and six for the totals – but if you want to reset only a particular phase, not reset the totals, or anything else you can just edit or remove the pair of numbers associated with any group of channels as-needed.
Once the channels are set it’s a simple matter of looping through that array and calculating the MMP address for each group and sending out the URL for the RESTful HTTPS request to trigger the read & clear.

Here’s the full flow for a RIO EMU, including an inject node that must provide the hostname and an admin-level API key. I’ve included two for in this example, but make sure you edit it with your info before deploying the flow.
Also, note that this does use the same HTTPS authentication linked in the original post above: Authenticating Opto API calls with HTTP request

[{"id":"f6b548b2a30bde25","type":"function","z":"9e233840361059cc","name":"Clear Energy Totals & Phases","func":"msg.headers = {\"apiKey\":msg.apiKey};\nhostname = msg.hostname;\n\n// List of starting channels and the total length of channels to read & clear\nchannels = [\n    [13 , 5], // 13-17 = five channels in phase A\n    [31 , 5], // 31-35 = five channels in phase B\n    [49 , 5], // 49-53 = five channels in phase C\n    [57 , 6]];// 57-62 = six 3-phase totals channels\n\nfor(i = 0; i < channels.length; i++) {\n    // Calculate the address offset based on the starting channel number stored in channels[i][0], convert to hex string\n    myAddress = (0xF01C8000 + (channels[i][0] * 0x08)).toString(16);\n    \n    // Uncomment the line below for debugging\n//    node.warn(channels[i][0] + \", \" + channels[i][1] + \" = \" + myAddress);\n\n    // Determine the URL based on hostname, current address, and length of stored in channels[i][1]\n    msg.url = \"https://\" + hostname + \"/manage/api/v1/io/local/mmp/address/\" + myAddress + \"?type=float&length=\" + channels[i][1]*2;\n\n    // Send this HTTPS request with msg.headers and msg.url for this group of channels\n    node.send(msg);\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":1580,"wires":[["a90893f9c0bdd485"]]},{"id":"5d15ec958b670021","type":"inject","z":"9e233840361059cc","name":"device information","props":[{"p":"hostname","v":"hostname-or-IP","vt":"str"},{"p":"apiKey","v":"zikA9FKdp3FRdBzRyBwkBnqFq5MmyHSH","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":1580,"wires":[["f6b548b2a30bde25"]]},{"id":"93881fc5.654f3","type":"inject","z":"9e233840361059cc","name":"second device","props":[{"p":"hostname","v":"another-hostname-or-IP","vt":"str"},{"p":"apiKey","v":"4uwr3Nbx7EKvbSrVLkKoVwaN2APWAQUv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":1620,"wires":[["f6b548b2a30bde25"]]},{"id":"a90893f9c0bdd485","type":"http request","z":"9e233840361059cc","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"7e4813585b6be2bf","persist":false,"proxy":"","authType":"","x":690,"y":1580,"wires":[["7609d29856336696"]]},{"id":"7609d29856336696","type":"debug","z":"9e233840361059cc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":850,"y":1580,"wires":[]},{"id":"7e4813585b6be2bf","type":"tls-config","name":"localhost","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false}]

If you’re using an EPIC with one or more power monitoring modules use this alternate flow which also reads in an array of modules (if you just have one module, just have an array with one index in it, for example [3] for one module installed in the fourth slot).

[{"id":"f6b548b2a30bde25","type":"function","z":"0928763894a95f05","name":"Clear Energy Totals & Phases","func":"msg.headers = {\"apiKey\":msg.apiKey};\nhostname = msg.hostname;\n// modules should be an array of numbers representing one or more module indices that have power monitoring modules installed\n// ex: msg.modules = [4] or = [3, 5, 7, 8]\nmodules = msg.modules;\n\n// List of starting channels and the total length of channels to read & clear\nchannels = [\n    [13 , 5], // 13-17 = five channels in phase A\n    [31 , 5], // 31-35 = five channels in phase B\n    [49 , 5], // 49-53 = five channels in phase C\n    [57 , 6]];// 57-62 = six 3-phase totals channels\n\nfor(o = 0; o < modules.length; o++){\n    for(i = 0; i < channels.length; i++) {\n        // Calculate the address offset based on the module and starting channel number (stored in channels[i][0]) & convert to hex string\n        myAddress = (0xF01C8000 + (modules[o] * 0x200) + (channels[i][0] * 0x08)).toString(16);\n        \n        // Uncomment the line below for debugging\n    //    node.warn(channels[i][0] + \", \" + channels[i][1] + \" = \" + myAddress);\n    \n        // Determine the URL based on hostname, current address, and length of stored in channels[i][1]\n        msg.url = \"https://\" + hostname + \"/manage/api/v1/io/local/mmp/address/\" + myAddress + \"?type=float&length=\" + channels[i][1]*2;\n    \n        // Send this HTTPS request with msg.headers and msg.url for this group of channels\n        node.send(msg);\n    }\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":410,"y":120,"wires":[["a90893f9c0bdd485"]]},{"id":"93881fc5.654f3","type":"inject","z":"0928763894a95f05","name":"second device","props":[{"p":"hostname","v":"another-hostname-or-IP","vt":"str"},{"p":"apiKey","v":"4uwr3Nbx7EKvbSrVLkKoVwaN2APWAQUv","vt":"str"},{"p":"modules","v":"[3]","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":160,"wires":[["f6b548b2a30bde25"]]},{"id":"5d15ec958b670021","type":"inject","z":"0928763894a95f05","name":"device information","props":[{"p":"hostname","v":"hostname-or-IP","vt":"str"},{"p":"apiKey","v":"zikA9FKdp3FRdBzRyBwkBnqFq5MmyHSH","vt":"str"},{"p":"modules","v":"[3, 5, 7, 9]","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":120,"wires":[["f6b548b2a30bde25"]]},{"id":"a90893f9c0bdd485","type":"http request","z":"0928763894a95f05","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"7e4813585b6be2bf","persist":false,"proxy":"","authType":"","credentials":{"user":"","password":""},"x":650,"y":120,"wires":[["7609d29856336696"]]},{"id":"7609d29856336696","type":"debug","z":"0928763894a95f05","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":120,"wires":[]},{"id":"7e4813585b6be2bf","type":"tls-config","name":"localhost","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false}]
4 Likes