Enphase Envoy - D7 firmware with JWT - A Different Approach

Hello Enphase owners,

In August 2021, I wrote a topic on the Home Assisant Community forum about a solution I came up with to get my Enphase Envoy working with the Energy Dashboard feature which had just been introduced at the time.

That topic had a lot of great discussions, good ideas and many opportunities to help others.
I hope to do the same thing with this topic.

Why am I starting a new topic?
On Saturday the 8th of July 2023, my Enphase Envoy software verison was remotely automatically upgraded by Enphase from D5.0.62 (5e57a9) to D7.6.175 (f79c8d).
I was not alone as many others had noticed the same thing happen to them around the same time.
This forced software upgrade had caused the native Enphase Envoy integration to no longer work for me.

I was aware that other users firmware had been automatically updated months before mine had and were reporting the same issue with the native integration, so I sort of expected it and was keeping an eye on what alternative solutions were available should it happen to me.

Here is the native integration repository that did not support D7 firmware at the time of writing this article.
homeassistant/components/enphase_envoy
Update: D7 firmware is now supported by the native integration.

Here are Enphase related HACS repositories that I am aware of (sorted by last update date):
briancmpbll/home_assistant_custom_envoy
posixx/home_assistant_custom_envoy (archived on Jul 14, 2023)
jrutski/home_assistant_envoy_d7_fw
DanBeard/enphase_envoy
jesserizzo/envoy_reader
vincentwolsink/home_assistant_enphase_envoy_installer

What I noticed when trying to understand the code in some of the repositories was that often the source of information was from https://envoy.local/production.json which returns “Content-Type: application/json”.

Example of https://envoy.local/production.json

There are also some alternative repositories which use a technology called MQ Telemetry Transport (MQTT). They operate differently by initiating an ongoing stream of data from the envoy at https://envoy.local/stream/meter/ which returns updated data every second as “Content-Type: text/event-stream”.
MQTT Repositories:
vk2him/Enphase-Envoy-mqtt-json
helderfmf/Enphase-Envoy-mqtt-json

Example of 2 seconds of data
image

I will not be discussing the MQTT solution here. Please visit the MQTT repositories and read the guides if you are interested in getting updates per second.

As you can see, there are a lot of people who are trying to fix issues found when trying to extract information from their Enphase Envoy and therefore a lot of options to try and evaluate what will or won’t work for you.

Why am I using a different approach?
I saw all of this variety of repositories as a little overwhelming and confusing, which prompted me to try and figure out how to extract ONLY the information that I want from the Envoy myself.

Previously on D5 firmware, all I was interested in was 2 data points/sensors: power consumption(kW), power production(kW).
From just those 2 sensors, I would calculate grid import/export and then record energy over time using Riemann sum integral.

1 new piece of information that is currently floating around is the Enphase Technical Brief.

The first thing I did was compare the performance of how long it takes to obtain information from the old endpoints https://envoy.local/production.json (2500ms or 2.5 seconds)
vs some of the new api endpoints
https://envoy.local/ivp/meters/readings (64ms)

For me, the results were consistently poor for https://envoy.local/production.json when compared to the much more efficient https://envoy.local/ivp/meters/readings so I decided to pursue extracting information from this endpoint instead.

What I immediately noticed was that the figures I was able to extract were

  • 2 x power in W (power production / power net)
  • 3 x energy lifetime odometer readings in Wh (energy produced /energy imported / energy exported)

It was immediately obvious to me that this data was exclusively related to both the production and consumption CT clamps.
I then assumed that perhaps the absense of any calculated data in the response is probably why the page load time is so much faster as the envoy does not have to perform and calculations before providing the data.

Here is what a CT clamp looks like
image

Every Enphase Envoy system has at least 1 CT Clamp installed for measuring the solar production and optionally have a second CT Clamp for consumption.

The consumption CT Clamp can be installed in 1 of 2 possible locations.

image

Mine is installed as load with solar production which appears to be the most common.

If your enphase envoy is set to total-consumption/load only, then I recommend following this post for load only users

Lets compare what I can see being reported at https://envoy.local/home right now:

image

Here is a sample of the information I am able to extract from
https://envoy.local/ivp/meters/readings

        "eid": 704643328,
        "timestamp": 1689894382,
        "actEnergyDlvd": 25392556.647,
        "actEnergyRcvd": 0.000,
        "apparentEnergy": 29290920.936,
        "reactEnergyLagg": 5638991.364,
        "reactEnergyLead": 58003.665,
        "instantaneousDemand": 687.208,
        "activePower": 687.208,
        "eid": 704643584,
        "timestamp": 1689894382,
        "actEnergyDlvd": 11970525.537,
        "actEnergyRcvd": 10297737.507,
        "apparentEnergy": 30839131.680,
        "reactEnergyLagg": 25562.047,
        "reactEnergyLead": 12126441.651,
        "instantaneousDemand": 892.938,
        "activePower": 892.938,

Note: Due to the 2.5 second lag/delay when getting data from production.json, it is impossible to get fully identical matching data for demonstration purposes. Please keep that in mind when comparing that it may be 1 watt different.

Looking at what I am able to get from https://envoy.local/ivp/meters/readings, I can get power production and power net.
From those 2 figures, I am able to calculate 3 additional figures: power consumption, power export, power import.

Source: /ivp/meters/readings Description W kW
value_json[0].activePower Power Production 687.208 0.69
value_json[1].activePower Power Net 892.938 0.89
template sensor calculation Power Consumption (Calculated) (production+net) 1580.146 1.58
template sensor calculation Power Export (Calculated) (production-consumption) 0.000 0.00
template sensor calculation Power Import (Calculated) (consumption-production) 892.938 0.89

I also noticed that I can get 3 lifetime energy sensors: Energy Production, Energy Import, Energy Export.
From those 3 figures, I am able to calculate an additional 2 figures: Energy Net, Energy Consumption.

Source: /ivp/meters/readings Description Wh MWh
value_json[0].actEnergyDlvd Energy Production 25392556.647 25.4
value_json[1].actEnergyDlvd Energy Import 11970525.537 12.0
value_json[1].actEnergyRcvd Energy Export 10297737.507 10.3
template sensor calculation Energy Net (Calculated) (import-export) 1672788.030 1.7
template sensor calculation Energy Consumption (Calculated) (production+net) 27065344.677 27.1

Note: You may also notice that the display at https://envoy.local/home does round this up to the nearest decimal point but this is just a cosmetic difference in the interface. My table also does this to match.

When comparing these tables with the data shown at https://envoy.local/home, all of my calculated data lines up and the math checks out.

Now I am happy with the results, the next thing I need to do is generate a 12 month JSON Web Token (JWT).

For those who aren’t aware, the new issue introduced with the D7 firmware, is that Enphase introduced a JSON Web Token (JWT) feature which is supposed to increase security.
This is done by making you login to an enphase website and generate a JWT which lasts for 12 months and then everytime you request data from the envoy, you need to provide this 408 character string to prove to the device you are authenticated to access. The JWT contains your serial number and your enphase username/email address as well as when it was issued and when it will expire.

Here is a sample of what the JWT token looks like when decoded at https://jwt.io/

This new JWT feature is what broke the native Enphase integration and why there are now HACS repositories that fill the gap that the native integration has with D7 firmware.

I figured, as the token is valid for 12 months, I will try to develop a way to automate this JWT generation in the future and focus on the more immediate issue of getting power and energy data from my Enphase Envoy instead.

There are currently 3 methods that essentially perform the same task.

  • The first method is via 2 seperate cURL commands.

You can run these cURL commands on any system that has cURL installed.

command:
curl -s -d 'user[email][email protected]&user[password]=MYPASSWORD' \
https://enlighten.enphaseenergy.com/login/login.json \
| jq -r .session_id

Result:
32CHARACTERSESSIONID

then I used my envoy serial number and that session ID in the next command to get my token.

curl -s 'https://enlighten.enphaseenergy.com/entrez-auth-token?serial_num=ENVOYSERIALNUMBER' \
-H 'cookie: _enlighten_4_session=32CHARACTERSESSIONID' \
| jq -r '.token'

Result:
408CHARACTERJWT
  • The second method is by using a python script.

You can run this on any system that has python installed.

Here is an example of me modifying the 3 inputs and then pasting it into python and the result is the 408 character token at the bottom which i have obfuscated for privacy.

Once I have generated the token, I will secure it in the /config/secrets.yaml file of home assistant by adding the line

enphase_api: "Bearer 408CharacterToken"

Note: please don’t forget to keep “Bearer “ in the front of the token or it will fail

Now that I have a 12 month token, I will start the process of setting up Home Assistant to obtain this data using the REST/RESTful integration.

rest:
  - headers:
      Authorization: !secret enphase_api
    verify_ssl: False
    scan_interval: 15
    resource: https://envoy.local/ivp/meters/readings
    sensor:

      - name: "Power Production"
        value_template: >
            {% set value = value_json[0].activePower | int(0) %}
            {% if value  <= 5 -%}
                0
            {% elif is_state('sun.sun','below_horizon') %}
                0
            {%- else -%}
                {{ value }}
            {%- endif %}
        device_class: power
        unit_of_measurement: W
        state_class: measurement
        icon: mdi:solar-panel

      - name: "Power Net"
        value_template: "{{ value_json[1].activePower | int(0) }}"
        state_class: measurement
        device_class: power
        unit_of_measurement: W
        icon: mdi:transmission-tower
        
      - name: "Energy Production"
        value_template: "{{ (value_json[0].actEnergyDlvd / 1000 | float(0)) | round(2) }}"
        device_class: energy
        unit_of_measurement: kWh
        state_class: total_increasing
        icon: mdi:solar-panel

      - name: "Energy Import"
        value_template: "{{ (value_json[1].actEnergyDlvd / 1000 | float(0)) | round(2) }}"
        device_class: energy
        unit_of_measurement: kWh
        state_class: total_increasing
        icon: mdi:transmission-tower

      - name: "Energy Export"
        value_template: "{{ (value_json[1].actEnergyRcvd / 1000 | float(0)) | round(2) }}"
        device_class: energy
        unit_of_measurement: kWh
        state_class: total_increasing
        icon: mdi:transmission-tower

This basically tells home assistant to visit https://envoy.local/ivp/meters/readings, ignore the self signed certifcate, use my 12 month token as authentication, and extract 5 pieces of information from a 5 kB reply, and do it every X seconds defined by the scan_interval.

The next thing I do is add the following 5 x template sensors to perform the calculations needed to fill in the gaps.

template:
  - sensor:
        name: Power Consumption
        state_class: measurement
        icon: mdi:home-lightning-bolt
        unit_of_measurement: W
        device_class: power
        state : >
          {{ states('sensor.power_production') | int(0) + states('sensor.power_net') | int(0) }}
          
  - sensor:
        name: Power Export
        state_class: measurement
        icon: mdi:transmission-tower
        unit_of_measurement: W
        device_class: power
        state : >
          {{ [0, states('sensor.power_production') | int(0) - states('sensor.power_consumption') | int(0) ] | max }}
          
  - sensor:
        name: Power Import
        state_class: measurement
        icon: mdi:transmission-tower
        unit_of_measurement: W
        device_class: power
        state : >
          {{ [0, states('sensor.power_consumption') | int(0) - states('sensor.power_production') | int(0) ] | max }}

  - sensor:
        name: Energy Net
        state_class: total
        icon: mdi:transmission-tower
        unit_of_measurement: kWh
        device_class: energy
        state : >
          {{ (states('sensor.energy_import') | float(0) - states('sensor.energy_export') | float(0)) | round(2) }}

  - sensor:
        name: Energy Consumption
        state_class: total
        icon: mdi:home-lightning-bolt
        unit_of_measurement: kWh
        device_class: energy
        state : >
          {{ (states('sensor.energy_production') | float(0) + states('sensor.energy_net') | float(0)) | round(2) }}

Note: Every time you modify the config/configuration.yaml file, you will have to reboot home assistant to load them at start up.

These 5 x template sensors will update the calculations at the same time that the 5 x rest sensors are updated every X seconds defined by the scan_interval.

Final result for 5 x Power sensors

Graph example

Final result for 5 x Energy sensors

Graph example (as these are lifetime figures, the increments are more subtle)

The thing I prefer about using these lifetime energy odometers is that they are direct from the Envoy and do not rely on the Reimann Sum Integration. No longer using Reimann Sum Integration will give energy dashboard more accuracy than before. For example, if I reboot home assistant, it wont miss out on any missing data.

Now, how do we get Energy dashboard to ingest this information?
We use an integration called utility_meter

I setup the following in /config/configuration.yaml

utility_meter:
  daily_energy:
    source: sensor.energy_import
    name: Daily Import
    cycle: daily
    tariffs:
      - OffPeak
      - Shoulder
      - Peak
  daily_energy_export:
    source: sensor.energy_export
    name: Daily Export
    cycle: daily
    tariffs:
      - buyback

How am I triggering the different tariffs?
I am using the following sensor to determine what tariff time it is right now.

  # Australian Ausgrid NSW Peak-shoulder-offpeak sensor defined
  # https://www.ausgrid.com.au/Your-energy-use/Meters/Time-of-use-pricing
  # Peak: 2pm - 8pm on working weekdays 1 November - 31 March;
  # Peak: 5pm - 9pm on working weekdays 1 June - 31 August
  # Off-peak: 10pm - 7am
  # Shoulder: all other times
  - sensor:
      name: TOU Period
      icon: mdi:clock-time-three-outline
      state: >
        {% set tou_period = 'shoulder' %}
        {% set n_month = now().month %}
        {% set n_hour = now().hour %}
        {% set is_summer = (n_month <= 3 or n_month >= 11) %}
        {% set is_winter = (6 <= n_month <= 8 ) %}
        {% if n_hour >= 22 or n_hour < 7 %}
          {% set tou_period = 'offpeak' %}
        {% elif ((is_summer and (14 <= n_hour <= 19))
           or (is_winter and (17 <= n_hour <= 20)))
           and (is_state("binary_sensor.workday_sensor", "on")) %}
          {% set tou_period = 'peak' %}
        {% endif %}
        {{tou_period}}

Note: this sensor.tou_period uses the workday integration.
You will need to add this integration for sensor.tou_period to work properly.

I must give credit to @Muppetteer for showing me this.

Use this view to confirm it is working the way you like it.
image

I am then using the following 3 automations to trigger what tariff I want utility_meter to attribute the consumption to depending on the current value of sensor.tou_period.

/config/automations.yaml

- alias: Tariff Shoulder
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.tou_period
    to: shoulder
  condition: []
  action:
  - service: select.select_option
    data:
      option: Shoulder
    target:
      entity_id:
      - select.daily_energy
  mode: single

- alias: Tariff Offpeak
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.tou_period
    to: offpeak
  condition: []
  action:
  - service: select.select_option
    data:
      option: OffPeak
    target:
      entity_id:
      - select.daily_energy
  mode: single

- alias: Tariff Peak
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.tou_period
    to: peak
  condition: []
  action:
  - service: select.select_option
    data:
      option: Peak
    target:
      entity_id:
      - select.daily_energy
  mode: single

Graph Example showing the values of each tariff increasing at the correct time of day as well as being reset at midnight every night.

At this point, I could add these to Energy Dashboard in their current state, however I now want to calculate the costs.

I created the following input_number helpers.

and then setup the following sensor to change the tariff price whenever sensor.tou_period changed its state.

template:
  - sensor:
      - name: Tariff Price
        unit_of_measurement: AUD/kWh
        state: >
         {% if is_state('sensor.tou_period', 'peak') %}
           {{ states('input_number.peak') | float(0) * states('input_number.discount') | float(0) }}
         {% elif is_state('sensor.tou_period', 'shoulder') %}
           {{ states('input_number.shoulder') | float(0) * states('input_number.discount') | float(0) }}
         {% else %}
           {{ states('input_number.offpeak') | float(0) * states('input_number.discount') | float(0) }}
         {% endif %}        

  - sensor:
      - name: Tariff Price Export
        unit_of_measurement: AUD/kWh
        state: >
                {{ states('input_number.feed_in') }}

Graph example
image

Now we can finally add the sensors to Energy Dashboard.
Go to Settings / Dashboards / Energy / Add Consumption

Individually add these 3 sensors, each with sensor.tariff_price for the current price
sensor.daily_import_offpeak
sensor.daily_import_peak
sensor.daily_import_shoulder

Then for Add Return use sensor.daily_energy_export_buyback to use sensor.tariff_price_export for the current price

Then add Solar Production and choose sensor.energy_production

How it should look.

After a few hours, it should start to look like this

with a table like this
image

That should do it!
Hope this helps :slight_smile:

17 Likes

You are a legend @del13r . Have you tried a shorter interval than 15 seconds?

2 Likes

Hi @HoundDog,
Thanks for that :slight_smile:

Yes, i have tried faster and the envoy handles it well, however I am trying to be mindful of the increased database storage in Home Assistant that is requried to retain the data these 10 x sensors create.
For example, if you are doing 15 seconds, thats 4 x more often than 60 seconds meaning you will need 4 x more space for the associated sensors, or 6 x more space for 10 second intervals.
I am monitoring the size of my Home Assistant database and I am starting to see it start creeping up in size. Not by a lot, but something I will keep an eye on.
Going further, I will next start working out what I can exclude from the recorder to keep the database size down.

Edit: I thought about it a little more, and now that I am getting accurate energy data direct from the envoy without using the reimann sum integration, home assistant no longer needs to be recording energy sensors as often anymore. I will keep polling the power sensors every X seconds on the original rest sensor and create a seperate rest sensor for just the energy figures that will have a higher scan_interval.

I am also aware there has been some very recent improvements made to the MQTT repositories lately.
Something to consider if you want updates per second for example.

1 Like

My envoy regularly loses connectivity (or at least stops responding) periodically. Hopefully moving to this might fix it.

Whoa, my envoy doesn’t have a CT clamp as far as I know… is it mandatory? Indeed I cannot see the difference between produced and consumed (I can’t tell beforehand how much solar power I’m using when total consumption is below the current production).

The production CT is mandatory.
Unfortunately the consumption CT is not mandatory.

Your options will depend on what model envoy you have, what country you are in, and if using single phase or 3 phase power.

Great work @del13r - thanks for the tremendous effort in putting this together. Re you point about the faster responses taking up more database storage, I exclude my 1 second reading from the recorder as the Energy dashboard that uses the 1 second data caters for it nicely. Also, thanks for adding teh links to my github addons - the one that ends in -v7 is a temporary one I’m using for testing and will disappear soon so you may want to remove that - the stable one is GitHub - vk2him/Enphase-Envoy-mqtt-json: Takes real time stream from Enphase Envoy and publishes to mqtt broker . Cheers

2 Likes

Weird, this is all I see on my enphase and, btw, the lifetime value is incorrect:

Hard to say exactly why.
I had an unrelated issue that I thought the envoy was causing, but it turned out it was actually my router had an ad blocker installed on it that was using up all the memory of the router. As soon as I uninstalled Diverson from my Asus Merlin Router, my connectivity problem went away.

Yes, I have seen other people report their lifetime odometer was reset after the upgrade to D7.
Also, that screenshot confirms that you dont have a consumption CT installed.

Thanks!

Sure thing, I’ve removed the temp repo.

1 Like

Interesting, I will investigate whether it’s possible to add it… current consumption is a figure I’d like to have.

Sorry that I’m a total noob here, but just got solar installed and trying to add it to HA (for which I’m also a noob). a couple of questions…

  1. does the REST command access homeassistant.local/ivp/meters/readings or envoy.local/ivp/meters/readings?

2. Where do I add the template for the sensors? is it in yaml? I can’t figure it out.

thanks, and more questions to follow I’m sure…

Edit:
Ok, got the sensors working, but now I can only really see the sensors work in History.

Can’t get them to work in energy. But my Energy Export is stuck on 1.4kWh. even when getting the info from the envoy. so not sure what’s going on there.

Hi @kramer

This will be true if the sun has gone down or it is in the middle of the night as there is no more excess solar to export to the grid.

Energy dashboard will only update 24 times a day, if that’s what you mean by stuck?

If you want a dashboard that updates every X seconds, you can try this by setting up a new custom dashboard and setup some gauges

This is how I do it with the built in gauges.

type: horizontal-stack
cards:
  - type: gauge
    entity: sensor.power_consumption
    max: 7500
    severity:
      red: 1
  - type: gauge
    entity: sensor.power_production
    max: 7500
    severity:
      green: 1
type: horizontal-stack
cards:
  - type: gauge
    entity: sensor.power_import
    max: 7500
    severity:
      red: 1
  - type: gauge
    entity: sensor.power_export
    max: 7500
    severity:
      green: 1

image

This next option is possible via a HACS frontend addon called power-flow-card-plus which will use the live power figures in kW to show a live dashboard.

type: custom:power-flow-card-plus
entities:
  grid:
    entity:
      consumption: sensor.power_import
      production: sensor.power_export
  solar:
    entity: sensor.power_production
  home:
    entity: sensor.power_consumption

Happy with the result

It accesses envoy.local, or whatever the private IP address is for your envoy on your home network.
The reason why I use envoy.local in my examples is some people might not use dhcp reservation on their router, so this envoy.local URL should still work if the ip address changes every week due to dhcp renewal.

Edit: aah, I finally saw what confused you and have updated the guide. I must have been in a rush and missed this typo. Thanks.

Yep, I’ve now named that to
sensor.power_consumption
however you will definitely need to get a consumption ct clamp installed first.

Hi, I am not being able to get values from my system to HA. logger does not show me any errors.
I generated a new code pasted in secrets.yaml, added rest code and template to configuration.yaml. is this the correct way?

Sounds right. Did you reboot home assistant to make home assistant load them? I might not have made that step clear. Sorry if that is the case.

Also, instead of envoy.local, try using the private ipv4 address like 192.168.1.114 or whatever it is on your network.

Same result. Yes, I did restart the full HA.

Also, when you try to access the envoy.local links using a browser do you get error like below? Could that be the reason that its blocking rest commands to enter the auth key?

Ok thanks.
Are you seeing any messages in the home assistant core log?

Occasionally you might see this for example:

Logger: homeassistant.components.rest.data
Source: components/rest/data.py:116
Integration: RESTful (documentation, issues)
First occurred: July 21, 2023 at 11:52:55 PM (1 occurrences)
Last logged: July 21, 2023 at 11:52:55 PM

Timeout while fetching data: https://192.168.1.114/ivp/meters/readings