Texas Electricity Grid Monitor

TexElecGrid

I created a monitor for the Texas Electricity Grid by scraping ERCOT’s web page. If anyone is interested in recreating it, here is the configuration.yaml entry:

sensor:
  - platform: scrape
    resource: https://www.ercot.com/content/cdr/html/as_capacity_monitor.html
    name: "Grid Operating Reserves"
    select: "body > div:nth-child(1) > table.tableStyle:nth-child(4) > tbody > tr:nth-child(42) > td.labelClassCenter:nth-child(2)"
    value_template: '{{ value.replace(",", "") | int }}'

And here is the code for the dashboard card:

type: gauge
entity: sensor.grid_operating_reserves
needle: true
min: 1
max: 7000
segments:
  - from: 1
    color: '#000000'
  - from: 1000
    color: '#FF0000'
  - from: 1750
    color: '#FFA500'
  - from: 2300
    color: '#FFFF00'
  - from: 2900
    color: '#008000'
name: Electricity Grid Operating Reserves
unit: MW

I use it to adjust my AC setting as the criticality increases.

2 Likes

Thanks for this! Looks great, and my AC will also set back now if (when) reserves get low. Much appreciated!

1 Like

image

I found a hidden API to pull the conditions that make up the text portion of the graph on the Ercot website

type: markdown
content: >-
  <table width="100%">

  <tr>

  <td><h2>{{states.sensor.ercot_status.attributes.title}}</td>

  <td align='right'><font color='#44739e'> <ha-icon
  icon="{{states.sensor.ercot_status_condition.attributes.icon}}"></font></ha-icon></td>

  </tr>

  <tr>

  <td colspan="2">

  {{states.sensor.ercot_status.attributes.condition_note}}<br>
   {{states.sensor.ercot_status_condition.state}}
  </td>

  </tr>

  </table>

In YAML add the sensors

sensor:

    - platform: rest

      scan_interval: 300

      unique_id: ercot_status

      icon: mdi:transmission-tower

      name: Ercot Status

      resource: "https://www.ercot.com/api/1/services/read/dashboards/daily-prc.json"

      value_template: "{{value_json.current_condition.state}}"

      json_attributes_path: "$.current_condition"

      json_attributes:

        - energy_level_value

        - title

        - condition_note

        - eea_level

    - platform: template

      sensors:

        ercot_status_condition:

          friendly_name: "Ercot Grid Condition"

          icon_template: >-

            {% if states.sensor.ercot_status.attributes.eea_level != 0 %}

              mdi:alert

            {%else%}

            mdi:transmission-tower

             {%endif%}

          value_template: >-

            {% if states.sensor.ercot_status.state == 'normal' %}

              The grid is operating under normal conditions.

            {% elif states.sensor.ercot_status.state != 'normal' and states.sensor.ercot_status.attributes.eea_level == 0 %}

              Energy conservation is requested.

            {% elif states.sensor.ercot_status.attributes.eea_level == 1 %}

              Energy Emergency Level 1 - Emergency operations have begun due to low power reserves, but there are no controlled outages at this time. Energy conservation is requested.

            {% elif states.sensor.ercot_status.attributes.eea_level == 2 %}

              Energy Emergency Level 2 - The emergency level has been raised due to continued low power reserves. Energy conservation is requested. It is advised to create a plan in case controlled outages are needed later. Those with critical medical needs should register with their local utility and have a backup plan.

            {% elif states.sensor.ercot_status.attributes.eea_level == 3 %}

              Energy Emergency Level 3 - The highest level of emergency operations. Local electric utilities have been instructed to begin controlled outages. Health and safety should be made a priority by using city or county instructions and resources. Energy conservation is critical.

            {%endif%}
1 Like

This is awesome! Thanks for digging into it.

Here is an updated sensor. I missed the system reserve (prc_value) attribute. It’s the same data as the reserve gauge sensor above but it can all be pulled as one request.

    - platform: rest
      scan_interval: 300
      unique_id: ercot_status
      icon: mdi:transmission-tower
      name: Ercot Status
      resource: "https://www.ercot.com/api/1/services/read/dashboards/daily-prc.json"
      value_template: "{{value_json.current_condition.state}}"
      json_attributes_path: "$.current_condition"
      json_attributes:
        - energy_level_value
        - title
        - condition_note
        - eea_level
        - prc_value

The value comes through as a string with a comma that you have to strip out so it will be usable in the guage.

        ercot_reserves:
          friendly_name: "Ercot Operating Reserves"
          unit_of_measurement: MW
          icon_template: mdi:transmission-tower
          value_template: >-
            {{states.sensor.ercot_status.attributes.prc_value.replace(",", "") }}

This is great. Thanks for sharing! I have added it to my electricity dashboard.

If you really want to nerd-out The grid capacity graph

image

the card:

type: custom:mini-graph-card
entities:
  - sensor.ercot_actual_demand
  - sensor.ercot_total_capacity
hours_to_show: 12

The sensors:

  multiscrape:

    - resource: "https://www.ercot.com/content/cdr/html/real_time_system_conditions.html"
      scan_interval: 300
      sensor:
        - unique_id: ercot_total_capacity
          unit_of_measurement: MW
          name: "Ercot Total Capacity"
          select: ".tableStyle > tbody:nth-child(1) > tr:nth-child(7) > td:nth-child(2)"
          icon: mdi:transmission-tower
        - unique_id: ercot_actual_demand
          unit_of_measurement: MW
          name: "Ercot Actual Demand"
          icon: mdi:transmission-tower
          select: ".tableStyle > tbody:nth-child(1) > tr:nth-child(6) > td:nth-child(2)"
        - unique_id: ercot_frequency
          unit_of_measurement: Hz
          name: "Ercot Frequency"
          icon: mdi:sine-wave
          select: ".tableStyle > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2)"

Sadly no solar here…

1 Like

Did you have to do anything special to get Smart Meter Texas working? I had it working for a while then the addon lost the ability to log in. I can log in manually with the same credentials but the addon won’t work for me.

I had the same issue. It actually will prevent the HA restore from completing. If you still have it installed I would recommend uninstalling it. I learned that the hard way.

I’m using the node-red implementation.

The instructions are a bit out of date. Below are the changes I had to make to get it to work.

in configuration.yaml add:

homeassistant:
  customize_glob:
    sensor.smart_meter_texas_*:
      state_class: total_increasing

For the SMT package:

smartmetertexas:
  mqtt:
    sensor:
      - state_topic: "smt/reading"
        unit_of_measurement: "kWh"
        name: Smart Meter Texas Current Reading
        unique_id: smart_meter_texas_current_reading
        device_class: energy

  utility_meter:
    hourly_energy:
      source: sensor.smart_meter_texas_current_reading
      name: Hourly Energy
      cycle: hourly
    daily_energy:
      source: sensor.smart_meter_texas_current_reading
      name: Daily Energy
      cycle: daily
    monthly_energy:
      source: sensor.smart_meter_texas_current_reading
      name: Monthly Energy
      cycle: monthly


  input_number:
    smtexas_energy_cost:
      icon: mdi:currency-usd
      name: Electricity Cost
      mode: box
      min: 0
      max: 2.50
      step: .001
      unit_of_measurement: "$/kWh"
2 Likes

Very cool. I looked into Smart Meter TX a while ago, but my utility (City of Austin) did not support it. Will check again to see if anything has changed.

This is somewhat untested but in theory, it should work. These 2 sensors return the least amout of forecast committed & total capacity for the day. My thought is if forecast availability drops below X (like last July) then do something.

I think ERCOT publishes a re-forecast every 5 minutes. Currently, the scan interval is set at 1 hour but that should be more than adequate.

ercot:
  sensor:  

    - platform: rest
      scan_interval: 3600
      unique_id: ercot_forecast_capacity_availability
      icon: mdi:transmission-tower
      name: "Ercot Forecast Capacity Availability"
      resource: "https://www.ercot.com/api/1/services/read/dashboards/todays-outlook.json"
      value_template: >-
        {%set vcount = namespace(forecastpercentage1=10,forecastpercentage2 = 0) %}
        {% for x in value_json.data %}
          {% if value_json.data[loop.index0].forecast|int == 1%}                
              {%set vcount.forecastpercentage2= value_json.data[loop.index0].demand|float /value_json.data[loop.index0].available|float %}
              {%if vcount.forecastpercentage1|float > vcount.forecastpercentage2|float%}
                {%set vcount.forecastpercentage1 = vcount.forecastpercentage2 %}
              {%endif%}
          {%endif%}                             
                          
        {%endfor%}
        {{(100-(vcount.forecastpercentage1|float*100))|round(1)}}

    - platform: rest
      scan_interval: 3600
      unique_id: ercot_forecast_commited_availability
      icon: mdi:transmission-tower
      name: "Ercot Forecast Commited Availability"
      resource: "https://www.ercot.com/api/1/services/read/dashboards/todays-outlook.json"
      value_template: >-
        {%set vcount = namespace(forecastpercentage1=10,forecastpercentage2 = 0) %}
        {% for x in value_json.data %}
          {% if value_json.data[loop.index0].forecast|int == 1%}                
              {%set vcount.forecastpercentage2= value_json.data[loop.index0].demand|float /value_json.data[loop.index0].capacity|float %}
              {%if vcount.forecastpercentage1|float > vcount.forecastpercentage2|float%}
                {%set vcount.forecastpercentage1 = vcount.forecastpercentage2 %}
              {%endif%}
          {%endif%}                                             
        {%endfor%}
        {{(100-(vcount.forecastpercentage1|float*100))|round(1)}}

So, the ERCOT grid gauge from the scrape was working perfectly, but then Home Assistant notified me that scrape had been moved and would break after two revisions. It was kind of cryptic, but I think the reason for the notice was that there is now a scrape integration in HA core.

I installed the integration and attempted to transfer the settings into the configuration with no luck.

Has anyone else moved this to the scrape integration, and if so what were the config parameters that needed to be entered?

For resource configure -
Resource:
https://www.ercot.com/content/cdr/html/as_capacity_monitor.html

Method: GET

Authentication method: basic

For sensor configure -
Select*:
body > div:nth-child(1) > table.tableStyle:nth-child(4) > tbody > tr:nth-child(42) > td.labelClassCenter:nth-child(2)

value template:
{{ value.replace(",", “”) | int }}

I use the REST sensor for the meter

- platform: rest

      scan_interval: 300

      unique_id: ercot_status

      icon: mdi:transmission-tower

      name: Ercot Status

      resource: "https://www.ercot.com/api/1/services/read/dashboards/daily-prc.json"

      value_template: "{{value_json.current_condition.state}}"

      json_attributes_path: "$.current_condition"

      json_attributes:

        - energy_level_value

        - title

        - condition_note

        - eea_level

        - prc_value

        - datetime

Looks like Ercot changed their API yesterday. the URI changed https://www.ercot.com/api/1/services/read/dashboards/supply-demand.json

Updated max capacity & committed sensor:

    - platform: rest
      scan_interval: 3600
      unique_id: ercot_forecast_capacity_availability
      icon: mdi:transmission-tower
      name: "Ercot Forecast Capacity Availability"
      resource: "https://www.ercot.com/api/1/services/read/dashboards/supply-demand.json"
      value_template: >-
        {%set vcount = namespace(forecastpercentage1=10,forecastpercentage2 = 0) %}
        {% for x in value_json.data %}
          {% if value_json.data[loop.index0].forecast|int == 1%}                
              {%set vcount.forecastpercentage2= value_json.data[loop.index0].demand|float /value_json.data[loop.index0].available|float %}
              {%if vcount.forecastpercentage1|float > vcount.forecastpercentage2|float%}
                {%set vcount.forecastpercentage1 = vcount.forecastpercentage2 %}
              {%endif%}
          {%endif%}                             
                          
        {%endfor%}
        {{(100-(vcount.forecastpercentage1|float*100))|round(1)}}

    - platform: rest
      scan_interval: 3600
      unique_id: ercot_forecast_commited_availability
      icon: mdi:transmission-tower
      name: "Ercot Forecast Commited Availability"
      resource: "https://www.ercot.com/api/1/services/read/dashboards/supply-demand.json"
      value_template: >-
        {%set vcount = namespace(forecastpercentage1=10,forecastpercentage2 = 0) %}
        {% for x in value_json.data %}
          {% if value_json.data[loop.index0].forecast|int == 1%}                
              {%set vcount.forecastpercentage2= value_json.data[loop.index0].demand|float /value_json.data[loop.index0].capacity|float %}
              {%if vcount.forecastpercentage1|float > vcount.forecastpercentage2|float%}
                {%set vcount.forecastpercentage1 = vcount.forecastpercentage2 %}
              {%endif%}
          {%endif%}                                             
        {%endfor%}
        {{(100-(vcount.forecastpercentage1|float*100))|round(1)}}

Thank you! I was thinking something was wrong.

Hi Mike - I’ve been using this method for a Scrape sensor successfully, until it stopped working on June 8. The data source is still there, but my sensor is showing “unknown”. I can’t decipher the Select logic,so could you provide the updated configuration to use to get this working again? Thanks!

I just had to update mine too and really didn’t understand why because it didn’t look like anything had changed. Here is what I am using now:

Resource: https://www.ercot.com/content/cdr/html/as_capacity_monitor.html

Sensor: body > div:nth-child(2) > table.tableStyle:nth-child(4) > tbody > tr:nth-child(53) > td.labelClassCenter:nth-child(2)

Hope that helps.

1 Like