Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Planned
- More translations.
- Finer precalculated sun data for sunrise and sunset.
- Adding more calculated attributes in the sensor section.
- Sun position lock, option to lock the suns position in the view (with rotational setting) , and make to house rotate accordingly instead.
- Adding card information about sun and alignement position


### Added
- Placeholder for upcoming changes.


## [0.2.5] - 2026-03-29
### Added
- Fixed Sun Azimuth visual mode (sun rotation lock) with scene rotation compensation, thank you @HACS-bank for the suggestion!
- Visual editor section/label for `Fixed Sun Azimuth (sun rotation)`.

### Changed
- Save Camera View now batches integration-backed values (`camera_rotation_h`, `camera_rotation_v`, and `fixed_sun_azimuth`) in a single `sunlight_visualizer.set_options` call to reduce flicker.
- Bumped integration/card release version to `0.2.5`.

## [0.2.3] - [0.2.4] 2026-03-08
### Added
- Card auto-size down by available width (`autoScaleWidth`, default `true`) with minimum width clamp at `250px`.
Expand Down
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Sunlight Visualizer
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/icon@2x.png" width="10%" height="10%">


[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=NoUsername10&repository=Sunlight_Visualizer&category=integration)

[![coffee_badge](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-donate-orange.svg)](https://www.buymeacoffee.com/DefaultLogin)

[<img src="https://my.home-assistant.io/badges/hacs_repository.svg" />](https://my.home-assistant.io/redirect/hacs_repository/?owner=NoUsername10&repository=Sunlight_Visualizer&category=integration)

<br>

An interactive sunlight intensity visualizer for Home Assistant.

Expand All @@ -16,21 +16,22 @@ It includes:
- Heat-Load HVAC Prep: Pre-cool when roof or wall sunlight rises, then relax setpoint when exposure drops.
- Solar Output Insights: Compare roof sun intensity/alignment with roof power to detect underperformance.
- Control blinds and awnings automatically depending on the sun’s position.
<br><br>
<details>
<summary>Sensor information</summary><br>
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/sensors.png" width="35%" height="35%">
</details>
<br>

<br>

Sensor information: <br>
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/sensors.png" width="50%" height="50%">

<br>

- A Lovelace card (`custom:sunlight-visualizer-card`) that renders a 2.5D house with accurate sun, shadows, roof/wall values, optional roof power, sky effects, and camera controls.

<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-day.png" width="25%" height="25%"> <img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-dawn.png" width="25%" height="25%"> <img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-night.png" width="25%" height="25%">
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-day.png" width="33%" height="33%"> <img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-dawn.png" width="33%" height="33%"> <img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/house-night.png" width="33%" height="33%">


GIF (on macOS Safari: right click + "Play animation"):
<br>
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/rotation.gif" width="40%" height="40%">
<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/rotation.gif" width="50%" height="50%">


## Instant Overview
Expand All @@ -42,6 +43,7 @@ GIF (on macOS Safari: right click + "Play animation"):
- Visuals: overhang, windows, door, roof panels, tree and adaptive shadows.
- Day/night scene with clouds, stars, moon, twilight gradients.
- Auto rotate + manual camera controls + save/restore view.
- Fixed sun position, azimuth. (Rotate scene) — Keep sun azimuth visually fixed and rotate the scene instead.
- Performance adaptation for slow displays.
- Test mode (if sun is down): Force Sun Fallback mode, the card displays `SUN OVERRIDE ENABLED`.

Expand Down Expand Up @@ -115,6 +117,7 @@ These are the most important settings in integration setup/options:
- House Direction preset or custom House Angle (The compass angle of the front door of your house)
- Roof Direction (`front`, `left`, `back`, `right`) Slop tilt of you ceiling.
- Ceiling Tilt
- Fixed sun position, azimuth. (Rotate scene)
- Update Interval
- Auto Rotate Speed
- Roof power settings:
Expand All @@ -135,6 +138,7 @@ In integration configuration/options you can tune:
- Camera Rotation H / V (default view)
- House Direction preset
- Roof Direction
- Fixed sun position, azimuth. (Rotate scene)
- Update Interval
- Auto rotate speed
- Force Sun Fallback:
Expand Down Expand Up @@ -169,6 +173,7 @@ You can also configure common card behavior visually:
- Auto rotation speed
- Auto-scale Width (auto downscale to fit narrow cards / devices)
- Camera controls
- Fixed sun position, azimuth. (Rotate scene)
- House/roof orientation values

<img src="https://github.com/NoUsername10/Sunlight_Visualizer/blob/main/assets/visual-card-configuration.png" width="70%" height="70%">
Expand All @@ -186,7 +191,7 @@ You can still override entities in YAML when needed.


## Validation
- Current release: `0.2.3` (validated for HACS + Hassfest).
- Current release: `0.2.5` (validated for HACS + Hassfest).
- HACS approved repository and installable as an Integration category repo.
- HACS validation workflow: `.github/workflows/hacs.yaml`
- Hassfest validation workflow: `.github/workflows/hassfest.yaml`
Expand Down Expand Up @@ -227,6 +232,9 @@ You can still override entities in YAML when needed.
- `select.house_direction`
- `select.roof_direction`

### Switch entities
- `switch.fixed_sun_azimuth_rotate_scene`

</details>


Expand All @@ -246,6 +254,8 @@ siSourceValue: sunlight_visualizer
rotationHEntity: number.house_camera_rotation_h
rotationVEntity: number.house_camera_rotation_v
houseAngleEntity: number.house_angle
fixedSunRotationEnabled: false
fixedSunAzimuthDeg: 225

wallFrontPctEntity: sensor.sun_wall_intensity_front
wallRightPctEntity: sensor.sun_wall_intensity_right
Expand Down
9 changes: 8 additions & 1 deletion custom_components/sunlight_visualizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
CONF_CAMERA_ROT_H,
CONF_CAMERA_ROT_V,
CONF_AUTO_ROTATE_SPEED,
CONF_FIXED_SUN_AZIMUTH,
CONF_FIXED_SUN_ROTATION_ENABLED,
CONF_ROOF_POWER_ENTITY,
CONF_ROOF_POWER_ENABLED,
CONF_ROOF_POWER_INVERT,
Expand All @@ -40,7 +42,8 @@
PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.NUMBER,
Platform.SELECT,
Platform.SELECT,
Platform.SWITCH,
]

# Local resource URL for the bundled card
Expand All @@ -59,6 +62,8 @@
vol.Optional(CONF_CAMERA_ROT_H): vol.All(vol.Coerce(int), vol.Range(min=0, max=359)),
vol.Optional(CONF_CAMERA_ROT_V): vol.All(vol.Coerce(int), vol.Range(min=0, max=90)),
vol.Optional(CONF_AUTO_ROTATE_SPEED): vol.All(vol.Coerce(float), vol.Range(min=1, max=90)),
vol.Optional(CONF_FIXED_SUN_AZIMUTH): vol.All(vol.Coerce(int), vol.Range(min=0, max=359)),
vol.Optional(CONF_FIXED_SUN_ROTATION_ENABLED): bool,
vol.Optional(CONF_ROOF_POWER_ENTITY): vol.Any(None, str),
vol.Optional(CONF_ROOF_POWER_ENABLED): bool,
vol.Optional(CONF_ROOF_POWER_INVERT): bool,
Expand All @@ -72,6 +77,8 @@
CONF_CAMERA_ROT_H,
CONF_CAMERA_ROT_V,
CONF_AUTO_ROTATE_SPEED,
CONF_FIXED_SUN_AZIMUTH,
CONF_FIXED_SUN_ROTATION_ENABLED,
CONF_ROOF_POWER_ENTITY,
CONF_ROOF_POWER_ENABLED,
CONF_ROOF_POWER_INVERT,
Expand Down
62 changes: 42 additions & 20 deletions custom_components/sunlight_visualizer/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
CONF_FORCE_SUN_FALLBACK,
CONF_FORCE_SUN_AZIMUTH,
CONF_FORCE_SUN_ELEVATION,
CONF_FIXED_SUN_ROTATION_ENABLED,
DIRECTIONS,
ROOF_DIRECTIONS,
DEFAULT_UPDATE_INTERVAL,
Expand All @@ -43,6 +44,7 @@
DEFAULT_FORCE_SUN_FALLBACK,
DEFAULT_FORCE_SUN_AZIMUTH,
DEFAULT_FORCE_SUN_ELEVATION,
DEFAULT_FIXED_SUN_ROTATION_ENABLED,
)


Expand Down Expand Up @@ -220,14 +222,9 @@ async def async_step_user(
vol.Range(min=1, max=90)
),
vol.Required(
CONF_ROOF_POWER_ENABLED,
default=DEFAULT_ROOF_POWER_ENABLED,
description="Enable roof power label"
): bool,
vol.Required(
CONF_ROOF_POWER_INVERT,
default=DEFAULT_ROOF_POWER_INVERT,
description="Invert roof power value (show positive)"
CONF_FIXED_SUN_ROTATION_ENABLED,
default=DEFAULT_FIXED_SUN_ROTATION_ENABLED,
description="Keep sun azimuth visually fixed and rotate the scene instead"
): bool,
})

Expand All @@ -243,6 +240,19 @@ async def async_step_user(
description="Optional solar power sensor (W) for roof label"
)] = roof_power_selector

schema_dict.update({
vol.Required(
CONF_ROOF_POWER_ENABLED,
default=DEFAULT_ROOF_POWER_ENABLED,
description="Enable roof power label"
): bool,
vol.Required(
CONF_ROOF_POWER_INVERT,
default=DEFAULT_ROOF_POWER_INVERT,
description="Invert roof power value (show positive)"
): bool,
})

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(schema_dict),
Expand Down Expand Up @@ -373,6 +383,10 @@ async def async_step_init(
current_force_sun = current_config.get(CONF_FORCE_SUN_FALLBACK, DEFAULT_FORCE_SUN_FALLBACK)
current_force_sun_az = current_config.get(CONF_FORCE_SUN_AZIMUTH, DEFAULT_FORCE_SUN_AZIMUTH)
current_force_sun_el = current_config.get(CONF_FORCE_SUN_ELEVATION, DEFAULT_FORCE_SUN_ELEVATION)
current_fixed_sun_rotation_enabled = current_config.get(
CONF_FIXED_SUN_ROTATION_ENABLED,
DEFAULT_FIXED_SUN_ROTATION_ENABLED,
)

# Get current location from config or HA
current_latitude = current_config.get(CONF_LATITUDE, self.hass.config.latitude)
Expand Down Expand Up @@ -443,6 +457,26 @@ async def async_step_init(
vol.Coerce(float),
vol.Range(min=1, max=90)
),
vol.Required(
CONF_FIXED_SUN_ROTATION_ENABLED,
default=current_fixed_sun_rotation_enabled,
description="Keep sun azimuth visually fixed and rotate the scene instead"
): bool,
})

if current_roof_power:
options_schema_dict[vol.Optional(
CONF_ROOF_POWER_ENTITY,
default=current_roof_power,
description="Optional solar power sensor (W) for roof label"
)] = roof_power_selector
else:
options_schema_dict[vol.Optional(
CONF_ROOF_POWER_ENTITY,
description="Optional solar power sensor (W) for roof label"
)] = roof_power_selector

options_schema_dict.update({
vol.Required(
CONF_ROOF_POWER_ENABLED,
default=current_config.get(CONF_ROOF_POWER_ENABLED, DEFAULT_ROOF_POWER_ENABLED),
Expand Down Expand Up @@ -470,18 +504,6 @@ async def async_step_init(
): vol.All(vol.Coerce(float), vol.Range(min=-90, max=90)),
})

if current_roof_power:
options_schema_dict[vol.Optional(
CONF_ROOF_POWER_ENTITY,
default=current_roof_power,
description="Optional solar power sensor (W) for roof label"
)] = roof_power_selector
else:
options_schema_dict[vol.Optional(
CONF_ROOF_POWER_ENTITY,
description="Optional solar power sensor (W) for roof label"
)] = roof_power_selector

options_schema = vol.Schema(options_schema_dict)

return self.async_show_form(
Expand Down
4 changes: 4 additions & 0 deletions custom_components/sunlight_visualizer/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
CONF_FORCE_SUN_FALLBACK = "force_sun_fallback"
CONF_FORCE_SUN_AZIMUTH = "force_sun_azimuth"
CONF_FORCE_SUN_ELEVATION = "force_sun_elevation"
CONF_FIXED_SUN_AZIMUTH = "fixed_sun_azimuth"
CONF_FIXED_SUN_ROTATION_ENABLED = "fixed_sun_rotation_enabled"

# Marker used by the SVG house card to auto-bind sensors from this integration
CARD_SOURCE_ATTR = "sunlight_visualizer_source"
Expand All @@ -47,6 +49,8 @@
DEFAULT_FORCE_SUN_FALLBACK = False
DEFAULT_FORCE_SUN_AZIMUTH = FALLBACK_SUN_AZIMUTH
DEFAULT_FORCE_SUN_ELEVATION = FALLBACK_SUN_ELEVATION
DEFAULT_FIXED_SUN_AZIMUTH = 225
DEFAULT_FIXED_SUN_ROTATION_ENABLED = False

# Wall types
WALLS = ["front", "left", "back", "right", "ceiling"]
Expand Down
2 changes: 1 addition & 1 deletion custom_components/sunlight_visualizer/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"integration_type": "service",
"iot_class": "calculated",
"issue_tracker": "https://github.com/NoUsername10/Sunlight_Visualizer/issues",
"version": "0.2.4"
"version": "0.2.5"
}
1 change: 1 addition & 0 deletions custom_components/sunlight_visualizer/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,4 @@ def extra_state_attributes(self):
CARD_SOURCE_ATTR: CARD_SOURCE_VALUE,
"camera_rotation": "v"
}

20 changes: 20 additions & 0 deletions custom_components/sunlight_visualizer/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
CONF_FORCE_SUN_AZIMUTH,
CONF_FORCE_SUN_ELEVATION,
CONF_AUTO_ROTATE_SPEED,
CONF_FIXED_SUN_AZIMUTH,
CONF_FIXED_SUN_ROTATION_ENABLED,
CONF_CEILING_TILT,
CONF_UPDATE_INTERVAL,
CONF_ADVANCED_MODE,
Expand All @@ -46,6 +48,8 @@
DEFAULT_FORCE_SUN_AZIMUTH,
DEFAULT_FORCE_SUN_ELEVATION,
DEFAULT_AUTO_ROTATE_SPEED,
DEFAULT_FIXED_SUN_AZIMUTH,
DEFAULT_FIXED_SUN_ROTATION_ENABLED,
WALLS,
DEFAULT_UPDATE_INTERVAL,
ROOF_DIRECTIONS,
Expand Down Expand Up @@ -859,6 +863,22 @@ def extra_state_attributes(self) -> dict[str, Any]:
if self._config_entry.options.get(CONF_AUTO_ROTATE_SPEED) is not None
else self._config_entry.data.get(CONF_AUTO_ROTATE_SPEED, DEFAULT_AUTO_ROTATE_SPEED)
),
'fixed_sun_azimuth': (
self._config_entry.options.get(CONF_FIXED_SUN_AZIMUTH)
if self._config_entry.options.get(CONF_FIXED_SUN_AZIMUTH) is not None
else self._config_entry.data.get(
CONF_FIXED_SUN_AZIMUTH,
DEFAULT_FIXED_SUN_AZIMUTH,
)
),
'fixed_sun_rotation_enabled': (
self._config_entry.options.get(CONF_FIXED_SUN_ROTATION_ENABLED)
if self._config_entry.options.get(CONF_FIXED_SUN_ROTATION_ENABLED) is not None
else self._config_entry.data.get(
CONF_FIXED_SUN_ROTATION_ENABLED,
DEFAULT_FIXED_SUN_ROTATION_ENABLED
)
),
'wall': self._wall,
'last_updated': self.coordinator.data.get('last_updated', ''),
'calculation_count': self.coordinator.calculation_count
Expand Down
16 changes: 16 additions & 0 deletions custom_components/sunlight_visualizer/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ set_options:
step: 1
mode: box

fixed_sun_azimuth:
name: Fixed sun azimuth
description: Fixed sun azimuth used by card visual fixed-sun mode (0-359).
selector:
number:
min: 0
max: 359
step: 1
mode: box

fixed_sun_rotation_enabled:
name: Fixed sun position, azimuth. (Rotate scene)
description: Keep sun azimuth visually fixed and rotate the scene instead
selector:
boolean:

auto_rotate_speed:
name: Auto-rotate speed
description: Card auto-rotation speed in degrees per second (1-90).
Expand Down
Loading
Loading