Skip to content

feat(scene): add a sun position scene trigger (altitude & azimuth) - #2893

Open
Pierre-Gilles wants to merge 2 commits into
masterfrom
claude/sun-position-trigger
Open

feat(scene): add a sun position scene trigger (altitude & azimuth)#2893
Pierre-Gilles wants to merge 2 commits into
masterfrom
claude/sun-position-trigger

Conversation

@Pierre-Gilles

Copy link
Copy Markdown
Contributor

Implements feature request: https://community.gladysassistant.com/t/ajouter-un-declencheur-de-scenes-selon-l-altitude-et-l-azimut-du-soleil/7881

⚠️ This PR was opened by an automated Claude Code run. It needs human review and real-life testing before being merged.

Description

Adds a new scene trigger "Sun position", so a scene can start when the sun reaches a given elevation above the horizon and/or a given compass direction. The use case from the forum: open/close the shutters of a specific facade when the sun actually hits it.

Server

  • Two new events in server/utils/constants.js:
    • time.check-sun-position (internal, scheduled every minute in scene.init, like the calendar check),
    • time.sun-position (the new trigger type).
  • New server/lib/scene/scene.checkSunPositionTriggers.js:
    • runs every minute, but returns immediately when no active scene uses the trigger (so house.get() is not called for nothing),
    • computes the position with the suncalc dependency already used by scene.dailyUpdate.js, for every house with coordinates,
    • suncalc.getPosition() returns radians with an azimuth measured from South; it is converted to degrees using the usual compass convention (0 = North, 90 = East, 180 = South, 270 = West), which is what the UI documents and what the user enters. This is explained in the JSDoc of convertSunPositionToDegrees,
    • keeps the last known position per house (sceneManager.sunPositions) and emits trigger.check with the current and previous position. The very first check of a house only stores the position.
  • New matcher in server/lib/scene/scene.triggers.js: the trigger has an optional condition on the altitude and an optional one on the azimuth (operators >, <, =; = matches within 0.5°, since the position is sampled every minute and never lands on an exact degree). Both conditions are combined with an AND, and the scene is started when the sun enters the configured area — it is not re-executed at every check while the sun stays in it.
  • Joi validation of the new trigger properties (sun_altitude_operator, sun_altitude in -90..90, sun_azimuth_operator, sun_azimuth in 0..360) in server/models/scene.js.

Front

  • New SunPositionTrigger.jsx (house selector with the existing "house without coordinates" warning, one operator + degrees input per axis, with help texts explaining both conventions), wired in TriggerCard.jsx and in the trigger type list of ChooseTriggerTypeCard.jsx.
  • Translations added in en, fr and de.

Notes for the reviewer

  • The altitude is negative at night: an azimuth-only trigger will also match at night, hence the help text suggesting to combine it with an altitude condition.
  • The check runs on the same one-minute tick as the calendar check; a scene therefore fires at most one minute after the sun reaches the configured position.

Forum

Forum: https://community.gladysassistant.com/t/ajouter-un-declencheur-de-scenes-selon-l-altitude-et-l-azimut-du-soleil/7881

Checklist

  • Tests pass: new tests added in server/test/lib/scene/scene.checkSunPositionTriggers.test.js (17 assertions on the conversion, the early return, the first-check behaviour and the emitted payload) and server/test/lib/scene/triggers/scene.trigger.sunPosition.test.js (matching, non-matching house, = tolerance, no re-trigger while the sun stays in the area, missing/invalid values, Joi validation). test/lib/scene/** + test/models/** run green (331 passing). The full server suite was also run: the only failures are pre-existing environment ones (gateway backup/restore needing the sqlite3 binary, docker and network tests), none in scene. npm run coverage and Cypress were not run in this environment.
  • Linter and prettier pass on both front and server (npm run eslint → 0 errors, prettier --check clean on the touched files, npm run compare-translations clean, npm run build on the front succeeds).
  • No undocumented breaking change

Generated by Claude Code

Add a new scene trigger "Sun position" that starts a scene when the sun
reaches a given elevation above the horizon and/or a given compass
direction, so that shutters can be opened/closed when the sun actually
hits a facade.

Server:
- new events time.check-sun-position (internal, scheduled every minute in
  scene.init) and time.sun-position (the trigger type)
- scene.checkSunPositionTriggers computes the sun position with suncalc
  for every located house, but only when at least one active scene uses
  the trigger. suncalc returns radians with an azimuth measured from
  South: it is converted to degrees with the usual compass convention
  (0 = North, 90 = East, 180 = South, 270 = West)
- the trigger has an optional condition on the altitude and an optional
  one on the azimuth (operators >, < and =, "=" matching within 0.5°).
  The scene is started when the sun *enters* the configured area, so it
  is not re-executed at every check while the sun stays there
- Joi validation of the new trigger properties

Front:
- new SunPositionTrigger component, wired in TriggerCard and in the
  trigger type list
- en, fr and de translations

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yxguVaLdJ8ZKmw5x3HePT
@github-actions github-actions Bot added area:server Node.js server code area:front Preact front-end area:database Database models, migrations labels Aug 15, 2026
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Pierre-Gilles, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d48147fd-0b76-40ba-bc9d-064f1b7df38c

📥 Commits

Reviewing files that changed from the base of the PR and between 64eb70b and b93b1fd.

📒 Files selected for processing (15)
  • front/src/config/i18n/de.json
  • front/src/config/i18n/en.json
  • front/src/config/i18n/fr.json
  • front/src/routes/scene/edit-scene/TriggerCard.jsx
  • front/src/routes/scene/edit-scene/triggers/ChooseTriggerTypeCard.jsx
  • front/src/routes/scene/edit-scene/triggers/SunPositionTrigger.jsx
  • server/lib/scene/index.js
  • server/lib/scene/scene.checkSunPositionTriggers.js
  • server/lib/scene/scene.init.js
  • server/lib/scene/scene.triggers.js
  • server/models/scene.js
  • server/test/lib/scene/scene.checkSunPositionTriggers.test.js
  • server/test/lib/scene/scene.init.test.js
  • server/test/lib/scene/triggers/scene.trigger.sunPosition.test.js
  • server/utils/constants.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the type:feature New user-facing feature or improvement label Aug 15, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 15, 2026

Copy link
Copy Markdown

Deploying gladys-plus with  Cloudflare Pages  Cloudflare Pages

Latest commit: b93b1fd
Status: ✅  Deploy successful!
Preview URL: https://6bac0c47.gladys-plus.pages.dev
Branch Preview URL: https://claude-sun-position-trigger.gladys-plus.pages.dev

View logs

@github-actions

Copy link
Copy Markdown
Contributor

🐳 A Docker image has been built for this branch and pushed to the GitHub Container Registry.

You can test this pull request (AMD64 only) by pulling the image below:

ghcr.io/gladysassistant/gladys-preview:claude-sun-position-trigger

For example, run it with:

sudo docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --cgroupns=host \
  --restart=always \
  --privileged \
  --network=host \
  --name gladys-claude-sun-position-trigger \
  -e NODE_ENV=production \
  -e SERVER_PORT=80 \
  -e TZ=Europe/Paris \
  -e SQLITE_FILE_PATH=/var/lib/gladysassistant/gladys-production.db \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /var/lib/gladysassistant:/var/lib/gladysassistant \
  -v /dev:/dev \
  -v /run/udev:/run/udev:ro \
  ghcr.io/gladysassistant/gladys-preview:claude-sun-position-trigger

This comment and the image are automatically updated on every new commit pushed to this pull request.

Need an ARM64 image (Raspberry Pi, Apple Silicon, …)? Comment /build-arm64 on this pull request.

@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.51%. Comparing base (2f7ef52) to head (b93b1fd).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff            @@
##           master    #2893    +/-   ##
========================================
  Coverage   99.51%   99.51%            
========================================
  Files        1235     1236     +1     
  Lines       88064    88232   +168     
========================================
+ Hits        87638    87806   +168     
  Misses        426      426            

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Pierre-Gilles Pierre-Gilles added the needs:human-review Automated review is not confident, maintainer must take a look label Aug 15, 2026 — with Cursor
@cursor
cursor Bot requested a review from atrovato August 15, 2026 17:14

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Thanks for a clean first cut of this trigger: early-return when no scene listens, enter-area matching (not re-fire while the sun stays in the zone), compass conversion documented, and solid unit tests for the happy path.

This is not risk:high (additive scene trigger, no migration, no auth/host/gateway). I am adding needs:human-review because it introduces a new scene trigger UX (half-plane vs azimuth window, night-time azimuth, enter-only after restart) and the PR itself asks for a human pass. Requesting @atrovato (author is Pierre-Gilles).

Requested changes

  1. Azimuth is circular, the matcher is not. Conversion can store 360 instead of 0, and = uses linear distance. Due north (including solar midnight) will not match = 0 / = 360 reliably, and > / < mis-detect enter/leave when the sun wraps 359° → 1°. Normalize to [0, 360) and use circular distance for =.
  2. Missing night warning in the UI. The PR text says azimuth-only should be combined with an altitude condition; that help string is not in en/fr/de.
  3. Facade use case vs a single operator. A shutter facade is an azimuth range. With one >/< per axis you cannot express “between 90° and 180°”. Please confirm with a human whether v1 is threshold-crossing (two scenes for open/close) or whether the trigger should be a from/to window.

Non-blocking

  • MCP sceneSchemas.js does not know time.sun-position (Gladys Plus / AI cannot create it). Weather-alert triggers have the same gap, so not a merge blocker, but it should be added in this PR or a fast follow-up.
  • triggerEvent.altitude / azimuth are passed to the scene but not registered in the variable picker (setVariablesTrigger), unlike calendar.
  • Joi allows saving with no house and with both axes on “No condition”; those scenes never fire.
  • No DEVICE_FEATURE_CATEGORIES / types changes.

I would not merge until (1) and (2) are fixed and a human has signed off on the range vs threshold UX.

Open in Web View Automation 

Sent by Cursor Automation: Automatic PR review

Comment thread server/lib/scene/scene.checkSunPositionTriggers.js Outdated
Comment thread server/lib/scene/scene.triggers.js Outdated
Comment thread server/lib/scene/scene.triggers.js
Comment thread front/src/config/i18n/en.json Outdated
…n trigger

Address review feedback on the sun position trigger:

- convertSunPositionToDegrees now normalizes the azimuth into [0, 360[
  after rounding: an azimuth just below North used to round to exactly
  360 instead of 0
- the "=" operator now uses the circular distance on the azimuth axis,
  so "= 0" also matches an azimuth of 359.8°. The altitude keeps a plain
  linear distance, and ">" / "<" stay plain comparisons on [0, 360[:
  an azimuth area crossing North cannot be described with a single
  condition, which is now documented in the code and in the UI help text
- the azimuth help text (en, fr, de) explains that the sun is due North
  below the horizon around solar midnight, so an azimuth condition should
  be combined with an altitude one to only start the scene during the day

Autofix-Pass: 1

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of b93b1fd (follow-up on 30bbaa36). The two code blockers from the last pass are fixed, tests cover the North wrap, and CI is green (including codecov/patch). This is not risk:high: additive time.sun-position trigger, no migration, no auth/host/gateway, no DEVICE_FEATURE_CATEGORIES / types.

Addressed

  • Azimuth is normalized to [0, 360) after rounding, so due North is never stored as 360.
  • = on azimuth uses circular distance (= 0 matches 359.8°).
  • azimuthHelp (en/fr/de) now warns that azimuth-only also matches at night (combine with altitude, e.g. > 0) and that a single > / < cannot describe a sector that crosses North.

Still needs:human-review — leaving the label and @atrovato (author is Pierre-Gilles / Claude Code). This is a new scene-trigger UX; the forum case is a facade window, and v1 is two threshold scenes (open azimuth > 90, close azimuth > 180) rather than a from/to range. That is a coherent v1 and is documented, but a maintainer should still sign off before merge, plus real-life testing as the PR asks.

Non-blocking residuals

  • MCP sceneSchemas.js still does not know time.sun-position (same gap as weather-alert triggers). Gladys Plus / AI cannot create this trigger until a follow-up.
  • triggerEvent.altitude / azimuth are not registered in the scene variable picker (setVariablesTrigger).
  • Conversion is duplicated with house.getSunState (sun dashboard). That helper can still round to 360; this trigger will not. Worth sharing later so the widget and the trigger always agree at North.
  • Joi still allows saving with no house and with both axes on “No condition” (those scenes never fire). First check after restart still only stores the position (enter-area by design).

Ready to merge from a code standpoint once a human is happy with the half-plane / two-scene facade UX.

Open in Web View Automation 

Sent by Cursor Automation: Automatic PR review

@cursor cursor Bot mentioned this pull request Aug 16, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:database Database models, migrations area:front Preact front-end area:server Node.js server code needs:human-review Automated review is not confident, maintainer must take a look type:feature New user-facing feature or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants