Solar Mask is a Home Assistant custom integration that computes the real sun visibility for any location on your property, taking into account the surrounding obstacles (buildings, trees, hills) defined by a horizon mask file.
Unlike cloud-based solar forecasting services, Solar Mask works entirely offline using pure astronomical calculations β no API key, no account, no network required.
The sun's position in the sky is perfectly predictable. What varies from one spot to another is what's blocking it: a neighbour's rooftop at 9 AM, a treeline in the afternoon, a hillside in winter.
Solar Mask lets you define that horizon β using a dedicated webapp Solar Mask Generator on your phone or the PVGIS web tool β and turns it into actionable Home Assistant entities. You can then use those entities to automate anything that depends on whether a specific zone is actually in sunlight: a pool pump, garden irrigation, a solar water heater, shutter control, etc.
- π Geometric sun visibility β compares the sun's real-time elevation to the horizon mask at each azimuth
- π Daily sunlit windows β computes start time, end time and total duration for each day
- π Fully offline β pure Python astronomy, zero external dependencies
- ποΈ Multi-zone support β configure as many zones as you need (pool, terrace, vegetable gardenβ¦)
- π‘ 6 entities per zone β binary sensor + 5 sensors, ready to use in automations and dashboards
- π Standard mask formats β CSV (Solar Mask Generator) and HOR (PVGIS / PVsyst) are both supported
For each configured zone named <zone>, the integration creates:
| Entity | Type | Description |
|---|---|---|
binary_sensor.<zone>_sunlit |
Binary sensor | ON = sun currently visible, OFF = in shadow |
sensor.<zone>_sun_effective_elevation |
Sensor (Β°) | Sun elevation minus mask at current azimuth β positive = visible |
sensor.<zone>_sun_start |
Sensor (timestamp) | Today's first sunlit moment |
sensor.<zone>_sun_end |
Sensor (timestamp) | Today's last sunlit moment |
sensor.<zone>_sun_duration |
Sensor (min) | Total sunlit minutes today |
sensor.<zone>_solar_diagram |
Sensor | JSON data for the Solar Mask Card dashboard visualization |
binary_sensor.<zone>_sunlit and sensor.<zone>_sun_effective_elevation both expose:
sun_azimuth: 187.3 # current sun azimuth in degrees (0=N, 90=E, 180=S, 270=W)
sun_elevation: 42.1 # real sun elevation in degrees
mask_elevation: 8.5 # horizon mask height at current azimuth
effective_elevation: 33.6 # sun_elevation β mask_elevation (>0 = visible)sensor.<zone>_sun_duration exposes a windows attribute β the list of all sunlit intervals today:
windows:
- start: "06:55"
end: "20:15"- In HACS, next to the search bar, click on the three dots β Custom repositories
- Add
https://github.com/38decibel/Solar-Maskwith category Integration - Install Solar Mask
- Restart Home Assistant
- Copy
custom_components/solar_mask/into your HAconfig/custom_components/folder - Restart Home Assistant
Add the following to your configuration.yaml:
solar_mask:
- name: pool
mask_file: /config/solar_masks/pool.csv
- name: terrace
mask_file: /config/solar_masks/terrace.csv
latitude: 45.1856 # optional β defaults to HA location
longitude: 5.7376 # optional β defaults to HA location| Parameter | Required | Description |
|---|---|---|
name |
β | Zone identifier β used as entity name prefix |
mask_file |
β | Absolute path to the horizon mask file |
latitude |
β | Override latitude for this zone (defaults to HA location) |
longitude |
β | Override longitude for this zone (defaults to HA location) |
Restart Home Assistant after any configuration change (but before make sur to had store the csv files)
A mask file describes the horizon around your zone as a series of (azimuth, elevation) pairs. Azimuth is the compass direction (0Β° = North, 90Β° = East, 180Β° = South, 270Β° = West). Elevation is the angle in degrees above the horizontal plane.
Solar Mask Generator is a zero-install web app for surveying the solar horizon mask of any location.
- Go to Solar Mask Generator
- Stand at the location you want to measure (pool edge, terrace, etc.)
- Slowly pan your phone around 360Β°, tapping to record horizon points wherever the skyline changes (building edge, treetop, hill)
- Export the data as CSV β the file is ready to use directly with Solar Mask
Tip: Take measurements on a clear day. Aim for at least one point every 10β15Β° for a smooth mask, and more points in areas with complex obstacles.
The European Commission's PVGIS tool provides a satellite-derived horizon profile for any location.
- Go to https://re.jrc.ec.europa.eu/pvg_tools/en/tools.html
- Check "Calculated horizon"
- Click Csv β this gives you a
.csvfile - Delete all unnecessary datas (keep only 'A' & 'H_hor' columns).
Note: PVGIS horizon data reflects the far-field topography (hills, mountains) but does not include nearby obstacles like buildings or trees. It is a useful starting point but should be combined with on-site measurements for best accuracy.
Solar Mask accepts two formats:
CSV format (Solar Mask Generator, PVGIS):
azimuth,elevation
0,3.5
30,5.2
60,12.0
90,18.5
...
350,3.1HOR format (PVsyst / PVGIS HOR export):
# Horizon file
0 3.5
30 5.2
60 12.0
...
Lines beginning with # are treated as comments. Both space-separated and comma-separated formats are auto-detected.
Pair Solar Mask with the associated custom card Solar Mask Card for a visual solar diagram showing the sun's path over your horizon mask directly in your Lovelace dashboard.
type: custom:solar-mask-card
entity: sensor.pool_solar_diagram
sun_entity: sensor.pool_sun_effective_elevation
title: "Solar diagram β Pool"
height: 340Run the pool pump only when the pool is in direct sunlight and total daily sunshine exceeds 2 hours:
automation:
- alias: "Pool pump β start when sunny"
trigger:
- platform: state
entity_id: binary_sensor.pool_sunlit
to: "on"
condition:
- condition: numeric_state
entity_id: sensor.pool_sun_duration
above: 120
action:
- service: switch.turn_on
target:
entity_id: switch.pool_pump
- alias: "Pool pump β stop when shaded"
trigger:
- platform: state
entity_id: binary_sensor.pool_sunlit
to: "off"
action:
- service: switch.turn_off
target:
entity_id: switch.pool_pumpWater the garden only before the sun reaches the terrace (to reduce evaporation):
automation:
- alias: "Irrigation β stop before sun hits terrace"
trigger:
- platform: state
entity_id: binary_sensor.terrace_sunlit
to: "on"
action:
- service: switch.turn_off
target:
entity_id: switch.garden_irrigationtemplate:
- sensor:
- name: "Pool sun start (time)"
state: >
{% set s = states('sensor.pool_sun_start') %}
{% if s not in ['unknown','unavailable'] %}
{{ as_datetime(s) | as_local | strftime('%H:%M') }}
{% else %}
--:--
{% endif %}
icon: mdi:weather-sunset-upSolar Mask uses a pure Python implementation of the Spencer/Grena solar position algorithm β the same family of algorithms used by professional PV simulation software. At each update (every 5 minutes by default):
- The sun's azimuth and elevation are computed for the current UTC time and the zone's coordinates
- The mask file is interpolated at the current azimuth to get the horizon elevation at that direction
- Effective elevation = sun elevation β mask elevation. If positive, the zone is sunlit.
Daily windows (start, end, duration) are computed once per day at midnight by sampling the full 24-hour period in 5-minute steps β all in a background thread to comply with Home Assistant's async architecture.
MIT License β see LICENSE for details.
