[Ephemeris] Binding to make the bridge with core Ephemeris functions (#16628)

* [ephemeris] Initial commit of the ephemeris binding

Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2024-05-26 18:58:48 +02:00 committed by GitHub
parent d6382d34a8
commit bae5b0c939
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1164 additions and 0 deletions

View File

@ -102,6 +102,7 @@
/bundles/org.openhab.binding.enocean/ @fruggy83
/bundles/org.openhab.binding.enphase/ @Hilbrand
/bundles/org.openhab.binding.enturno/ @klocsson
/bundles/org.openhab.binding.ephemeris/ @clinique
/bundles/org.openhab.binding.epsonprojector/ @mlobstein
/bundles/org.openhab.binding.etherrain/ @dfad1469
/bundles/org.openhab.binding.evcc/ @florian-h05

View File

@ -501,6 +501,11 @@
<artifactId>org.openhab.binding.enturno</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.ephemeris</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.epsonprojector</artifactId>

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,105 @@
# Ephemeris Binding
The Ephemeris Binding makes the bridge with Ephemeris core actions.
It provides access to Ephemeris data via Items without requiring usage of a scripting language.
The binding will search your Jollyday event definition files in the sub folder `/misc/ephemeris` located in the configuration folder of openHAB (e.g. for a linux system : /etc/openhab/misc/ephemeris/)
## Supported Things
The binding handles the following Things:
* default holiday data (`holiday`)
* custom holiday file (`custom`)
* daysets (`dayset`)
* weekend (`weekend`)
## Discovery
The binding discovers `weekend` and `holiday` things.
## Binding Configuration
There is no configuration at binding level.
## Thing Configuration
### `custom` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|-----------------|---------|---------------------------------------------------|---------|----------|----------|
| fileName | text | Name of the XML file in the configuration folder | N/A | yes | no |
The file has to use the syntax described here : https://www.openhab.org/docs/configuration/actions.html#custom-bank-holidays
### `dayset` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|-----------------|---------|---------------------------|---------|----------|----------|
| name | text | Name of the dayset used | N/A | yes | no |
## Channels
### `weekend` Channels
| Name | Type | Description |
|----------|--------|---------------------------------------------------------------|
| today | Switch | Set to ON if today is a weekend day, OFF in the other case |
| tomorrow | Switch | Set to ON if tomorrow is a weekend day, OFF in the other case |
### `dayset` Channels
| Name | Type | Description |
|----------|--------|---------------------------------------------------------------------|
| today | Switch | Set to ON if today is in the given dayset, OFF in the other case |
| tomorrow | Switch | Set to ON if tomorrow is in the given dayset, OFF in the other case |
### `holiday` Channels
| Name | Type | Description |
|------------------|-------------|------------------------------------------------|
| title-today | String | Name of today's holiday if any, NULL otherwise |
| holiday-today | Switch | Set to ON if today is a holiday |
| holiday-tomorrow | Switch | Set to ON if tomorrow is a holiday |
| next-title | String | Name of the next coming holiday |
| next-start | DateTime | Start date of the next coming holiday |
| days-remaining | Number:Time | Remaining days until next holiday |
### `custom` Channels
| Name | Type | Description |
|----------------|-------------|----------------------------------------|
| title-today | String | Title of the currently present event |
| event-today | Switch | Set to ON if an event exists today |
| event-tomorrow | Switch | Set to ON if an event exists tomorrow |
| next-title | String | Title of the next starting event |
| next-start | DateTime | Start date of the next coming event |
| days-remaining | Number:Time | Remaining days until next event |
## Full Example
### Thing Configuration
```java
Thing ephemeris:holiday:local "Holidays"
Thing ephemeris:weekend:local "Week-end"
Thing ephemeris:custom:events "Event" [fileName="events.xml"]
```
### Item Configuration
```java
String ToD_Event_Current "Event Today" <calendar> (gEvents) {channel="ephemeris:custom:events:title-today"}
String ToD_Event_Next "Event Next" <calendar> (gEvents) {channel="ephemeris:custom:events:next-title"}
Number:Time ToD_Event_Next_Left "Event In" <calendar> (gEvents) ["Measurement","Duration"] {channel="ephemeris:custom:events:days-remaining", unit="day"}
Switch ToD_Week_End_Current "Week-End" <calendar> (gWeekEnd) {channel="ephemeris:weekend:local:today"}
Switch ToD_Week_End_Tomorrow "Week-End Tomorrow" <calendar> (gWeekEnd) {channel="ephemeris:weekend:local:tomorrow"}
String ToD_Holiday_Current "Holiday Today" <calendar> (gHoliday) {channel="ephemeris:holiday:local:title-today"}
String ToD_Holiday_Next "Holiday Next" <calendar> (gHoliday) {channel="ephemeris:holiday:local:next-title"}
Number:Time ToD_Holiday_Next_Left "Holiday In" <calendar> (gHoliday) ["Measurement","Duration"] {channel="ephemeris:holiday:local:days-remaining", unit="day"}
```

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 https://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>4.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.ephemeris</artifactId>
<name>openHAB Add-ons :: Bundles :: Ephemeris Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.ephemeris-${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-ephemeris" description="Ephemeris Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ephemeris/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal;
import java.io.File;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.OpenHAB;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link EphemerisBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class EphemerisBindingConstants {
public static final String BINDING_ID = "ephemeris";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CUSTOM = new ThingTypeUID(BINDING_ID, "custom");
public static final ThingTypeUID THING_TYPE_HOLIDAY = new ThingTypeUID(BINDING_ID, "holiday");
public static final ThingTypeUID THING_TYPE_DAYSET = new ThingTypeUID(BINDING_ID, "dayset");
public static final ThingTypeUID THING_TYPE_WEEKEND = new ThingTypeUID(BINDING_ID, "weekend");
// List of all Channel ids
public static final String CHANNEL_CURRENT_EVENT = "title-today";
public static final String CHANNEL_NEXT_EVENT = "next-title";
public static final String CHANNEL_NEXT_START = "next-start";
public static final String CHANNEL_NEXT_REMAINING = "days-remaining";
public static final String CHANNEL_TODAY = "today";
public static final String CHANNEL_TOMORROW = "tomorrow";
public static final String CHANNEL_HOLIDAY_TODAY = "holiday-today";
public static final String CHANNEL_HOLIDAY_TOMORROW = "holiday-tomorrow";
public static final String CHANNEL_EVENT_TODAY = "event-today";
public static final String CHANNEL_EVENT_TOMORROW = "event-tomorrow";
// Folder for xml storage eg: /etc/openhab/misc/ephemeris
public static final String BINDING_DATA_PATH = "%s%smisc%s%s".formatted(OpenHAB.getConfigFolder(), File.separator,
File.separator, BINDING_ID);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Exception raised by Ephemeris Handlers
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class EphemerisException extends Exception {
private static final long serialVersionUID = -8813754360966576513L;
private final ThingStatusDetail statusDetail;
public EphemerisException(String message, ThingStatusDetail statusDetail) {
super(message);
this.statusDetail = statusDetail;
}
public ThingStatusDetail getStatusDetail() {
return statusDetail;
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.io.File;
import java.time.ZoneId;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ephemeris.internal.handler.CustomHandler;
import org.openhab.binding.ephemeris.internal.handler.DaysetHandler;
import org.openhab.binding.ephemeris.internal.handler.HolidayHandler;
import org.openhab.binding.ephemeris.internal.handler.WeekendHandler;
import org.openhab.binding.ephemeris.internal.providers.EphemerisDescriptionProvider;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.i18n.TimeZoneProvider;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EphemerisHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.ephemeris", service = ThingHandlerFactory.class)
public class EphemerisHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CUSTOM, THING_TYPE_HOLIDAY,
THING_TYPE_DAYSET, THING_TYPE_WEEKEND);
private final Logger logger = LoggerFactory.getLogger(EphemerisHandlerFactory.class);
private final EphemerisManager ephemerisManager;
private final ZoneId zoneId;
private final EphemerisDescriptionProvider descriptionProvider;
@Activate
public EphemerisHandlerFactory(final @Reference EphemerisManager ephemerisManager,
final @Reference TimeZoneProvider timeZoneProvider,
final @Reference EphemerisDescriptionProvider descriptionProvider) {
this.ephemerisManager = ephemerisManager;
this.zoneId = timeZoneProvider.getTimeZone();
this.descriptionProvider = descriptionProvider;
File folder = new File(BINDING_DATA_PATH);
if (!folder.exists()) {
logger.info("Please, create the folder '{}' to store your custom Jollyday definition files.",
BINDING_DATA_PATH);
}
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_CUSTOM.equals(thingTypeUID)) {
return new CustomHandler(thing, ephemerisManager, zoneId);
} else if (THING_TYPE_HOLIDAY.equals(thingTypeUID)) {
return new HolidayHandler(thing, ephemerisManager, zoneId, descriptionProvider);
} else if (THING_TYPE_DAYSET.equals(thingTypeUID)) {
return new DaysetHandler(thing, ephemerisManager, zoneId);
} else if (THING_TYPE_WEEKEND.equals(thingTypeUID)) {
return new WeekendHandler(thing, ephemerisManager, zoneId);
}
return null;
}
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link DaysetConfiguration} class contains fields mapping Dayset Thing configuration parameters.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class DaysetConfiguration {
public String name = "";
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FileConfiguration} class contains fields mapping File Thing configuration parameters.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class FileConfiguration {
public String fileName = "";
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.discovery;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.util.Map;
import java.util.Optional;
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.TranslationProvider;
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 EphemerisDiscoveryService} creates default available Ephemeris Things.
*
* @author Gaël L'hopital - Initial Contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.ephemeris")
public class EphemerisDiscoveryService extends AbstractDiscoveryService {
private static final int DISCOVER_TIMEOUT_SECONDS = 2;
private static final String LOCAL = "local";
private static final ThingUID HOLIDAY_THING = new ThingUID(THING_TYPE_HOLIDAY, LOCAL);
private static final ThingUID WEEKEND_THING = new ThingUID(THING_TYPE_WEEKEND, LOCAL);
private final Logger logger = LoggerFactory.getLogger(EphemerisDiscoveryService.class);
private Optional<ScheduledFuture<?>> discoveryJob = Optional.empty();
@Activate
public EphemerisDiscoveryService(final @Reference LocaleProvider localeProvider,
final @Reference TranslationProvider i18nProvider, @Nullable Map<String, Object> configProperties) {
super(Set.of(THING_TYPE_HOLIDAY, THING_TYPE_WEEKEND), DISCOVER_TIMEOUT_SECONDS, true);
this.localeProvider = localeProvider;
this.i18nProvider = i18nProvider;
activate(configProperties);
}
@Override
protected void startScan() {
logger.debug("Starting Ephemeris discovery scan");
createResults();
}
@Override
protected void startBackgroundDiscovery() {
discoveryJob = Optional.of(scheduler.schedule(this::createResults, 2, TimeUnit.SECONDS));
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping Ephemeris device background discovery");
discoveryJob.ifPresent(job -> job.cancel(true));
discoveryJob = Optional.empty();
}
public void createResults() {
thingDiscovered(DiscoveryResultBuilder.create(HOLIDAY_THING).build());
thingDiscovered(DiscoveryResultBuilder.create(WEEKEND_THING).build());
}
}

View File

@ -0,0 +1,96 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BaseEphemerisHandler} is the base class for Ephemeris Things. It takes care of
* update logic and update scheduling once a day.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public abstract class BaseEphemerisHandler extends BaseThingHandler {
private static final int REFRESH_FIRST_HOUR_OF_DAY = 0;
private static final int REFRESH_FIRST_MINUTE_OF_DAY = 1;
private final Logger logger = LoggerFactory.getLogger(BaseEphemerisHandler.class);
private final ZoneId zoneId;
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
protected final EphemerisManager ephemeris;
public BaseEphemerisHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
super(thing);
this.zoneId = zoneId;
this.ephemeris = ephemerisManager;
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
refreshJob = Optional.of(scheduler.schedule(this::updateData, 1, TimeUnit.SECONDS));
}
@Override
public void dispose() {
refreshJob.ifPresent(job -> job.cancel(true));
refreshJob = Optional.empty();
super.dispose();
}
private void updateData() {
ZonedDateTime now = ZonedDateTime.now().withZoneSameLocal(zoneId);
logger.debug("Updating {} channels", getThing().getUID());
try {
internalUpdate(now.truncatedTo(ChronoUnit.DAYS));
updateStatus(ThingStatus.ONLINE);
ZonedDateTime nextUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
.withMinute(REFRESH_FIRST_MINUTE_OF_DAY).truncatedTo(ChronoUnit.MINUTES);
long delay = ChronoUnit.MINUTES.between(now, nextUpdate);
logger.debug("Scheduling next {} update in {} minutes", getThing().getUID(), delay);
refreshJob = Optional.of(scheduler.schedule(this::updateData, delay, TimeUnit.MINUTES));
} catch (EphemerisException e) {
updateStatus(ThingStatus.OFFLINE, e.getStatusDetail(), e.getMessage());
}
}
protected abstract void internalUpdate(ZonedDateTime today) throws EphemerisException;
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (RefreshType.REFRESH.equals(command)) {
updateData();
}
}
}

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.binding.ephemeris.internal.configuration.FileConfiguration;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* The {@link CustomHandler} delivers user defined Jollyday definition events.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class CustomHandler extends JollydayHandler {
private Optional<File> definitionFile = Optional.empty();
public CustomHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
super(thing, ephemerisManager, zoneId);
}
@Override
public void initialize() {
String fileName = getConfigAs(FileConfiguration.class).fileName;
if (fileName.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"'fileName' can not be blank or empty");
return;
}
File file = new File(BINDING_DATA_PATH, fileName);
if (!file.exists()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Missing file: %s".formatted(file.getAbsolutePath()));
return;
}
definitionFile = Optional.of(file);
super.initialize();
}
@Override
protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
String event = getEvent(today);
updateState(CHANNEL_EVENT_TODAY, OnOffType.from(event != null));
event = getEvent(today.plusDays(1));
updateState(CHANNEL_EVENT_TOMORROW, OnOffType.from(event != null));
super.internalUpdate(today);
}
@Override
protected @Nullable String getEvent(ZonedDateTime day) throws EphemerisException {
String path = definitionFile.get().getAbsolutePath();
try {
return ephemeris.getBankHolidayName(day, path);
} catch (IllegalStateException e) {
throw new EphemerisException("Incorrect syntax", ThingStatusDetail.NONE);
} catch (FileNotFoundException e) {
throw new EphemerisException("File is absent: " + path, ThingStatusDetail.CONFIGURATION_ERROR);
}
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.binding.ephemeris.internal.configuration.DaysetConfiguration;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
/**
* The {@link DaysetHandler} delivers system default dayset data.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class DaysetHandler extends BaseEphemerisHandler {
private String dayset = "";
public DaysetHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
super(thing, ephemerisManager, zoneId);
}
@Override
public void initialize() {
DaysetConfiguration config = getConfigAs(DaysetConfiguration.class);
dayset = config.name;
super.initialize();
}
@Override
protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
updateState(CHANNEL_TODAY, OnOffType.from(ephemeris.isInDayset(dayset, today)));
updateState(CHANNEL_TOMORROW, OnOffType.from(ephemeris.isInDayset(dayset, today.plusDays(1))));
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.binding.ephemeris.internal.providers.EphemerisDescriptionProvider;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.StateOption;
/**
* The {@link HolidayHandler} delivers system default Holidays data.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class HolidayHandler extends JollydayHandler {
public HolidayHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId,
EphemerisDescriptionProvider descriptionProvider) {
super(thing, ephemerisManager, zoneId);
// Search all holidays in the coming year, using a map to avoid duplicates
Map<String, StateOption> events = new HashMap<>();
ZonedDateTime now = ZonedDateTime.now();
// Scans 13 monthes to be sure to catch mobile holidays
for (int offset = 0; offset < 398; offset++) {
String event = getEvent(now.plusDays(offset));
if (event != null) {
String description = ephemeris.getHolidayDescription(event);
events.put(event, new StateOption(event, description == null ? event : description));
}
}
// Set descriptions for these events
List<StateOption> stateOptions = events.values().stream().toList();
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_CURRENT_EVENT), stateOptions);
descriptionProvider.setStateOptions(new ChannelUID(thing.getUID(), CHANNEL_NEXT_EVENT), stateOptions);
}
@Override
protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
updateState(CHANNEL_HOLIDAY_TODAY, OnOffType.from(getEvent(today) != null));
updateState(CHANNEL_HOLIDAY_TOMORROW, OnOffType.from(getEvent(today.plusDays(1)) != null));
super.internalUpdate(today);
}
@Override
protected @Nullable String getEvent(ZonedDateTime day) {
return ephemeris.getBankHolidayName(day);
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link JollydayHandler} handles common parts for Jollyday file based events
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public abstract class JollydayHandler extends BaseEphemerisHandler {
public JollydayHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
super(thing, ephemerisManager, zoneId);
}
@Override
protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
String todayEvent = getEvent(today);
updateState(CHANNEL_CURRENT_EVENT, toStringType(todayEvent));
String nextEvent = null;
ZonedDateTime nextDay = today;
for (int offset = 1; offset < 366 && (nextEvent == null || nextEvent.isEmpty()); offset++) {
nextDay = today.plusDays(offset);
nextEvent = getEvent(nextDay);
}
updateState(CHANNEL_NEXT_EVENT, toStringType(nextEvent));
updateState(CHANNEL_NEXT_REMAINING,
nextEvent != null ? new QuantityType<>(Duration.between(today, nextDay).toDays(), Units.DAY)
: UnDefType.UNDEF);
updateState(CHANNEL_NEXT_START, nextEvent != null ? new DateTimeType(nextDay) : UnDefType.UNDEF);
}
protected abstract @Nullable String getEvent(ZonedDateTime day) throws EphemerisException;
protected State toStringType(@Nullable String event) {
return event == null || event.isEmpty() ? UnDefType.NULL : new StringType(event);
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.handler;
import static org.openhab.binding.ephemeris.internal.EphemerisBindingConstants.*;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.ephemeris.internal.EphemerisException;
import org.openhab.core.ephemeris.EphemerisManager;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Thing;
/**
* The {@link WeekendHandler} delivers system default Weekend data.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class WeekendHandler extends BaseEphemerisHandler {
public WeekendHandler(Thing thing, EphemerisManager ephemerisManager, ZoneId zoneId) {
super(thing, ephemerisManager, zoneId);
}
@Override
protected void internalUpdate(ZonedDateTime today) throws EphemerisException {
updateState(CHANNEL_TODAY, OnOffType.from(ephemeris.isWeekend(today)));
updateState(CHANNEL_TOMORROW, OnOffType.from(ephemeris.isWeekend(today.plusDays(1))));
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2024 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.ephemeris.internal.providers;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Gaël L'hopital - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, EphemerisDescriptionProvider.class })
@NonNullByDefault
public class EphemerisDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public EphemerisDescriptionProvider(final @Reference EventPublisher eventPublisher, //
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="ephemeris" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>Ephemeris Binding</name>
<description>The Ephemeris Binding makes Ephemeris core actions accessible to Items</description>
<connection>none</connection>
</addon:addon>

View File

@ -0,0 +1,86 @@
# add-on
addon.ephemeris.name = Ephemeris Binding
addon.ephemeris.description = The Ephemeris Binding makes Ephemeris core actions accessible to Items
# thing types
thing-type.ephemeris.custom.label = Custom Jollyday File
thing-type.ephemeris.custom.description = Events defined in a Jollyday file
thing-type.ephemeris.custom.channel.event-today.label = Event Today
thing-type.ephemeris.custom.channel.event-today.description = Set to ON if an event exists today
thing-type.ephemeris.custom.channel.event-tomorrow.label = Event Tomorrow
thing-type.ephemeris.custom.channel.event-tomorrow.description = Set to ON if an event exists tomorrow
thing-type.ephemeris.dayset.label = Dayset
thing-type.ephemeris.dayset.description = Events based on a given dayset
thing-type.ephemeris.dayset.channel.today.label = Today in Dayset
thing-type.ephemeris.dayset.channel.today.description = Set to ON if today is in the given dayset, OFF in the other case
thing-type.ephemeris.dayset.channel.tomorrow.label = Tomorrow in Dayset
thing-type.ephemeris.dayset.channel.tomorrow.description = Set to ON if tomorrow is in the given dayset, OFF in the other case
thing-type.ephemeris.holiday.label = Ephemeris Holidays
thing-type.ephemeris.holiday.description = Holidays based on system default Ephemeris configuration
thing-type.ephemeris.holiday.channel.holiday-today.label = Holiday Today
thing-type.ephemeris.holiday.channel.holiday-today.description = Set to ON if today is a holiday
thing-type.ephemeris.holiday.channel.holiday-tomorrow.label = Holiday Tomorrow
thing-type.ephemeris.holiday.channel.holiday-tomorrow.description = Set to ON if tomorrow is a holiday
thing-type.ephemeris.holiday.channel.next-title.description = Name of the next coming holiday
thing-type.ephemeris.holiday.channel.title-today.description = Name of today's holiday if any, NULL otherwise
thing-type.ephemeris.weekend.label = Weekend
thing-type.ephemeris.weekend.description = Events based on the system default week-end dayset
thing-type.ephemeris.weekend.channel.today.label = Weekend Today
thing-type.ephemeris.weekend.channel.today.description = Set to ON if today is a weekend day, OFF in the other case
thing-type.ephemeris.weekend.channel.tomorrow.label = Weekend Tomorrow
thing-type.ephemeris.weekend.channel.tomorrow.description = Set to ON if tomorrow is a weekend day, OFF in the other case
# thing types config
thing-type.config.ephemeris.custom.fileName.label = File Name
thing-type.config.ephemeris.custom.fileName.description = Name of a Jollyday XML file in the configuration folder.
thing-type.config.ephemeris.dayset.name.label = Name
thing-type.config.ephemeris.dayset.name.description = Name of the dayset.
# channel types
channel-type.ephemeris.days-remaining.label = Remaining Days
channel-type.ephemeris.days-remaining.description = Remaining days until next event
channel-type.ephemeris.event-current-title.label = Current Event Title
channel-type.ephemeris.event-current-title.description = Title of the currently present event
channel-type.ephemeris.event-next-start.label = Next Event Start
channel-type.ephemeris.event-next-start.description = Start date of the next coming event
channel-type.ephemeris.event-next-start.state.pattern = %1$tY-%1$tm-%1$td
channel-type.ephemeris.event-next-title.label = Next Event Title
channel-type.ephemeris.event-next-title.description = Title of the next starting event
channel-type.ephemeris.in-dayset.label = In Dayset
# channel types
channel-type.ephemeris.in-dayset.description = Set to ON if the day is in the dayset, OFF in the other case
# thing types
thing-type.ephemeris.file.label = Ephemeris File
thing-type.ephemeris.file.description = Events defined in a Jollyday file
thing-type.ephemeris.file.channel.event-today.label = Event Today
thing-type.ephemeris.file.channel.event-today.description = Set to ON if an event exists today
thing-type.ephemeris.file.channel.event-tomorrow.label = Event Tomorrow
thing-type.ephemeris.file.channel.event-tomorrow.description = Set to ON if an event exists tomorrow
# thing types config
thing-type.config.ephemeris.file.fileName.label = File Name
thing-type.config.ephemeris.file.fileName.description = Name of a Jollyday XML file in the binding folder.
# channel types
channel-type.ephemeris.remaining-days.label = Remaining Days
channel-type.ephemeris.remaining-days.description = Days until next event
# thing types
thing-type.ephemeris.default.label = Ephemeris
thing-type.ephemeris.default.description = Events based on system default Ephemeris configuration
# discovery result
discovery.ephemeris.holiday.local.label = Local Holiday
discovery.ephemeris.weekend.local.label = Local Weekend

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="ephemeris"
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="custom">
<label>Custom Jollyday File</label>
<description>Events defined in a Jollyday file</description>
<channels>
<channel id="title-today" typeId="event-current-title"/>
<channel id="event-today" typeId="in-dayset">
<label>Event Today</label>
<description>Set to ON if an event exists today</description>
</channel>
<channel id="event-tomorrow" typeId="in-dayset">
<label>Event Tomorrow</label>
<description>Set to ON if an event exists tomorrow</description>
</channel>
<channel id="next-title" typeId="event-next-title"/>
<channel id="next-start" typeId="event-next-start"/>
<channel id="days-remaining" typeId="days-remaining"/>
</channels>
<config-description>
<parameter name="fileName" type="text" required="true">
<label>File Name</label>
<description>Name of a Jollyday XML file in the configuration folder.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="holiday">
<label>Ephemeris Holidays</label>
<description>Holidays based on system default Ephemeris configuration</description>
<channels>
<channel id="title-today" typeId="event-current-title">
<description>Name of today's holiday if any, NULL otherwise </description>
</channel>
<channel id="holiday-today" typeId="in-dayset">
<label>Holiday Today</label>
<description>Set to ON if today is a holiday</description>
</channel>
<channel id="holiday-tomorrow" typeId="in-dayset">
<label>Holiday Tomorrow</label>
<description>Set to ON if tomorrow is a holiday</description>
</channel>
<channel id="next-title" typeId="event-next-title">
<description>Name of the next coming holiday</description>
</channel>
<channel id="next-start" typeId="event-next-start"/>
<channel id="days-remaining" typeId="days-remaining"/>
</channels>
</thing-type>
<thing-type id="dayset">
<label>Dayset</label>
<description>Events based on a given dayset</description>
<channels>
<channel id="today" typeId="in-dayset">
<label>Today In Dayset</label>
<description>Set to ON if today is in the given dayset, OFF in the other case</description>
</channel>
<channel id="tomorrow" typeId="in-dayset">
<label>Tomorrow In Dayset</label>
<description>Set to ON if tomorrow is in the given dayset, OFF in the other case</description>
</channel>
</channels>
<config-description>
<parameter name="name" type="text" required="true">
<label>Name</label>
<description>Name of the dayset.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="weekend">
<label>Weekend</label>
<description>Events based on the system default weekend dayset</description>
<channels>
<channel id="today" typeId="in-dayset">
<label>Weekend Today</label>
<description>Set to ON if today is a weekend day, OFF in the other case</description>
</channel>
<channel id="tomorrow" typeId="in-dayset">
<label>Weekend Tomorrow</label>
<description>Set to ON if tomorrow is a weekend day, OFF in the other case</description>
</channel>
</channels>
</thing-type>
<channel-type id="in-dayset">
<item-type>Switch</item-type>
<label>In Dayset</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="event-current-title">
<item-type>String</item-type>
<label>Current Event Title</label>
<description>Title of the currently present event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="event-next-title">
<item-type>String</item-type>
<label>Next Event Title</label>
<description>Title of the next starting event</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="event-next-start">
<item-type>DateTime</item-type>
<label>Next Event Start</label>
<description>Start date of the next coming event</description>
<state readOnly="true" pattern="%1$tY-%1$tm-%1$td"/>
</channel-type>
<channel-type id="days-remaining">
<item-type>Number:Time</item-type>
<label>Remaining Days</label>
<description>Remaining days until next event</description>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -134,6 +134,7 @@
<module>org.openhab.binding.enocean</module>
<module>org.openhab.binding.enphase</module>
<module>org.openhab.binding.enturno</module>
<module>org.openhab.binding.ephemeris</module>
<module>org.openhab.binding.epsonprojector</module>
<module>org.openhab.binding.etherrain</module>
<module>org.openhab.binding.evcc</module>