My take at Time of Day

Although there are other approaches defined, I wanted to share my take at Time of Day. With my approach, I define the time of day based on solar elevation and whether the time is before or after noontime (12:00, not the solar noon). The dawn and dusk are defined as how they are scientifically defined (6, 12 and 18 degrees below horizon). For the sunrise and sunset times, I decided to take the interval [-3°; +3°] for sun elevation.

The rule is triggered whenever sun azimut (compass direction) is updated. In practice, this means that the rule is triggered at every update of the sun info by the astro binding. This can’t be achieved with other sun channels.

The default astro binding only updates each 300 seconds, so I decided to add new astro things that update each 60 seconds in /etc/openhab2/things/astro.things:

astro:sun:home [ geolocation="lat,lon,alt", interval=60 ]
astro:moon:home [ geolocation="lat,lon", interval=60 ]

Replace lat, and lon with your place’s latitude and longitude (in decimal degrees). The parameter alt is optional but is used e.g. for the radiation items provided by the astro binding.

Here are the relevant astro items (defined in /etc/openhab2/items/astro.items):

Group			gAstro						"Astro binding"				<sun_clouds>		(W66a)
Group			gAstroSun					"Astro binding: Sun"		<sun>				(W66a, gAstro)

Number:Angle	Astro_Sun_Azimuth			"Azimut [%.2f %unit%]"					<incline>		(gAstroSun)	{ channel="astro:sun:home:position#azimuth" }
Number:Angle	Astro_Sun_Elevation			"Elevation [%.2f %unit%]"					<incline>		(gAstroSun)	{ channel="astro:sun:home:position#elevation" }

// Time of day (set by a rule)
String			Rule_setTimeOfDay				"Time of day [%s]"			<tod>

For Rule_setTimeOfDay I set as icon tod but of course you’ll have to provide them. You can make custom dynamic icons by storing them in /etc/openhab2/icons/classic/ and by naming them as follows:

Time of day state SVG format PNG format
(default) tod.svg tod.png
MORNING_NIGHT tod-morning_night.svg tod-morning_night.png
ASTRO_DAWN tod-astro_dawn.svg tod-astro_dawn.png
NAUTIC_DAWN tod-nautic_dawn.svg tod-nautic_dawn.png
CIVIL_DAWN tod-civil_dawn.svg tod-civil_dawn.png
SUNRISE tod-sunrise.svg tod-sunrise.png
MORNING tod-morning.svg tod-morning.png
AFTERNOON tod-afternoon.svg tod-afternoon.png
SUNSET tod-sunset.svg tod-sunset.png
CIVIL_DUSK tod-civil_dusk.svg tod-civil_dusk.png
NAUTIC_DUSK tod-nautic_dusk.svg tod-nautic_dusk.png
ASTRO_DUSK tod-astro_dusk.svg tod-astro_dusk.png
EVENING_NIGHT tod-evening_night.svg tod-evening_night.png

And here comes the rule (store it in /etc/openhab2/automation/jsr223/python/personal/):

# Time Of Day

from core.rules import rule
from core.triggers import when

from core.actions import LogAction
from core.actions import Telegram

from org.joda.time import DateTime


# Rule initialization (used for uniquely identifying rules in case of problems):
rule_init_timestamp = DateTime.now()
logTitle = "time_of_day.py@{ts}".format(ts=rule_init_timestamp.toString("HH:mm:ss"))
ruleTimeStamp = " -- (Rule set initialised {date} at {ts})".format(
    date=rule_init_timestamp.toString("E d MMM yyyy"),
    ts=rule_init_timestamp.toString("HH:mm:ss (z)"),
)
rulePrefix = "Time Of Day | "


# The following items must exist in the item registry:
class my_rule_items:
    TIME_OF_DAY = "Astro_Time_Of_Day"
    SUN_AZIMUT = "Astro_Sun_Azimut"
    SUN_ELEVATION = "Astro_Sun_Elevation"


# The following Time Of Day states are defined (triggered by sun elevation and AM/PM):
class TimeOfDayStates:
    MORNING_NIGHT = "MORNING_NIGHT"
    ASTRO_DAWN = "ASTRO_DAWN"
    NAUTIC_DAWN = "NAUTIC_DAWN"
    CIVIL_DAWN = "CIVIL_DAWN"
    SUNRISE = "SUNRISE"
    MORNING = "MORNING"
    AFTERNOON = "AFTERNOON"
    SUNSET = "SUNSET"
    CIVIL_DUSK = "CIVIL_DUSK"
    NAUTIC_DUSK = "NAUTIC_DUSK"
    ASTRO_DUSK = "ASTRO_DUSK"
    EVENING_NIGHT = "EVENING_NIGHT"


def get_sun_elevation():
    global logTitle
    logPrefix = "get_sun_elevation(): "
    sun_elevation_item = itemRegistry.getItem(my_rule_items.SUN_ELEVATION)
    if sun_elevation_item is None:
        LogAction.logWarn(
            logTitle,
            logPrefix
            + u"⚠️ Sun Elevation Item '{}' not found in Item Registry".format(
                my_rule_items.SUN_ELEVATION
            ),
        )
        return None
    if not isinstance(sun_elevation_item.state, QuantityType):
        LogAction.logWarn(
            logTitle,
            logPrefix
            + u"⚠️ Incorrect state for elevation ({}): {}".format(
                sun_elevation_item.state, type(sun_elevation_item.state)
            ),
        )
        return None
    elevation = sun_elevation_item.state.floatValue()
    LogAction.logDebug(
        logTitle, logPrefix + u"Sun elevation = {} °".format("%.2f" % elevation)
    )
    return elevation


@rule(
    rulePrefix + "Set the time of day",
    description=u"Set the time of day depending on solar elevation and time."
    + ruleTimeStamp,
    tags=["TimeOfDay", "Astro", ruleTimeStamp],
)
@when("System started")
@when("Item {} changed".format(my_rule_items.SUN_AZIMUT))
def Rule_setTimeOfDay(event):
    global logTitle
    logPrefix = "Rule_setTimeOfDay(): "

    dt_now = DateTime.now()
    am = dt_now.isBefore(dt_now.withTimeAtStartOfDay().plusHours(12))

    elevation = get_sun_elevation()

    tod_item = itemRegistry.getItem(my_rule_items.TIME_OF_DAY)
    if tod_item is None:
        LogAction.logError(
            logTitle,
            logPrefix
            + u"⚠️ Time Of Day Item '{}' not found in Item Registry".format(
                my_rule_items.TIME_OF_DAY
            ),
        )
        return
    if elevation > 3.0:
        tod = TimeOfDayStates.MORNING if am else TimeOfDayStates.AFTERNOON
    elif elevation > -3.0:
        tod = TimeOfDayStates.SUNRISE if am else TimeOfDayStates.SUNSET
    elif elevation > -6.0:
        tod = TimeOfDayStates.CIVIL_DAWN if am else TimeOfDayStates.CIVIL_DUSK
    elif elevation > -12.0:
        tod = TimeOfDayStates.NAUTIC_DAWN if am else TimeOfDayStates.NAUTIC_DUSK
    elif elevation > -18.0:
        tod = TimeOfDayStates.ASTRO_DAWN if am else TimeOfDayStates.ASTRO_DUSK
    else:
        tod = TimeOfDayStates.MORNING_NIGHT if am else TimeOfDayStates.EVENING_NIGHT

    if str(tod_item.state) != tod:
        events.sendCommand(tod_item, tod)


# NOTE - "when System shuts down" does not yet work. Using workaround, see:
# https://openhab-scripters.github.io/openhab-helper-libraries/Guides/Triggers.html
def scriptUnloaded():
    # call rule when this file is unloaded
    global logTitle
    logPrefix = "scriptUnloaded(): "

    LogAction.logInfo(
        logTitle,
        logPrefix
        + "Shutting down -- 'scriptUnloaded()' hook -- Clearing the state of '{}'".format(
            my_rule_items.TIME_OF_DAY
        ),
    )
    tod_item = itemRegistry.getItem(my_rule_items.TIME_OF_DAY)
    if tod_item:
        events.postUpdate(tod_item, UnDefType.UNDEF)


# Initialise the time of day when the script is (re)loaded:
Rule_setTimeOfDay(None)

Now you can use Astro_Time_Of_Day in your rules.

Just in case you want to render the time of date nicely, here’s a map transform I use (store e.g. in /etc/openhab2/transforms/ as e.g. astro.map):

MORNING_NIGHT=Morning Night
ASTRO_DAWN=Astronomic Dawn
NAUTIC_DAWN=Nautic Dawn
CIVIL_DAWN=Civil Dawn
SUN_RISE=Sunrise
SUNRISE=Sunrise
MORNING=Morning
DAYLIGHT=Daylight
AFTERNOON=Afternoon
SUN_SET=Sunset
SUNSET=Sunset
CIVIL_DUSK=Civil Dusk
NAUTIC_DUSK=Nautic Dusk
ASTRO_DUSK=Astronomic Dusk
EVENING_NIGHT=Evening Night
NOON=Noon
NIGHT=Night
//
NULL=Unknown (NULL)
-=Unknown (-)

This way you can render it nicely in a sitemap, as in:

Default item=Astro_Time_Of_Day label="Time of day [MAP(astro.map):%s]"

Have fun!

10 Likes

Very interesting piece of code, but what is the advantage toward the Channel

astro:sun:home:phase#name

?

I had the same question really. But I can see an advantage if you want to create some times of day that are not using the scientific definition but based on the sun elevation.

My approach allows for more flexibility. If you only need automations based on astro:sun:home:phase#name then you don’t need this, of course.

I find it useful to define some of the times of day depending on the elevation of the sun, and to distinguish between morning (going from dark to light) and evening (going from light to dark). Indeed, I’d rather start wit a tad less light in the morning than in the evening since our eyes haven’t seen bright daylight in the (winter) morning.