2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.ahawastecollection.internal;
15 import java.io.IOException;
16 import java.time.Duration;
17 import java.time.ZonedDateTime;
18 import java.util.Collections;
19 import java.util.Date;
21 import java.util.concurrent.locks.Lock;
22 import java.util.concurrent.locks.ReentrantLock;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
27 import org.openhab.core.cache.ExpiringCache;
28 import org.openhab.core.i18n.TimeZoneProvider;
29 import org.openhab.core.library.types.DateTimeType;
30 import org.openhab.core.scheduler.CronScheduler;
31 import org.openhab.core.scheduler.ScheduledCompletableFuture;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link AhaWasteCollectionHandler} is responsible for handling commands, which are
45 * sent to one of the channels.
47 * @author Sönke Küper - Initial contribution
50 public class AhaWasteCollectionHandler extends BaseThingHandler {
52 private static final String DAILY_MIDNIGHT = "30 0 0 * * ? *";
54 /** Scheduler to schedule jobs */
55 private final CronScheduler cronScheduler;
56 private final Lock monitor = new ReentrantLock();
57 private final ExpiringCache<Map<WasteType, CollectionDate>> cache;
59 private final TimeZoneProvider timeZoneProvider;
60 private final Logger logger = LoggerFactory.getLogger(AhaWasteCollectionHandler.class);
62 private @Nullable AhaWasteCollectionConfiguration config;
63 private @Nullable AhaCollectionSchedule collectionSchedule;
65 private @Nullable ScheduledCompletableFuture<?> dailyJob;
67 private final AhaCollectionScheduleFactory scheduleFactory;
69 public AhaWasteCollectionHandler(final Thing thing, final CronScheduler scheduler,
70 final TimeZoneProvider timeZoneProvider, final AhaCollectionScheduleFactory scheduleFactory) {
72 this.cronScheduler = scheduler;
73 this.timeZoneProvider = timeZoneProvider;
74 this.scheduleFactory = scheduleFactory;
75 this.cache = new ExpiringCache<>(Duration.ofMinutes(5), this::loadCollectionDates);
78 private Map<WasteType, CollectionDate> loadCollectionDates() {
80 final Map<WasteType, CollectionDate> collectionDates = this.collectionSchedule.getCollectionDates();
81 this.updateStatus(ThingStatus.ONLINE);
82 return collectionDates;
83 } catch (final IOException e) {
84 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
85 return Collections.emptyMap();
90 public void handleCommand(final ChannelUID channelUID, final Command command) {
91 if (command instanceof RefreshType) {
92 this.scheduler.execute(this::updateCollectionDates);
94 this.logger.warn("The AHA Abfuhrkalender is a read-only binding and can not handle commands");
99 public void initialize() {
100 this.config = this.getConfigAs(AhaWasteCollectionConfiguration.class);
102 final String commune = this.config.commune;
103 final String street = this.config.street;
104 final String houseNumber = this.config.houseNumber;
105 final String houseNumberAddon = this.config.houseNumberAddon;
106 final String collectionPlace = this.config.collectionPlace;
108 if (commune.isBlank() || street.isBlank() || houseNumber.isBlank() || collectionPlace.isBlank()) {
109 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110 "Parameters are mandatory and must be configured");
114 this.collectionSchedule = this.scheduleFactory.create(commune, street, houseNumber, houseNumberAddon,
117 this.updateStatus(ThingStatus.UNKNOWN);
119 this.scheduler.execute(() -> {
120 final boolean online = this.updateCollectionDates();
128 * Schedules an job that updates the collection dates at midnight.
130 private void restartJob() {
131 this.logger.debug("Restarting jobs for thing {}", this.getThing().getUID());
135 if (this.getThing().getStatus() == ThingStatus.ONLINE) {
136 this.dailyJob = this.cronScheduler.schedule(this::updateCollectionDates, DAILY_MIDNIGHT);
137 this.logger.debug("Scheduled {} at midnight", this.dailyJob);
138 // Execute daily startup job immediately
139 this.updateCollectionDates();
142 this.monitor.unlock();
147 * Stops all jobs for this thing.
149 private void stopJob() {
152 final ScheduledCompletableFuture<?> job = this.dailyJob;
156 this.dailyJob = null;
158 this.monitor.unlock();
162 private boolean updateCollectionDates() {
163 final Map<WasteType, CollectionDate> collectionDates = this.cache.getValue();
164 if (collectionDates == null || collectionDates.isEmpty()) {
168 this.logger.debug("Retrieved {} collection entries.", collectionDates.size());
169 this.updateChannels(collectionDates);
174 * Refreshes the channel values with the given {@link CollectionDate}s.
176 private void updateChannels(final Map<WasteType, CollectionDate> collectionDates) {
177 for (final Channel channel : this.getThing().getChannels()) {
179 final WasteType wasteType = getWasteTypeByChannel(channel.getUID().getId());
181 final CollectionDate collectionDate = collectionDates.get(wasteType);
182 if (collectionDate == null) {
183 this.logger.debug("No collection dates found for waste type: {}", wasteType);
187 final Date nextCollectionDate = collectionDate.getDates().get(0);
189 final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(nextCollectionDate.toInstant(),
190 this.timeZoneProvider.getTimeZone());
191 this.updateState(channel.getUID(), new DateTimeType(zonedDateTime));
195 private static WasteType getWasteTypeByChannel(final String channelId) {
197 case AhaWasteCollectionBindingConstants.BIOWASTE:
198 return WasteType.BIO_WASTE;
199 case AhaWasteCollectionBindingConstants.LEIGHTWEIGHT_PACKAGING:
200 return WasteType.LIGHT_PACKAGES;
201 case AhaWasteCollectionBindingConstants.PAPER:
202 return WasteType.PAPER;
203 case AhaWasteCollectionBindingConstants.GENERAL_WASTE:
204 return WasteType.GENERAL_WASTE;
206 throw new IllegalArgumentException("Unknown channel type: " + channelId);