Days Into Moon Phase - An Experiment in Automations, Tempates and Input Variables

This may seem like a trivial project. But I found it to be more challenging that I expected, and it touches on some odd syntax which I haven’t seen many examples of, so I suspect others might find it helpful. Also, some of you might be able to point out mistakes or better ways of doing this.

I like to know the phase of the moon. It helps me internalize the times of tides better than trying to memorize the exact times of the highs and lows for the coming days. What works best is when I know how many days we are past the last full or new moon. Text like “Waxing gibbous” isn’t specific enough.

Far better would be to see something like “3 days since the last full moon.”

The first data point I need to track is the previous quarter phase (new, full and first and last quarters.)

Next I need to keep a count of how many days it’s been since then. From what I’ve read, the best way to store persistent variables in HA is to use input_text and input_number. Here’s what I ended up with (in configuration.yaml):

# Store days since last moon phase strings
    name: Last Moon Phase

# Store days since last moon phase
    name: "Days Since Last Moon"
    min: 0
    max: 8
    step: 1
    unit_of_measurement: 'Days'

I don’t really need the min:, max: or step:, but they were in the sample I copied so I figured I’d leave them in as an example for other projects.

Next I need a way to populate these values. My logic goes like this: the sensor.moon state changes eight times per lunar month. For four of those, I want to change the moon_last_phase input text, and reset the moon_days_elapsed counter to zero.

For the other four state changes, I want to just increment the moon_days_elapsed counter.

Here’s the automation I came up with.

# Update days since last moon phase
- id: moon_days
  alias: Moon Days Since Phase
  description: Set days since last moon phase
  mode: single
  - platform: state
    - sensor.moon
    - choose:
# If we're at one of the four primary phases
# Change the saved phase and reset the elapsed days
        - conditions:
            - condition: template
              value_template: "{{ states('sensor.moon') in ['full_moon', 'new_moon', 'first_quarter', 'last_quarter'] }}"
            - service: input_number.set_value
                entity_id: input_number.moon_days_elapsed
                value: 0
            - service: input_text.set_value
                entity_id: input_text.moon_last_phase
                value: "{{ states('sensor.moon') }}"
# If we're at one of the in-between phases
# Increment the elapsed days
        - conditions:
            - condition: template
              value_template: "{{ states('sensor.moon') in ['waxing_crescent', 'waxing_gibbous', 'waning_crescent', 'waning_gibbous'] }}"
            - service: input_number.set_value
                entity_id: input_number.moon_days_elapsed
                value: "{{ states('input_number.moon_days_elapsed') | int + 1 }}"

So far I’ve only tested this through one phase change. There could be syntax errors or typos which will show up in future changes.

Also, I’m not 100% certain that this automation will only run once for each change. If it somehow gets triggered at any other time, that will mess up the whole thing.

The last piece was to display the results as a phrase. For this I used a template:

# Moon phase string
      - name: "Moon Phase String"
        unique_id: "moon_phase_string"
        state: "{{states('input_number.moon_days_elapsed') | int }} days since {{states('input_text.moon_last_phase') | regex_replace(find='_', replace=' ', ignorecase=False) }}"

I used a regular expression to replace the hyphen ("-") in the state with a space. I never did figure out why, when you display the sate of sensor.moon in a dashboard it prints it in mixed case with no hyphen, like “Waxing gibbous.” I’m OK with just removing the hyphen.

When I displayed this template, I noticed it included an icon. Of course I wanted the icon to match the text. So I added some logic to the template. Here’s the final result:

# Moon phase string
      - name: "Moon Phase String"
        unique_id: "moon_phase_string"
        state: "{{states('input_number.moon_days_elapsed') | int }} days since {{states('input_text.moon_last_phase') | regex_replace(find='_', replace=' ', ignorecase=False) }}"
        icon: >
          {% if is_state("sensor.moon", "full_moon") %}
          {% elif is_state("sensor.moon", "waning_gibbous") %}
          {% elif is_state("sensor.moon", "last_quarter") %}
          {% elif is_state("sensor.moon", "waning_crescent") %}
          {% elif is_state("sensor.moon", "new_moon") %}
          {% elif is_state("sensor.moon", "waxing_crescent") %}
          {% elif is_state("sensor.moon", "first_quarter") %}
          {% elif is_state("sensor.moon", "waxing_gibbous") %}
          {% endif %}

As it happens, today the moon was in its last quarter, and the automation did its job. My Tides card looked like this today:

It does look a little odd with the zero there, but I know what it means.

That’s it. Feel free to use, improve or criticize it.

The advent of trigger-based template sensors and their ability to maintain their state over restart has made many of the steps you have taken superfluous.

  - trigger:
      - platform: state
        entity_id: sensor.moon
          - full_moon
          - last_quarter
          - first_quarter
          - new_moon
          - unknown
          - unavailable
      - name: Moon Last Phase
        state: "{{ trigger.to_state.state | replace('_', ' ') }}"
          change_date: "{{ today_at() }}"

  - sensor:    
      - name: "Moon Phase String"
        unique_id: "moon_phase_string"
        state: |
          {{ (state_attr('sensor.moon_last_phase', 'change_date') - today_at()).days }} 
          days since {{states('sensor.moon_last_phase') }}
        icon: |
          {% set phase = states('sensor.moon') %}
          {{ 'mdi:moon-new' if phase in ['full_moon', 'new_moon'] 
          else 'mdi:moon-'~phase | replace('_', '-') }}

The issue with this method is that the sensor’s state will be unknown until it triggers for the first time.

Nice! I thought there must be a better way!!

I notice now that my example won’t work anyway, because it’s only triggered on state changes. The state doesn’t change for up to 7 days.

Sometimes I get fixated on one approach, and am blinded to the obvious. I knew this would be a learning experience.

Thank you!!

Ug. Looking at it again, the problem is that there are eight state changes in a (lunar) month, and I only care about four of them.

Put another way, I don’t care how long it’s been since it changed to, say, waxing gibbous. I care how long it’s been since (in that example) it was at first quarter. Actually I’d be OK just knowing how long it’s been since new or full. I threw in the quarters to see if I’d get used to that level of detail.

In my original example, it looks like just changing the trigger will get me what I want:

  - platform: time
    at: 00:04:30

Through sheer coincidence, there was a state change today (from last quarter to waning crescent) so the existing trigger fired, and everything else worked as expected. With the new trigger, this should happen every day.

I don’t know if you’ve seen this integration but one of the sensors it provides is a moon phase in percent.

I haven’t found anything definitive about what the percentage actually is and since I’ve never really watched it too closely I can’t tell you from personal experience either.

But from just an educated guess I think it should be how far we are from the full moon.

Right now it says 34.5% and it’s been decreasing so I think 100% should full and 0% should be new.

Maybe it can help you somehow.

Yes, illuminated percent can be useful. I suppose it could also mean % through the cycle, although I haven’t really seen it used that way.

It’s not really what I was after though. I’ve seen a few other integrations with all kinds of astronomical data, but they seem to be much more than I need. I have astronomical apps for that.

If you are interested here is a template that will count days since last quarter phase (new, 1st qtr, full, 3rd qtr).

first create a trend sensor to tell you which direction the percent from the above integration is going (true is increasing)

  - platform: trend
        entity_id: sensor.astroweather_moon_phase

then the template for the template sensor is:

{% set pct = states('sensor.astroweather_moon_phase') | float %}
{% if is_state('binary_sensor.moon_phase_dir', 'true') %}
  {% if pct < 50 %}
    {{ (pct / 50) * 7 }}
  {% elif pct < 100 %}
    {{ (pct - 50)/ 50 * 7 }}
  {% else %}
  {% endif %}
{% else%}
  {% if pct > 50 %}
    {{ (100 - pct) / 7 }}
  {% else %}
    {{ (50 - pct)/50 * 7 }}
  {% endif %}
{% endif %}

No other automations or triggers are needed AFAIK.

Obviously not fully tested in the real world but manually inputting values it seems to work.