From 0f8053de9d410bb76480f492f9b4b6e5ba187d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Wed, 22 Jul 2020 20:49:34 +0200 Subject: [PATCH] [astro] Introducing AstroActions service. (#8021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introducing AstroActions service. Signed-off-by: Gaël L'hopital --- bundles/org.openhab.binding.astro/README.md | 60 +++++++ .../astro/internal/action/AstroActions.java | 158 ++++++++++++++++++ .../astro/internal/action/IAstroActions.java | 36 ++++ .../internal/handler/AstroThingHandler.java | 31 +++- .../astro/internal/handler/MoonHandler.java | 26 ++- .../astro/internal/handler/SunHandler.java | 42 +++-- 6 files changed, 332 insertions(+), 21 deletions(-) create mode 100644 bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java create mode 100644 bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/IAstroActions.java diff --git a/bundles/org.openhab.binding.astro/README.md b/bundles/org.openhab.binding.astro/README.md index 104355c0f6d79..fc7d467b5690e 100644 --- a/bundles/org.openhab.binding.astro/README.md +++ b/bundles/org.openhab.binding.astro/README.md @@ -199,6 +199,66 @@ then end ``` +## Rule Actions + +Multiple actions are supported by this binding. In classic rules these are accessible as shown in the example below: + +Getting sunActions variable in scripts + +``` + val sunActions = getActions("astro","astro:sun:local") + if(null === sunActions) { + logInfo("actions", "sunActions not found, check thing ID") + return + } else { + // do something with sunActions + } +``` + +### getEventTime(sunPhaseName, moment, date) + +Retrieves date and time (ZonedDateTime) of the requested phase name. +Thing method only applies to Sun thing type. + +* `sunPhaseName` (String), values: `SUN_RISE, ASTRO_DAWN, NAUTIC_DAWN, CIVIL_DAWN, CIVIL_DUSK, NAUTIC_DUSK, ASTRO_DUSK, SUN_SET, DAYLIGHT, NIGHT`. Mandatory. + +* `date` (ZonedDateTime), only the date part of this parameter will be considered - defaulted to now() if null. + +* `moment` (String), values: `START, END` - defaulted to `START` if null. + +Example : + +``` + val sunEvent = "SUN_SET" + val today = ZonedDateTime.now; + val sunEventTime = sunActions.getEventTime(sunEvent,today,"START") + logInfo("AstroActions","{} will happen at : {}", sunEvent, sunEventTime.toString) +``` + +### getElevation(timeStamp) + +Retrieves the elevation (QuantityType) of the sun at the requested instant. +Thing method applies to Sun and Moon. + +* `timeStamp` (ZonedDateTime) - defaulted to now() if null. + + +### getAzimuth(timeStamp) + +Retrieves the azimuth (QuantityType) of the sun at the requested instant. +Thing method applies to Sun and Moon. + +* `timeStamp` (ZonedDateTime) - defaulted to now() if null. + +Example : + +``` + val azimuth = sunActions.getAzimuth(sunEventTime) + val elevation = sunActions.getElevation(sunEventTime) + logInfo("AstroActions", "{} will be positioned at elevation {} - azimuth {}",sunEvent, elevation.toString,azimuth.toString) +``` + + ## Tips Do not worry if for example the "astro dawn" is undefined at your location. diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java new file mode 100644 index 0000000000000..74001613b20fa --- /dev/null +++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.astro.internal.action; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.time.ZonedDateTime; + +import javax.measure.quantity.Angle; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.thing.binding.ThingActions; +import org.eclipse.smarthome.core.thing.binding.ThingActionsScope; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.openhab.binding.astro.internal.AstroBindingConstants; +import org.openhab.binding.astro.internal.handler.AstroThingHandler; +import org.openhab.binding.astro.internal.handler.SunHandler; +import org.openhab.binding.astro.internal.model.SunPhaseName; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.ActionOutput; +import org.openhab.core.automation.annotation.RuleAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {AstroActions } defines rule actions for the Astro binding. + *

+ * Note:The static method invokeMethodOf handles the case where + * the test actions instanceof AstroActions fails. This test can fail + * due to an issue in openHAB core v2.5.0 where the {@link AstroActions} class + * can be loaded by a different classloader than the actions instance. + * + * @author Gaël L'hopital - Initial contribution + */ +@ThingActionsScope(name = "astro") +@NonNullByDefault +public class AstroActions implements ThingActions, IAstroActions { + + private final Logger logger = LoggerFactory.getLogger(AstroActions.class); + protected @Nullable AstroThingHandler handler; + + public AstroActions() { + logger.debug("Astro actions service instanciated"); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof AstroThingHandler) { + this.handler = (AstroThingHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return this.handler; + } + + @Override + @RuleAction(label = "Astro : Get Azimuth", description = "Get the azimuth of the sun for a given time") + public @Nullable @ActionOutput(name = "getAzimuth", label = "Azimuth", type = "org.eclipse.smarthome.core.library.types.QuantityType") QuantityType getAzimuth( + @ActionInput(name = "date", label = "Date", required = false, description = "Considered date") @Nullable ZonedDateTime date) { + logger.debug("Astro action 'getAzimuth' called"); + AstroThingHandler theHandler = this.handler; + if (theHandler != null) { + return theHandler.getAzimuth(date != null ? date : ZonedDateTime.now()); + } else { + logger.info("Astro Action service ThingHandler is null!"); + } + return null; + } + + @Override + @RuleAction(label = "Astro : Get Elevation", description = "Get the Elevation of the sun for a given time") + public @Nullable @ActionOutput(name = "getElevation", label = "Elevation", type = "org.eclipse.smarthome.core.library.types.QuantityType") QuantityType getElevation( + @ActionInput(name = "date", label = "Date", required = false, description = "Considered date") @Nullable ZonedDateTime date) { + logger.debug("Astro action 'getElevation' called"); + AstroThingHandler theHandler = this.handler; + if (theHandler != null) { + return theHandler.getElevation(date != null ? date : ZonedDateTime.now()); + } else { + logger.info("Astro Action service ThingHandler is null!"); + } + return null; + } + + @Override + @RuleAction(label = "Sun : Get Event Time", description = "Get the date time of a given planet event") + public @Nullable @ActionOutput(name = "getEventTime", type = "java.time.ZonedDateTime") ZonedDateTime getEventTime( + @ActionInput(name = "phaseName", label = "Phase", required = true, description = "Requested phase") String phaseName, + @ActionInput(name = "date", label = "Date", required = false, description = "Considered date") @Nullable ZonedDateTime date, + @ActionInput(name = "moment", label = "Moment", required = false, defaultValue = "START", description = "Either START or END") @Nullable String moment) { + logger.debug("Sun action 'getEventTime' called"); + try { + if (handler instanceof SunHandler) { + SunHandler handler = (SunHandler) this.handler; + SunPhaseName phase = SunPhaseName.valueOf(phaseName.toUpperCase()); + return handler.getEventTime(phase, date != null ? date : ZonedDateTime.now(), + moment == null || AstroBindingConstants.EVENT_START.equalsIgnoreCase(moment)); + } else { + logger.info("Astro Action service ThingHandler is not a SunHandler!"); + } + } catch (IllegalArgumentException e) { + logger.info("Parameter {} is not a valid phase name", phaseName); + } + return null; + } + + public static @Nullable QuantityType getElevation(@Nullable ThingActions actions, + @Nullable ZonedDateTime date) { + return invokeMethodOf(actions).getElevation(date); + } + + public static @Nullable QuantityType getAzimuth(@Nullable ThingActions actions, + @Nullable ZonedDateTime date) { + return invokeMethodOf(actions).getAzimuth(date); + } + + public static @Nullable ZonedDateTime getEventTime(@Nullable ThingActions actions, @Nullable String phaseName, + @Nullable ZonedDateTime date, @Nullable String moment) { + if (phaseName != null) { + return invokeMethodOf(actions).getEventTime(phaseName, date, moment); + } else { + throw new IllegalArgumentException("phaseName can not be null"); + } + } + + private static IAstroActions invokeMethodOf(@Nullable ThingActions actions) { + if (actions == null) { + throw new IllegalArgumentException("actions cannot be null"); + } + if (actions.getClass().getName().equals(AstroActions.class.getName())) { + if (actions instanceof IAstroActions) { + return (IAstroActions) actions; + } else { + return (IAstroActions) Proxy.newProxyInstance(IAstroActions.class.getClassLoader(), + new Class[] { IAstroActions.class }, (Object proxy, Method method, Object[] args) -> { + Method m = actions.getClass().getDeclaredMethod(method.getName(), + method.getParameterTypes()); + return m.invoke(actions, args); + }); + } + } + throw new IllegalArgumentException("Actions is not an instance of AstroActions"); + } + +} diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/IAstroActions.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/IAstroActions.java new file mode 100644 index 0000000000000..df0dbd06a7700 --- /dev/null +++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/IAstroActions.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.astro.internal.action; + +import java.time.ZonedDateTime; + +import javax.measure.quantity.Angle; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.QuantityType; + +/** + * The {@link IAstroActions} defines the interface for all thing actions supported by the binding. + * + * @author Gaël L'hopital - Initial contribution + */ +@NonNullByDefault +public interface IAstroActions { + public @Nullable ZonedDateTime getEventTime(String phaseName, @Nullable ZonedDateTime date, + @Nullable String moment); + + public @Nullable QuantityType getAzimuth(@Nullable ZonedDateTime date); + + public @Nullable QuantityType getElevation(@Nullable ZonedDateTime date); +} diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java index fc8eb7492d204..42ded68a19ede 100644 --- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java +++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java @@ -17,7 +17,10 @@ import static org.eclipse.smarthome.core.types.RefreshType.REFRESH; import java.lang.invoke.MethodHandles; +import java.time.ZonedDateTime; import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -27,12 +30,15 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import javax.measure.quantity.Angle; + import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateFormatUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.i18n.TimeZoneProvider; +import org.eclipse.smarthome.core.library.types.QuantityType; import org.eclipse.smarthome.core.scheduler.CronScheduler; import org.eclipse.smarthome.core.scheduler.ScheduledCompletableFuture; import org.eclipse.smarthome.core.thing.Channel; @@ -40,12 +46,15 @@ import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.astro.internal.action.AstroActions; import org.openhab.binding.astro.internal.config.AstroChannelConfig; import org.openhab.binding.astro.internal.config.AstroThingConfig; import org.openhab.binding.astro.internal.job.Job; import org.openhab.binding.astro.internal.job.PositionalJob; import org.openhab.binding.astro.internal.model.Planet; +import org.openhab.binding.astro.internal.model.Position; import org.openhab.binding.astro.internal.util.PropertyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -318,7 +327,9 @@ private void tidyScheduledFutures() { /** * Calculates and publishes the daily Astro data. */ - public abstract void publishDailyInfo(); + public void publishDailyInfo() { + publishPositionalInfo(); + } /** * Calculates and publishes the interval Astro data. @@ -339,4 +350,22 @@ private void tidyScheduledFutures() { * Returns the daily calculation {@link Job} (cannot be {@code null}) */ protected abstract Job getDailyJob(); + + protected abstract @Nullable Position getPositionAt(ZonedDateTime date); + + public @Nullable QuantityType getAzimuth(ZonedDateTime date) { + Position position = getPositionAt(date); + return position != null ? position.getAzimuth() : null; + } + + public @Nullable QuantityType getElevation(ZonedDateTime date) { + Position position = getPositionAt(date); + return position != null ? position.getElevation() : null; + } + + @Override + public Collection> getServices() { + return Collections.singletonList(AstroActions.class); + } + } diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/MoonHandler.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/MoonHandler.java index 737bfbd0475ae..d3c8f5d0cf71d 100644 --- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/MoonHandler.java +++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/MoonHandler.java @@ -14,8 +14,10 @@ import static org.openhab.binding.astro.internal.AstroBindingConstants.THING_TYPE_MOON; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.Set; @@ -30,6 +32,7 @@ import org.openhab.binding.astro.internal.job.Job; import org.openhab.binding.astro.internal.model.Moon; import org.openhab.binding.astro.internal.model.Planet; +import org.openhab.binding.astro.internal.model.Position; /** * The MoonHandler is responsible for updating calculated moon data. @@ -54,15 +57,9 @@ public MoonHandler(Thing thing, final CronScheduler scheduler, final TimeZonePro super(thing, scheduler, timeZoneProvider); } - @Override - public void publishDailyInfo() { - initializeMoon(); - publishPositionalInfo(); - } - @Override public void publishPositionalInfo() { - initializeMoon(); + moon = getMoonAt(ZonedDateTime.now()); Double latitude = thingConfig.latitude; Double longitude = thingConfig.longitude; moonCalc.setPositionalInfo(Calendar.getInstance(), latitude != null ? latitude : 0, @@ -91,10 +88,21 @@ protected Job getDailyJob() { return new DailyJobMoon(thing.getUID().getAsString(), this); } - private void initializeMoon() { + private Moon getMoonAt(ZonedDateTime date) { Double latitude = thingConfig.latitude; Double longitude = thingConfig.longitude; - moon = moonCalc.getMoonInfo(Calendar.getInstance(), latitude != null ? latitude : 0, + return moonCalc.getMoonInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0, longitude != null ? longitude : 0); } + + @Override + protected @Nullable Position getPositionAt(ZonedDateTime date) { + Moon localMoon = getMoonAt(date); + Double latitude = thingConfig.latitude; + Double longitude = thingConfig.longitude; + moonCalc.setPositionalInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0, + longitude != null ? longitude : 0, localMoon); + return localMoon.getPosition(); + } + } diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/SunHandler.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/SunHandler.java index ccab3cabee083..29ca6982dd898 100644 --- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/SunHandler.java +++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/SunHandler.java @@ -14,8 +14,10 @@ import static org.openhab.binding.astro.internal.AstroBindingConstants.THING_TYPE_SUN; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.Set; @@ -29,7 +31,10 @@ import org.openhab.binding.astro.internal.job.DailyJobSun; import org.openhab.binding.astro.internal.job.Job; import org.openhab.binding.astro.internal.model.Planet; +import org.openhab.binding.astro.internal.model.Position; +import org.openhab.binding.astro.internal.model.Range; import org.openhab.binding.astro.internal.model.Sun; +import org.openhab.binding.astro.internal.model.SunPhaseName; /** * The SunHandler is responsible for updating calculated sun data. @@ -54,19 +59,14 @@ public SunHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProv super(thing, scheduler, timeZoneProvider); } - @Override - public void publishDailyInfo() { - initializeSun(); - publishPositionalInfo(); - } - @Override public void publishPositionalInfo() { - initializeSun(); + sun = getSunAt(ZonedDateTime.now()); Double latitude = thingConfig.latitude; Double longitude = thingConfig.longitude; + Double altitude = thingConfig.altitude; sunCalc.setPositionalInfo(Calendar.getInstance(), latitude != null ? latitude : 0, - longitude != null ? longitude : 0, thingConfig.altitude, sun); + longitude != null ? longitude : 0, altitude != null ? altitude : 0, sun); publishPlanet(); } @@ -91,10 +91,30 @@ protected Job getDailyJob() { return new DailyJobSun(thing.getUID().getAsString(), this); } - private void initializeSun() { + private Sun getSunAt(ZonedDateTime date) { Double latitude = thingConfig.latitude; Double longitude = thingConfig.longitude; - sun = sunCalc.getSunInfo(Calendar.getInstance(), latitude != null ? latitude : 0, - longitude != null ? longitude : 0, thingConfig.altitude, thingConfig.useMeteorologicalSeason); + Double altitude = thingConfig.altitude; + return sunCalc.getSunInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0, + longitude != null ? longitude : 0, altitude != null ? altitude : 0, + thingConfig.useMeteorologicalSeason); } + + public @Nullable ZonedDateTime getEventTime(SunPhaseName sunPhase, ZonedDateTime date, boolean begin) { + Range eventRange = getSunAt(date).getAllRanges().get(sunPhase); + Calendar cal = begin ? eventRange.getStart() : eventRange.getEnd(); + return ZonedDateTime.ofInstant(cal.toInstant(), date.getZone()); + } + + @Override + protected @Nullable Position getPositionAt(ZonedDateTime date) { + Sun localSun = getSunAt(date); + Double latitude = thingConfig.latitude; + Double longitude = thingConfig.longitude; + Double altitude = thingConfig.altitude; + sunCalc.setPositionalInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0, + longitude != null ? longitude : 0, altitude != null ? altitude : 0, localSun); + return localSun.getPosition(); + } + }