added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.astro</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,269 @@
# Astro Binding
The Astro binding is used for calculating
* many DateTime and positional values for sun and moon.
* Radiation levels (direct, diffuse and total) of the sun during the day
## Supported Things
This binding supports two Things: Sun and Moon
## Discovery
If a system location is set, "Local Sun" and a "Local Moon" will be automatically discovered for this location.
If the system location is changed, the background discovery updates the configuration of "Local Sun" and "Local Moon" automatically.
## Binding Configuration
No binding configuration required.
## Thing Configuration
All Things require the parameter `geolocation` (as `<latitude>,<longitude>[,<altitude in m>]`) for which the calculation is done.
The altitude segment is optional and sharpens results provided by the Radiation group.
Optionally, a refresh `interval` (in seconds) can be defined to also calculate positional data like azimuth and elevation.
Season calculation can be switched from equinox based calculation to meteorological based (starting on the first day of the given month).
This is done by setting `useMeteorologicalSeason` to true in the advanced setting of the sun.
## Channels
* **thing** `sun`
* **group** `rise, set, noon, night, morningNight, astroDawn, nauticDawn, civilDawn, astroDusk, nauticDusk, civilDusk, eveningNight, daylight`
* **channel**
* `start, end` (DateTime)
* `duration` (Number:Time)
* **group** `position`
* **channel**
* `azimuth, elevation` (Number:Angle)
* `shadeLength` (Number)
* **group** `radiation`
* **channel**
* `direct, diffuse, total` (Number:Intensity)
* **group** `zodiac`
* **channel**
* `start, end` (DateTime)
* `sign` (String), values: `ARIES, TAURUS, GEMINI, CANCER, LEO, VIRGO, LIBRA, SCORPIO, SAGITTARIUS, CAPRICORN, AQUARIUS, PISCES`
* **group** `season`
* **channel**:
* `spring, summer, autumn, winter` (DateTime)
* `name`,`nextName` (String), values `SPRING, SUMMER, AUTUMN, WINTER`
* `timeLeft` (Number:Time)
* **group** `eclipse`
* **channel**:
* `total, partial, ring` (DateTime)
* `totalElevation, partialElevation, ringElevation` (Number:Angle)
* **group** `phase`
* **channel**
* `name` (String), values: `SUN_RISE, ASTRO_DAWN, NAUTIC_DAWN, CIVIL_DAWN, CIVIL_DUSK, NAUTIC_DUSK, ASTRO_DUSK, SUN_SET, DAYLIGHT, NIGHT`
* **thing** `moon`
* **group** `rise, set`
* **channel**
* `start, end` (DateTime)
* **group** `phase`
* **channel**:
* `firstQuarter, thirdQuarter, full, new` (DateTime)
* `age` (Number:Time)
* `ageDegree` (Number:Angle)
* `agePercent, illumination` (Number:Dimensionless)
* `name` (String), values: `NEW, WAXING_CRESCENT, FIRST_QUARTER, WAXING_GIBBOUS, FULL, WANING_GIBBOUS, THIRD_QUARTER, WANING_CRESCENT`
* **group** `eclipse`
* **channel**:
* `total, partial` (DateTime)
* `totalElevation, partialElevation` (Number:Angle)
* **group** `distance`
* **channel**:
* `date` (DateTime)
* `distance` (Number:Length)
* **group** `perigee`
* **channel**:
* `date` (DateTime),
* `distance` (Number:Length)
* **group** `apogee`
* **channel**:
* `date` (DateTime)
* `distance` (Number:Length)
* **group** `zodiac`
* **channel**
* `sign` (String), values: `ARIES, TAURUS, GEMINI, CANCER, LEO, VIRGO, LIBRA, SCORPIO, SAGITTARIUS, CAPRICORN, AQUARIUS, PISCES`
* **group** `position`
* **channel**
* `azimuth, elevation` (Number:Angle)
### Trigger Channels
* **thing** `sun`
* **group** `rise, set, noon, night, morningNight, astroDawn, nauticDawn, civilDawn, astroDusk, nauticDusk, civilDusk, eveningNight, daylight`
* **event** `START, END`
* **group** `eclipse`
* **event**: `TOTAL, PARTIAL, RING`
* **thing** `moon`
* **group** `rise`
* **event** `START`
* **group** `set`
* **event** `END`
* **group** `phase`
* **event**: `FIRST_QUARTER, THIRD_QUARTER, FULL, NEW`
* **group** `eclipse`
* **event**: `TOTAL, PARTIAL`
* **group** `perigee`
* **event**: `PERIGEE`
* **group** `apogee`
* **event**: `APOGEE`
### Channel config
**Offsets:** For each event group you can optionally configure an `offset` in minutes.
The `offset` must be configured in the channel properties for the corresponding thing.
The minimum allowed offset is -1440 and the maximum allowed offset is 1440.
**Earliest/Latest:** For each trigger channel and `start`, `end` datetime value, you can optionally configure the `earliest` and `latest` time of the day.
e.g `sun#set earliest=18:00, latest=20:00`
sunset is 17:40, but `earliest` is set to 18:00 so the event/datetime value is moved to 18:00.
OR
sunset is 22:10 but `latest` is set to 20:00 so the event/datetime value is moved 20:00.
## Full Example
Things:
```
astro:sun:home [ geolocation="52.5200066,13.4049540,100", interval=60 ]
astro:moon:home [ geolocation="52.5200066,13.4049540", interval=60 ]
```
or optionally with an event offset
```
astro:sun:home [ geolocation="52.5200066,13.4049540,100", interval=60 ] {
Channels:
Type rangeEvent : rise#event [
offset=-30
]
}
astro:moon:home [ geolocation="52.5200066,13.4049540", interval=60 ]
```
or a datetime offset
```
astro:sun:home [ geolocation="52.5200066,13.4049540,100", interval=60 ] {
Channels:
Type start : rise#start [
offset=5
]
Type end : rise#end [
offset=5
]
}
```
or an offset and latest
```
astro:sun:home [ geolocation="52.5200066,13.4049540,100", interval=60 ] {
Channels:
Type rangeEvent : rise#event [
offset=-10,
latest="08:00"
]
}
```
Items:
```
DateTime Sunrise_Time "Sunrise [%1$tH:%1$tM]" { channel="astro:sun:home:rise#start" }
DateTime Sunset_Time "Sunset [%1$tH:%1$tM]" { channel="astro:sun:home:set#start" }
Number:Angle Azimuth "Azimuth" { channel="astro:sun:home:position#azimuth" }
Number:Angle Elevation "Elevation" { channel="astro:sun:home:position#elevation" }
String MoonPhase "MoonPhase" { channel="astro:moon:home:phase#name" }
Number:Length MoonDistance "MoonDistance [%.1f %unit%]" { channel="astro:moon:home:distance#distance" }
Number:Intensity Total_Radiation "Radiation [%.2f %unit%]" { channel="astro:sun:home:radiation#total" }
Number:Intensity Diffuse_Radiation "Diffuse Radiation [%.2f %unit%]" { channel="astro:sun:home:radiation#diffuse" }
```
Events:
```
rule "example trigger rule"
when
Channel 'astro:sun:home:rise#event' triggered START
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(phaseName, date, moment)
Retrieves date and time (ZonedDateTime) of the requested phase name.
Thing method only applies to Sun thing type.
* `phaseName` (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<Angle>) 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<Angle>) 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.
The reason might be that you live in a northern country and it is summer, such that the sun is not 18 degrees below the horizon in the morning.
For details see [this Wikipedia article](https://en.wikipedia.org/wiki/Dawn).
The "civil dawn" event might often be the better choice.

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.astro</artifactId>
<name>openHAB Add-ons :: Bundles :: Astro Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.astro-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-astro" description="Astro Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.astro/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,76 @@
/**
* 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AstroBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Made non-Instantiable
*/
@NonNullByDefault
public final class AstroBindingConstants {
/** Constructor */
private AstroBindingConstants() {
throw new IllegalAccessError("Non-instantiable");
}
public static final String BINDING_ID = "astro";
public static final String SUN = "sun";
public static final String MOON = "moon";
public static final String LOCAL = "local";
// things
public static final ThingTypeUID THING_TYPE_SUN = new ThingTypeUID(BINDING_ID, SUN);
public static final ThingTypeUID THING_TYPE_MOON = new ThingTypeUID(BINDING_ID, MOON);
// events
public static final String EVENT_START = "START";
public static final String EVENT_END = "END";
public static final String EVENT_PHASE_FIRST_QUARTER = "FIRST_QUARTER";
public static final String EVENT_PHASE_THIRD_QUARTER = "THIRD_QUARTER";
public static final String EVENT_PHASE_FULL = "FULL";
public static final String EVENT_PHASE_NEW = "NEW";
public static final String EVENT_PERIGEE = "PERIGEE";
public static final String EVENT_APOGEE = "APOGEE";
// event channelIds
public static final String EVENT_CHANNEL_ID_MOON_PHASE = "phase#event";
public static final String EVENT_CHANNEL_ID_ECLIPSE = "eclipse#event";
public static final String EVENT_CHANNEL_ID_PERIGEE = "perigee#event";
public static final String EVENT_CHANNEL_ID_APOGEE = "apogee#event";
public static final String EVENT_CHANNEL_ID_RISE = "rise#event";
public static final String EVENT_CHANNEL_ID_SET = "set#event";
public static final String EVENT_CHANNEL_ID_NOON = "noon#event";
public static final String EVENT_CHANNEL_ID_NIGHT = "night#event";
public static final String EVENT_CHANNEL_ID_MORNING_NIGHT = "morningNight#event";
public static final String EVENT_CHANNEL_ID_ASTRO_DAWN = "astroDawn#event";
public static final String EVENT_CHANNEL_ID_NAUTIC_DAWN = "nauticDawn#event";
public static final String EVENT_CHANNEL_ID_CIVIL_DAWN = "civilDawn#event";
public static final String EVENT_CHANNEL_ID_ASTRO_DUSK = "astroDusk#event";
public static final String EVENT_CHANNEL_ID_NAUTIC_DUSK = "nauticDusk#event";
public static final String EVENT_CHANNEL_ID_CIVIL_DUSK = "civilDusk#event";
public static final String EVENT_CHANNEL_ID_EVENING_NIGHT = "eveningNight#event";
public static final String EVENT_CHANNEL_ID_DAYLIGHT = "daylight#event";
public static final String CHANNEL_ID_SUN_PHASE_NAME = "phase#name";
}

View File

@@ -0,0 +1,91 @@
/**
* 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;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.handler.MoonHandler;
import org.openhab.binding.astro.internal.handler.SunHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link AstroHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.astro", service = ThingHandlerFactory.class)
public class AstroHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
.concat(SunHandler.SUPPORTED_THING_TYPES.stream(), MoonHandler.SUPPORTED_THING_TYPES.stream())
.collect(Collectors.toSet());
private static final Map<String, AstroThingHandler> ASTRO_THING_HANDLERS = new HashMap<>();
private final CronScheduler scheduler;
private final TimeZoneProvider timeZoneProvider;
@Activate
public AstroHandlerFactory(final @Reference CronScheduler scheduler,
final @Reference TimeZoneProvider timeZoneProvider) {
this.scheduler = scheduler;
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
AstroThingHandler thingHandler = null;
if (thingTypeUID.equals(THING_TYPE_SUN)) {
thingHandler = new SunHandler(thing, scheduler, timeZoneProvider);
} else if (thingTypeUID.equals(THING_TYPE_MOON)) {
thingHandler = new MoonHandler(thing, scheduler, timeZoneProvider);
}
if (thingHandler != null) {
ASTRO_THING_HANDLERS.put(thing.getUID().toString(), thingHandler);
}
return thingHandler;
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ASTRO_THING_HANDLERS.remove(thing.getUID().toString());
}
public static @Nullable AstroThingHandler getHandler(String thingUid) {
return ASTRO_THING_HANDLERS.get(thingUid);
}
}

View File

@@ -0,0 +1,162 @@
/**
* 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.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.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {AstroActions } defines rule actions for the Astro binding.
* <p>
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
* the test <i>actions instanceof AstroActions</i> 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 <i>actions</i> 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.openhab.core.library.types.QuantityType<javax.measure.quantity.Angle>") QuantityType<Angle> 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.openhab.core.library.types.QuantityType<javax.measure.quantity.Angle>") QuantityType<Angle> 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 {
AstroThingHandler theHandler = this.handler;
if (theHandler != null) {
if (theHandler instanceof SunHandler) {
SunHandler handler = (SunHandler) theHandler;
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!");
}
} else {
logger.info("Astro Action service ThingHandler is null!");
}
} catch (IllegalArgumentException e) {
logger.info("Parameter {} is not a valid phase name", phaseName);
}
return null;
}
public static @Nullable QuantityType<Angle> getElevation(@Nullable ThingActions actions,
@Nullable ZonedDateTime date) {
return invokeMethodOf(actions).getElevation(date);
}
public static @Nullable QuantityType<Angle> 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");
}
}

View File

@@ -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.openhab.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<Angle> getAzimuth(@Nullable ZonedDateTime date);
public @Nullable QuantityType<Angle> getElevation(@Nullable ZonedDateTime date);
}

View File

@@ -0,0 +1,857 @@
/**
* 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.calc;
import java.math.BigDecimal;
import java.util.Calendar;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.EclipseKind;
import org.openhab.binding.astro.internal.model.EclipseType;
import org.openhab.binding.astro.internal.model.Moon;
import org.openhab.binding.astro.internal.model.MoonDistance;
import org.openhab.binding.astro.internal.model.MoonPhase;
import org.openhab.binding.astro.internal.model.MoonPhaseName;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Zodiac;
import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
* Calculates the phase, eclipse, rise, set, distance, illumination and age of
* the moon.
*
* @author Gerhard Riegler - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
* @see based on the calculations of
* http://www.computus.de/mondphase/mondphase.htm azimuth/elevation and
* zodiac based on http://lexikon.astronomie.info/java/sunmoon/
*/
public class MoonCalc {
private static final double NEW_MOON = 0;
private static final double FULL_MOON = 0.5;
private static final double FIRST_QUARTER = 0.25;
private static final double LAST_QUARTER = 0.75;
/**
* Calculates all moon data at the specified coordinates
*/
public Moon getMoonInfo(Calendar calendar, double latitude, double longitude) {
Moon moon = new Moon();
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
double julianDateMidnight = DateTimeUtils.midnightDateToJulianDate(calendar);
double[] riseSet = getRiseSet(calendar, latitude, longitude);
Calendar rise = DateTimeUtils.timeToCalendar(calendar, riseSet[0]);
Calendar set = DateTimeUtils.timeToCalendar(calendar, riseSet[1]);
if (rise == null || set == null) {
Calendar tomorrow = (Calendar) calendar.clone();
tomorrow.add(Calendar.DAY_OF_MONTH, 1);
double[] riseSeTomorrow = getRiseSet(tomorrow, latitude, longitude);
if (rise == null) {
rise = DateTimeUtils.timeToCalendar(tomorrow, riseSeTomorrow[0]);
}
if (set == null) {
set = DateTimeUtils.timeToCalendar(tomorrow, riseSeTomorrow[1]);
}
}
moon.setRise(new Range(rise, rise));
moon.setSet(new Range(set, set));
MoonPhase phase = moon.getPhase();
phase.setNew(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, NEW_MOON)));
phase.setFirstQuarter(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FIRST_QUARTER)));
phase.setFull(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FULL_MOON)));
phase.setThirdQuarter(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, LAST_QUARTER)));
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
double jdate = getEclipse(calendar, EclipseType.MOON, julianDateMidnight, eclipseKind);
eclipse.set(eclipseKind, DateTimeUtils.toCalendar(jdate), new Position());
});
double decimalYear = DateTimeUtils.getDecimalYear(calendar);
MoonDistance apogee = moon.getApogee();
double apogeeJd = getApogee(julianDate, decimalYear);
apogee.setDate(DateTimeUtils.toCalendar(apogeeJd));
apogee.setDistance(getDistance(apogeeJd));
MoonDistance perigee = moon.getPerigee();
double perigeeJd = getPerigee(julianDate, decimalYear);
perigee.setDate(DateTimeUtils.toCalendar(perigeeJd));
perigee.setDistance(getDistance(perigeeJd));
return moon;
}
/**
* Calculates the moon illumination and distance.
*/
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, Moon moon) {
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
setMoonPhase(calendar, moon);
setAzimuthElevationZodiac(julianDate, latitude, longitude, moon);
MoonDistance distance = moon.getDistance();
distance.setDate(Calendar.getInstance());
distance.setDistance(getDistance(julianDate));
}
/**
* Calculates the age and the current phase.
*/
private void setMoonPhase(Calendar calendar, Moon moon) {
MoonPhase phase = moon.getPhase();
double julianDateEndOfDay = DateTimeUtils.endOfDayDateToJulianDate(calendar);
double parentNewMoon = getPreviousPhase(calendar, julianDateEndOfDay, NEW_MOON);
double age = Math.abs(parentNewMoon - julianDateEndOfDay);
phase.setAge((int) age);
long parentNewMoonMillis = DateTimeUtils.toCalendar(parentNewMoon).getTimeInMillis();
long ageRangeTimeMillis = phase.getNew().getTimeInMillis() - parentNewMoonMillis;
long ageCurrentMillis = System.currentTimeMillis() - parentNewMoonMillis;
double agePercent = ageRangeTimeMillis != 0 ? ageCurrentMillis * 100.0 / ageRangeTimeMillis : 0;
phase.setAgePercent(agePercent);
phase.setAgeDegree(3.6 * agePercent);
double illumination = getIllumination(DateTimeUtils.dateToJulianDate(calendar));
phase.setIllumination(illumination);
boolean isWaxing = age < (29.530588853 / 2);
if (DateTimeUtils.isSameDay(calendar, phase.getNew())) {
phase.setName(MoonPhaseName.NEW);
} else if (DateTimeUtils.isSameDay(calendar, phase.getFirstQuarter())) {
phase.setName(MoonPhaseName.FIRST_QUARTER);
} else if (DateTimeUtils.isSameDay(calendar, phase.getThirdQuarter())) {
phase.setName(MoonPhaseName.THIRD_QUARTER);
} else if (DateTimeUtils.isSameDay(calendar, phase.getFull())) {
phase.setName(MoonPhaseName.FULL);
} else if (illumination >= 0 && illumination < 50) {
phase.setName(isWaxing ? MoonPhaseName.WAXING_CRESCENT : MoonPhaseName.WANING_CRESCENT);
} else if (illumination >= 50 && illumination < 100) {
phase.setName(isWaxing ? MoonPhaseName.WAXING_GIBBOUS : MoonPhaseName.WANING_GIBBOUS);
}
}
/**
* Calculates moonrise and moonset.
*/
private double[] getRiseSet(Calendar calendar, double latitude, double longitude) {
double lambda = prepareCoordinate(longitude, 180);
if (longitude > 0) {
lambda *= -1;
}
double phi = prepareCoordinate(latitude, 90);
if (latitude < 0) {
phi *= -1;
}
double moonJd = Math.floor(DateTimeUtils.midnightDateToJulianDate(calendar)) - 2400000.0;
moonJd -= ((calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / 60000.0) / 1440.0;
double sphi = SN(phi);
double cphi = CS(phi);
double sinho = SN(8.0 / 60.0);
int hour = 1;
double utrise = -1;
double utset = -1;
do {
double yminus = SINALT(moonJd, hour - 1, lambda, cphi, sphi) - sinho;
double yo = SINALT(moonJd, hour, lambda, cphi, sphi) - sinho;
double yplus = SINALT(moonJd, hour + 1, lambda, cphi, sphi) - sinho;
double[] quadRet = QUAD(yminus, yo, yplus);
if (quadRet[3] == 1) {
if (yminus < 0) {
utrise = hour + quadRet[1];
} else {
utset = hour + quadRet[1];
}
}
if (quadRet[3] == 2) {
if (quadRet[0] < 0) {
utrise = hour + quadRet[2];
utset = hour + quadRet[1];
} else {
utrise = hour + quadRet[1];
utset = hour + quadRet[2];
}
}
yminus = yplus;
hour += 2;
} while (hour < 25 && (utrise == -1 || utset == -1));
double rise = prepareTime(utrise);
double set = prepareTime(utset);
return new double[] { rise, set };
}
/**
* Prepares the coordinate for moonrise and moonset calculation.
*/
private double prepareCoordinate(double coordinate, double system) {
double c = Math.abs(coordinate);
if (c - Math.floor(c) >= .599) {
c = Math.floor(c) + (c - Math.floor(c)) / 1 * .6;
}
if (c > system) {
c = Math.floor(c) % system + (c - Math.floor(c));
}
return Math.round(c * 100.0) / 100.0;
}
/**
* Prepares a time value for converting to a calendar object.
*/
private double prepareTime(double riseSet) {
if (riseSet == -1) {
return riseSet;
}
double riseMinute = (riseSet - Math.floor(riseSet)) * 60.0 / 100.0;
double rounded;
if (riseMinute >= .595) {
riseMinute = 0;
rounded = riseSet + 1;
} else {
rounded = riseSet;
}
rounded = Math.floor(rounded) + riseMinute;
BigDecimal bd = new BigDecimal(Double.toString(rounded));
bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
}
/**
* Calculates the moon phase.
*/
private double calcMoonPhase(double k, double mode) {
double kMod = Math.floor(k) + mode;
double t = kMod / 1236.85;
double e = var_e(t);
double m = var_m(kMod, t);
double m1 = var_m1(kMod, t);
double f = var_f(kMod, t);
double o = var_o(kMod, t);
double jd = var_jde(kMod, t);
if (mode == NEW_MOON) {
jd += -.4072 * SN(m1) + .17241 * e * SN(m) + .01608 * SN(2 * m1) + .01039 * SN(2 * f)
+ .00739 * e * SN(m1 - m) - .00514 * e * SN(m1 + m) + .00208 * e * e * SN(2 * m)
- .00111 * SN(m1 - 2 * f) - .00057 * SN(m1 + 2 * f);
jd += .00056 * e * SN(2 * m1 + m) - .00042 * SN(3 * m1) + .00042 * e * SN(m + 2 * f)
+ .00038 * e * SN(m - 2 * f) - .00024 * e * SN(2 * m1 - m) - .00017 * SN(o)
- .00007 * SN(m1 + 2 * m) + .00004 * SN(2 * m1 - 2 * f);
jd += .00004 * SN(3 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(2 * m1 + 2 * f)
- .00003 * SN(m1 + m + 2 * f) + .00003 * SN(m1 - m + 2 * f) - .00002 * SN(m1 - m - 2 * f)
- .00002 * SN(3 * m1 + m);
jd += .00002 * SN(4 * m1);
} else if (mode == FULL_MOON) {
jd += -.40614 * SN(m1) + .17302 * e * SN(m) + .01614 * SN(2 * m1) + .01043 * SN(2 * f)
+ .00734 * e * SN(m1 - m) - .00515 * e * SN(m1 + m) + .00209 * e * e * SN(2 * m)
- .00111 * SN(m1 - 2 * f) - .00057 * SN(m1 + 2 * f);
jd += .00056 * e * SN(2 * m1 + m) - .00042 * SN(3 * m1) + .00042 * e * SN(m + 2 * f)
+ .00038 * e * SN(m - 2 * f) - .00024 * e * SN(2 * m1 - m) - .00017 * SN(o)
- .00007 * SN(m1 + 2 * m) + .00004 * SN(2 * m1 - 2 * f);
jd += .00004 * SN(3 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(2 * m1 + 2 * f)
- .00003 * SN(m1 + m + 2 * f) + .00003 * SN(m1 - m + 2 * f) - .00002 * SN(m1 - m - 2 * f)
- .00002 * SN(3 * m1 + m);
jd += .00002 * SN(4 * m1);
} else {
jd += -.62801 * SN(m1) + .17172 * e * SN(m) - .01183 * e * SN(m1 + m) + .00862 * SN(2 * m1)
+ .00804 * SN(2 * f) + .00454 * e * SN(m1 - m) + .00204 * e * e * SN(2 * m) - .0018 * SN(m1 - 2 * f)
- .0007 * SN(m1 + 2 * f);
jd += -.0004 * SN(3 * m1) - .00034 * e * SN(2 * m1 - m) + .00032 * e * SN(m + 2 * f)
+ .00032 * e * SN(m - 2 * f) - .00028 * e * e * SN(m1 + 2 * m) + .00027 * e * SN(2 * m1 + m)
- .00017 * SN(o);
jd += -.00005 * SN(m1 - m - 2 * f) + .00004 * SN(2 * m1 + 2 * f) - .00004 * SN(m1 + m + 2 * f)
+ .00004 * SN(m1 - 2 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(3 * m)
+ .00002 * SN(2 * m1 - 2 * f);
jd += .00002 * SN(m1 - m + 2 * f) - .00002 * SN(3 * m1 + m);
double w = .00306 - .00038 * e * CS(m) + .00026 * CS(m1) - .00002 * CS(m1 - m) + .00002 * CS(m1 + m)
+ .00002 * CS(2 * f);
jd += (mode == FIRST_QUARTER) ? w : -w;
}
return moonCorrection(jd, t, kMod);
}
/**
* Calculates the eclipse.
*/
private double getEclipse(double k, EclipseType typ, EclipseKind eclipse) {
double kMod = Math.floor(k) + ((typ == EclipseType.SUN) ? 0 : 0.5);
double t = kMod / 1236.85;
double f = var_f(kMod, t);
double jd = 0;
double ringTest = 0;
if (SN(Math.abs(f)) <= .36) {
double o = var_o(kMod, t);
double f1 = f - .02665 * SN(o);
double a1 = 299.77 + .107408 * kMod - .009173 * t * t;
double e = var_e(t);
double m = var_m(kMod, t);
double m1 = var_m1(kMod, t);
double p = .207 * e * SN(m) + .0024 * e * SN(2 * m) - .0392 * SN(m1) + .0116 * SN(2 * m1)
- .0073 * e * SN(m1 + m) + .0067 * e * SN(m1 - m) + .0118 * SN(2 * f1);
double q = 5.2207 - .0048 * e * CS(m) + .002 * e * CS(2 * m) - .3299 * CS(m1) - .006 * e * CS(m1 + m)
+ .0041 * e * CS(m1 - m);
double g = (p * CS(f1) + q * SN(f1)) * (1 - .0048 * CS(Math.abs(f1)));
double u = .0059 + .0046 * e * CS(m) - .0182 * CS(m1) + .0004 * CS(2 * m1) - .0005 * CS(m + m1);
jd = var_jde(kMod, t);
jd += (typ == EclipseType.MOON) ? -.4065 * SN(m1) + .1727 * e * SN(m) : -.4075 * SN(m1) + .1721 * e * SN(m);
jd += .0161 * SN(2 * m1) - .0097 * SN(2 * f1) + .0073 * e * SN(m1 - m) - .005 * e * SN(m1 + m)
- .0023 * SN(m1 - 2 * f1) + .0021 * e * SN(2 * m);
jd += .0012 * SN(m1 + 2 * f1) + .0006 * e * SN(2 * m1 + m) - .0004 * SN(3 * m1) - .0003 * e * SN(m + 2 * f1)
+ .0003 * SN(a1) - .0002 * e * SN(m - 2 * f1) - .0002 * e * SN(2 * m1 - m) - .0002 * SN(o);
switch (typ) {
case MOON:
if ((1.0248 - u - Math.abs(g)) / .545 <= 0) {
jd = 0; // no moon eclipse
}
if (eclipse == EclipseKind.PARTIAL && (1.0128 - u - Math.abs(g)) / .545 > 0
&& (.4678 - u) * (.4678 - u) - g * g > 0) {
jd = 0; // no partial moon eclipse
}
if (eclipse == EclipseKind.TOTAL
&& ((1.0128 - u - Math.abs(g)) / .545 <= 0 != (.4678 - u) * (.4678 - u) - g * g <= 0)) {
jd = 0; // no total moon eclipse
}
break;
case SUN:
if (Math.abs(g) > 1.5433 + u) {
jd = 0; // no sun eclipse
}
if (eclipse == EclipseKind.PARTIAL && ((g >= -.9972 && g <= .9972)
|| (Math.abs(g) >= .9972 && Math.abs(g) < .9972 + Math.abs(u)))) {
jd = 0; // no partial sun eclipse
}
if (eclipse != EclipseKind.PARTIAL) {
if ((g < -.9972 || g > .9972) || (Math.abs(g) < .9972 && Math.abs(g) > .9972 + Math.abs(u))) {
jd = 0; // no ring or total sun eclipse
}
if (u > .0047 || u >= .00464 * Math.sqrt(1 - g * g)) {
ringTest = 1; // no total sun eclipse
}
if (ringTest == 1 && eclipse == EclipseKind.TOTAL) {
jd = 0;
}
if (ringTest == 0 && eclipse == EclipseKind.RING) {
jd = 0;
}
}
break;
}
}
return jd;
}
/**
* Calculates the illumination.
*/
private double getIllumination(double jd) {
double t = (jd - 2451545) / 36525;
double d = 297.8502042 + 445267.11151686 * t - .00163 * t * t + t * t * t / 545868 - t * t * t * t / 113065000;
double m = 357.5291092 + 35999.0502909 * t - .0001536 * t * t + t * t * t / 24490000;
double m1 = 134.9634114 + 477198.8676313 * t + .008997 * t * t + t * t * t / 69699 - t * t * t * t / 14712000;
double i = 180 - d - 6.289 * SN(m1) + 2.1 * SN(m) - 1.274 * SN(2 * d - m1) - .658 * SN(2 * d)
- .241 * SN(2 * m1) - .110 * SN(d);
return (1 + CS(i)) / 2 * 100.0;
}
/**
* Calculates the next moon phase.
*/
private double getNextPhase(Calendar cal, double midnightJd, double mode) {
double tz = 0;
double phaseJd = 0;
do {
double k = var_k(cal, tz);
tz += 1;
phaseJd = calcMoonPhase(k, mode);
} while (phaseJd <= midnightJd);
return phaseJd;
}
/**
* Calculates the previous moon phase.
*/
public double getPreviousPhase(Calendar cal, double jd, double mode) {
double tz = 0;
double phaseJd = 0;
do {
double k = var_k(cal, tz);
tz -= 1;
phaseJd = calcMoonPhase(k, mode);
} while (phaseJd > jd);
return phaseJd;
}
/**
* Calculates the next eclipse.
*/
protected double getEclipse(Calendar cal, EclipseType type, double midnightJd, EclipseKind eclipse) {
double tz = 0;
double eclipseJd = 0;
do {
double k = var_k(cal, tz);
tz += 1;
eclipseJd = getEclipse(k, type, eclipse);
} while (eclipseJd <= midnightJd);
return eclipseJd;
}
/**
* Calculates the date, where the moon is furthest away from the earth.
*/
private double getApogee(double julianDate, double decimalYear) {
double k = Math.floor((decimalYear - 1999.97) * 13.2555) + .5;
double jd = 0;
do {
double t = k / 1325.55;
double d = 171.9179 + 335.9106046 * k - .010025 * t * t - .00001156 * t * t * t
+ .000000055 * t * t * t * t;
double m = 347.3477 + 27.1577721 * k - .0008323 * t * t - .000001 * t * t * t;
double f = 316.6109 + 364.5287911 * k - .0125131 * t * t - .0000148 * t * t * t;
jd = 2451534.6698 + 27.55454988 * k - .0006886 * t * t - .000001098 * t * t * t + .0000000052 * t * t
+ .4392 * SN(2 * d) + .0684 * SN(4 * d) + (.0456 - .00011 * t) * SN(m)
+ (.0426 - .00011 * t) * SN(2 * d - m) + .0212 * SN(2 * f);
jd += -.0189 * SN(d) + .0144 * SN(6 * d) + .0113 * SN(4 * d - m) + .0047 * SN(2 * d + 2 * f)
+ .0036 * SN(d + m) + .0035 * SN(8 * d) + .0034 * SN(6 * d - m) - .0034 * SN(2 * d - 2 * f)
+ .0022 * SN(2 * d - 2 * m) - .0017 * SN(3 * d);
jd += .0013 * SN(4 * d + 2 * f) + .0011 * SN(8 * d - m) + .001 * SN(4 * d - 2 * m) + .0009 * SN(10 * d)
+ .0007 * SN(3 * d + m) + .0006 * SN(2 * m) + .0005 * SN(2 * d + m) + .0005 * SN(2 * d + 2 * m)
+ .0004 * SN(6 * d + 2 * f);
jd += .0004 * SN(6 * d - 2 * m) + .0004 * SN(10 * d - m) - .0004 * SN(5 * d) - .0004 * SN(4 * d - 2 * f)
+ .0003 * SN(2 * f + m) + .0003 * SN(12 * d) + .0003 * SN(2 * d + 2 * f - m) - .0003 * SN(d - m);
k += 1;
} while (jd < julianDate);
return jd;
}
/**
* Calculates the date, where the moon is closest to the earth.
*/
private double getPerigee(double julianDate, double decimalYear) {
double k = Math.floor((decimalYear - 1999.97) * 13.2555);
double jd = 0;
do {
double t = k / 1325.55;
double d = 171.9179 + 335.9106046 * k - .010025 * t * t - .00001156 * t * t * t
+ .000000055 * t * t * t * t;
double m = 347.3477 + 27.1577721 * k - .0008323 * t * t - .000001 * t * t * t;
double f = 316.6109 + 364.5287911 * k - .0125131 * t * t - .0000148 * t * t * t;
jd = 2451534.6698 + 27.55454988 * k - .0006886 * t * t - .000001098 * t * t * t + .0000000052 * t * t
- 1.6769 * SN(2 * d) + .4589 * SN(4 * d) - .1856 * SN(6 * d) + .0883 * SN(8 * d);
jd += -(.0773 + .00019 * t) * SN(2 * d - m) + (.0502 - .00013 * t) * SN(m) - .046 * SN(10 * d)
+ (.0422 - .00011 * t) * SN(4 * d - m) - .0256 * SN(6 * d - m) + .0253 * SN(12 * d) + .0237 * SN(d);
jd += .0162 * SN(8 * d - m) - .0145 * SN(14 * d) + .0129 * SN(2 * f) - .0112 * SN(3 * d)
- .0104 * SN(10 * d - m) + .0086 * SN(16 * d) + .0069 * SN(12 * d - m) + .0066 * SN(5 * d)
- .0053 * SN(2 * d + 2 * f);
jd += -.0052 * SN(18 * d) - .0046 * SN(14 * d - m) - .0041 * SN(7 * d) + .004 * SN(2 * d + m)
+ .0032 * SN(20 * d) - .0032 * SN(d + m) + .0031 * SN(16 * d - m);
jd += -.0029 * SN(4 * d + m) - .0027 * SN(2 * d - 2 * m) + .0024 * SN(4 * d - 2 * m)
- .0021 * SN(6 * d - 2 * m) - .0021 * SN(22 * d) - .0021 * SN(18 * d - m);
jd += .0019 * SN(6 * d + m) - .0018 * SN(11 * d) - .0014 * SN(8 * d + m) - .0014 * SN(4 * d - 2 * f)
- .0014 * SN(6 * d - 2 * f) + .0014 * SN(3 * d + m) - .0014 * SN(5 * d + m) + .0013 * SN(13 * d);
jd += .0013 * SN(20 * d - m) + .0011 * SN(3 * d + 2 * m) - .0011 * SN(4 * d + 2 * f - 2 * m)
- .001 * SN(d + 2 * m) - .0009 * SN(22 * d - m) - .0008 * SN(4 * f) + .0008 * SN(6 * d - 2 * f)
+ .0008 * SN(2 * d - 2 * f + m);
jd += .0007 * SN(2 * m) + .0007 * SN(2 * f - m) + .0007 * SN(2 * d + 4 * f) - .0006 * SN(2 * f - 2 * m)
- .0006 * SN(2 * d - 2 * f + 2 * m) + .0006 * SN(24 * d) + .0005 * SN(4 * d - 4 * f)
+ .0005 * SN(2 * d + 2 * m) - .0004 * SN(d - m) + .0027 * SN(9 * d) + .0027 * SN(4 * d + 2 * f);
k += 1;
} while (jd < julianDate);
return jd;
}
/**
* Calculates the distance from the moon to earth.
*/
private double getDistance(double jd) {
double t = (jd - 2451545) / 36525;
double d = 297.8502042 + 445267.11151686 * t - .00163 * t * t + t * t * t / 545868 - t * t * t * t / 113065000;
double m = 357.5291092 + 35999.0502909 * t - .0001536 * t * t + t * t * t / 24490000;
double m1 = 134.9634114 + 477198.8676313 * t + .008997 * t * t + t * t * t / 69699 - t * t * t * t / 14712000;
double f = 93.27209929999999 + 483202.0175273 * t - .0034029 * t * t - t * t * t / 3526000
+ t * t * t * t / 863310000;
double sr = 385000.56 + getCoefficient(d, m, m1, f) / 1000;
return sr;
}
public double[] calcMoon(double t) {
double p2 = 6.283185307;
double arc = 206264.8062;
double coseps = .91748;
double sineps = .39778;
double lo = FRAK(.606433 + 1336.855225 * t);
double l = p2 * FRAK(.374897 + 1325.55241 * t);
double ls = p2 * FRAK(.993133 + 99.997361 * t);
double d = p2 * FRAK(.827361 + 1236.853086 * t);
double f = p2 * FRAK(.259086 + 1342.227825 * t);
double dl = 22640 * Math.sin(l) - 4586 * Math.sin(l - 2 * d) + 2370 * Math.sin(2 * d) + 769 * Math.sin(2 * l)
- 668 * Math.sin(ls) - 412 * Math.sin(2 * f) - 212 * Math.sin(2 * l - 2 * d)
- 206 * Math.sin(l + ls - 2 * d) + 192 * Math.sin(l + 2 * d) - 165 * Math.sin(ls - 2 * d)
- 125 * Math.sin(d) - 110 * Math.sin(l + ls) + 148 * Math.sin(l - ls) - 55 * Math.sin(2 * f - 2 * d);
double s = f + (dl + 412 * Math.sin(2 * f) + 541 * Math.sin(ls)) / arc;
double h = f - 2 * d;
double n = -526 * Math.sin(h) + 44 * Math.sin(l + h) - 31 * Math.sin(-l + h) - 23 * Math.sin(ls + h)
+ 11 * Math.sin(-ls + h) - 25 * Math.sin(-2 * l + f) + 21 * Math.sin(-l + f);
double lmoon = p2 * FRAK(lo + dl / 1296000);
double bmoon = (18520 * Math.sin(s) + n) / arc;
double cb = Math.cos(bmoon);
double x = cb * Math.cos(lmoon);
double v = cb * Math.sin(lmoon);
double w = Math.sin(bmoon);
double y = coseps * v - sineps * w;
double z = sineps * v + coseps * w;
double rho = Math.sqrt(1 - z * z);
double dec = (360 / p2) * Math.atan(z / rho);
double ra = (48 / p2) * Math.atan(y / (x + rho));
if (ra < 0) {
ra += 24;
}
return new double[] { dec, ra };
}
private double CS(double x) {
return Math.cos(x * SunCalc.DEG2RAD);
}
private double SN(double x) {
return Math.sin(x * SunCalc.DEG2RAD);
}
private double SINALT(double moonJd, int hour, double lambda, double cphi, double sphi) {
double jdo = moonJd + hour / 24.0;
double t = (jdo - 51544.5) / 36525.0;
double decra[] = calcMoon(t);
double tau = 15.0 * (LMST(jdo, lambda) - decra[1]);
return sphi * SN(decra[0]) + cphi * CS(decra[0]) * CS(tau);
}
private double LMST(double moonJd, double lambda) {
double moonJdo = Math.floor(moonJd);
double ut = (moonJd - moonJdo) * 24.0;
double t = (moonJdo - 51544.5) / 36525.0;
double gmst = 6.697374558 + 1.0027379093 * ut + (8640184.812866 + (.093104 - .0000062 * t) * t) * t / 3600.0;
return 24.0 * FRAK((gmst - lambda / 15.0) / 24.0);
}
private double FRAK(double x) {
double ret = x - (int) (x);
if (ret < 0) {
ret += 1;
}
return ret;
}
private double[] QUAD(double yminus, double yo, double yplus) {
double nz = 0;
double a = .5 * (yminus + yplus) - yo;
double b = .5 * (yplus - yminus);
double c = yo;
double xe = -b / (2 * a);
double ye = (a * xe + b) * xe + c;
double dis = b * b - 4 * a * c;
double zero1 = 0;
double zero2 = 0;
if (dis >= 0) {
double dx = .5 * Math.sqrt(dis) / Math.abs(a);
zero1 = xe - dx;
zero2 = xe + dx;
if (Math.abs(zero1) <= 1) {
nz += 1;
}
if (Math.abs(zero2) <= 1) {
nz += 1;
}
if (zero1 < -1) {
zero1 = zero2;
}
}
return new double[] { ye, zero1, zero2, nz };
}
private double var_o(double k, double t) {
return 124.7746 - 1.5637558 * k + .0020691 * t * t + .00000215 * t * t * t;
}
private double var_f(double k, double t) {
return 160.7108 + 390.67050274 * k - .0016341 * t * t - .00000227 * t * t * t + .000000011 * t * t * t * t;
}
private double var_m1(double k, double t) {
return 201.5643 + 385.81693528 * k + .1017438 * t * t + .00001239 * t * t * t - .000000058 * t * t * t * t;
}
private double var_m(double k, double t) {
return 2.5534 + 29.10535669 * k - .0000218 * t * t - .00000011 * t * t * t;
}
private double var_e(double t) {
return 1 - .002516 * t - .0000074 * t * t;
}
private double var_jde(double k, double t) {
return 2451550.09765 + 29.530588853 * k + .0001337 * t * t - .00000015 * t * t * t
+ .00000000073 * t * t * t * t;
}
private double var_k(Calendar cal, double tz) {
return (cal.get(Calendar.YEAR) + (cal.get(Calendar.DAY_OF_YEAR) + tz) / 365 - 2000) * 12.3685;
}
private double moonCorrection(double jd, double t, double k) {
double ret = jd;
ret += .000325 * SN(299.77 + .107408 * k - .009173 * t * t) + .000165 * SN(251.88 + .016321 * k)
+ .000164 * SN(251.83 + 26.651886 * k) + .000126 * SN(349.42 + 36.412478 * k)
+ .00011 * SN(84.66 + 18.206239 * k);
ret += .000062 * SN(141.74 + 53.303771 * k) + .00006 * SN(207.14 + 2.453732 * k)
+ .000056 * SN(154.84 + 7.30686 * k) + .000047 * SN(34.52 + 27.261239 * k)
+ .000042 * SN(207.19 + .121824 * k) + .00004 * SN(291.34 + 1.844379 * k);
ret += .000037 * SN(161.72 + 24.198154 * k) + .000035 * SN(239.56 + 25.513099 * k)
+ .000023 * SN(331.55 + 3.592518 * k);
return ret;
}
private double getCoefficient(double d, double m, double m1, double f) {
int[] kd = new int[] { 0, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 1, 0, 2, 0, 0, 4, 0, 4, 2, 2, 1, 1, 2, 2, 4, 2, 0, 2, 2,
1, 2, 0, 0, 2, 2, 2, 4, 0, 3, 2, 4, 0, 2, 2, 2, 4, 0, 4, 1, 2, 0, 1, 3, 4, 2, 0, 1, 2, 2 };
int[] km = new int[] { 0, 0, 0, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, -1, 0, 0, 0, 1, 0,
-1, 0, -2, 1, 2, -2, 0, 0, -1, 0, 0, 1, -1, 2, 2, 1, -1, 0, 0, -1, 0, 1, 0, 1, 0, 0, -1, 2, 1, 0, 0 };
int[] km1 = new int[] { 1, -1, 0, 2, 0, 0, -2, -1, 1, 0, -1, 0, 1, 0, 1, 1, -1, 3, -2, -1, 0, -1, 0, 1, 2, 0,
-3, -2, -1, -2, 1, 0, 2, 0, -1, 1, 0, -1, 2, -1, 1, -2, -1, -1, -2, 0, 1, 4, 0, -2, 0, 2, 1, -2, -3, 2,
1, -1, 3, -1 };
int[] kf = new int[] { 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, -2, 2, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
0, 0, 0, 0, 0, 0, -2, 2, 0, 2, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, -2, -2, 0, 0, 0, 0, 0, 0, 0, -2 };
int[] kr = new int[] { -20905355, -3699111, -2955968, -569925, 48888, -3149, 246158, -152138, -170733, -204586,
-129620, 108743, 104755, 10321, 0, 79661, -34782, -23210, -21636, 24208, 30824, -8379, -16675, -12831,
-10445, -11650, 14403, -7003, 0, 10056, 6322, -9884, 5751, 0, -4950, 4130, 0, -3958, 0, 3258, 2616,
-1897, -2117, 2354, 0, 0, -1423, -1117, -1571, -1739, 0, -4421, 0, 0, 0, 0, 1165, 0, 0, 8752 };
double sr = 0;
for (int t = 0; t < 60; t++) {
sr += kr[t] * CS(kd[t] * d + km[t] * m + km1[t] * m1 + kf[t] * f);
}
return sr;
}
/**
* Sets the azimuth, elevation and zodiac in the moon object.
*/
private void setAzimuthElevationZodiac(double julianDate, double latitude, double longitude, Moon moon) {
double lat = latitude * SunCalc.DEG2RAD;
double lon = longitude * SunCalc.DEG2RAD;
double gmst = toGMST(julianDate);
double lmst = toLMST(gmst, lon) * 15. * SunCalc.DEG2RAD;
double d = julianDate - 2447891.5;
double anomalyMean = 360 * SunCalc.DEG2RAD / 365.242191 * d + 4.87650757829735 - 4.935239984568769;
double nu = anomalyMean + 360.0 * SunCalc.DEG2RAD / Math.PI * 0.016713 * Math.sin(anomalyMean);
double sunLon = mod2Pi(nu + 4.935239984568769);
double l0 = 318.351648 * SunCalc.DEG2RAD;
double p0 = 36.340410 * SunCalc.DEG2RAD;
double n0 = 318.510107 * SunCalc.DEG2RAD;
double i = 5.145396 * SunCalc.DEG2RAD;
double l = 13.1763966 * SunCalc.DEG2RAD * d + l0;
double mMoon = l - 0.1114041 * SunCalc.DEG2RAD * d - p0;
double n = n0 - 0.0529539 * SunCalc.DEG2RAD * d;
double c = l - sunLon;
double ev = 1.2739 * SunCalc.DEG2RAD * Math.sin(2 * c - mMoon);
double ae = 0.1858 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
double a3 = 0.37 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
double mMoon2 = mMoon + ev - ae - a3;
double ec = 6.2886 * SunCalc.DEG2RAD * Math.sin(mMoon2);
double a4 = 0.214 * SunCalc.DEG2RAD * Math.sin(2 * mMoon2);
double l2 = l + ev + ec - ae + a4;
double v = 0.6583 * SunCalc.DEG2RAD * Math.sin(2 * (l2 - sunLon));
double l3 = l2 + v;
double n2 = n - 0.16 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
double moonLon = mod2Pi(n2 + Math.atan2(Math.sin(l3 - n2) * Math.cos(i), Math.cos(l3 - n2)));
double moonLat = Math.asin(Math.sin(l3 - n2) * Math.sin(i));
double raDec[] = ecl2Equ(moonLat, moonLon, julianDate);
double distance = (1 - 0.00301401) / (1 + 0.054900 * Math.cos(mMoon2 + ec)) * 384401;
double raDecTopo[] = geoEqu2TopoEqu(raDec, distance, lat, lmst);
double azAlt[] = equ2AzAlt(raDecTopo[0], raDecTopo[1], lat, lmst);
Position position = moon.getPosition();
position.setAzimuth(azAlt[0] * SunCalc.RAD2DEG);
position.setElevation(azAlt[1] * SunCalc.RAD2DEG + refraction(azAlt[1]));
// zodiac
double idxd = Math.floor(moonLon * SunCalc.RAD2DEG / 30);
int idx = 0;
if (idxd < 0) {
idx = (int) (Math.ceil(idxd));
} else {
idx = (int) (Math.floor(idxd));
}
if (idx >= 0 || idx <= ZodiacSign.values().length) {
moon.setZodiac(new Zodiac(ZodiacSign.values()[idx]));
}
}
private double mod2Pi(double x) {
return (mod(x, 2. * Math.PI));
}
private double mod(double a, double b) {
return (a - Math.floor(a / b) * b);
}
/**
* Transform equatorial coordinates (ra/dec) to horizonal coordinates
* (azimuth/altitude).
*/
private double[] equ2AzAlt(double ra, double dec, double geolat, double lmst) {
double cosdec = Math.cos(dec);
double sindec = Math.sin(dec);
double lha = lmst - ra;
double coslha = Math.cos(lha);
double sinlha = Math.sin(lha);
double coslat = Math.cos(geolat);
double sinlat = Math.sin(geolat);
double n = -cosdec * sinlha;
double d = sindec * coslat - cosdec * coslha * sinlat;
double az = mod2Pi(Math.atan2(n, d));
double alt = Math.asin(sindec * sinlat + cosdec * coslha * coslat);
return new double[] { az, alt };
}
/**
* Transform ecliptical coordinates (lon/lat) to equatorial coordinates
* (ra/dec)
*/
private double[] ecl2Equ(double lat, double lon, double jd) {
double t = (jd - 2451545.0) / 36525.0;
double eps = (23. + (26 + 21.45 / 60.) / 60. + t * (-46.815 + t * (-0.0006 + t * 0.00181)) / 3600.)
* SunCalc.DEG2RAD;
double coseps = Math.cos(eps);
double sineps = Math.sin(eps);
double sinlon = Math.sin(lon);
double ra = mod2Pi(Math.atan2((sinlon * coseps - Math.tan(lat) * sineps), Math.cos(lon)));
double dec = Math.asin(Math.sin(lat) * coseps + Math.cos(lat) * sineps * sinlon);
return new double[] { ra, dec };
}
/**
* Transform geocentric equatorial coordinates (rA/dec) to topocentric
* equatorial coordinates.
*/
private double[] geoEqu2TopoEqu(double[] raDec, double distance, double observerLat, double lmst) {
double cosdec = Math.cos(raDec[1]);
double sindec = Math.sin(raDec[1]);
double coslst = Math.cos(lmst);
double sinlst = Math.sin(lmst);
double coslat = Math.cos(observerLat);
double sinlat = Math.sin(observerLat);
double rho = getCenterDistance(observerLat);
double x = distance * cosdec * Math.cos(raDec[0]) - rho * coslat * coslst;
double y = distance * cosdec * Math.sin(raDec[0]) - rho * coslat * sinlst;
double z = distance * sindec - rho * sinlat;
double distanceTopocentric = Math.sqrt(x * x + y * y + z * z);
double raTopo = mod2Pi(Math.atan2(y, x));
double decTopo = Math.asin(z / distanceTopocentric);
return new double[] { raTopo, decTopo };
}
/**
* Convert julian date to greenwich mean sidereal time.
*/
private double toGMST(double jd) {
double ut = (jd - 0.5 - Math.floor(jd - 0.5)) * 24.;
double jdMod = Math.floor(jd - 0.5) + 0.5;
double t = (jdMod - 2451545.0) / 36525.0;
double t0 = 6.697374558 + t * (2400.051336 + t * 0.000025862);
return (mod(t0 + ut * 1.002737909, 24.));
}
/**
* Convert greenwich mean sidereal time to local mean sidereal time.
*/
private double toLMST(double gmst, double lon) {
return mod(gmst + SunCalc.RAD2DEG * lon / 15., 24.);
}
/**
* Returns geocentric distance from earth center.
*/
private double getCenterDistance(double lat) {
double co = Math.cos(lat);
double si = Math.sin(lat);
double fl = 1.0 - 1.0 / 298.257223563;
fl = fl * fl;
si = si * si;
double u = 1.0 / Math.sqrt(co * co + fl * si);
double a = 6378.137 * u;
double b = 6378.137 * fl * u;
return Math.sqrt(a * a * co * co + b * b * si);
}
/**
* Returns altitude increase in altitude in degrees. Rough refraction
* formula using standard atmosphere: 1015 mbar and 10°C.
*/
private double refraction(double alt) {
int pressure = 1015;
int temperature = 10;
double altdeg = alt * SunCalc.RAD2DEG;
if (altdeg < -2 || altdeg >= 90) {
return 0;
}
if (altdeg > 15) {
return 0.00452 * pressure / ((273 + temperature) * Math.tan(alt));
}
double y = alt;
double d = 0.0;
double p = (pressure - 80.0) / 930.0;
double q = 0.0048 * (temperature - 10.0);
double y0 = y;
double d0 = d;
double n = 0.0;
for (int i = 0; i < 3; i++) {
n = y + (7.31 / (y + 4.4));
n = 1.0 / Math.tan(n * SunCalc.DEG2RAD);
d = n * p / (60.0 + q * (n + 39.0));
n = y - y0;
y0 = d - d0 - n;
n = ((n != 0.0) && (y0 != 0.0)) ? y - n * (alt + d - y) / y0 : alt + d;
y0 = y;
d0 = d;
y = n;
}
return d;
}
}

View File

@@ -0,0 +1,178 @@
/**
* 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.calc;
import java.util.Calendar;
import org.openhab.binding.astro.internal.model.Season;
import org.openhab.binding.astro.internal.model.SeasonName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
* Calculates the seasons of the year.
*
* @author Gerhard Riegler - Initial contribution
* @see based on the calculations of http://stellafane.org/misc/equinox.html
*/
public class SeasonCalc {
private int currentYear;
private Season currentSeason;
/**
* Returns the seasons of the year of the specified calendar.
*/
public Season getSeason(Calendar calendar, double latitude, boolean useMeteorologicalSeason) {
int year = calendar.get(Calendar.YEAR);
boolean isSouthernHemisphere = latitude < 0.0;
Season season = currentSeason;
if (currentYear != year) {
season = new Season();
if (!isSouthernHemisphere) {
season.setSpring(calcEquiSol(0, year));
season.setSummer(calcEquiSol(1, year));
season.setAutumn(calcEquiSol(2, year));
season.setWinter(calcEquiSol(3, year));
} else {
season.setSpring(calcEquiSol(2, year));
season.setSummer(calcEquiSol(3, year));
season.setAutumn(calcEquiSol(0, year));
season.setWinter(calcEquiSol(1, year));
}
currentSeason = season;
currentYear = year;
}
if (useMeteorologicalSeason) {
atMidnightOfFirstMonthDay(season.getSpring());
atMidnightOfFirstMonthDay(season.getSummer());
atMidnightOfFirstMonthDay(season.getAutumn());
atMidnightOfFirstMonthDay(season.getWinter());
}
season.setName(!isSouthernHemisphere ? getCurrentSeasonNameNorthern(calendar)
: getCurrentSeasonNameSouthern(calendar));
return season;
}
private void atMidnightOfFirstMonthDay(Calendar calendar) {
calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
}
/**
* Returns the current season name for the northern hemisphere.
*/
private SeasonName getCurrentSeasonNameNorthern(Calendar calendar) {
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getSpring().getTimeInMillis()
|| currentMillis >= currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SPRING;
} else if (currentMillis >= currentSeason.getSummer().getTimeInMillis()
&& currentMillis < currentSeason.getAutumn().getTimeInMillis()) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.AUTUMN;
}
return null;
}
/**
* Returns the current season name for the southern hemisphere.
*/
private SeasonName getCurrentSeasonNameSouthern(Calendar calendar) {
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getAutumn().getTimeInMillis()
|| currentMillis >= currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.AUTUMN;
} else if (currentMillis >= currentSeason.getWinter().getTimeInMillis()
&& currentMillis < currentSeason.getSpring().getTimeInMillis()) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SPRING;
}
return null;
}
/**
* Calculates the date of the season.
*/
private Calendar calcEquiSol(int season, int year) {
double estimate = calcInitial(season, year);
double t = (estimate - 2451545.0) / 36525;
double w = 35999.373 * t - 2.47;
double dl = 1 + 0.0334 * cosDeg(w) + 0.0007 * cosDeg(2 * w);
double s = periodic24(t);
double julianDate = estimate + ((0.00001 * s) / dl);
return DateTimeUtils.toCalendar(julianDate);
}
/**
* Calculate an initial guess of the Equinox or Solstice of a given year.
*/
private double calcInitial(int season, int year) {
double y = (year - 2000) / 1000d;
switch (season) {
case 0:
return 2451623.80984 + 365242.37404 * y + 0.05169 * Math.pow(y, 2) - 0.00411 * Math.pow(y, 3)
- 0.00057 * Math.pow(y, 4);
case 1:
return 2451716.56767 + 365241.62603 * y + 0.00325 * Math.pow(y, 2) + 0.00888 * Math.pow(y, 3)
- 0.00030 * Math.pow(y, 4);
case 2:
return 2451810.21715 + 365242.01767 * y - 0.11575 * Math.pow(y, 2) + 0.00337 * Math.pow(y, 3)
+ 0.00078 * Math.pow(y, 4);
case 3:
return 2451900.05952 + 365242.74049 * y - 0.06223 * Math.pow(y, 2) - 0.00823 * Math.pow(y, 3)
+ 0.00032 * Math.pow(y, 4);
}
return 0;
}
/**
* Calculate 24 periodic terms
*/
private double periodic24(double T) {
int[] a = new int[] { 485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12,
12, 9, 8 };
double[] b = new double[] { 324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81,
297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73,
15.45 };
double[] c = new double[] { 1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906,
9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029,
31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074 };
double result = 0;
for (int i = 0; i < 24; i++) {
result += a[i] * cosDeg(b[i] + (c[i] * T));
}
return result;
}
/**
* Cosinus of a degree value.
*/
private double cosDeg(double deg) {
return Math.cos(deg * Math.PI / 180);
}
}

View File

@@ -0,0 +1,339 @@
/**
* 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.calc;
import java.util.Calendar;
import java.util.Map.Entry;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.EclipseType;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.model.Radiation;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPhaseName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
* Calculates the SunPosition (azimuth, elevation) and Sun data.
*
* @author Gerhard Riegler - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
* @see based on the calculations of http://www.suncalc.net
*/
public class SunCalc {
private static final double J2000 = 2451545.0;
private static final double SC = 1367; // Solar constant in W/m²
public static final double DEG2RAD = Math.PI / 180;
public static final double RAD2DEG = 180. / Math.PI;
private static final double M0 = 357.5291 * DEG2RAD;
private static final double M1 = 0.98560028 * DEG2RAD;
private static final double J0 = 0.0009;
private static final double J1 = 0.0053;
private static final double J2 = -0.0069;
private static final double C1 = 1.9148 * DEG2RAD;
private static final double C2 = 0.0200 * DEG2RAD;
private static final double C3 = 0.0003 * DEG2RAD;
private static final double P = 102.9372 * DEG2RAD;
private static final double E = 23.45 * DEG2RAD;
private static final double TH0 = 280.1600 * DEG2RAD;
private static final double TH1 = 360.9856235 * DEG2RAD;
private static final double SUN_ANGLE = -0.83;
private static final double SUN_DIAMETER = 0.53 * DEG2RAD; // sun diameter
private static final double H0 = SUN_ANGLE * DEG2RAD;
private static final double H1 = -6.0 * DEG2RAD; // nautical twilight angle
private static final double H2 = -12.0 * DEG2RAD; // astronomical twilight
// angle
private static final double H3 = -18.0 * DEG2RAD; // darkness angle
private static final double MINUTES_PER_DAY = 60 * 24;
private static final int CURVE_TIME_INTERVAL = 20; // 20 minutes
private static final double JD_ONE_MINUTE_FRACTION = 1.0 / 60 / 24;
/**
* Calculates the sun position (azimuth and elevation).
*/
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, Double altitude, Sun sun) {
double lw = -longitude * DEG2RAD;
double phi = latitude * DEG2RAD;
double j = DateTimeUtils.dateToJulianDate(calendar);
double m = getSolarMeanAnomaly(j);
double c = getEquationOfCenter(m);
double lsun = getEclipticLongitude(m, c);
double d = getSunDeclination(lsun);
double a = getRightAscension(lsun);
double th = getSiderealTime(j, lw);
double azimuth = getAzimuth(th, a, phi, d) / DEG2RAD;
double elevation = getElevation(th, a, phi, d) / DEG2RAD;
double shadeLength = getShadeLength(elevation);
Position position = sun.getPosition();
position.setAzimuth(azimuth + 180);
position.setElevation(elevation);
position.setShadeLength(shadeLength);
setRadiationInfo(calendar, elevation, altitude, sun);
}
/**
* Calculates sun radiation data.
*/
public void setRadiationInfo(Calendar calendar, double elevation, Double altitude, Sun sun) {
double sinAlpha = Math.sin(DEG2RAD * elevation);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
int daysInYear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
// Direct Solar Radiation (in W/m²) at the atmosphere entry
// At sunrise/sunset - calculations limits are reached
double rOut = (elevation > 3) ? SC * (0.034 * Math.cos(DEG2RAD * (360 * dayOfYear / daysInYear)) + 1) : 0;
double altitudeRatio = (altitude != null) ? 1 / Math.pow((1 - (6.5 / 288) * (altitude / 1000.0)), 5.256) : 1;
double m = (Math.sqrt(1229 + Math.pow(614 * sinAlpha, 2)) - 614 * sinAlpha) * altitudeRatio;
// Direct radiation after atmospheric layer
// 0.6 = Coefficient de transmissivité
double rDir = rOut * Math.pow(0.6, m) * sinAlpha;
// Diffuse Radiation
double rDiff = rOut * (0.271 - 0.294 * Math.pow(0.6, m)) * sinAlpha;
double rTot = rDir + rDiff;
Radiation radiation = sun.getRadiation();
radiation.setDirect(rDir);
radiation.setDiffuse(rDiff);
radiation.setTotal(rTot);
}
/**
* Returns true, if the sun is up all day (no rise and set).
*/
private boolean isSunUpAllDay(Calendar calendar, double latitude, double longitude, Double altitude) {
Calendar cal = DateTimeUtils.truncateToMidnight(calendar);
Sun sun = new Sun();
for (int minutes = 0; minutes <= MINUTES_PER_DAY; minutes += CURVE_TIME_INTERVAL) {
setPositionalInfo(cal, latitude, longitude, altitude, sun);
if (sun.getPosition().getElevationAsDouble() < SUN_ANGLE) {
return false;
}
cal.add(Calendar.MINUTE, CURVE_TIME_INTERVAL);
}
return true;
}
/**
* Calculates all sun rise and sets at the specified coordinates.
*/
public Sun getSunInfo(Calendar calendar, double latitude, double longitude, Double altitude,
boolean useMeteorologicalSeason) {
return getSunInfo(calendar, latitude, longitude, altitude, false, useMeteorologicalSeason);
}
private Sun getSunInfo(Calendar calendar, double latitude, double longitude, Double altitude, boolean onlyAstro,
boolean useMeteorologicalSeason) {
double lw = -longitude * DEG2RAD;
double phi = latitude * DEG2RAD;
double j = DateTimeUtils.midnightDateToJulianDate(calendar) + 0.5;
double n = getJulianCycle(j, lw);
double js = getApproxSolarTransit(0, lw, n);
double m = getSolarMeanAnomaly(js);
double c = getEquationOfCenter(m);
double lsun = getEclipticLongitude(m, c);
double d = getSunDeclination(lsun);
double jtransit = getSolarTransit(js, m, lsun);
double w0 = getHourAngle(H0, phi, d);
double w1 = getHourAngle(H0 + SUN_DIAMETER, phi, d);
double jset = getSunsetJulianDate(w0, m, lsun, lw, n);
double jsetstart = getSunsetJulianDate(w1, m, lsun, lw, n);
double jrise = getSunriseJulianDate(jtransit, jset);
double jriseend = getSunriseJulianDate(jtransit, jsetstart);
double w2 = getHourAngle(H1, phi, d);
double jnau = getSunsetJulianDate(w2, m, lsun, lw, n);
double jciv2 = getSunriseJulianDate(jtransit, jnau);
double w3 = getHourAngle(H2, phi, d);
double w4 = getHourAngle(H3, phi, d);
double jastro = getSunsetJulianDate(w3, m, lsun, lw, n);
double jdark = getSunsetJulianDate(w4, m, lsun, lw, n);
double jnau2 = getSunriseJulianDate(jtransit, jastro);
double jastro2 = getSunriseJulianDate(jtransit, jdark);
Sun sun = new Sun();
sun.setAstroDawn(new Range(DateTimeUtils.toCalendar(jastro2), DateTimeUtils.toCalendar(jnau2)));
sun.setAstroDusk(new Range(DateTimeUtils.toCalendar(jastro), DateTimeUtils.toCalendar(jdark)));
if (onlyAstro) {
return sun;
}
sun.setNoon(new Range(DateTimeUtils.toCalendar(jtransit),
DateTimeUtils.toCalendar(jtransit + JD_ONE_MINUTE_FRACTION)));
sun.setRise(new Range(DateTimeUtils.toCalendar(jrise), DateTimeUtils.toCalendar(jriseend)));
sun.setSet(new Range(DateTimeUtils.toCalendar(jsetstart), DateTimeUtils.toCalendar(jset)));
sun.setCivilDawn(new Range(DateTimeUtils.toCalendar(jciv2), DateTimeUtils.toCalendar(jrise)));
sun.setCivilDusk(new Range(DateTimeUtils.toCalendar(jset), DateTimeUtils.toCalendar(jnau)));
sun.setNauticDawn(new Range(DateTimeUtils.toCalendar(jnau2), DateTimeUtils.toCalendar(jciv2)));
sun.setNauticDusk(new Range(DateTimeUtils.toCalendar(jnau), DateTimeUtils.toCalendar(jastro)));
boolean isSunUpAllDay = isSunUpAllDay(calendar, latitude, longitude, altitude);
// daylight
Range daylightRange = new Range();
if (sun.getRise().getStart() == null && sun.getRise().getEnd() == null) {
if (isSunUpAllDay) {
daylightRange = new Range(DateTimeUtils.truncateToMidnight(calendar),
DateTimeUtils.truncateToMidnight(addDays(calendar, 1)));
}
} else {
daylightRange = new Range(sun.getRise().getEnd(), sun.getSet().getStart());
}
sun.setDaylight(daylightRange);
// morning night
Sun sunYesterday = getSunInfo(addDays(calendar, -1), latitude, longitude, altitude, true,
useMeteorologicalSeason);
Range morningNightRange = null;
if (sunYesterday.getAstroDusk().getEnd() != null
&& DateTimeUtils.isSameDay(sunYesterday.getAstroDusk().getEnd(), calendar)) {
morningNightRange = new Range(sunYesterday.getAstroDusk().getEnd(), sun.getAstroDawn().getStart());
} else if (isSunUpAllDay || sun.getAstroDawn().getStart() == null) {
morningNightRange = new Range();
} else {
morningNightRange = new Range(DateTimeUtils.truncateToMidnight(calendar), sun.getAstroDawn().getStart());
}
sun.setMorningNight(morningNightRange);
// evening night
Range eveningNightRange = null;
if (sun.getAstroDusk().getEnd() != null && DateTimeUtils.isSameDay(sun.getAstroDusk().getEnd(), calendar)) {
eveningNightRange = new Range(sun.getAstroDusk().getEnd(),
DateTimeUtils.truncateToMidnight(addDays(calendar, 1)));
} else {
eveningNightRange = new Range();
}
sun.setEveningNight(eveningNightRange);
// night
if (isSunUpAllDay) {
sun.setNight(new Range());
} else {
Sun sunTomorrow = getSunInfo(addDays(calendar, 1), latitude, longitude, altitude, true,
useMeteorologicalSeason);
sun.setNight(new Range(sun.getAstroDusk().getEnd(), sunTomorrow.getAstroDawn().getStart()));
}
// eclipse
Eclipse eclipse = sun.getEclipse();
MoonCalc mc = new MoonCalc();
eclipse.getKinds().forEach(eclipseKind -> {
double jdate = mc.getEclipse(calendar, EclipseType.SUN, j, eclipseKind);
eclipse.set(eclipseKind, DateTimeUtils.toCalendar(jdate), new Position());
});
SunZodiacCalc zodiacCalc = new SunZodiacCalc();
zodiacCalc.getZodiac(calendar).ifPresent(z -> sun.setZodiac(z));
SeasonCalc seasonCalc = new SeasonCalc();
sun.setSeason(seasonCalc.getSeason(calendar, latitude, useMeteorologicalSeason));
// phase
for (Entry<SunPhaseName, Range> rangeEntry : sun.getAllRanges().entrySet()) {
SunPhaseName entryPhase = rangeEntry.getKey();
if (rangeEntry.getValue().matches(Calendar.getInstance())) {
if (entryPhase == SunPhaseName.MORNING_NIGHT || entryPhase == SunPhaseName.EVENING_NIGHT) {
sun.getPhase().setName(SunPhaseName.NIGHT);
} else {
sun.getPhase().setName(entryPhase);
}
}
}
return sun;
}
/**
* Adds the specified days to the calendar.
*/
private Calendar addDays(Calendar calendar, int days) {
Calendar cal = (Calendar) calendar.clone();
cal.add(Calendar.DAY_OF_MONTH, days);
return cal;
}
// all the following methods are translated to java based on the javascript
// calculations of http://www.suncalc.net
private double getJulianCycle(double j, double lw) {
return Math.round(j - J2000 - J0 - lw / (2 * Math.PI));
}
private double getApproxSolarTransit(double ht, double lw, double n) {
return J2000 + J0 + (ht + lw) / (2 * Math.PI) + n;
}
private double getSolarMeanAnomaly(double js) {
return M0 + M1 * (js - J2000);
}
private double getEquationOfCenter(double m) {
return C1 * Math.sin(m) + C2 * Math.sin(2 * m) + C3 * Math.sin(3 * m);
}
private double getEclipticLongitude(double m, double c) {
return m + P + c + Math.PI;
}
private double getSolarTransit(double js, double m, double lsun) {
return js + (J1 * Math.sin(m)) + (J2 * Math.sin(2 * lsun));
}
private double getSunDeclination(double lsun) {
return Math.asin(Math.sin(lsun) * Math.sin(E));
}
private double getRightAscension(double lsun) {
return Math.atan2(Math.sin(lsun) * Math.cos(E), Math.cos(lsun));
}
private double getSiderealTime(double j, double lw) {
return TH0 + TH1 * (j - J2000) - lw;
}
private double getAzimuth(double th, double a, double phi, double d) {
double h = th - a;
return Math.atan2(Math.sin(h), Math.cos(h) * Math.sin(phi) - Math.tan(d) * Math.cos(phi));
}
private double getElevation(double th, double a, double phi, double d) {
return Math.asin(Math.sin(phi) * Math.sin(d) + Math.cos(phi) * Math.cos(d) * Math.cos(th - a));
}
private double getShadeLength(double elevation) {
return 1 / Math.tan(elevation * DEG2RAD);
}
private double getHourAngle(double h, double phi, double d) {
return Math.acos((Math.sin(h) - Math.sin(phi) * Math.sin(d)) / (Math.cos(phi) * Math.cos(d)));
}
private double getSunsetJulianDate(double w0, double m, double Lsun, double lw, double n) {
return getSolarTransit(getApproxSolarTransit(w0, lw, n), m, Lsun);
}
private double getSunriseJulianDate(double jtransit, double jset) {
return jtransit - (jset - jtransit);
}
}

View File

@@ -0,0 +1,90 @@
/**
* 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.calc;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.model.SunZodiac;
import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
* Calculates the sign and range of the current zodiac.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class SunZodiacCalc {
private Map<Integer, List<SunZodiac>> zodiacsByYear = new HashMap<>();
/**
* Returns the zodiac for the specified calendar.
*/
public Optional<SunZodiac> getZodiac(Calendar calendar) {
int year = calendar.get(Calendar.YEAR);
List<SunZodiac> zodiacs;
if (zodiacsByYear.containsKey(year)) {
zodiacs = zodiacsByYear.get(year);
} else {
zodiacs = calculateZodiacs(year);
zodiacsByYear.clear();
zodiacsByYear.put(year, zodiacs);
}
return zodiacs.stream().filter(z -> z.isValid(calendar)).findFirst();
}
/**
* Calculates the zodiacs for the current year.
*/
private List<SunZodiac> calculateZodiacs(int year) {
List<SunZodiac> zodiacs = new ArrayList<>();
zodiacs.add(new SunZodiac(ZodiacSign.ARIES,
DateTimeUtils.getRange(year, Calendar.MARCH, 21, year, Calendar.APRIL, 19)));
zodiacs.add(new SunZodiac(ZodiacSign.TAURUS,
DateTimeUtils.getRange(year, Calendar.APRIL, 20, year, Calendar.MAY, 20)));
zodiacs.add(new SunZodiac(ZodiacSign.GEMINI,
DateTimeUtils.getRange(year, Calendar.MAY, 21, year, Calendar.JUNE, 20)));
zodiacs.add(new SunZodiac(ZodiacSign.CANCER,
DateTimeUtils.getRange(year, Calendar.JUNE, 21, year, Calendar.JULY, 22)));
zodiacs.add(new SunZodiac(ZodiacSign.LEO,
DateTimeUtils.getRange(year, Calendar.JULY, 23, year, Calendar.AUGUST, 22)));
zodiacs.add(new SunZodiac(ZodiacSign.VIRGO,
DateTimeUtils.getRange(year, Calendar.AUGUST, 23, year, Calendar.SEPTEMBER, 22)));
zodiacs.add(new SunZodiac(ZodiacSign.LIBRA,
DateTimeUtils.getRange(year, Calendar.SEPTEMBER, 23, year, Calendar.OCTOBER, 22)));
zodiacs.add(new SunZodiac(ZodiacSign.SCORPIO,
DateTimeUtils.getRange(year, Calendar.OCTOBER, 23, year, Calendar.NOVEMBER, 21)));
zodiacs.add(new SunZodiac(ZodiacSign.SAGITTARIUS,
DateTimeUtils.getRange(year, Calendar.NOVEMBER, 22, year, Calendar.DECEMBER, 21)));
zodiacs.add(new SunZodiac(ZodiacSign.CAPRICORN,
DateTimeUtils.getRange(year, Calendar.DECEMBER, 22, year + 1, Calendar.JANUARY, 19)));
zodiacs.add(new SunZodiac(ZodiacSign.CAPRICORN,
DateTimeUtils.getRange(year - 1, Calendar.DECEMBER, 22, year, Calendar.JANUARY, 19)));
zodiacs.add(new SunZodiac(ZodiacSign.AQUARIUS,
DateTimeUtils.getRange(year, Calendar.JANUARY, 20, year, Calendar.FEBRUARY, 18)));
zodiacs.add(new SunZodiac(ZodiacSign.PISCES,
DateTimeUtils.getRange(year, Calendar.FEBRUARY, 19, year, Calendar.MARCH, 20)));
return zodiacs;
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Channel configuration from Eclipse SmartHome.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class AstroChannelConfig {
public int offset = 0;
public @Nullable String earliest;
public @Nullable String latest;
}

View File

@@ -0,0 +1,56 @@
/**
* 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.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Thing configuration from Eclipse SmartHome.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class AstroThingConfig {
public @Nullable String geolocation;
public @Nullable Double altitude;
public @Nullable Double latitude;
public @Nullable Double longitude;
public boolean useMeteorologicalSeason;
public int interval = 300;
/**
* Splits the geolocation into latitude and longitude.
*/
public void parseGeoLocation() {
if (geolocation != null) {
String[] geoParts = geolocation.split(",");
if (geoParts.length == 2) {
latitude = toDouble(geoParts[0]);
longitude = toDouble(geoParts[1]);
} else if (geoParts.length == 3) {
latitude = toDouble(geoParts[0]);
longitude = toDouble(geoParts[1]);
altitude = toDouble(geoParts[2]);
}
}
}
private @Nullable Double toDouble(String value) {
try {
return Double.parseDouble(value.trim());
} catch (NumberFormatException ex) {
}
return null;
}
}

View File

@@ -0,0 +1,124 @@
/**
* 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.discovery;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link AstroDiscoveryService} creates things based on the configured location.
*
* @author Gerhard Riegler - Initial Contribution
* @author Stefan Triller - Use configured location
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.astro")
public class AstroDiscoveryService extends AbstractDiscoveryService {
private static final int DISCOVER_TIMEOUT_SECONDS = 2;
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
private static final Set<String> METEO_BASED_COUNTRIES = new HashSet<>(Arrays.asList("NZ", "AU"));
private static final ThingUID SUN_THING = new ThingUID(THING_TYPE_SUN, LOCAL);
private static final ThingUID MOON_THING = new ThingUID(THING_TYPE_MOON, LOCAL);
private final Logger logger = LoggerFactory.getLogger(AstroDiscoveryService.class);
private final LocationProvider locationProvider;
private @Nullable ScheduledFuture<?> astroDiscoveryJob;
private @Nullable PointType previousLocation;
@Activate
public AstroDiscoveryService(final @Reference LocationProvider locationProvider,
final @Reference LocaleProvider localeProvider, final @Reference TranslationProvider i18nProvider,
@Nullable Map<String, @Nullable Object> configProperties) {
super(new HashSet<>(Arrays.asList(new ThingTypeUID(BINDING_ID, "-"))), DISCOVER_TIMEOUT_SECONDS, true);
this.locationProvider = locationProvider;
this.localeProvider = localeProvider;
this.i18nProvider = i18nProvider;
activate(configProperties);
}
@Override
protected void startScan() {
logger.debug("Starting Astro discovery scan");
PointType location = locationProvider.getLocation();
if (location == null) {
logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
return;
}
createResults(location);
}
@Override
protected void startBackgroundDiscovery() {
if (astroDiscoveryJob == null) {
astroDiscoveryJob = scheduler.scheduleWithFixedDelay(() -> {
PointType currentLocation = locationProvider.getLocation();
if (currentLocation != null && !Objects.equals(currentLocation, previousLocation)) {
logger.debug("Location has been changed from {} to {}: Creating new discovery results",
previousLocation, currentLocation);
createResults(currentLocation);
previousLocation = currentLocation;
}
}, 0, LOCATION_CHANGED_CHECK_INTERVAL, TimeUnit.SECONDS);
logger.debug("Scheduled astro location-changed job every {} seconds", LOCATION_CHANGED_CHECK_INTERVAL);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping Astro device background discovery");
ScheduledFuture<?> discoveryJob = astroDiscoveryJob;
if (discoveryJob != null) {
discoveryJob.cancel(true);
}
astroDiscoveryJob = null;
}
public void createResults(PointType location) {
String propGeolocation;
String country = localeProvider.getLocale().getCountry();
propGeolocation = String.format("%s,%s,%s", location.getLatitude(), location.getLongitude(),
location.getAltitude());
thingDiscovered(DiscoveryResultBuilder.create(SUN_THING).withLabel("Local Sun")
.withProperty("geolocation", propGeolocation)
.withProperty("useMeteorologicalSeason", METEO_BASED_COUNTRIES.contains(country))
.withRepresentationProperty("geolocation").build());
thingDiscovered(DiscoveryResultBuilder.create(MOON_THING).withLabel("Local Moon")
.withProperty("geolocation", propGeolocation).withRepresentationProperty("geolocation").build());
}
}

View File

@@ -0,0 +1,370 @@
/**
* 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.handler;
import static org.openhab.core.thing.ThingStatus.*;
import static org.openhab.core.thing.type.ChannelKind.TRIGGER;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.lang.invoke.MethodHandles;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.measure.quantity.Angle;
import org.apache.commons.lang.time.DateFormatUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.scheduler.ScheduledCompletableFuture;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base ThingHandler for all Astro handlers.
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public abstract class AstroThingHandler extends BaseThingHandler {
private static final String DAILY_MIDNIGHT = "30 0 0 * * ? *";
/** Logger Instance */
protected final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/** Scheduler to schedule jobs */
private final CronScheduler cronScheduler;
protected final TimeZoneProvider timeZoneProvider;
private final Lock monitor = new ReentrantLock();
private final Set<ScheduledFuture<?>> scheduledFutures = new HashSet<>();
private int linkedPositionalChannels = 0;
protected AstroThingConfig thingConfig = new AstroThingConfig();
private @Nullable ScheduledCompletableFuture<?> dailyJob;
public AstroThingHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
super(thing);
this.cronScheduler = scheduler;
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void initialize() {
logger.debug("Initializing thing {}", getThing().getUID());
String thingUid = getThing().getUID().toString();
thingConfig = getConfigAs(AstroThingConfig.class);
boolean validConfig = true;
String geoLocation = thingConfig.geolocation;
if (geoLocation == null || geoLocation.trim().isEmpty()) {
logger.error("Astro parameter geolocation is mandatory and must be configured, disabling thing '{}'",
thingUid);
validConfig = false;
} else {
thingConfig.parseGeoLocation();
}
if (thingConfig.latitude == null || thingConfig.longitude == null) {
logger.error(
"Astro parameters geolocation could not be split into latitude and longitude, disabling thing '{}'",
thingUid);
validConfig = false;
}
if (thingConfig.interval < 1 || thingConfig.interval > 86400) {
logger.error("Astro parameter interval must be in the range of 1-86400, disabling thing '{}'", thingUid);
validConfig = false;
}
if (validConfig) {
logger.debug("{}", thingConfig);
updateStatus(ONLINE);
restartJobs();
} else {
updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
}
logger.debug("Thing {} initialized {}", getThing().getUID(), getThing().getStatus());
}
@Override
public void dispose() {
logger.debug("Disposing thing {}", getThing().getUID());
stopJobs();
logger.debug("Thing {} disposed", getThing().getUID());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (REFRESH == command) {
logger.debug("Refreshing {}", channelUID);
publishChannelIfLinked(channelUID);
} else {
logger.warn("The Astro-Binding is a read-only binding and can not handle commands");
}
}
/**
* Iterates all channels of the thing and updates their states.
*/
public void publishPlanet() {
Planet planet = getPlanet();
if (planet == null) {
return;
}
logger.debug("Publishing planet {} for thing {}", planet.getClass().getSimpleName(), getThing().getUID());
for (Channel channel : getThing().getChannels()) {
if (channel.getKind() != TRIGGER) {
publishChannelIfLinked(channel.getUID());
}
}
}
/**
* Publishes the channel with data if it's linked.
*/
public void publishChannelIfLinked(ChannelUID channelUID) {
Planet planet = getPlanet();
if (isLinked(channelUID.getId()) && planet != null) {
final Channel channel = getThing().getChannel(channelUID.getId());
if (channel == null) {
logger.error("Cannot find channel for {}", channelUID);
return;
}
try {
AstroChannelConfig config = channel.getConfiguration().as(AstroChannelConfig.class);
updateState(channelUID,
PropertyUtils.getState(channelUID, config, planet, timeZoneProvider.getTimeZone()));
} catch (Exception ex) {
logger.error("Can't update state for channel {} : {}", channelUID, ex.getMessage(), ex);
}
}
}
/**
* Schedules a positional and a daily job at midnight for Astro calculation and starts it immediately too. Removes
* already scheduled jobs first.
*/
private void restartJobs() {
logger.debug("Restarting jobs for thing {}", getThing().getUID());
monitor.lock();
try {
stopJobs();
if (getThing().getStatus() == ONLINE) {
String thingUID = getThing().getUID().toString();
// Daily Job
Job runnable = getDailyJob();
dailyJob = cronScheduler.schedule(runnable, DAILY_MIDNIGHT);
logger.debug("Scheduled {} at midnight", dailyJob);
// Execute daily startup job immediately
runnable.run();
// Repeat positional job every configured seconds
// Use scheduleAtFixedRate to avoid time drift associated with scheduleWithFixedDelay
if (isPositionalChannelLinked()) {
Job positionalJob = new PositionalJob(thingUID);
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(positionalJob, 0, thingConfig.interval,
TimeUnit.SECONDS);
scheduledFutures.add(future);
logger.info("Scheduled {} every {} seconds", positionalJob, thingConfig.interval);
}
}
} finally {
monitor.unlock();
}
}
/**
* Stops all jobs for this thing.
*/
private void stopJobs() {
logger.debug("Stopping scheduled jobs for thing {}", getThing().getUID());
monitor.lock();
try {
ScheduledCompletableFuture<?> job = dailyJob;
if (job != null) {
job.cancel(true);
}
dailyJob = null;
for (ScheduledFuture<?> future : scheduledFutures) {
if (!future.isDone()) {
future.cancel(true);
}
}
scheduledFutures.clear();
} catch (Exception ex) {
logger.error("{}", ex.getMessage(), ex);
} finally {
monitor.unlock();
}
}
@Override
public void channelLinked(ChannelUID channelUID) {
linkedChannelChange(channelUID, 1);
publishChannelIfLinked(channelUID);
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
linkedChannelChange(channelUID, -1);
}
/**
* Counts positional channels and restarts Astro jobs.
*/
private void linkedChannelChange(ChannelUID channelUID, int step) {
if (Arrays.asList(getPositionalChannelIds()).contains(channelUID.getId())) {
int oldValue = linkedPositionalChannels;
linkedPositionalChannels += step;
if (oldValue == 0 && linkedPositionalChannels > 0 || oldValue > 0 && linkedPositionalChannels == 0) {
restartJobs();
}
}
}
/**
* Returns {@code true}, if at least one positional channel is linked.
*/
private boolean isPositionalChannelLinked() {
List<String> positionalChannels = Arrays.asList(getPositionalChannelIds());
for (Channel channel : getThing().getChannels()) {
String id = channel.getUID().getId();
if (isLinked(id) && positionalChannels.contains(id)) {
return true;
}
}
return false;
}
/**
* Emits an event for the given channel.
*/
public void triggerEvent(String channelId, String event) {
final Channel channel = getThing().getChannel(channelId);
if (channel == null) {
logger.warn("Event {} in thing {} does not exist, please recreate the thing", event, getThing().getUID());
return;
}
triggerChannel(channel.getUID(), event);
}
/**
* Adds the provided {@link Job} to the queue (cannot be {@code null})
*
* @return {@code true} if the {@code job} is added to the queue, otherwise {@code false}
*/
public void schedule(Job job, Calendar eventAt) {
long sleepTime;
monitor.lock();
try {
tidyScheduledFutures();
sleepTime = eventAt.getTimeInMillis() - new Date().getTime();
ScheduledFuture<?> future = scheduler.schedule(job, sleepTime, TimeUnit.MILLISECONDS);
scheduledFutures.add(future);
} finally {
monitor.unlock();
}
if (logger.isDebugEnabled()) {
String formattedDate = DateFormatUtils.ISO_DATETIME_FORMAT.format(eventAt);
logger.debug("Scheduled {} in {}ms (at {})", job, sleepTime, formattedDate);
}
}
private void tidyScheduledFutures() {
for (Iterator<ScheduledFuture<?>> iterator = scheduledFutures.iterator(); iterator.hasNext();) {
ScheduledFuture<?> future = iterator.next();
if (future.isDone()) {
logger.trace("Tidying up done future {}", future);
iterator.remove();
}
}
}
/**
* Calculates and publishes the daily Astro data.
*/
public void publishDailyInfo() {
publishPositionalInfo();
}
/**
* Calculates and publishes the interval Astro data.
*/
public abstract void publishPositionalInfo();
/**
* Returns the {@link Planet} instance (cannot be {@code null})
*/
public abstract @Nullable Planet getPlanet();
/**
* Returns the channelIds for positional calculation (cannot be {@code null})
*/
protected abstract String[] getPositionalChannelIds();
/**
* Returns the daily calculation {@link Job} (cannot be {@code null})
*/
protected abstract Job getDailyJob();
public abstract @Nullable Position getPositionAt(ZonedDateTime date);
public @Nullable QuantityType<Angle> getAzimuth(ZonedDateTime date) {
Position position = getPositionAt(date);
return position != null ? position.getAzimuth() : null;
}
public @Nullable QuantityType<Angle> getElevation(ZonedDateTime date) {
Position position = getPositionAt(date);
return position != null ? position.getElevation() : null;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(AstroActions.class);
}
}

View File

@@ -0,0 +1,110 @@
/**
* 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.handler;
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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.calc.MoonCalc;
import org.openhab.binding.astro.internal.job.DailyJobMoon;
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;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
/**
* The MoonHandler is responsible for updating calculated moon data.
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public class MoonHandler extends AstroThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_MOON));
private final String[] positionalChannelIds = new String[] { "phase#name", "phase#age", "phase#agePercent",
"phase#ageDegree", "phase#illumination", "position#azimuth", "position#elevation", "zodiac#sign" };
private final MoonCalc moonCalc = new MoonCalc();
private @NonNullByDefault({}) Moon moon;
/**
* Constructor
*/
public MoonHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
super(thing, scheduler, timeZoneProvider);
}
@Override
public void publishPositionalInfo() {
moon = getMoonAt(ZonedDateTime.now());
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
moonCalc.setPositionalInfo(Calendar.getInstance(), latitude != null ? latitude : 0,
longitude != null ? longitude : 0, moon);
moon.getEclipse().setElevations(this, timeZoneProvider);
publishPlanet();
}
@Override
public @Nullable Planet getPlanet() {
return moon;
}
@Override
public void dispose() {
super.dispose();
moon = null;
}
@Override
protected String[] getPositionalChannelIds() {
return positionalChannelIds;
}
@Override
protected Job getDailyJob() {
return new DailyJobMoon(thing.getUID().getAsString(), this);
}
private Moon getMoonAt(ZonedDateTime date) {
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
return moonCalc.getMoonInfo(GregorianCalendar.from(date), latitude != null ? latitude : 0,
longitude != null ? longitude : 0);
}
@Override
public @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();
}
}

View File

@@ -0,0 +1,122 @@
/**
* 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.handler;
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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.calc.SunCalc;
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;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
/**
* The SunHandler is responsible for updating calculated sun data.
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public class SunHandler extends AstroThingHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_SUN));
private final String[] positionalChannelIds = new String[] { "position#azimuth", "position#elevation",
"radiation#direct", "radiation#diffuse", "radiation#total" };
private final SunCalc sunCalc = new SunCalc();
private @NonNullByDefault({}) Sun sun;
/**
* Constructor
*/
public SunHandler(Thing thing, final CronScheduler scheduler, final TimeZoneProvider timeZoneProvider) {
super(thing, scheduler, timeZoneProvider);
}
@Override
public void publishPositionalInfo() {
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, altitude != null ? altitude : 0, sun);
sun.getEclipse().setElevations(this, timeZoneProvider);
publishPlanet();
}
@Override
public @Nullable Planet getPlanet() {
return sun;
}
@Override
public void dispose() {
super.dispose();
sun = null;
}
@Override
protected String[] getPositionalChannelIds() {
return positionalChannelIds;
}
@Override
protected Job getDailyJob() {
return new DailyJobSun(thing.getUID().getAsString(), this);
}
private Sun getSunAt(ZonedDateTime date) {
Double latitude = thingConfig.latitude;
Double longitude = thingConfig.longitude;
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
public @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();
}
}

View File

@@ -0,0 +1,49 @@
/**
* 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.job;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This class contains the default methods required for different jobs
*
* @author Amit Kumar Mondal - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractJob implements Job {
private final String thingUID;
public AbstractJob(String thingUID) {
this.thingUID = thingUID;
}
@Override
public String getThingUID() {
return thingUID;
}
/**
* Ensures the truth of an expression involving one or more parameters to the
* calling method.
*
* @param expression a boolean expression
* @param errorMessage the exception message to use if the check fails
* @throws IllegalArgumentException if {@code expression} is false
*/
public static void checkArgument(boolean expression, String errorMessage) {
if (!expression) {
throw new IllegalArgumentException(errorMessage);
}
}
}

View File

@@ -0,0 +1,63 @@
/**
* 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.job;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link CompositeJob} comprises multiple {@link Job}s to be executed in order
*
* @author Markus Rathgeb - Initial contribution
* @author Amit Kumar Mondal - Minor modifications
*/
@NonNullByDefault
public final class CompositeJob extends AbstractJob {
private final List<Job> jobs;
/**
* Constructor
*
* @param thingUID thing UID
* @param jobs the jobs to execute
* @throws IllegalArgumentException
* if {@code jobs} is {@code null} or empty
*/
public CompositeJob(String thingUID, List<Job> jobs) {
super(thingUID);
this.jobs = jobs;
boolean notMatched = jobs.stream().anyMatch(j -> !j.getThingUID().equals(thingUID));
checkArgument(!notMatched, "The jobs must associate the same thing UID");
}
@Override
public void run() {
jobs.forEach(j -> {
try {
j.run();
} catch (RuntimeException ex) {
LOGGER.warn("Job execution failed.", ex);
}
});
}
@Override
public String toString() {
return jobs.stream().map(j -> j.toString()).collect(Collectors.joining(" + "));
}
}

View File

@@ -0,0 +1,89 @@
/**
* 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.job;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import static org.openhab.binding.astro.internal.job.Job.scheduleEvent;
import java.util.Calendar;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.Moon;
import org.openhab.binding.astro.internal.model.MoonPhase;
import org.openhab.binding.astro.internal.model.Planet;
/**
* Daily scheduled jobs for Moon planet
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class DailyJobMoon extends AbstractJob {
private final AstroThingHandler handler;
/**
* Constructor
*
* @param thingUID the Thing UID
* @param handler the {@link AstroThingHandler} instance
* @throws IllegalArgumentException if {@code thingUID} or {@code handler} is {@code null}
*/
public DailyJobMoon(String thingUID, AstroThingHandler handler) {
super(thingUID);
this.handler = handler;
}
@Override
public void run() {
handler.publishDailyInfo();
String thingUID = getThingUID();
LOGGER.debug("Scheduled Astro event-jobs for thing {}", thingUID);
Planet planet = handler.getPlanet();
if (planet == null) {
LOGGER.error("Planet not instantiated");
return;
}
Moon moon = (Moon) planet;
scheduleEvent(thingUID, handler, moon.getRise().getStart(), EVENT_START, EVENT_CHANNEL_ID_RISE, false);
scheduleEvent(thingUID, handler, moon.getSet().getEnd(), EVENT_END, EVENT_CHANNEL_ID_SET, false);
MoonPhase moonPhase = moon.getPhase();
scheduleEvent(thingUID, handler, moonPhase.getFirstQuarter(), EVENT_PHASE_FIRST_QUARTER,
EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getThirdQuarter(), EVENT_PHASE_THIRD_QUARTER,
EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getFull(), EVENT_PHASE_FULL, EVENT_CHANNEL_ID_MOON_PHASE, false);
scheduleEvent(thingUID, handler, moonPhase.getNew(), EVENT_PHASE_NEW, EVENT_CHANNEL_ID_MOON_PHASE, false);
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(thingUID, handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false);
}
});
scheduleEvent(thingUID, handler, moon.getPerigee().getDate(), EVENT_PERIGEE, EVENT_CHANNEL_ID_PERIGEE, false);
scheduleEvent(thingUID, handler, moon.getApogee().getDate(), EVENT_APOGEE, EVENT_CHANNEL_ID_APOGEE, false);
}
@Override
public String toString() {
return "Daily job moon " + getThingUID();
}
}

View File

@@ -0,0 +1,106 @@
/**
* 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.job;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import static org.openhab.binding.astro.internal.job.Job.*;
import static org.openhab.binding.astro.internal.model.SunPhaseName.*;
import java.util.Calendar;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.model.Eclipse;
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Sun;
/**
* Daily scheduled jobs For Sun planet
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class DailyJobSun extends AbstractJob {
private final AstroThingHandler handler;
/**
* Constructor
*
* @param thingUID the Thing UID
* @param handler the {@link AstroThingHandler} instance
* @throws IllegalArgumentException
* if {@code thingUID} or {@code handler} is {@code null}
*/
public DailyJobSun(String thingUID, AstroThingHandler handler) {
super(thingUID);
this.handler = handler;
}
@Override
public void run() {
handler.publishDailyInfo();
String thingUID = getThingUID();
LOGGER.debug("Scheduled Astro event-jobs for thing {}", thingUID);
Planet planet = handler.getPlanet();
if (planet == null) {
LOGGER.error("Planet not instantiated");
return;
}
Sun sun = (Sun) planet;
scheduleRange(thingUID, handler, sun.getRise(), EVENT_CHANNEL_ID_RISE);
scheduleRange(thingUID, handler, sun.getSet(), EVENT_CHANNEL_ID_SET);
scheduleRange(thingUID, handler, sun.getNoon(), EVENT_CHANNEL_ID_NOON);
scheduleRange(thingUID, handler, sun.getNight(), EVENT_CHANNEL_ID_NIGHT);
scheduleRange(thingUID, handler, sun.getMorningNight(), EVENT_CHANNEL_ID_MORNING_NIGHT);
scheduleRange(thingUID, handler, sun.getAstroDawn(), EVENT_CHANNEL_ID_ASTRO_DAWN);
scheduleRange(thingUID, handler, sun.getNauticDawn(), EVENT_CHANNEL_ID_NAUTIC_DAWN);
scheduleRange(thingUID, handler, sun.getCivilDawn(), EVENT_CHANNEL_ID_CIVIL_DAWN);
scheduleRange(thingUID, handler, sun.getAstroDusk(), EVENT_CHANNEL_ID_ASTRO_DUSK);
scheduleRange(thingUID, handler, sun.getNauticDusk(), EVENT_CHANNEL_ID_NAUTIC_DUSK);
scheduleRange(thingUID, handler, sun.getCivilDusk(), EVENT_CHANNEL_ID_CIVIL_DUSK);
scheduleRange(thingUID, handler, sun.getEveningNight(), EVENT_CHANNEL_ID_EVENING_NIGHT);
scheduleRange(thingUID, handler, sun.getDaylight(), EVENT_CHANNEL_ID_DAYLIGHT);
Eclipse eclipse = sun.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = eclipse.getDate(eclipseKind);
if (eclipseDate != null) {
scheduleEvent(thingUID, handler, eclipseDate, eclipseKind.toString(), EVENT_CHANNEL_ID_ECLIPSE, false);
}
});
// schedule republish jobs
schedulePublishPlanet(thingUID, handler, sun.getZodiac().getEnd());
schedulePublishPlanet(thingUID, handler, sun.getSeason().getNextSeason());
// schedule phase jobs
scheduleSunPhase(thingUID, handler, SUN_RISE, sun.getRise().getStart());
scheduleSunPhase(thingUID, handler, SUN_SET, sun.getSet().getStart());
scheduleSunPhase(thingUID, handler, NIGHT, sun.getNight().getStart());
scheduleSunPhase(thingUID, handler, DAYLIGHT, sun.getDaylight().getStart());
scheduleSunPhase(thingUID, handler, ASTRO_DAWN, sun.getAstroDawn().getStart());
scheduleSunPhase(thingUID, handler, NAUTIC_DAWN, sun.getNauticDawn().getStart());
scheduleSunPhase(thingUID, handler, CIVIL_DAWN, sun.getCivilDawn().getStart());
scheduleSunPhase(thingUID, handler, ASTRO_DUSK, sun.getAstroDusk().getStart());
scheduleSunPhase(thingUID, handler, NAUTIC_DUSK, sun.getNauticDusk().getStart());
scheduleSunPhase(thingUID, handler, CIVIL_DUSK, sun.getCivilDusk().getStart());
}
@Override
public String toString() {
return "Daily job sun " + getThingUID();
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.job;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.AstroHandlerFactory;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
/**
* Scheduled job to trigger events
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class EventJob extends AbstractJob {
private final String channelID;
private final String event;
/**
* Constructor
*
* @param thingUID thing UID
* @param channelID channel ID
* @param event Event name
* @throws IllegalArgumentException
* if any of the arguments is {@code null}
*/
public EventJob(String thingUID, String channelID, String event) {
super(thingUID);
this.channelID = channelID;
this.event = event;
}
@Override
public void run() {
AstroThingHandler astroHandler = AstroHandlerFactory.getHandler(getThingUID());
if (astroHandler != null) {
astroHandler.triggerEvent(channelID, event);
} else {
LOGGER.trace("AstroThingHandler is null");
}
}
@Override
public String toString() {
return "Event job " + getThingUID() + "/" + channelID + "/" + event;
}
}

View File

@@ -0,0 +1,176 @@
/**
* 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.job;
import static java.util.Arrays.asList;
import static java.util.Calendar.SECOND;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang.time.DateUtils.truncatedEquals;
import static org.openhab.binding.astro.internal.AstroBindingConstants.*;
import static org.openhab.binding.astro.internal.util.DateTimeUtils.*;
import java.lang.invoke.MethodHandles;
import java.util.Calendar;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.config.AstroChannelConfig;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.SunPhaseName;
import org.openhab.core.scheduler.SchedulerRunnable;
import org.openhab.core.thing.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The interface to be implemented by classes which represent a 'job' to be performed
*
* @author Amit Kumar Mondal - Initial contribution
*/
@NonNullByDefault
public interface Job extends SchedulerRunnable, Runnable {
/** Logger Instance */
public final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* Schedules the provided {@link Job} instance
*
* @param thingUID the UID of the Thing instance
* @param astroHandler the {@link AstroThingHandler} instance
* @param job the {@link Job} instance to schedule
* @param eventAt the {@link Calendar} instance denoting scheduled instant
*/
public static void schedule(String thingUID, AstroThingHandler astroHandler, Job job, Calendar eventAt) {
try {
Calendar today = Calendar.getInstance();
if (isSameDay(eventAt, today) && isTimeGreaterEquals(eventAt, today)) {
astroHandler.schedule(job, eventAt);
}
} catch (Exception ex) {
LOGGER.error("{}", ex.getMessage(), ex);
}
}
/**
* Schedules an {@link EventJob} instance
*
* @param thingUID the Thing UID
* @param astroHandler the {@link AstroThingHandler} instance
* @param eventAt the {@link Calendar} instance denoting scheduled instant
* @param event the event ID
* @param channelId the channel ID
*/
public static void scheduleEvent(String thingUID, AstroThingHandler astroHandler, Calendar eventAt, String event,
String channelId, boolean configAlreadyApplied) {
scheduleEvent(thingUID, astroHandler, eventAt, singletonList(event), channelId, configAlreadyApplied);
}
/**
* Schedules an {@link EventJob} instance
*
* @param thingUID the Thing UID
* @param astroHandler the {@link AstroThingHandler} instance
* @param eventAt the {@link Calendar} instance denoting scheduled instant
* @param events the event IDs to schedule
* @param channelId the channel ID
*/
public static void scheduleEvent(String thingUID, AstroThingHandler astroHandler, Calendar eventAt,
List<String> events, String channelId, boolean configAlreadyApplied) {
if (events.isEmpty()) {
return;
}
final Calendar instant;
if (!configAlreadyApplied) {
final Channel channel = astroHandler.getThing().getChannel(channelId);
if (channel == null) {
LOGGER.warn("Cannot find channel '{}' for thing '{}'.", channelId, astroHandler.getThing().getUID());
return;
}
AstroChannelConfig config = channel.getConfiguration().as(AstroChannelConfig.class);
instant = applyConfig(eventAt, config);
} else {
instant = eventAt;
}
List<Job> jobs = events.stream().map(e -> new EventJob(thingUID, channelId, e)).collect(toList());
schedule(thingUID, astroHandler, new CompositeJob(thingUID, jobs), instant);
}
/**
* Schedules {@link Channel} events
*
* @param thingUID the Thing UID
* @param astroHandler the {@link AstroThingHandler} instance
* @param range the {@link Range} instance
* @param channelId the channel ID
*/
public static void scheduleRange(String thingUID, AstroThingHandler astroHandler, Range range, String channelId) {
Calendar start = range.getStart();
Calendar end = range.getEnd();
// depending on the location you might not have a valid range for day/night, so skip the events:
if (start == null || end == null) {
return;
}
final Channel channel = astroHandler.getThing().getChannel(channelId);
if (channel == null) {
LOGGER.warn("Cannot find channel '{}' for thing '{}'.", channelId, astroHandler.getThing().getUID());
return;
}
AstroChannelConfig config = channel.getConfiguration().as(AstroChannelConfig.class);
Calendar configStart = applyConfig(start, config);
Calendar configEnd = applyConfig(end, config);
if (truncatedEquals(configStart, configEnd, SECOND)) {
scheduleEvent(thingUID, astroHandler, configStart, asList(EVENT_START, EVENT_END), channelId, true);
} else {
scheduleEvent(thingUID, astroHandler, configStart, EVENT_START, channelId, true);
scheduleEvent(thingUID, astroHandler, configEnd, EVENT_END, channelId, true);
}
}
/**
* Schedules Planet events
*
* @param thingUID the Thing UID
* @param astroHandler the {@link AstroThingHandler} instance
* @param eventAt the {@link Calendar} instance denoting scheduled instant
*/
public static void schedulePublishPlanet(String thingUID, AstroThingHandler astroHandler, Calendar eventAt) {
Job publishJob = new PublishPlanetJob(thingUID);
schedule(thingUID, astroHandler, publishJob, eventAt);
}
/**
* Schedules {@link SunPhaseJob}
*
* @param thingUID the Thing UID
* @param astroHandler the {@link AstroThingHandler} instance
* @param sunPhaseName {@link SunPhaseName} instance
* @param eventAt the {@link Calendar} instance denoting scheduled instant
*/
public static void scheduleSunPhase(String thingUID, AstroThingHandler astroHandler, SunPhaseName sunPhaseName,
Calendar eventAt) {
Job sunPhaseJob = new SunPhaseJob(thingUID, sunPhaseName);
schedule(thingUID, astroHandler, sunPhaseJob, eventAt);
}
/**
* Returns the thing UID that is associated with this {@link Job} (cannot be {@code null})
*/
public String getThingUID();
}

View File

@@ -0,0 +1,53 @@
/**
* 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.job;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.AstroHandlerFactory;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
/**
* Scheduled job for planet positions
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class PositionalJob extends AbstractJob {
/**
* Constructor
*
* @param thingUID thing UID
* @throws IllegalArgumentException
* if the provided argument is {@code null}
*/
public PositionalJob(String thingUID) {
super(thingUID);
}
@Override
public void run() {
AstroThingHandler astroHandler = AstroHandlerFactory.getHandler(getThingUID());
if (astroHandler != null) {
astroHandler.publishPositionalInfo();
} else {
LOGGER.trace("AstroThingHandler is null");
}
}
@Override
public String toString() {
return "Positional job " + getThingUID();
}
}

View File

@@ -0,0 +1,53 @@
/**
* 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.job;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.AstroHandlerFactory;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
/**
* Scheduled job for planets
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class PublishPlanetJob extends AbstractJob {
/**
* Constructor
*
* @param thingUID thing UID
* @throws IllegalArgumentException
* if the provided argument is {@code null}
*/
public PublishPlanetJob(String thingUID) {
super(thingUID);
}
@Override
public void run() {
AstroThingHandler astroHandler = AstroHandlerFactory.getHandler(getThingUID());
if (astroHandler != null) {
astroHandler.publishDailyInfo();
} else {
LOGGER.trace("AstroThingHandler is null");
}
}
@Override
public String toString() {
return "Publish planet job " + getThingUID();
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.job;
import static org.openhab.binding.astro.internal.AstroBindingConstants.CHANNEL_ID_SUN_PHASE_NAME;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.astro.internal.AstroHandlerFactory;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPhaseName;
import org.openhab.core.thing.Channel;
/**
* Scheduled job for Sun phase change
*
* @author Gerhard Riegler - Initial contribution
* @author Amit Kumar Mondal - Implementation to be compliant with ESH Scheduler
*/
@NonNullByDefault
public final class SunPhaseJob extends AbstractJob {
private final SunPhaseName sunPhaseName;
/**
* Constructor
*
* @param thingUID thing UID
* @param sunPhaseName {@link SunPhaseName} name
* @throws IllegalArgumentException
* if any of the arguments is {@code null}
*/
public SunPhaseJob(String thingUID, SunPhaseName sunPhaseName) {
super(thingUID);
this.sunPhaseName = sunPhaseName;
}
@Override
public void run() {
AstroThingHandler astroHandler = AstroHandlerFactory.getHandler(getThingUID());
if (astroHandler != null) {
Channel phaseNameChannel = astroHandler.getThing().getChannel(CHANNEL_ID_SUN_PHASE_NAME);
if (phaseNameChannel != null) {
Planet planet = astroHandler.getPlanet();
if (planet != null && planet instanceof Sun) {
final Sun typedSun = (Sun) planet;
typedSun.getPhase().setName(sunPhaseName);
astroHandler.publishChannelIfLinked(phaseNameChannel.getUID());
}
} else {
LOGGER.trace("{}", "Phase Name Channel is null");
}
} else {
LOGGER.trace("AstroThingHandler is null");
}
}
@Override
public String toString() {
return "Sun phase job " + getThingUID() + "/" + sunPhaseName;
}
}

View File

@@ -0,0 +1,112 @@
/**
* 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.model;
import java.util.AbstractMap.SimpleEntry;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.handler.AstroThingHandler;
import org.openhab.core.i18n.TimeZoneProvider;
/**
* Holds eclipse informations.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public class Eclipse {
private final Map<EclipseKind, @Nullable Entry<Calendar, @Nullable Double>> entries = new HashMap<>();
public Eclipse(EclipseKind... eclipses) {
for (EclipseKind eclipseKind : eclipses) {
entries.put(eclipseKind, null);
}
}
public Set<EclipseKind> getKinds() {
return entries.keySet();
}
/**
* Returns the date of the next total eclipse.
*/
public @Nullable Calendar getTotal() {
return getDate(EclipseKind.TOTAL);
}
/**
* Returns the date of the next partial eclipse.
*/
public @Nullable Calendar getPartial() {
return getDate(EclipseKind.PARTIAL);
}
/**
* Returns the date of the next ring eclipse.
*/
public @Nullable Calendar getRing() {
return getDate(EclipseKind.RING);
}
/**
* Returns the elevation of the next total eclipse.
*/
public @Nullable Double getTotalElevation() {
return getElevation(EclipseKind.TOTAL);
}
/**
* Returns the elevation of the next partial eclipse.
*/
public @Nullable Double getPartialElevation() {
return getElevation(EclipseKind.PARTIAL);
}
/**
* Returns the elevation of the next ring eclipse.
*/
public @Nullable Double getRingElevation() {
return getElevation(EclipseKind.RING);
}
public @Nullable Calendar getDate(EclipseKind eclipseKind) {
Entry<Calendar, @Nullable Double> entry = entries.get(eclipseKind);
return entry != null ? entry.getKey() : null;
}
private @Nullable Double getElevation(EclipseKind eclipseKind) {
Entry<Calendar, @Nullable Double> entry = entries.get(eclipseKind);
return entry != null ? entry.getValue() : null;
}
public void set(EclipseKind eclipseKind, Calendar eclipseDate, @Nullable Position position) {
entries.put(eclipseKind,
new SimpleEntry<>(eclipseDate, position != null ? position.getElevationAsDouble() : null));
}
public void setElevations(AstroThingHandler astroHandler, TimeZoneProvider timeZoneProvider) {
getKinds().forEach(eclipseKind -> {
Calendar eclipseDate = getDate(eclipseKind);
if (eclipseDate != null) {
set(eclipseKind, eclipseDate,
astroHandler.getPositionAt(eclipseDate.toInstant().atZone(timeZoneProvider.getTimeZone())));
}
});
}
}

View File

@@ -0,0 +1,27 @@
/**
* 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.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* All kind of eclipses.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public enum EclipseKind {
PARTIAL,
TOTAL,
RING;
}

View File

@@ -0,0 +1,26 @@
/**
* 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.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Astro objects susceptible of being eclipsed.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public enum EclipseType {
SUN,
MOON;
}

View File

@@ -0,0 +1,126 @@
/**
* 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.model;
/**
* Holds the calculated moon data.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Moon extends RiseSet implements Planet {
private MoonPhase phase = new MoonPhase();
private MoonDistance apogee = new MoonDistance();
private MoonDistance perigee = new MoonDistance();
private MoonDistance distance = new MoonDistance();
private Eclipse eclipse = new Eclipse(EclipseKind.PARTIAL, EclipseKind.TOTAL);
private Position position = new Position();
private Zodiac zodiac = new Zodiac(null);
/**
* Returns the moon phase.
*/
public MoonPhase getPhase() {
return phase;
}
/**
* Sets the moon phase.
*/
public void setPhase(MoonPhase phase) {
this.phase = phase;
}
/**
* Returns the apogee.
*/
public MoonDistance getApogee() {
return apogee;
}
/**
* Sets the apogee.
*/
public void setApogee(MoonDistance apogee) {
this.apogee = apogee;
}
/**
* Returns the perigee.
*/
public MoonDistance getPerigee() {
return perigee;
}
/**
* Sets the perigee.
*/
public void setPerigee(MoonDistance perigee) {
this.perigee = perigee;
}
/**
* Returns the eclipses.
*/
public Eclipse getEclipse() {
return eclipse;
}
/**
* Sets the eclipses.
*/
public void setEclipse(Eclipse eclipse) {
this.eclipse = eclipse;
}
/**
* Returns the current distance.
*/
public MoonDistance getDistance() {
return distance;
}
/**
* Sets the current distance.
*/
public void setDistance(MoonDistance distance) {
this.distance = distance;
}
/**
* Returns the position.
*/
public Position getPosition() {
return position;
}
/**
* Sets the position.
*/
public void setPosition(Position position) {
this.position = position;
}
/**
* Returns the zodiac.
*/
public Zodiac getZodiac() {
return zodiac;
}
/**
* Sets the zodiac.
*/
public void setZodiac(Zodiac zodiac) {
this.zodiac = zodiac;
}
}

View File

@@ -0,0 +1,62 @@
/**
* 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.model;
import static org.openhab.core.library.unit.MetricPrefix.KILO;
import static org.openhab.core.library.unit.SIUnits.METRE;
import java.util.Calendar;
import javax.measure.quantity.Length;
import org.openhab.core.library.types.QuantityType;
/**
* Holds a distance informations.
*
* @author Gerhard Riegler - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
*/
public class MoonDistance {
private Calendar date;
private double distance;
/**
* Returns the date of the calculated distance.
*/
public Calendar getDate() {
return date;
}
/**
* Sets the date of the calculated distance.
*/
public void setDate(Calendar date) {
this.date = date;
}
/**
* Returns the distance in kilometers.
*/
public QuantityType<Length> getDistance() {
return new QuantityType<>(distance, KILO(METRE));
}
/**
* Sets the distance in kilometers.
*/
public void setDistance(double kilometer) {
this.distance = kilometer;
}
}

View File

@@ -0,0 +1,167 @@
/**
* 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.model;
import java.util.Calendar;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Time;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* Holds the calculates moon phase informations.
*
* @author Gerhard Riegler - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
*/
public class MoonPhase {
private Calendar firstQuarter;
private Calendar full;
private Calendar thirdQuarter;
private Calendar _new;
private int age;
private double illumination;
private double agePercent;
private double ageDegree;
private MoonPhaseName name;
/**
* Returns the date at which the moon is in the first quarter.
*/
public Calendar getFirstQuarter() {
return firstQuarter;
}
/**
* Sets the date at which the moon is in the first quarter.
*/
public void setFirstQuarter(Calendar firstQuarter) {
this.firstQuarter = firstQuarter;
}
/**
* Returns the date of the full moon.
*/
public Calendar getFull() {
return full;
}
/**
* Sets the date of the full moon.
*/
public void setFull(Calendar full) {
this.full = full;
}
/**
* Returns the date at which the moon is in the third quarter.
*/
public Calendar getThirdQuarter() {
return thirdQuarter;
}
/**
* Sets the date at which the moon is in the third quarter.
*/
public void setThirdQuarter(Calendar thirdQuarter) {
this.thirdQuarter = thirdQuarter;
}
/**
* Returns the date of the new moon.
*/
public Calendar getNew() {
return _new;
}
/**
* Sets the date of the new moon.
*/
public void setNew(Calendar _new) {
this._new = _new;
}
/**
* Returns the age in days.
*/
public QuantityType<Time> getAge() {
return new QuantityType<>(age, SmartHomeUnits.DAY);
}
/**
* Sets the age in days.
*/
public void setAge(int age) {
this.age = age;
}
/**
* Returns the illumination.
*/
public QuantityType<Dimensionless> getIllumination() {
return new QuantityType<>(illumination, SmartHomeUnits.PERCENT);
}
/**
* Sets the illumination.
*/
public void setIllumination(double illumination) {
this.illumination = illumination;
}
/**
* Returns the phase name.
*/
public MoonPhaseName getName() {
return name;
}
/**
* Sets the phase name.
*/
public void setName(MoonPhaseName name) {
this.name = name;
}
/**
* Returns the age in degree.
*/
public QuantityType<Angle> getAgeDegree() {
return new QuantityType<>(ageDegree, SmartHomeUnits.DEGREE_ANGLE);
}
/**
* Sets the age in degree.
*/
public void setAgeDegree(double ageDegree) {
this.ageDegree = ageDegree;
}
/**
* Returns the age in percent.
*/
public QuantityType<Dimensionless> getAgePercent() {
return new QuantityType<>(agePercent, SmartHomeUnits.PERCENT);
}
/**
* Sets the age in percent.
*/
public void setAgePercent(double agePercent) {
this.agePercent = agePercent;
}
}

View File

@@ -0,0 +1,29 @@
/**
* 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.model;
/**
* All moon phases.
*
* @author Gerhard Riegler - Initial contribution
*/
public enum MoonPhaseName {
NEW,
WAXING_CRESCENT,
FIRST_QUARTER,
WAXING_GIBBOUS,
FULL,
WANING_GIBBOUS,
THIRD_QUARTER,
WANING_CRESCENT
}

View File

@@ -0,0 +1,21 @@
/**
* 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.model;
/**
* Marker interface for all planets.
*
* @author Gerhard Riegler - Initial contribution
*/
public interface Planet {
}

View File

@@ -0,0 +1,87 @@
/**
* 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.model;
import javax.measure.quantity.Angle;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* Holds the calculated azimuth and elevation.
*
* @author Gerhard Riegler - Initial contribution
* @author Gaël L'hopital - Added shade length
* @author Christoph Weitkamp - Introduced UoM
*/
public class Position {
private double azimuth;
private double elevation;
private double shadeLength;
public Position() {
}
public Position(double azimuth, double elevation, double shadeLength) {
this.azimuth = azimuth;
this.elevation = elevation;
this.shadeLength = shadeLength;
}
/**
* Returns the azimuth.
*/
public QuantityType<Angle> getAzimuth() {
return new QuantityType<>(azimuth, SmartHomeUnits.DEGREE_ANGLE);
}
/**
* Sets the azimuth.
*/
public void setAzimuth(double azimuth) {
this.azimuth = azimuth;
}
/**
* Returns the elevation.
*/
public QuantityType<Angle> getElevation() {
return new QuantityType<>(elevation, SmartHomeUnits.DEGREE_ANGLE);
}
public double getElevationAsDouble() {
return elevation;
}
/**
* Sets the elevation.
*/
public void setElevation(double elevation) {
this.elevation = elevation;
}
/**
* Returns the shade length.
*/
public double getShadeLength() {
return shadeLength;
}
/**
* Sets the shade length.
*/
public void setShadeLength(double shadeLength) {
this.shadeLength = shadeLength;
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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.model;
import org.openhab.core.library.dimension.Intensity;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* Holds the calculated direct, diffuse and total
*
* @author Gaël L'hopital - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
*/
public class Radiation {
private double direct;
private double diffuse;
private double total;
public Radiation() {
}
public Radiation(double direct, double diffuse, double total) {
this.direct = direct;
this.diffuse = diffuse;
this.total = total;
}
/**
* Sets the direct radiation.
*/
public void setDirect(double direct) {
this.direct = direct;
}
/**
* Sets the diffuse radiation.
*/
public void setDiffuse(double diffuse) {
this.diffuse = diffuse;
}
/**
* Sets the total radiation.
*/
public void setTotal(double total) {
this.total = total;
}
/**
* Returns the total radiation.
*/
public QuantityType<Intensity> getTotal() {
return new QuantityType<>(total, SmartHomeUnits.IRRADIANCE);
}
/**
* Returns the direct radiation.
*/
public QuantityType<Intensity> getDirect() {
return new QuantityType<>(direct, SmartHomeUnits.IRRADIANCE);
}
/**
* Returns the diffuse radiation.
*/
public QuantityType<Intensity> getDiffuse() {
return new QuantityType<>(diffuse, SmartHomeUnits.IRRADIANCE);
}
}

View File

@@ -0,0 +1,84 @@
/**
* 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.model;
import static org.openhab.core.library.unit.MetricPrefix.MILLI;
import java.util.Calendar;
import javax.measure.quantity.Time;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* Range class which holds a start and a end calendar object.
*
* @author Gerhard Riegler - Initial contribution
* @author Christoph Weitkamp - Introduced UoM
*/
public class Range {
private Calendar start;
private Calendar end;
public Range() {
}
public Range(Calendar start, Calendar end) {
this.start = start;
this.end = end;
}
/**
* Returns the start of the range.
*/
public Calendar getStart() {
return start;
}
/**
* Returns the end of the range.
*/
public Calendar getEnd() {
return end;
}
/**
* Returns the duration in minutes.
*/
public QuantityType<Time> getDuration() {
if (start == null || end == null) {
return null;
}
if (start.after(end)) {
return new QuantityType<>(0, SmartHomeUnits.MINUTE);
}
return new QuantityType<>(end.getTimeInMillis() - start.getTimeInMillis(), MILLI(SmartHomeUnits.SECOND))
.toUnit(SmartHomeUnits.MINUTE);
}
/**
* Returns true, if the given calendar matches into the range.
*/
public boolean matches(Calendar cal) {
if (start == null && end == null) {
return false;
}
long matchStart = start != null ? start.getTimeInMillis()
: DateTimeUtils.truncateToMidnight(cal).getTimeInMillis();
long matchEnd = end != null ? end.getTimeInMillis() : DateTimeUtils.endOfDayDate(cal).getTimeInMillis();
return cal.getTimeInMillis() >= matchStart && cal.getTimeInMillis() < matchEnd;
}
}

View File

@@ -0,0 +1,54 @@
/**
* 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.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Base class for the rise and set ranges.
*
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
public abstract class RiseSet {
private Range rise = new Range();
private Range set = new Range();
/**
* Returns the rise range.
*/
public Range getRise() {
return rise;
}
/**
* Sets the rise range.
*/
public void setRise(Range rise) {
this.rise = rise;
}
/**
* Returns the set range.
*/
public Range getSet() {
return set;
}
/**
* Sets the set range.
*/
public void setSet(Range set) {
this.set = set;
}
}

View File

@@ -0,0 +1,135 @@
/**
* 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.model;
import static org.openhab.core.library.unit.MetricPrefix.MILLI;
import java.util.Calendar;
import javax.measure.quantity.Time;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
/**
* Holds the season dates of the year and the current name.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Season {
private Calendar spring;
private Calendar summer;
private Calendar autumn;
private Calendar winter;
private SeasonName name;
/**
* Returns the date of the beginning of spring.
*/
public Calendar getSpring() {
return spring;
}
/**
* Sets the date of the beginning of spring.
*/
public void setSpring(Calendar spring) {
this.spring = spring;
}
/**
* Returns the date of the beginning of summer.
*/
public Calendar getSummer() {
return summer;
}
/**
* Sets the date of the beginning of summer.
*/
public void setSummer(Calendar summer) {
this.summer = summer;
}
/**
* Returns the date of the beginning of autumn.
*/
public Calendar getAutumn() {
return autumn;
}
/**
* Sets the date of the beginning of autumn.
*/
public void setAutumn(Calendar autumn) {
this.autumn = autumn;
}
/**
* Returns the date of the beginning of winter.
*/
public Calendar getWinter() {
return winter;
}
/**
* Returns the date of the beginning of winter.
*/
public void setWinter(Calendar winter) {
this.winter = winter;
}
/**
* Returns the current season name.
*/
public SeasonName getName() {
return name;
}
/**
* Sets the current season name.
*/
public void setName(SeasonName name) {
this.name = name;
}
/**
* Returns the next season.
*/
public Calendar getNextSeason() {
return DateTimeUtils.getNext(spring, summer, autumn, winter);
}
/**
* Returns the next season name.
*/
public SeasonName getNextName() {
int ordinal = name.ordinal() + 1;
if (ordinal > 3) {
ordinal = 0;
}
return SeasonName.values()[ordinal];
}
/**
* Returns the time left for current season
*/
public QuantityType<Time> getTimeLeft() {
Calendar now = Calendar.getInstance();
Calendar next = getNextSeason();
return new QuantityType<>(next.getTimeInMillis() - now.getTimeInMillis(), MILLI(SmartHomeUnits.SECOND))
.toUnit(SmartHomeUnits.DAY);
}
}

View File

@@ -0,0 +1,25 @@
/**
* 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.model;
/**
* All season names.
*
* @author Gerhard Riegler - Initial contribution
*/
public enum SeasonName {
SPRING,
SUMMER,
AUTUMN,
WINTER
}

View File

@@ -0,0 +1,294 @@
/**
* 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.model;
import java.util.HashMap;
import java.util.Map;
/**
* Holds the calculated sun data.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Sun extends RiseSet implements Planet {
private Map<SunPhaseName, Range> ranges = new HashMap<>();
private Position position = new Position();
private SunZodiac zodiac = new SunZodiac(null, null);
private Season season = new Season();
private Eclipse eclipse = new Eclipse(EclipseKind.PARTIAL, EclipseKind.TOTAL, EclipseKind.RING);
private Radiation radiation = new Radiation();
private SunPhase phase = new SunPhase();
/**
* Returns the astro dawn range.
*/
public Range getAstroDawn() {
return ranges.get(SunPhaseName.ASTRO_DAWN);
}
/**
* Sets the astro dawn range.
*/
public void setAstroDawn(Range astroDawn) {
ranges.put(SunPhaseName.ASTRO_DAWN, astroDawn);
}
/**
* Returns the nautic dawn range.
*/
public Range getNauticDawn() {
return ranges.get(SunPhaseName.NAUTIC_DAWN);
}
/**
* Sets the nautic dawn range.
*/
public void setNauticDawn(Range nauticDawn) {
ranges.put(SunPhaseName.NAUTIC_DAWN, nauticDawn);
}
/**
* Returns the civil dawn range.
*/
public Range getCivilDawn() {
return ranges.get(SunPhaseName.CIVIL_DAWN);
}
/**
* Sets the civil dawn range.
*/
public void setCivilDawn(Range civilDawn) {
ranges.put(SunPhaseName.CIVIL_DAWN, civilDawn);
}
/**
* Returns the civil dusk range.
*/
public Range getCivilDusk() {
return ranges.get(SunPhaseName.CIVIL_DUSK);
}
/**
* Sets the civil dusk range.
*/
public void setCivilDusk(Range civilDusk) {
ranges.put(SunPhaseName.CIVIL_DUSK, civilDusk);
}
/**
* Returns the nautic dusk range.
*/
public Range getNauticDusk() {
return ranges.get(SunPhaseName.NAUTIC_DUSK);
}
/**
* Sets the nautic dusk range.
*/
public void setNauticDusk(Range nauticDusk) {
ranges.put(SunPhaseName.NAUTIC_DUSK, nauticDusk);
}
/**
* Returns the astro dusk range.
*/
public Range getAstroDusk() {
return ranges.get(SunPhaseName.ASTRO_DUSK);
}
/**
* Sets the astro dusk range.
*/
public void setAstroDusk(Range astroDusk) {
ranges.put(SunPhaseName.ASTRO_DUSK, astroDusk);
}
/**
* Returns the noon range, start and end is always equal.
*/
public Range getNoon() {
return ranges.get(SunPhaseName.NOON);
}
/**
* Sets the noon range.
*/
public void setNoon(Range noon) {
ranges.put(SunPhaseName.NOON, noon);
}
/**
* Returns the daylight range.
*/
public Range getDaylight() {
return ranges.get(SunPhaseName.DAYLIGHT);
}
/**
* Sets the daylight range.
*/
public void setDaylight(Range daylight) {
ranges.put(SunPhaseName.DAYLIGHT, daylight);
}
/**
* Returns the morning night range.
*/
public Range getMorningNight() {
return ranges.get(SunPhaseName.MORNING_NIGHT);
}
/**
* Sets the morning night range.
*/
public void setMorningNight(Range morningNight) {
ranges.put(SunPhaseName.MORNING_NIGHT, morningNight);
}
/**
* Returns the evening night range.
*/
public Range getEveningNight() {
return ranges.get(SunPhaseName.EVENING_NIGHT);
}
/**
* Sets the evening night range.
*/
public void setEveningNight(Range eveningNight) {
ranges.put(SunPhaseName.EVENING_NIGHT, eveningNight);
}
/**
* Returns the night range.
*/
public Range getNight() {
return ranges.get(SunPhaseName.NIGHT);
}
/**
* Sets the night range.
*/
public void setNight(Range night) {
ranges.put(SunPhaseName.NIGHT, night);
}
/**
* Sets the rise range.
*/
@Override
public void setRise(Range rise) {
super.setRise(rise);
ranges.put(SunPhaseName.SUN_RISE, rise);
}
/**
* Sets the set range.
*/
@Override
public void setSet(Range set) {
super.setSet(set);
ranges.put(SunPhaseName.SUN_SET, set);
}
/**
* Returns the sun position.
*/
public Position getPosition() {
return position;
}
/**
* Returns the sun radiation
*/
public Radiation getRadiation() {
return radiation;
}
/**
* Sets the sun position.
*/
public void setPosition(Position position) {
this.position = position;
}
/**
* Returns the zodiac.
*/
public SunZodiac getZodiac() {
return zodiac;
}
/**
* Sets the zodiac.
*/
public void setZodiac(SunZodiac zodiac) {
this.zodiac = zodiac;
}
/**
* Returns the seasons.
*/
public Season getSeason() {
return season;
}
/**
* Sets the seasons.
*/
public void setSeason(Season season) {
this.season = season;
}
/**
* Returns the eclipses.
*/
public Eclipse getEclipse() {
return eclipse;
}
/**
* Sets the eclipses.
*/
public void setEclipse(Eclipse eclipse) {
this.eclipse = eclipse;
}
/**
* Returns the sun phase.
*/
public SunPhase getPhase() {
return phase;
}
/**
* Sets the sun phase.
*/
public void setPhase(SunPhase phase) {
this.phase = phase;
}
/**
* Returns all ranges of the sun.
*/
public Map<SunPhaseName, Range> getAllRanges() {
return ranges;
}
}

View File

@@ -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.model;
/**
* Holds the calculated sun phase informations.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SunPhase {
private SunPhaseName name;
/**
* Returns the sun phase.
*/
public SunPhaseName getName() {
return name;
}
/**
* Sets the sun phase.
*/
public void setName(SunPhaseName name) {
this.name = name;
}
}

View File

@@ -0,0 +1,34 @@
/**
* 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.model;
/**
* All sun phases.
*
* @author Gerhard Riegler - Initial contribution
*/
public enum SunPhaseName {
SUN_RISE,
ASTRO_DAWN,
NAUTIC_DAWN,
CIVIL_DAWN,
CIVIL_DUSK,
NAUTIC_DUSK,
ASTRO_DUSK,
SUN_SET,
DAYLIGHT,
NOON,
NIGHT,
MORNING_NIGHT,
EVENING_NIGHT
}

View File

@@ -0,0 +1,58 @@
/**
* 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.model;
import java.util.Calendar;
/**
* Extends the zodiac with a date range.
*
* @author Gerhard Riegler - Initial contribution
*/
public class SunZodiac extends Zodiac {
private Range range;
/**
* Creates a Zodiac with a sign and a range.
*/
public SunZodiac(ZodiacSign sign, Range range) {
super(sign);
this.range = range;
}
/**
* Returns she start of the zodiac.
*/
public Calendar getStart() {
return range == null ? null : range.getStart();
}
/**
* Returns the end of the zodiac.
*/
public Calendar getEnd() {
return range == null ? null : range.getEnd();
}
/**
* Returns true, if the zodiac is valid on the specified calendar object.
*/
public boolean isValid(Calendar calendar) {
if (range == null || range.getStart() == null || range.getEnd() == null) {
return false;
}
return range.getStart().getTimeInMillis() <= calendar.getTimeInMillis()
&& range.getEnd().getTimeInMillis() >= calendar.getTimeInMillis();
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.model;
/**
* Holds the sign of the zodiac.
*
* @author Gerhard Riegler - Initial contribution
*/
public class Zodiac {
private ZodiacSign sign;
public Zodiac(ZodiacSign sign) {
this.sign = sign;
}
/**
* Returns the sign of the zodiac.
*/
public ZodiacSign getSign() {
return sign;
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.model;
/**
* All zodiac signs.
*
* @author Gerhard Riegler - Initial contribution
*/
public enum ZodiacSign {
ARIES,
TAURUS,
GEMINI,
CANCER,
LEO,
VIRGO,
LIBRA,
SCORPIO,
SAGITTARIUS,
CAPRICORN,
AQUARIUS,
PISCES
}

View File

@@ -0,0 +1,242 @@
/**
* 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.util;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Pattern;
import org.apache.commons.lang.time.DateUtils;
import org.openhab.binding.astro.internal.config.AstroChannelConfig;
import org.openhab.binding.astro.internal.model.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Common used DateTime functions.
*
* @author Gerhard Riegler - Initial contribution
*/
public class DateTimeUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeUtils.class);
private static final Pattern HHMM_PATTERN = Pattern.compile("^([0-1][0-9]|2[0-3])(:[0-5][0-9])$");
public static final double J1970 = 2440588.0;
public static final double MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
/** Constructor */
private DateTimeUtils() {
throw new IllegalAccessError("Non-instantiable");
}
/**
* Truncates the time from the calendar object.
*/
public static Calendar truncateToMidnight(Calendar calendar) {
return DateUtils.truncate(calendar, Calendar.DAY_OF_MONTH);
}
/**
* Creates a Range object within the specified months and days. The start
* time is midnight, the end time is end of the day.
*/
public static Range getRange(int startYear, int startMonth, int startDay, int endYear, int endMonth, int endDay) {
Calendar start = Calendar.getInstance();
start.set(Calendar.YEAR, startYear);
start.set(Calendar.MONTH, startMonth);
start.set(Calendar.DAY_OF_MONTH, startDay);
start = truncateToMidnight(start);
Calendar end = Calendar.getInstance();
end.set(Calendar.YEAR, endYear);
end.set(Calendar.MONTH, endMonth);
end.set(Calendar.DAY_OF_MONTH, endDay);
end.set(Calendar.HOUR_OF_DAY, 23);
end.set(Calendar.MINUTE, 59);
end.set(Calendar.SECOND, 59);
end.set(Calendar.MILLISECOND, 999);
return new Range(start, end);
}
/**
* Returns a calendar object from a julian date.
*/
public static Calendar toCalendar(double julianDate) {
if (Double.compare(julianDate, Double.NaN) == 0 || julianDate == 0) {
return null;
}
long millis = (long) ((julianDate + 0.5 - J1970) * MILLISECONDS_PER_DAY);
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(millis);
return DateUtils.round(cal, Calendar.MINUTE);
}
/**
* Returns the julian date from the calendar object.
*/
public static double dateToJulianDate(Calendar calendar) {
return calendar.getTimeInMillis() / MILLISECONDS_PER_DAY - 0.5 + J1970;
}
/**
* Returns the midnight julian date from the calendar object.
*/
public static double midnightDateToJulianDate(Calendar calendar) {
return dateToJulianDate(truncateToMidnight(calendar));
}
/**
* Returns the end of day from the calendar object.
*/
public static Calendar endOfDayDate(Calendar calendar) {
Calendar cal = (Calendar) calendar.clone();
cal = DateUtils.ceiling(cal, Calendar.DATE);
cal.add(Calendar.MILLISECOND, -1);
return cal;
}
/**
* Returns the end of day julian date from the calendar object.
*/
public static double endOfDayDateToJulianDate(Calendar calendar) {
return dateToJulianDate(endOfDayDate(calendar));
}
/**
* Returns the year of the calendar object as a decimal value.
*/
public static double getDecimalYear(Calendar calendar) {
return calendar.get(Calendar.YEAR)
+ (double) calendar.get(Calendar.DAY_OF_YEAR) / calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
}
/**
* Converts the time (hour.minute) to a calendar object.
*/
public static Calendar timeToCalendar(Calendar calendar, double time) {
if (time < 0.0) {
return null;
}
Calendar cal = (Calendar) calendar.clone();
int hour = 0;
int minute = 0;
if (time == 24.0) {
cal.add(Calendar.DAY_OF_MONTH, 1);
} else {
hour = (int) time;
minute = (int) ((time * 100) - (hour * 100));
}
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, minute);
return DateUtils.truncate(cal, Calendar.MINUTE);
}
/**
* Returns true, if two calendar objects are on the same day ignoring time.
*/
public static boolean isSameDay(Calendar cal1, Calendar cal2) {
return cal1 != null && cal2 != null && DateUtils.isSameDay(cal1, cal2);
}
/**
* Returns a date object from a calendar.
*/
public static Date getDate(Calendar calendar) {
return calendar == null ? null : calendar.getTime();
}
/**
* Returns the next Calendar from today.
*/
public static Calendar getNext(Calendar... calendars) {
Calendar now = Calendar.getInstance();
Calendar next = null;
for (Calendar calendar : calendars) {
if (calendar.after(now) && (next == null || calendar.before(next))) {
next = calendar;
}
}
return next;
}
/**
* Returns true, if cal1 is greater or equal than cal2, ignoring seconds.
*/
public static boolean isTimeGreaterEquals(Calendar cal1, Calendar cal2) {
Calendar truncCal1 = DateUtils.truncate(cal1, Calendar.MINUTE);
Calendar truncCal2 = DateUtils.truncate(cal2, Calendar.MINUTE);
return truncCal1.getTimeInMillis() >= truncCal2.getTimeInMillis();
}
/**
* Applies the config to the given calendar.
*/
public static Calendar applyConfig(Calendar cal, AstroChannelConfig config) {
Calendar cCal = cal;
if (config.offset != 0) {
Calendar cOffset = Calendar.getInstance();
cOffset.setTime(cCal.getTime());
cOffset.add(Calendar.MINUTE, config.offset);
cCal = cOffset;
}
Calendar cEarliest = adjustTime(cCal, getMinutesFromTime(config.earliest));
if (cCal.before(cEarliest)) {
return cEarliest;
}
Calendar cLatest = adjustTime(cCal, getMinutesFromTime(config.latest));
if (cCal.after(cLatest)) {
return cLatest;
}
return cCal;
}
private static Calendar adjustTime(Calendar cal, int minutes) {
if (minutes > 0) {
Calendar cTime = Calendar.getInstance();
cTime = DateUtils.truncate(cal, Calendar.DAY_OF_MONTH);
cTime.add(Calendar.MINUTE, minutes);
return cTime;
}
return cal;
}
/**
* Parses a HH:MM string and returns the minutes.
*/
private static int getMinutesFromTime(String configTime) {
if (configTime != null) {
String time = configTime.trim();
if (!time.isEmpty()) {
try {
if (!HHMM_PATTERN.matcher(time).matches()) {
throw new NumberFormatException();
} else {
String[] elements = time.split(":");
int hour = Integer.parseInt(elements[0]);
int minutes = Integer.parseInt(elements[1]);
return (hour * 60) + minutes;
}
} catch (NumberFormatException ex) {
LOGGER.warn(
"Can not parse astro channel configuration '{}' to hour and minutes, use pattern hh:mm, ignoring!",
time);
}
}
}
return 0;
}
}

View File

@@ -0,0 +1,109 @@
/**
* 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.util;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.astro.internal.config.AstroChannelConfig;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* Methods to get the value from a property of an object.
*
* @author Gerhard Riegler - Initial contribution
* @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType
* @author Christoph Weitkamp - Introduced UoM
*/
@NonNullByDefault
public class PropertyUtils {
/** Constructor */
private PropertyUtils() {
throw new IllegalAccessError("Non-instantiable");
}
/**
* Returns the state of the channel.
*/
public static State getState(ChannelUID channelUID, AstroChannelConfig config, Object instance, ZoneId zoneId)
throws Exception {
Object value = getPropertyValue(channelUID, instance);
if (value == null) {
return UnDefType.UNDEF;
} else if (value instanceof State) {
return (State) value;
} else if (value instanceof Calendar) {
Calendar cal = (Calendar) value;
GregorianCalendar gregorianCal = (GregorianCalendar) DateTimeUtils.applyConfig(cal, config);
cal.setTimeZone(TimeZone.getTimeZone(zoneId));
ZonedDateTime zoned = gregorianCal.toZonedDateTime().withFixedOffsetZone();
return new DateTimeType(zoned);
} else if (value instanceof Number) {
BigDecimal decimalValue = new BigDecimal(value.toString()).setScale(2, RoundingMode.HALF_UP);
return new DecimalType(decimalValue);
} else if (value instanceof String || value instanceof Enum) {
return new StringType(value.toString());
} else {
throw new IllegalStateException("Unsupported value type " + value.getClass().getSimpleName());
}
}
/**
* Returns the property value from the object instance, nested properties are possible. If the propertyName is for
* example rise.start, the methods getRise().getStart() are called.
*/
public static @Nullable Object getPropertyValue(ChannelUID channelUID, Object instance) throws Exception {
String[] properties = channelUID.getId().split("#");
return getPropertyValue(instance, properties, 0);
}
/**
* Iterates through the nested properties and returns the getter value.
*/
@SuppressWarnings("all")
private static @Nullable Object getPropertyValue(Object instance, String[] properties, int nestedIndex)
throws Exception {
String propertyName = properties[nestedIndex];
Method m = instance.getClass().getMethod(toGetterString(propertyName), null);
Object result = m.invoke(instance, (Object[]) null);
if (nestedIndex + 1 < properties.length) {
return getPropertyValue(result, properties, nestedIndex + 1);
}
return result;
}
/**
* Converts the string to a getter property.
*/
private static String toGetterString(String str) {
StringBuilder sb = new StringBuilder();
sb.append("get");
sb.append(Character.toTitleCase(str.charAt(0)));
sb.append(str.substring(1));
return sb.toString();
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="astro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Astro Binding</name>
<description>The Astro binding calculates astronomical data from sun and moon.</description>
<author>Gerhard Riegler</author>
</binding:binding>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:astro:moonconfig">
<parameter name="geolocation" type="text" required="true">
<context>location</context>
<label>Location</label>
<description>The latitude, longitude and altitude separated with a comma (lat,long,[alt]).</description>
</parameter>
<parameter name="interval" type="integer" min="1" max="86400" unit="s">
<label>Interval</label>
<description>Refresh interval for positional data calculation in seconds.</description>
<default>300</default>
</parameter>
</config-description>
<config-description uri="thing-type:astro:sunconfig">
<parameter name="geolocation" type="text" required="true">
<context>location</context>
<label>Location</label>
<description>The latitude, longitude and altitude separated with a comma (lat,long,[alt]).</description>
</parameter>
<parameter name="useMeteorologicalSeason" type="boolean" required="true">
<label>Meteorological Season</label>
<description>Uses meteorological season calculation method instead of equinox method.</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
<parameter name="interval" type="integer" min="1" max="86400" unit="s">
<label>Interval</label>
<description>Refresh interval for positional data calculation in seconds.</description>
<default>300</default>
</parameter>
</config-description>
<config-description uri="channel-type:astro:config">
<parameter name="offset" type="integer" min="-1440" max="1440" unit="min">
<label>Offset</label>
<description>Moves an event or datetime value forward or backward (in minutes).</description>
<default>0</default>
</parameter>
<parameter name="earliest" type="text" pattern="^([0-1][0-9]|2[0-3])(:[0-5][0-9])$">
<label>Earliest</label>
<description>The earliest time of the day for the event or the datetime value (hh:mm).</description>
</parameter>
<parameter name="latest" type="text" pattern="^([0-1][0-9]|2[0-3])(:[0-5][0-9])$">
<label>Latest</label>
<description>The latest time of the day for the event or the datetime value (hh:mm).</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,3 @@
# Discovery result
discovery.astro.sun.local.label = Local Sun
discovery.astro.moon.local.label = Local Moon

View File

@@ -0,0 +1,188 @@
# binding
binding.astro.name = Astro Binding
binding.astro.description = Das Astro Binding berechnet astronomische Daten von Sonne und Mond.
# thing types
thing-type.astro.sun.label = Astronomische Sonnendaten
thing-type.astro.sun.description = Stellt astronomische Sonnendaten zur Verfügung
thing-type.astro.sun.group.rise.label = Sonnenaufgang
thing-type.astro.sun.group.set.label = Sonnenuntergang
thing-type.astro.sun.group.noon.label = Mittag
thing-type.astro.sun.group.night.label = Nacht
thing-type.astro.sun.group.morningNight.label = Morgendliche Nacht
thing-type.astro.sun.group.astroDawn.label = Astronomische Morgendämmerung
thing-type.astro.sun.group.nauticDawn.label = Nautische Morgendämmerung
thing-type.astro.sun.group.civilDawn.label = Bürgerliche Morgendämmerung
thing-type.astro.sun.group.astroDusk.label = Astronomische Abenddämmerung
thing-type.astro.sun.group.nauticDusk.label = Nautische Abenddämmerung
thing-type.astro.sun.group.civilDusk.label = Bürgerliche Abenddämmerung
thing-type.astro.sun.group.eveningNight.label = Abendliche Nacht
thing-type.astro.sun.group.daylight.label = Tageslicht
thing-type.astro.sun.group.position.description = Position der Sonne
thing-type.astro.moon.label = Astronomische Monddaten
thing-type.astro.moon.description = Stellt astronomische Monddaten zur Verfügung
thing-type.astro.moon.group.rise.label = Mondaufgang
thing-type.astro.moon.group.set.label = Monduntergang
thing-type.astro.moon.group.distance.description = Die Entfernung zum Mond
thing-type.astro.moon.group.perigee.label = Perigäum
thing-type.astro.moon.group.perigee.description = Das Perigäum des Mondes
thing-type.astro.moon.group.apogee.label = Apogäum
thing-type.astro.moon.group.apogee.description = Das Apogäum des Mondes
thing-type.astro.moon.group.position.description = Die Position des Mondes
# thing types config
thing-type.config.astro.sunconfig.geolocation.label = Ort der Sonnendaten
thing-type.config.astro.sunconfig.geolocation.description = Ort der Sonnendaten in geographischen Koordinaten (Breitengrad/Längengrad/Höhe).
thing-type.config.astro.sunconfig.useMeteorologicalSeason.label = Meteorologische Jahreszeit
thing-type.config.astro.sunconfig.useMeteorologicalSeason.description = Verwendung der meteorologischen Berechnungsmethode für die Jahreszeiten anstelle der astronomischen.
thing-type.config.astro.sunconfig.interval.label = Aktualisierungsintervall
thing-type.config.astro.sunconfig.interval.description = Intervall zur Aktualisierung der Positionsdaten (in s).
thing-type.config.astro.moonconfig.geolocation.label = Ort der Monddaten
thing-type.config.astro.moonconfig.geolocation.description = Ort der Monddaten in geographischen Koordinaten (Breitengrad/Längengrad/Höhe).
thing-type.config.astro.moonconfig.interval.label = Aktualisierungsintervall
thing-type.config.astro.moonconfig.interval.description = Intervall zur Aktualisierung der Positionsdaten (in s).
# channel group types
channel-group-type.astro.position.label = Position
channel-group-type.astro.position.description = Die Position des Himmelskörpers
channel-group-type.astro.radiation.label = Strahlung
channel-group-type.astro.radiation.description = Das Strahlungslevel der Sonne
channel-group-type.astro.sunRange.label = Spannweite
channel-group-type.astro.moonRange.label = Spannweite
channel-group-type.astro.sunZodiac.label = Sternzeichen
channel-group-type.astro.sunZodiac.description = Das Sternzeichen der Sonne
channel-group-type.astro.season.label = Jahreszeit
channel-group-type.astro.season.description = Die Jahreszeiten in diesem Jahr
channel-group-type.astro.sunEclipse.label = Finsternisse
channel-group-type.astro.sunEclipse.description = Zeitpunkt der nächsten Sonnenfinsternis
channel-group-type.astro.sunPhase.label = Sonnenphasen
channel-group-type.astro.sunPhase.description = Zeigt die Details der aktuellen Sonnenphase an
channel-group-type.astro.moonPhase.label = Mondphasen
channel-group-type.astro.moonPhase.description = Zeigt die Details der aktuellen und der nächsten Mondphasen an
channel-group-type.astro.moonEclipse.label = Finsternisse
channel-group-type.astro.moonEclipse.description = Zeitpunkt der nächsten Mondfinsternis
channel-group-type.astro.distance.label = Entfernung
channel-group-type.astro.distance.description = Entfernungsdaten
channel-group-type.astro.moonZodiac.label = Sternzeichen
channel-group-type.astro.moonZodiac.description = Das Sternzeichen des Mondes
channel-group-type.astro.moonEclipse.channel.total.label = Totale Mondfinsternis
channel-group-type.astro.moonEclipse.channel.total.description = Zeitpunkt der nächsten totalen Mondfinsternis
channel-group-type.astro.moonEclipse.channel.partial.label = Partielle Mondfinsternis
channel-group-type.astro.moonEclipse.channel.partial.description = Zeitpunkt der nächsten partiellen Mondfinsternis
# channel types
channel-type.astro.azimuth.label = Azimut
channel-type.astro.azimuth.description = Das Azimut des Himmelskörpers
channel-type.astro.elevation.label = Höhenwinkel
channel-type.astro.elevation.description = Der Höhenwinkel des Himmelskörpers
channel-type.astro.shadeLength.label = Schattenlängenverhältnis
channel-type.astro.shadeLength.description = Projiziertes Schattenlängenverhältnis (Abgeleitet vom Höhenwinkel)
channel-type.astro.directRadiation.label = Direkte Strahlung
channel-type.astro.directRadiation.description = Höhe der Strahlung nach Eindringen in die atmosphärische Schicht
channel-type.astro.diffuseRadiation.label = Diffuse Strahlung
channel-type.astro.diffuseRadiation.description = Höhe der Strahlung, nach Beugung durch Wolken und Atmosphäre
channel-type.astro.totalRadiation.label = Gesamtstrahlung
channel-type.astro.totalRadiation.description = Gesamtmenge der Strahlung auf dem Boden
channel-type.astro.start.label = Startzeit
channel-type.astro.start.description = Die Startzeit des Ereignisses
channel-type.astro.end.label = Endzeit
channel-type.astro.end.description = Die Endzeit des Ereignisses
channel-type.astro.duration.label = Dauer
channel-type.astro.duration.description = Die Dauer des Ereignisses
channel-type.astro.sign.label = Sternzeichen
channel-type.astro.sign.description = Das Sternzeichen
channel-type.astro.sign.state.option.ARIES = Widder
channel-type.astro.sign.state.option.TAURUS = Stier
channel-type.astro.sign.state.option.GEMINI = Zwilling
channel-type.astro.sign.state.option.CANCER = Krebs
channel-type.astro.sign.state.option.LEO = Löwe
channel-type.astro.sign.state.option.VIRGO = Jungfrau
channel-type.astro.sign.state.option.LIBRA = Waage
channel-type.astro.sign.state.option.SCORPIO = Skorpion
channel-type.astro.sign.state.option.SAGITTARIUS = Schütze
channel-type.astro.sign.state.option.CAPRICORN = Steinbock
channel-type.astro.sign.state.option.AQUARIUS = Wassermann
channel-type.astro.sign.state.option.PISCES = Fische
channel-type.astro.seasonName.label = Jahreszeit
channel-type.astro.seasonName.description = Der Name der aktuellen Jahreszeit
channel-type.astro.seasonName.state.option.SPRING = Frühling
channel-type.astro.seasonName.state.option.SUMMER = Sommer
channel-type.astro.seasonName.state.option.AUTUMN = Herbst
channel-type.astro.seasonName.state.option.WINTER = Winter
channel-type.astro.spring.label = Frühling
channel-type.astro.spring.description = Frühlingsanfang
channel-type.astro.summer.label = Sommer
channel-type.astro.summer.description = Sommeranfang
channel-type.astro.autumn.label = Herbst
channel-type.astro.autumn.description = Herbstanfang
channel-type.astro.winter.label = Winter
channel-type.astro.winter.description = Winteranfang
channel-type.astro.total.label = Totale Sonnenfinsternis
channel-type.astro.total.description = Zeitpunkt der nächsten totalen Sonnenfinsternis
channel-type.astro.partial.label = Partielle Sonnenfinsternis
channel-type.astro.partial.description = Zeitpunkt der nächsten partiellen Sonnenfinsternis
channel-type.astro.ring.label = Ringförmige Sonnenfinsternis
channel-type.astro.ring.description = Zeitpunkt der nächsten ringförmigen Sonnenfinsternis
channel-type.astro.sunPhaseName.label = Sonnenphase
channel-type.astro.sunPhaseName.description = Der Name der aktuellen Sonnenphase
channel-type.astro.sunPhaseName.state.option.SUN_RISE = Sonnenaufgang
channel-type.astro.sunPhaseName.state.option.ASTRO_DAWN = Astronomische Morgendämmerung
channel-type.astro.sunPhaseName.state.option.NAUTIC_DAWN = Nautische Dämmerung
channel-type.astro.sunPhaseName.state.option.CIVIL_DAWN = Bürgerliche Morgendämmerung
channel-type.astro.sunPhaseName.state.option.CIVIL_DUSK = Bürgerliche Abenddämmerung
channel-type.astro.sunPhaseName.state.option.NAUTIC_DUSK = Nautische Abenddämmerung
channel-type.astro.sunPhaseName.state.option.ASTRO_DUSK = Astronomische Abenddämmerung
channel-type.astro.sunPhaseName.state.option.SUN_SET = Sonnenuntergang
channel-type.astro.sunPhaseName.state.option.DAYLIGHT = Tageslicht
channel-type.astro.sunPhaseName.state.option.NOON = Mittag
channel-type.astro.sunPhaseName.state.option.NIGHT = Nacht
channel-type.astro.firstQuarter.label = Erstes Viertel
channel-type.astro.firstQuarter.description = Zeitpunkt des nächsten erstes Viertels
channel-type.astro.thirdQuarter.label = Drittes Viertel
channel-type.astro.thirdQuarter.description = Zeitpunkt des nächsten dritten Viertels
channel-type.astro.fullMoon.label = Vollmond
channel-type.astro.fullMoon.description = Zeitpunkt des nächsten Vollmondes
channel-type.astro.newMoon.label = Neumond
channel-type.astro.newMoon.description = Zeitpunkt des nächsten Neumondes
channel-type.astro.age.label = Mondalter
channel-type.astro.age.description = Das Alter des Mondes in Tagen
channel-type.astro.ageDegree.label = Mondalter
channel-type.astro.ageDegree.description = Das Alter des Mondes
channel-type.astro.agePercent.label = Mondalter
channel-type.astro.agePercent.description = Das Alter des Mondes
channel-type.astro.illumination.label = Beleuchtungsstärke des Mondes
channel-type.astro.illumination.description = Die Beleuchtungsstärke des Mondes
channel-type.astro.phaseName.label = Mondphase
channel-type.astro.phaseName.description = Der Name der aktuellen Mondphase
channel-type.astro.phaseName.state.option.NEW = Neumond
channel-type.astro.phaseName.state.option.WAXING_CRESCENT = Zunehmende Sichel
channel-type.astro.phaseName.state.option.FIRST_QUARTER = Erstes Viertel
channel-type.astro.phaseName.state.option.WAXING_GIBBOUS = Zunehmender Halbmond
channel-type.astro.phaseName.state.option.FULL = Vollmond
channel-type.astro.phaseName.state.option.WANING_GIBBOUS = Abnehmender Halbmond
channel-type.astro.phaseName.state.option.THIRD_QUARTER = Drittes Viertel
channel-type.astro.phaseName.state.option.WANING_CRESCENT = Abnehmende Sichel
channel-type.astro.distanceDate.label = Datum
channel-type.astro.distanceDate.description = Zeitpunkt wenn der Abstand erreicht ist
channel-type.astro.distance.label = Entfernung
channel-type.astro.distance.description = Die Entfernung des Objekts
# channel types config
channel-type.config.astro.config.offset.label = Verschiebung
channel-type.config.astro.config.offset.description = Verschiebt den Zeitpunkt eines Triggers vor oder zurück (in Minuten).
channel-type.config.astro.config.earliest.label = Frühester Zeitpunkt
channel-type.config.astro.config.earliest.description = Frühester Zeitpunkt eines Triggers am aktuellen Tag (in hh:mm).
channel-type.config.astro.config.latest.label = Spätester Zeitpunkt
channel-type.config.astro.config.latest.description = Spätester Zeitpunkt eines Triggers am aktuellen Tag (in hh:mm).
# Discovery result
discovery.astro.sun.local.label = Lokale Sonnendaten
discovery.astro.moon.local.label = Lokale Monddaten

View File

@@ -0,0 +1,162 @@
# binding
binding.astro.name = Astro ba\u011flant\u0131s\u0131
binding.astro.description = Astro ba\u011flant\u0131s\u0131 ay ve güne\u015f bilgilerini hesaplamaya yarar.
# thing types
thing-type.astro.sun.label = Astronomik güne\u015f verileri
thing-type.astro.sun.description = Astronomik güne\u015f verileri sunar
thing-type.astro.sun.group.rise.label = Gündo\u011fumu
thing-type.astro.sun.group.set.label = Günbat\u0131\u015f\u0131
thing-type.astro.sun.group.noon.label = Ö\u011fle
thing-type.astro.sun.group.night.label = Gece
thing-type.astro.sun.group.morningNight.label = Sabah gecesi
thing-type.astro.sun.group.astroDawn.label = Astronomik \u015fafak
thing-type.astro.sun.group.nauticDawn.label = Denizcilik \u015fafa\u011f\u0131
thing-type.astro.sun.group.civilDawn.label = Medeni \u015fafak
thing-type.astro.sun.group.astroDusk.label = Astronomik alacakaranl\u0131k
thing-type.astro.sun.group.nauticDusk.label = Deizcilik alacakaranl\u0131\u011f\u0131
thing-type.astro.sun.group.civilDusk.label = Medeni alacakaranl\u0131k
thing-type.astro.sun.group.eveningNight.label = Ak\u015fam gecesi
thing-type.astro.sun.group.daylight.label = Gün \u0131\u015f\u0131\u011f\u0131
thing-type.astro.sun.group.position.description = Güne\u015fin konumu
thing-type.astro.moon.label = Astronomik ay verileri
thing-type.astro.moon.description = Astronomik ay verileri sa\u011flar
thing-type.astro.moon.group.rise.label = Ay do\u011fu\u015fu
thing-type.astro.moon.group.set.label = Ay bat\u0131\u015f\u0131
thing-type.astro.moon.group.distance.description = Aya olan mesafe
thing-type.astro.moon.group.perigee.label = Yerberi
thing-type.astro.moon.group.perigee.description = Ay\u0131n yerberi
thing-type.astro.moon.group.apogee.label = Yeröte
thing-type.astro.moon.group.apogee.description = Ay\u0131n yerötesi
thing-type.astro.moon.group.position.description = Ay\u0131n konumu
# channel group types
channel-group-type.astro.position.label = Konum
channel-group-type.astro.position.description = Göksel bedenin konumu
channel-group-type.astro.radiation.label = I\u015f\u0131n\u0131m
channel-group-type.astro.radiation.description = Güne\u015fin \u0131\u015f\u0131n\u0131m seviyesi
channel-group-type.astro.sunRange.label = Aç\u0131kl\u0131k
channel-group-type.astro.moonRange.label = Aç\u0131kl\u0131k
channel-group-type.astro.sunZodiac.label = Zodyak
channel-group-type.astro.sunZodiac.description = Güne\u015fin zodyak i\u015fareti
channel-group-type.astro.season.label = Mevsim
channel-group-type.astro.season.description = Bu y\u0131l mevsimler
channel-group-type.astro.sunEclipse.label = Tutulmalar
channel-group-type.astro.sunEclipse.description = Bir sonraki güne\u015f tutulmas\u0131 zaman\u0131
channel-group-type.astro.sunPhase.label = Güne\u015f evreleri
channel-group-type.astro.sunPhase.description = Mevcut güne\u015f evresinin ayr\u0131nt\u0131lar\u0131n\u0131 görüntüler
channel-group-type.astro.moonPhase.label = Ay evreleri
channel-group-type.astro.moonPhase.description = Mevcut ve sonraki ay evreleri ayr\u0131nt\u0131lar\u0131n\u0131 görüntüler.
channel-group-type.astro.moonEclipse.label = Tutulmalar
channel-group-type.astro.moonEclipse.description = Bir sonraki ay tutulmas\u0131 zaman\u0131
channel-group-type.astro.distance.label = Mesafe
channel-group-type.astro.distance.description = Mesafe verileri
channel-group-type.astro.moonZodiac.label = Zodyak
channel-group-type.astro.moonZodiac.description = Ay\u0131n Zodyak i\u015fareti
channel-group-type.astro.moonEclipse.channel.total.label = Toplam ay tutulmas\u0131
channel-group-type.astro.moonEclipse.channel.total.description = Bir sonraki toplam ay tutulmas\u0131 zaman\u0131
channel-group-type.astro.moonEclipse.channel.partial.label = K\u0131smi ay tutulmas\u0131
channel-group-type.astro.moonEclipse.channel.partial.description = Bir sonraki k\u0131smi ay tutulmas\u0131 zaman\u0131
# channel types
channel-type.astro.azimuth.label = Aç\u0131sal konum
channel-type.astro.azimuth.description = Gök cisminin aç\u0131sal konumu
channel-type.astro.elevation.label = Yükseklik aç\u0131s\u0131
channel-type.astro.elevation.description = Gök cisminin yükseklik aç\u0131s\u0131
channel-type.astro.shadeLength.label = Gölge uzunluk oran\u0131
channel-type.astro.shadeLength.description = Yans\u0131t\u0131lan gölge uzunluk oran\u0131 (yükseklik aç\u0131s\u0131ndan türetilmi\u015ftir)
channel-type.astro.directRadiation.label = Do\u011frudan \u0131\u015f\u0131n\u0131m
channel-type.astro.directRadiation.description = Atmosferik katmana nüfuz ettikten sonra \u0131\u015f\u0131n\u0131m derecesi
channel-type.astro.diffuseRadiation.label = Da\u011f\u0131n\u0131k \u0131\u015f\u0131n\u0131m
channel-type.astro.diffuseRadiation.description = Bulutlar\u0131n ve atmosferin da\u011f\u0131tmas\u0131ndan sonra \u0131\u015f\u0131n\u0131m derecesi
channel-type.astro.totalRadiation.label = Toplam \u0131\u015f\u0131n\u0131m
channel-type.astro.totalRadiation.description = Yere ula\u015fan toplam \u0131\u015f\u0131n\u0131m miktar\u0131
channel-type.astro.start.label = Baslang\u0131ç zaman\u0131
channel-type.astro.start.description = Etkinli\u011fin ba\u015flang\u0131ç zaman\u0131
channel-type.astro.end.label = Biti\u015f zaman\u0131
channel-type.astro.end.description = Etkinli\u011fin biti\u015f zaman\u0131
channel-type.astro.duration.label = Süre
channel-type.astro.duration.description = Etkinli\u011fin süresi
channel-type.astro.sign.label = Burç
channel-type.astro.sign.description = Burç
channel-type.astro.sign.state.option.ARIES = Koç
channel-type.astro.sign.state.option.TAURUS = Bo\u011fa
channel-type.astro.sign.state.option.GEMINI = \u0130kizler
channel-type.astro.sign.state.option.CANCER = Yengeç
channel-type.astro.sign.state.option.LEO = Aslan
channel-type.astro.sign.state.option.VIRGO = Ba\u015fak
channel-type.astro.sign.state.option.LIBRA = Terazi
channel-type.astro.sign.state.option.SCORPIO = Akrep
channel-type.astro.sign.state.option.SAGITTARIUS = Yay
channel-type.astro.sign.state.option.CAPRICORN = O\u011flak
channel-type.astro.sign.state.option.AQUARIUS = Kova
channel-type.astro.sign.state.option.PISCES = Bal\u0131k
channel-type.astro.seasonName.label = Mevsim
channel-type.astro.seasonName.description = \u015eu anki mevsimin ad\u0131
channel-type.astro.seasonName.state.option.SPRING = \u0130lkbahar
channel-type.astro.seasonName.state.option.SUMMER = Yaz
channel-type.astro.seasonName.state.option.AUTUMN = Sonbahar
channel-type.astro.seasonName.state.option.WINTER = K\u0131\u015f
channel-type.astro.spring.label = \u0130lkbahar
channel-type.astro.spring.description = \u0130lkbahar ba\u015flang\u0131c\u0131
channel-type.astro.summer.label = Yaz
channel-type.astro.summer.description = Yaz ba\u015flang\u0131c\u0131
channel-type.astro.autumn.label = Sonbahar
channel-type.astro.autumn.description = Sonbahar ba\u015flang\u0131c\u0131
channel-type.astro.winter.label = K\u0131\u015f
channel-type.astro.winter.description = K\u0131\u015f ba\u015flang\u0131c\u0131
channel-type.astro.total.label = Toplu güne\u015f tutulmas\u0131
channel-type.astro.total.description = Bir sonraki toplu güne\u015f tutulmas\u0131 zaman\u0131
channel-type.astro.partial.label = K\u0131smi güne\u015f tutulmas\u0131
channel-type.astro.partial.description = Bir sonraki k\u0131smi güne\u015f tutulmas\u0131 zaman\u0131
channel-type.astro.ring.label = Halka \u015feklindeki güne\u015f tutulmas\u0131
channel-type.astro.ring.description = Bir sonraki halka \u015feklindeki güne\u015f tutulmas\u0131 zaman\u0131
channel-type.astro.sunPhaseName.label = Güne\u015f evresi
channel-type.astro.sunPhaseName.description = Güncel güne\u015f evresinin ad\u0131
channel-type.astro.sunPhaseName.state.option.SUN_RISE = Gün do\u011fumu
channel-type.astro.sunPhaseName.state.option.ASTRO_DAWN = Astronomik \u015fafak
channel-type.astro.sunPhaseName.state.option.NAUTIC_DAWN = Denizcilik alacakaranl\u0131\u011f\u0131
channel-type.astro.sunPhaseName.state.option.CIVIL_DAWN = Medeni \u015fafak
channel-type.astro.sunPhaseName.state.option.CIVIL_DUSK = Medeni alacakaranl\u0131k
channel-type.astro.sunPhaseName.state.option.NAUTIC_DUSK = Denizcilik alacakaranl\u0131\u011f\u0131
channel-type.astro.sunPhaseName.state.option.ASTRO_DUSK = Denizcilik alacakaranl\u0131\u011f\u0131
channel-type.astro.sunPhaseName.state.option.SUN_SET = Gün bat\u0131m\u0131
channel-type.astro.sunPhaseName.state.option.DAYLIGHT = Gün \u0131\u015f\u0131\u011f\u0131
channel-type.astro.sunPhaseName.state.option.NOON = Ö\u011fle vakti
channel-type.astro.sunPhaseName.state.option.NIGHT = Gece
channel-type.astro.firstQuarter.label = \u0130lk çeyrek
channel-type.astro.firstQuarter.description = Bir sonraki ilk çeyre\u011fin zaman\u0131
channel-type.astro.thirdQuarter.label = Üçüncü çeyrek
channel-type.astro.thirdQuarter.description = Bir sonraki üçüncü çeyre\u011fin zaman\u0131
channel-type.astro.fullMoon.label = Dolunay
channel-type.astro.fullMoon.description = Bir sonraki dolunay\u0131n zaman\u0131
channel-type.astro.newMoon.label = Yeni ay
channel-type.astro.newMoon.description = Bir sonraki yeni ay\u0131n zaman\u0131
channel-type.astro.age.label = Ay ya\u015f\u0131
channel-type.astro.age.description = Gün olarak ay ya\u015f\u0131
channel-type.astro.ageDegree.label = Ay ya\u015f\u0131
channel-type.astro.ageDegree.description = Ay ya\u015f\u0131
channel-type.astro.agePercent.label = Ay ya\u015f\u0131
channel-type.astro.agePercent.description = Ay ya\u015f\u0131
channel-type.astro.illumination.label = Ay\u0131n ayd\u0131nl\u0131\u011f\u0131
channel-type.astro.illumination.description = Ay\u0131n ayd\u0131nl\u0131\u011f\u0131
channel-type.astro.phaseName.label = Ay evresi
channel-type.astro.phaseName.description = Mevcut ay evresinin ad\u0131
channel-type.astro.phaseName.state.option.NEW = Yeni ay
channel-type.astro.phaseName.state.option.WAXING_CRESCENT = Artan orak
channel-type.astro.phaseName.state.option.FIRST_QUARTER = \u0130lk dördün
channel-type.astro.phaseName.state.option.WAXING_GIBBOUS = \u015ei\u015fkin ay
channel-type.astro.phaseName.state.option.FULL = Dolunay
channel-type.astro.phaseName.state.option.WANING_GIBBOUS = Azalan Hilal
channel-type.astro.phaseName.state.option.THIRD_QUARTER = Üçüncü çeyrek
channel-type.astro.phaseName.state.option.WANING_CRESCENT = Azalan orak
channel-type.astro.distanceDate.label = Tarih
channel-type.astro.distanceDate.description = Mesafeye ula\u015f\u0131ld\u0131\u011f\u0131 zaman
channel-type.astro.distance.label = Mesafe
channel-type.astro.distance.description = Nesnenin uzakl\u0131\u011f\u0131
# Discovery result
discovery.astro.sun.local.label = Yerel güne\u015f bilgileri
discovery.astro.moon.local.label = Yerel ay bilgileri

View File

@@ -0,0 +1,485 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="astro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- position -->
<channel-group-type id="position">
<label>Position</label>
<description>The position of the planet</description>
<channels>
<channel id="azimuth" typeId="azimuth"/>
<channel id="elevation" typeId="elevation"/>
<channel id="shadeLength" typeId="shadeLength"/>
</channels>
</channel-group-type>
<channel-group-type id="radiation">
<label>Radiation</label>
<description>The radiation level of the sun</description>
<channels>
<channel id="direct" typeId="directRadiation"/>
<channel id="diffuse" typeId="diffuseRadiation"/>
<channel id="total" typeId="totalRadiation"/>
</channels>
</channel-group-type>
<channel-type id="azimuth">
<item-type>Number:Angle</item-type>
<label>Azimuth</label>
<description>The azimuth of the planet</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="elevation">
<item-type>Number:Angle</item-type>
<label>Elevation</label>
<description>The elevation of the planet</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="shadeLength">
<item-type>Number</item-type>
<label>Shade Length Ratio</label>
<description>Projected shade length ratio (derived from Elevation)</description>
<state readOnly="true" pattern="%.2f"/>
</channel-type>
<channel-type id="directRadiation">
<item-type>Number:Intensity</item-type>
<label>Direct Radiation</label>
<description>Level of radiation after penetration of the atmospheric layer</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="diffuseRadiation">
<item-type>Number:Intensity</item-type>
<label>Diffuse Radiation</label>
<description>Level of radiation diffracted by clouds and atmosphere</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="totalRadiation">
<item-type>Number:Intensity</item-type>
<label>Total Radiation</label>
<description>Total quantity of radiation on the ground</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<!-- sun range -->
<channel-group-type id="sunRange">
<label>Range</label>
<description>Range for a sun event</description>
<channels>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="duration" typeId="duration"/>
<channel id="event" typeId="rangeEvent"/>
</channels>
</channel-group-type>
<channel-group-type id="moonRange">
<label>Range</label>
<description>Range for a moon event</description>
<channels>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="event" typeId="rangeEvent"/>
</channels>
</channel-group-type>
<channel-type id="start">
<item-type>DateTime</item-type>
<label>Start Time</label>
<description>The start time of the event</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<channel-type id="end">
<item-type>DateTime</item-type>
<label>End Time</label>
<description>The end time of the event</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>The duration of the event</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="rangeEvent">
<kind>trigger</kind>
<label>Range Event</label>
<description>Range event</description>
<event>
<options>
<option value="START">start</option>
<option value="END">end</option>
</options>
</event>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<!-- sunZodiac -->
<channel-group-type id="sunZodiac">
<label>Zodiac</label>
<description>The zodiac of the sun</description>
<channels>
<channel id="start" typeId="start"/>
<channel id="end" typeId="end"/>
<channel id="sign" typeId="sign"/>
</channels>
</channel-group-type>
<channel-type id="sign">
<item-type>String</item-type>
<label>Sign</label>
<description>The sign of the zodiac</description>
<state readOnly="true">
<options>
<option value="ARIES">Aries</option>
<option value="TAURUS">Taurus</option>
<option value="GEMINI">Gemini</option>
<option value="CANCER">Cancer</option>
<option value="LEO">Leo</option>
<option value="VIRGO">Virgo</option>
<option value="LIBRA">Libra</option>
<option value="SCORPIO">Scorpio</option>
<option value="SAGITTARIUS">Sagittarius</option>
<option value="CAPRICORN">Capricorn</option>
<option value="AQUARIUS">Aquarius</option>
<option value="PISCES">Pisces</option>
</options>
</state>
</channel-type>
<!-- season -->
<channel-group-type id="season">
<label>Season</label>
<description>The seasons this year</description>
<channels>
<channel id="name" typeId="seasonName"/>
<channel id="spring" typeId="spring"/>
<channel id="summer" typeId="summer"/>
<channel id="autumn" typeId="autumn"/>
<channel id="winter" typeId="winter"/>
<channel id="nextName" typeId="seasonName">
<label>Next Season</label>
<description>The name of the next season</description>
</channel>
<channel id="timeLeft" typeId="duration">
<label>Time Left</label>
<description>The time remaining before season change</description>
</channel>
</channels>
</channel-group-type>
<channel-type id="seasonName">
<item-type>String</item-type>
<label>Season Name</label>
<description>The name of the current season</description>
<state readOnly="true">
<options>
<option value="SPRING">Spring</option>
<option value="SUMMER">Summer</option>
<option value="AUTUMN">Autumn</option>
<option value="WINTER">Winter</option>
</options>
</state>
</channel-type>
<channel-type id="spring">
<item-type>DateTime</item-type>
<label>Spring</label>
<description>The beginning of spring</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="summer">
<item-type>DateTime</item-type>
<label>Summer</label>
<description>The beginning of summer</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="autumn">
<item-type>DateTime</item-type>
<label>Autumn</label>
<description>The beginning of autumn</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="winter">
<item-type>DateTime</item-type>
<label>Winter</label>
<description>The beginning of winter</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<!-- sun eclipse -->
<channel-group-type id="sunEclipse">
<label>Eclipses</label>
<description>The DateTime of the next sun eclipses</description>
<channels>
<channel id="total" typeId="total"/>
<channel id="totalElevation" typeId="elevation"/>
<channel id="partial" typeId="partial"/>
<channel id="partialElevation" typeId="elevation"/>
<channel id="ring" typeId="ring"/>
<channel id="ringElevation" typeId="elevation"/>
<channel id="event" typeId="sunEclipseEvent"/>
</channels>
</channel-group-type>
<channel-type id="total">
<item-type>DateTime</item-type>
<label>Total Eclipse</label>
<description>The DateTime of the next total eclipse</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="partial">
<item-type>DateTime</item-type>
<label>Partial Eclipse</label>
<description>The DateTime of the next partial eclipse</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="ring">
<item-type>DateTime</item-type>
<label>Ring Eclipse</label>
<description>The DateTime of the next ring eclipse</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="sunEclipseEvent">
<kind>trigger</kind>
<label>Sun Eclipse Event</label>
<description>Sun eclipse event</description>
<event>
<options>
<option value="TOTAL">total</option>
<option value="PARTIAL">partial</option>
<option value="RING">ring</option>
</options>
</event>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<!-- sunphase -->
<channel-group-type id="sunPhase">
<label>Sun Phase</label>
<description>The details of the current sun phase</description>
<channels>
<channel id="name" typeId="sunPhaseName"/>
</channels>
</channel-group-type>
<channel-type id="sunPhaseName">
<item-type>String</item-type>
<label>Sun Phase Name</label>
<description>The name of the current sun phase</description>
<state readOnly="true">
<options>
<option value="SUN_RISE">Sunrise</option>
<option value="ASTRO_DAWN">Astro dawn</option>
<option value="NAUTIC_DAWN">Nautic dawn</option>
<option value="CIVIL_DAWN">Civil dawn</option>
<option value="CIVIL_DUSK">Civil dusk</option>
<option value="NAUTIC_DUSK">Nautic dusk</option>
<option value="ASTRO_DUSK">Astro dusk</option>
<option value="SUN_SET">Sunset</option>
<option value="DAYLIGHT">Daylight</option>
<option value="NOON">Noon</option>
<option value="NIGHT">Night</option>
</options>
</state>
</channel-type>
<!-- moonphase -->
<channel-group-type id="moonPhase">
<label>Moon Phase</label>
<description>The details of the current and next moon phases</description>
<channels>
<channel id="firstQuarter" typeId="firstQuarter"/>
<channel id="thirdQuarter" typeId="thirdQuarter"/>
<channel id="full" typeId="fullMoon"/>
<channel id="new" typeId="newMoon"/>
<channel id="age" typeId="age"/>
<channel id="ageDegree" typeId="ageDegree"/>
<channel id="agePercent" typeId="agePercent"/>
<channel id="illumination" typeId="illumination"/>
<channel id="name" typeId="phaseName"/>
<channel id="event" typeId="phaseEvent"/>
</channels>
</channel-group-type>
<channel-type id="firstQuarter">
<item-type>DateTime</item-type>
<label>First Quarter</label>
<description>The DateTime the moon is in the first quarter</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="thirdQuarter">
<item-type>DateTime</item-type>
<label>Third Quarter</label>
<description>The DateTime the moon is in the third quarter</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="fullMoon">
<item-type>DateTime</item-type>
<label>Full Moon</label>
<description>The DateTime for full moon</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="newMoon">
<item-type>DateTime</item-type>
<label>New Moon</label>
<description>The DateTime for new moon</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="age">
<item-type>Number:Time</item-type>
<label>Moon Age</label>
<description>The age of the moon in days</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="ageDegree">
<item-type>Number:Angle</item-type>
<label>Moon Age</label>
<description>The age of the moon</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="agePercent">
<item-type>Number:Dimensionless</item-type>
<label>Moon Age</label>
<description>The age of the moon</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="illumination">
<item-type>Number:Dimensionless</item-type>
<label>Moon Illumination</label>
<description>The illumination of the moon</description>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="phaseName">
<item-type>String</item-type>
<label>Moon Phase Name</label>
<description>The name of the current moon phase</description>
<state readOnly="true">
<options>
<option value="NEW">New moon</option>
<option value="WAXING_CRESCENT">Waxing crescent</option>
<option value="FIRST_QUARTER">First quarter</option>
<option value="WAXING_GIBBOUS">Waxing gibbous</option>
<option value="FULL">Full moon</option>
<option value="WANING_GIBBOUS">Waning gibbous</option>
<option value="THIRD_QUARTER">Third quarter</option>
<option value="WANING_CRESCENT">Waning crescent</option>
</options>
</state>
</channel-type>
<channel-type id="phaseEvent">
<kind>trigger</kind>
<label>Moon Phase Event</label>
<description>Moon phase event</description>
<event>
<options>
<option value="FIRST_QUARTER">first quarter</option>
<option value="THIRD_QUARTER">third quarter</option>
<option value="FULL">full</option>
<option value="NEW">new</option>
</options>
</event>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<!-- moon eclipse -->
<channel-group-type id="moonEclipse">
<label>Eclipses</label>
<description>The DateTime of the next moon eclipses</description>
<channels>
<channel id="total" typeId="total"/>
<channel id="totalElevation" typeId="elevation"/>
<channel id="partial" typeId="partial"/>
<channel id="partialElevation" typeId="elevation"/>
<channel id="event" typeId="moonEclipseEvent"/>
</channels>
</channel-group-type>
<channel-type id="moonEclipseEvent">
<kind>trigger</kind>
<label>Moon Eclipse Event</label>
<description>Moon eclipse event</description>
<event>
<options>
<option value="TOTAL">total</option>
<option value="PARTIAL">partial</option>
</options>
</event>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<!-- distance -->
<channel-group-type id="distance">
<label>Distance</label>
<description>Distance data</description>
<channels>
<channel id="date" typeId="distanceDate"/>
<channel id="distance" typeId="distance"/>
<channel id="event" typeId="distanceEvent"/>
</channels>
</channel-group-type>
<channel-type id="distanceDate">
<item-type>DateTime</item-type>
<label>Date</label>
<description>The DateTime when the distance is reached</description>
<state readOnly="true" pattern="%1$tF %1$tR"/>
</channel-type>
<channel-type id="distance">
<item-type>Number:Length</item-type>
<label>Distance</label>
<description>The distance of the object</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="distanceEvent">
<kind>trigger</kind>
<label>Moon Distance Event</label>
<description>Moon distance event</description>
<event>
<options>
<option value="PERIGEE">perigee</option>
<option value="APOGEE">apogee</option>
</options>
</event>
<config-description-ref uri="channel-type:astro:config"/>
</channel-type>
<!-- moonZodiac -->
<channel-group-type id="moonZodiac">
<label>Zodiac</label>
<description>The zodiac of the moon</description>
<channels>
<channel id="sign" typeId="sign"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="astro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="moon">
<label>Astro Moon Data</label>
<description>Provides astronomical data from the moon</description>
<channel-groups>
<channel-group id="rise" typeId="moonRange">
<label>Moonrise</label>
<description>The moonrise event range</description>
</channel-group>
<channel-group id="set" typeId="moonRange">
<label>Moonset</label>
<description>The moonset event range</description>
</channel-group>
<channel-group id="phase" typeId="moonPhase"/>
<channel-group id="eclipse" typeId="moonEclipse"/>
<channel-group id="distance" typeId="distance">
<description>The distance to the moon</description>
</channel-group>
<channel-group id="perigee" typeId="distance">
<label>Perigee</label>
<description>The perigee of the moon</description>
</channel-group>
<channel-group id="apogee" typeId="distance">
<label>Apogee</label>
<description>The apogee of the moon</description>
</channel-group>
<channel-group id="position" typeId="position">
<description>The position of the moon</description>
</channel-group>
<channel-group id="zodiac" typeId="moonZodiac"/>
</channel-groups>
<representation-property>geolocation</representation-property>
<config-description-ref uri="thing-type:astro:moonconfig"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="astro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="sun">
<label>Astro Sun Data</label>
<description>Provides astronomical data from the sun</description>
<channel-groups>
<channel-group id="rise" typeId="sunRange">
<label>Sunrise</label>
<description>The sunrise event range</description>
</channel-group>
<channel-group id="set" typeId="sunRange">
<label>Sunset</label>
<description>The sunset event range</description>
</channel-group>
<channel-group id="noon" typeId="sunRange">
<label>Noon</label>
<description>The noon event range</description>
</channel-group>
<channel-group id="night" typeId="sunRange">
<label>Night</label>
<description>The night event range</description>
</channel-group>
<channel-group id="morningNight" typeId="sunRange">
<label>Morning Night</label>
<description>The morning night event range</description>
</channel-group>
<channel-group id="astroDawn" typeId="sunRange">
<label>Astro Dawn</label>
<description>The astro dawn event range</description>
</channel-group>
<channel-group id="nauticDawn" typeId="sunRange">
<label>Nautic Dawn</label>
<description>The nautic dawn event range</description>
</channel-group>
<channel-group id="civilDawn" typeId="sunRange">
<label>Civil Dawn</label>
<description>The civil dawn event range</description>
</channel-group>
<channel-group id="astroDusk" typeId="sunRange">
<label>Astro Dusk</label>
<description>The astro dusk event range</description>
</channel-group>
<channel-group id="nauticDusk" typeId="sunRange">
<label>Nautic Dusk</label>
<description>The nautic dusk event range</description>
</channel-group>
<channel-group id="civilDusk" typeId="sunRange">
<label>Civil Dusk</label>
<description>The civil dusk event range</description>
</channel-group>
<channel-group id="eveningNight" typeId="sunRange">
<label>Evening Night</label>
<description>The evening night event range</description>
</channel-group>
<channel-group id="daylight" typeId="sunRange">
<label>Daylight</label>
<description>The daylight event range</description>
</channel-group>
<channel-group id="position" typeId="position">
<description>The position of the sun</description>
</channel-group>
<channel-group id="radiation" typeId="radiation"/>
<channel-group id="zodiac" typeId="sunZodiac"/>
<channel-group id="season" typeId="season"/>
<channel-group id="eclipse" typeId="sunEclipse"/>
<channel-group id="phase" typeId="sunPhase"/>
</channel-groups>
<representation-property>geolocation</representation-property>
<config-description-ref uri="thing-type:astro:sunconfig"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,322 @@
/**
* 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.calc;
import static org.junit.Assert.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPhaseName;
/***
* Specific unit tests to check if {@link SunCalc} generates correct data for
* Amsterdam city on 27 February 2019. In particular the following cases are
* covered:
* <ul>
* <li>checks if generated data are the same (with some accuracy) as produced by
* haevens-above.com</li>
* <li>checks if the generated {@link Sun#getAllRanges()} are consistent with
* each other</li>
* </ul>
*
* @author Witold Markowski - Initial contribution
* @see <a href="https://github.com/openhab/openhab-addons/issues/5006">[astro]
* Sun Phase returns UNDEF</a>
* @see <a href="https://www.heavens-above.com/sun.aspx">Heavens Above Sun</a>
*/
public class SunCalcTest {
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("Europe/Amsterdam");
private static final Calendar FEB_27_2019 = SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 1, 0, TIME_ZONE);
private static final double AMSTERDAM_LATITUDE = 52.367607;
private static final double AMSTERDAM_LONGITUDE = 4.8978293;
private static final double AMSTERDAM_ALTITUDE = 0.0;
private static final int ACCURACY_IN_MILLIS = 3 * 60 * 1000;
private SunCalc sunCalc;
@Before
public void init() {
sunCalc = new SunCalc();
}
@Test
public void testGetSunInfoForOldDate() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertNotNull(sun.getNight());
assertNotNull(sun.getAstroDawn());
assertNotNull(sun.getNauticDawn());
assertNotNull(sun.getCivilDawn());
assertNotNull(sun.getRise());
assertNotNull(sun.getDaylight());
assertNotNull(sun.getNoon());
assertNotNull(sun.getSet());
assertNotNull(sun.getCivilDusk());
assertNotNull(sun.getNauticDusk());
assertNotNull(sun.getAstroDusk());
assertNotNull(sun.getNight());
assertNotNull(sun.getMorningNight());
assertNotNull(sun.getEveningNight());
// for an old date the phase is always null
assertNull(sun.getPhase().getName());
}
@Test
public void testGetSunInfoForAstronomicalDawnAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 05:39 till 06:18
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 5, 39, TIME_ZONE).getTimeInMillis(),
sun.getAstroDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 18, TIME_ZONE).getTimeInMillis(),
sun.getAstroDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForNauticDawnAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 06:18 till 06:58
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 18, TIME_ZONE).getTimeInMillis(),
sun.getNauticDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 58, TIME_ZONE).getTimeInMillis(),
sun.getNauticDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForCivilDawnAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 06:58 till 07:32
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 58, TIME_ZONE).getTimeInMillis(),
sun.getCivilDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 7, 32, TIME_ZONE).getTimeInMillis(),
sun.getCivilDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForRiseAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 07:32
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 7, 32, TIME_ZONE).getTimeInMillis(),
sun.getRise().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForSunNoonAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 12:54
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 12, 54, TIME_ZONE).getTimeInMillis(),
sun.getNoon().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForSetAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 18:15
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 15, TIME_ZONE).getTimeInMillis(),
sun.getSet().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForCivilDuskAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 18:15 till 18:50
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 15, TIME_ZONE).getTimeInMillis(),
sun.getCivilDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 50, TIME_ZONE).getTimeInMillis(),
sun.getCivilDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForNauticDuskAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 18:50 till 19:29
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 50, TIME_ZONE).getTimeInMillis(),
sun.getNauticDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 19, 29, TIME_ZONE).getTimeInMillis(),
sun.getNauticDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
public void testGetSunInfoForAstronomicalDuskAccuracy() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
// expected result from haevens-above.com is 27 Feb 2019 19:29 till 20:09
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 19, 29, TIME_ZONE).getTimeInMillis(),
sun.getAstroDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 20, 9, TIME_ZONE).getTimeInMillis(),
sun.getAstroDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
}
@Test
@Ignore
public void testRangesForCoherenceBetweenNightEndAndAstroDawnStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.NIGHT).getEnd(),
sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getStart());
}
@Test
public void testRangesForCoherenceBetweenMorningNightEndAndAstroDawnStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.MORNING_NIGHT).getEnd(),
sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getStart());
}
@Test
public void testRangesForCoherenceBetweenAstroDownEndAndNauticDawnStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getEnd(),
sun.getAllRanges().get(SunPhaseName.NAUTIC_DAWN).getStart());
}
@Test
public void testRangesForCoherenceBetweenNauticDawnEndAndCivilDawnStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.NAUTIC_DAWN).getEnd(),
sun.getAllRanges().get(SunPhaseName.CIVIL_DAWN).getStart());
}
@Test
public void testRangesForCoherenceBetweenCivilDawnEndAndSunRiseStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.CIVIL_DAWN).getEnd(),
sun.getAllRanges().get(SunPhaseName.SUN_RISE).getStart());
}
@Test
public void testRangesForCoherenceBetweenSunRiseEndAndDaylightStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.SUN_RISE).getEnd(),
sun.getAllRanges().get(SunPhaseName.DAYLIGHT).getStart());
}
@Test
public void testRangesForCoherenceBetweenDaylightEndAndSunSetStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.DAYLIGHT).getEnd(),
sun.getAllRanges().get(SunPhaseName.SUN_SET).getStart());
}
@Test
public void testRangesForCoherenceBetweenSunSetEndAndCivilDuskStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.SUN_SET).getEnd(),
sun.getAllRanges().get(SunPhaseName.CIVIL_DUSK).getStart());
}
@Test
public void testRangesForCoherenceBetweenCivilDuskEndAndNauticDuskStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.CIVIL_DUSK).getEnd(),
sun.getAllRanges().get(SunPhaseName.NAUTIC_DUSK).getStart());
}
@Test
public void testRangesForCoherenceBetweenNauticDuskEndAndAstroDuskStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.NAUTIC_DUSK).getEnd(),
sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getStart());
}
@Test
public void testRangesForCoherenceBetweenAstroDuskEndAndNightStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getEnd(),
sun.getAllRanges().get(SunPhaseName.NIGHT).getStart());
}
@Test
public void testRangesForCoherenceBetweenAstroDuskEndAndEveningNightStart() {
Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getEnd(),
sun.getAllRanges().get(SunPhaseName.EVENING_NIGHT).getStart());
}
/***
* Constructs a <code>GregorianCalendar</code> with the given date and time set
* for the provided time zone.
*
* @param year
* the value used to set the <code>YEAR</code> calendar field in the
* calendar.
* @param month
* the value used to set the <code>MONTH</code> calendar field in the
* calendar. Month value is 0-based. e.g., 0 for January.
* @param dayOfMonth
* the value used to set the <code>DAY_OF_MONTH</code> calendar field
* in the calendar.
* @param hourOfDay
* the value used to set the <code>HOUR_OF_DAY</code> calendar field
* in the calendar.
* @param minute
* the value used to set the <code>MINUTE</code> calendar field in
* the calendar.
* @param zone
* the given time zone.
* @return
*/
private static Calendar newCalendar(int year, int month, int dayOfMonth, int hourOfDay, int minute, TimeZone zone) {
Calendar result = new GregorianCalendar(year, month, dayOfMonth, hourOfDay, minute);
result.setTimeZone(zone);
return result;
}
@Test
public void testAstroAndMeteoSeasons() {
Sun meteoSun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE,
true);
Sun equiSun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE,
false);
assertEquals(meteoSun.getSeason().getSpring().get(Calendar.MONTH),
equiSun.getSeason().getSpring().get(Calendar.MONTH));
assertEquals(meteoSun.getSeason().getSpring().get(Calendar.YEAR),
equiSun.getSeason().getSpring().get(Calendar.YEAR));
assertEquals(1, meteoSun.getSeason().getSpring().get(Calendar.DAY_OF_MONTH));
assertFalse(meteoSun.getSeason().getSpring().get(Calendar.DAY_OF_MONTH) == equiSun.getSeason().getSpring()
.get(Calendar.DAY_OF_MONTH));
}
}

View File

@@ -0,0 +1,170 @@
/**
* 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.model;
import static org.junit.Assert.*;
import java.time.ZoneId;
import org.junit.Before;
import org.junit.Test;
import org.openhab.binding.astro.internal.config.AstroChannelConfig;
import org.openhab.binding.astro.internal.util.PropertyUtils;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.UnDefType;
/***
* A set of standard unit test of {@link Sun} class. In particular it checks if
* {@link Sun#getAllRanges()} contains a correct {@link SunPhaseName}.
*
* @author Witold Markowski - Initial contribution
* @see <a href="https://github.com/openhab/openhab-addons/issues/5006">[astro]
* Sun Phase returns UNDEF</a>
*/
public class SunTest {
private Sun sun;
private AstroChannelConfig config;
private static ZoneId ZONE = ZoneId.systemDefault();
@Before
public void init() {
sun = new Sun();
config = new AstroChannelConfig();
}
@Test
public void testConstructor() throws Exception {
assertNotNull(sun.getPhase());
assertEquals(UnDefType.UNDEF,
PropertyUtils.getState(new ChannelUID("astro:sun:home:phase#name"), config, sun, ZONE));
}
@Test
public void testGetStateWhenNullPhaseName() throws Exception {
sun.getPhase().setName(null);
assertEquals(UnDefType.UNDEF,
PropertyUtils.getState(new ChannelUID("astro:sun:home:phase#name"), config, sun, ZONE));
}
@Test
public void testGetStateWhenNotNullPhaseName() throws Exception {
sun.getPhase().setName(SunPhaseName.DAYLIGHT);
assertEquals(new StringType("DAYLIGHT"),
PropertyUtils.getState(new ChannelUID("astro:sun:home:phase#name"), config, sun, ZONE));
}
@Test(expected = NullPointerException.class)
public void testGetStateWhenNullPhase() throws Exception {
sun.setPhase(null);
assertNull(sun.getPhase());
assertEquals(UnDefType.UNDEF,
PropertyUtils.getState(new ChannelUID("astro:sun:home:phase#name"), config, sun, ZONE));
}
@Test
public void testGetAllRangesForNight() {
sun.setNight(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.NIGHT));
}
@Test
public void testGetAllRangesForMorningNight() {
sun.setMorningNight(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.MORNING_NIGHT));
}
@Test
public void testGetAllRangesForAstroDawn() {
sun.setAstroDawn(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.ASTRO_DAWN));
}
@Test
public void testGetAllRangesForNauticDawn() {
sun.setNauticDawn(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.NAUTIC_DAWN));
}
@Test
public void testGetAllRangesForCivilDawn() {
sun.setCivilDawn(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.CIVIL_DAWN));
}
@Test
public void testGetAllRangesForRise() {
sun.setRise(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.SUN_RISE));
}
@Test
public void testGetAllRangesForDaylight() {
sun.setDaylight(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.DAYLIGHT));
}
@Test
public void testGetAllRangesForNoon() {
sun.setNoon(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.NOON));
}
@Test
public void testGetAllRangesForSet() {
sun.setSet(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.SUN_SET));
}
@Test
public void testGetAllRangesForCivilDusk() {
sun.setCivilDusk(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.CIVIL_DUSK));
}
@Test
public void testGetAllRangesForNauticDusk() {
sun.setNauticDusk(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.NAUTIC_DUSK));
}
@Test
public void testGetAllRangesForAstroDusk() {
sun.setAstroDusk(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.ASTRO_DUSK));
}
@Test
public void testGetAllRangesForEveningNight() {
sun.setEveningNight(new Range());
assertTrue(sun.getAllRanges().containsKey(SunPhaseName.EVENING_NIGHT));
}
}