[ahawastecollection] Initial Contribution (#10387)
Signed-off-by: Sönke Küper <soenkekueper@gmx.de>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.ahawastecollection-${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-ahawastecollection" description="aha Waste Collection Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle dependency="true">mvn:org.jsoup/jsoup/1.8.3</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.ahawastecollection/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
|
||||
|
||||
/**
|
||||
* Schedule that returns the next collection dates from the aha website.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AhaCollectionSchedule {
|
||||
|
||||
/**
|
||||
* Returns the next collection dates per {@link WasteType}.
|
||||
*/
|
||||
public Map<WasteType, CollectionDate> getCollectionDates() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Factory for creating an {@link AhaCollectionSchedule}.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AhaCollectionScheduleFactory {
|
||||
|
||||
/**
|
||||
* Creates an new {@link AhaCollectionSchedule} for the given location.
|
||||
*/
|
||||
public AhaCollectionSchedule create(final String commune, final String street, final String houseNumber,
|
||||
final String houseNumberAddon, final String collectionPlace);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.jsoup.Connection.Method;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Schedule that returns the next collection dates from the aha website.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
final class AhaCollectionScheduleImpl implements AhaCollectionSchedule {
|
||||
|
||||
private static final Pattern TIME_PATTERN = Pattern.compile("\\S\\S,\\s(\\d\\d.\\d\\d.\\d\\d\\d\\d)");
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy");
|
||||
private static final String WEBSITE_URL = "https://www.aha-region.de/abholtermine/abfuhrkalender/";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AhaCollectionScheduleImpl.class);
|
||||
private final String commune;
|
||||
private final String street;
|
||||
private final String houseNumber;
|
||||
private final String houseNumberAddon;
|
||||
private final String collectionPlace;
|
||||
|
||||
/**
|
||||
* Creates an new {@link AhaCollectionScheduleImpl} for the given location.
|
||||
*/
|
||||
public AhaCollectionScheduleImpl(final String commune, final String street, final String houseNumber,
|
||||
final String houseNumberAddon, final String collectionPlace) {
|
||||
this.commune = commune;
|
||||
this.street = street;
|
||||
this.houseNumber = houseNumber;
|
||||
this.houseNumberAddon = houseNumberAddon;
|
||||
this.collectionPlace = collectionPlace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<WasteType, CollectionDate> getCollectionDates() throws IOException {
|
||||
final Document doc = Jsoup.connect(WEBSITE_URL) //
|
||||
.method(Method.POST) //
|
||||
.data("gemeinde", this.commune) //
|
||||
.data("strasse", this.street) //
|
||||
.data("hausnr", this.houseNumber) //
|
||||
.data("hausnraddon", this.houseNumberAddon) //
|
||||
.data("ladeort", this.collectionPlace) //
|
||||
.data("anzeigen", "Suchen") //
|
||||
.get();
|
||||
|
||||
final Elements table = doc.select("table");
|
||||
|
||||
if (table.size() == 0) {
|
||||
logger.warn("No result table found.");
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
final Iterator<Element> rowIt = table.get(0).getElementsByTag("tr").iterator();
|
||||
final Map<WasteType, CollectionDate> result = new HashMap<>();
|
||||
|
||||
while (rowIt.hasNext()) {
|
||||
final Element currentRow = rowIt.next();
|
||||
if (!currentRow.tagName().equals("tr")) {
|
||||
continue;
|
||||
}
|
||||
// Skip header, empty and download button rows.
|
||||
if (isHeader(currentRow) || isDelimiterOrDownloadRow(currentRow)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no following row is present, no collection dates can be parsed
|
||||
if (!rowIt.hasNext()) {
|
||||
logger.warn("No row with collection dates found.");
|
||||
break;
|
||||
}
|
||||
final Element collectionDatesRow = rowIt.next();
|
||||
|
||||
final CollectionDate date = this.parseRows(currentRow, collectionDatesRow);
|
||||
if (date != null) {
|
||||
result.put(date.getType(), date);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the row with the waste type and the following row with the collection dates.
|
||||
*
|
||||
* @param wasteTypeRow Row that contains the waste type information
|
||||
* @param collectionDatesRow Row that contains the collection date informations.
|
||||
* @return The parsed {@link CollectionDate} or <code>null</code> if information could not be parsed.
|
||||
*/
|
||||
@Nullable
|
||||
private CollectionDate parseRows(Element wasteTypeRow, Element collectionDatesRow) {
|
||||
// Try to extract the waste Type from the first row
|
||||
final Elements wasteTypeElement = wasteTypeRow.select("td").select("strong");
|
||||
if (wasteTypeElement.size() != 1) {
|
||||
this.logger.warn("Could not parse waste type row: {}", wasteTypeRow.toString());
|
||||
return null;
|
||||
}
|
||||
final WasteType wasteType = parseWasteType(wasteTypeElement.get(0));
|
||||
|
||||
// Try to extract the collection dates from the second row
|
||||
final Elements collectionDatesColumns = collectionDatesRow.select("td");
|
||||
if (collectionDatesColumns.size() != 3) {
|
||||
this.logger.warn("collection dates row could not be parsed.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Element collectionDatesColumn = collectionDatesColumns.get(1);
|
||||
final List<Date> collectionDates = parseTimes(collectionDatesColumn);
|
||||
|
||||
if (!collectionDates.isEmpty()) {
|
||||
return new CollectionDate(wasteType, collectionDates);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the row is an (empty) delimiter row or if its an row that contains the download
|
||||
* buttons for ical.
|
||||
*/
|
||||
private boolean isDelimiterOrDownloadRow(Element currentRow) {
|
||||
final Elements columns = currentRow.select("td");
|
||||
return columns.size() == 1 && columns.get(0).text().isBlank() || !columns.select("form").isEmpty();
|
||||
}
|
||||
|
||||
private boolean isHeader(Element currentRow) {
|
||||
return !currentRow.select("th").isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the waste types from the given {@link Element table cell}.
|
||||
*/
|
||||
private static WasteType parseWasteType(final Element element) {
|
||||
String value = element.text().trim();
|
||||
final int firstSpace = value.indexOf(" ");
|
||||
if (firstSpace > 0) {
|
||||
value = value.substring(0, firstSpace);
|
||||
}
|
||||
return WasteType.parseValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the {@link CollectionDate} from the given {@link Element table cell}.
|
||||
*/
|
||||
private List<Date> parseTimes(final Element element) {
|
||||
final List<Date> result = new ArrayList<>();
|
||||
final String value = element.text();
|
||||
final Matcher matcher = TIME_PATTERN.matcher(value);
|
||||
while (matcher.find()) {
|
||||
final String dateValue = matcher.group(1);
|
||||
try {
|
||||
synchronized (DATE_FORMAT) {
|
||||
result.add(DATE_FORMAT.parse(dateValue));
|
||||
}
|
||||
} catch (final ParseException e) {
|
||||
this.logger.warn("Could not parse date: {}", dateValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link AhaWasteCollectionBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AhaWasteCollectionBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "ahawastecollection";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SCHEDULE = new ThingTypeUID(BINDING_ID, "collectionSchedule");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String GENERAL_WASTE = "generalWaste";
|
||||
public static final String LEIGHTWEIGHT_PACKAGING = "leightweightPackaging";
|
||||
public static final String BIOWASTE = "bioWaste";
|
||||
public static final String PAPER = "paper";
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AhaWasteCollectionConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AhaWasteCollectionConfiguration {
|
||||
|
||||
/**
|
||||
* Commune.
|
||||
*/
|
||||
public String commune = "";
|
||||
|
||||
/**
|
||||
* Street.
|
||||
*/
|
||||
public String street = "";
|
||||
|
||||
/**
|
||||
* House number.
|
||||
*/
|
||||
public String houseNumber = "";
|
||||
|
||||
/**
|
||||
* House number addon.
|
||||
*/
|
||||
public String houseNumberAddon = "";
|
||||
|
||||
/**
|
||||
* Collection place.
|
||||
*/
|
||||
public String collectionPlace = "";
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
|
||||
import org.openhab.core.cache.ExpiringCache;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
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.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
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 AhaWasteCollectionHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AhaWasteCollectionHandler extends BaseThingHandler {
|
||||
|
||||
private static final String DAILY_MIDNIGHT = "30 0 0 * * ? *";
|
||||
|
||||
/** Scheduler to schedule jobs */
|
||||
private final CronScheduler cronScheduler;
|
||||
private final Lock monitor = new ReentrantLock();
|
||||
private final ExpiringCache<Map<WasteType, CollectionDate>> cache;
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final Logger logger = LoggerFactory.getLogger(AhaWasteCollectionHandler.class);
|
||||
|
||||
private @Nullable AhaWasteCollectionConfiguration config;
|
||||
private @Nullable AhaCollectionSchedule collectionSchedule;
|
||||
|
||||
private @Nullable ScheduledCompletableFuture<?> dailyJob;
|
||||
|
||||
private final AhaCollectionScheduleFactory scheduleFactory;
|
||||
|
||||
public AhaWasteCollectionHandler(final Thing thing, final CronScheduler scheduler,
|
||||
final TimeZoneProvider timeZoneProvider, final AhaCollectionScheduleFactory scheduleFactory) {
|
||||
super(thing);
|
||||
this.cronScheduler = scheduler;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.scheduleFactory = scheduleFactory;
|
||||
this.cache = new ExpiringCache<>(Duration.ofMinutes(5), this::loadCollectionDates);
|
||||
}
|
||||
|
||||
private Map<WasteType, CollectionDate> loadCollectionDates() {
|
||||
try {
|
||||
final Map<WasteType, CollectionDate> collectionDates = this.collectionSchedule.getCollectionDates();
|
||||
this.updateStatus(ThingStatus.ONLINE);
|
||||
return collectionDates;
|
||||
} catch (final IOException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
this.scheduler.execute(this::updateCollectionDates);
|
||||
} else {
|
||||
this.logger.warn("The AHA Abfuhrkalender is a read-only binding and can not handle commands");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.config = this.getConfigAs(AhaWasteCollectionConfiguration.class);
|
||||
|
||||
final String commune = this.config.commune;
|
||||
final String street = this.config.street;
|
||||
final String houseNumber = this.config.houseNumber;
|
||||
final String houseNumberAddon = this.config.houseNumberAddon;
|
||||
final String collectionPlace = this.config.collectionPlace;
|
||||
|
||||
if (commune.isBlank() || street.isBlank() || houseNumber.isBlank() || collectionPlace.isBlank()) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Parameters are mandatory and must be configured");
|
||||
return;
|
||||
}
|
||||
|
||||
this.collectionSchedule = this.scheduleFactory.create(commune, street, houseNumber, houseNumberAddon,
|
||||
collectionPlace);
|
||||
|
||||
this.updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
this.scheduler.execute(() -> {
|
||||
final boolean online = this.updateCollectionDates();
|
||||
if (online) {
|
||||
this.restartJob();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an job that updates the collection dates at midnight.
|
||||
*/
|
||||
private void restartJob() {
|
||||
this.logger.debug("Restarting jobs for thing {}", this.getThing().getUID());
|
||||
this.monitor.lock();
|
||||
try {
|
||||
this.stopJob();
|
||||
if (this.getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
this.dailyJob = this.cronScheduler.schedule(this::updateCollectionDates, DAILY_MIDNIGHT);
|
||||
this.logger.debug("Scheduled {} at midnight", this.dailyJob);
|
||||
// Execute daily startup job immediately
|
||||
this.updateCollectionDates();
|
||||
}
|
||||
} finally {
|
||||
this.monitor.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops all jobs for this thing.
|
||||
*/
|
||||
private void stopJob() {
|
||||
this.monitor.lock();
|
||||
try {
|
||||
final ScheduledCompletableFuture<?> job = this.dailyJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
this.dailyJob = null;
|
||||
} finally {
|
||||
this.monitor.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateCollectionDates() {
|
||||
final Map<WasteType, CollectionDate> collectionDates = this.cache.getValue();
|
||||
if (collectionDates == null || collectionDates.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.debug("Retrieved {} collection entries.", collectionDates.size());
|
||||
this.updateChannels(collectionDates);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the channel values with the given {@link CollectionDate}s.
|
||||
*/
|
||||
private void updateChannels(final Map<WasteType, CollectionDate> collectionDates) {
|
||||
for (final Channel channel : this.getThing().getChannels()) {
|
||||
final WasteType wasteType = getWasteTypeByChannel(channel.getUID().getId());
|
||||
|
||||
final CollectionDate collectionDate = collectionDates.get(wasteType);
|
||||
if (collectionDate == null) {
|
||||
this.logger.warn("No collection dates found for waste type: {}", wasteType);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Date nextCollectionDate = collectionDate.getDates().get(0);
|
||||
|
||||
final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(nextCollectionDate.toInstant(),
|
||||
this.timeZoneProvider.getTimeZone());
|
||||
this.updateState(channel.getUID(), new DateTimeType(zonedDateTime));
|
||||
}
|
||||
}
|
||||
|
||||
private static WasteType getWasteTypeByChannel(final String channelId) {
|
||||
switch (channelId) {
|
||||
case AhaWasteCollectionBindingConstants.BIOWASTE:
|
||||
return WasteType.BIO_WASTE;
|
||||
case AhaWasteCollectionBindingConstants.LEIGHTWEIGHT_PACKAGING:
|
||||
return WasteType.LIGHT_PACKAGES;
|
||||
case AhaWasteCollectionBindingConstants.PAPER:
|
||||
return WasteType.PAPER;
|
||||
case AhaWasteCollectionBindingConstants.GENERAL_WASTE:
|
||||
return WasteType.GENERAL_WASTE;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown channel type: " + channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import static org.openhab.binding.ahawastecollection.internal.AhaWasteCollectionBindingConstants.THING_TYPE_SCHEDULE;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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 AhaWasteCollectionHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.ahawastecollection", service = ThingHandlerFactory.class)
|
||||
public class AhaWasteCollectionHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SCHEDULE);
|
||||
private final CronScheduler scheduler;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Activate
|
||||
public AhaWasteCollectionHandlerFactory(final @Reference CronScheduler scheduler,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.scheduler = scheduler;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(final Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_SCHEDULE.equals(thingTypeUID)) {
|
||||
final AhaCollectionScheduleFactory factory = new AhaCollectionScheduleFactory() {
|
||||
|
||||
@Override
|
||||
public AhaCollectionScheduleImpl create(final String commune, final String street,
|
||||
final String houseNumber, final String houseNumberAddon, final String collectionPlace) {
|
||||
return new AhaCollectionScheduleImpl(commune, street, houseNumber, houseNumberAddon,
|
||||
collectionPlace);
|
||||
}
|
||||
};
|
||||
return new AhaWasteCollectionHandler(thing, this.scheduler, this.timeZoneProvider, factory);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains the next collection dates for an given waste type.
|
||||
*
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
final class CollectionDate {
|
||||
|
||||
/**
|
||||
* Type of waste that is collected.
|
||||
*/
|
||||
public enum WasteType {
|
||||
/**
|
||||
* General waste.
|
||||
*/
|
||||
GENERAL_WASTE,
|
||||
/**
|
||||
* Bio waste.
|
||||
*/
|
||||
BIO_WASTE,
|
||||
/**
|
||||
* Paper.
|
||||
*/
|
||||
PAPER,
|
||||
/**
|
||||
* Light packaging.
|
||||
*/
|
||||
LIGHT_PACKAGES;
|
||||
|
||||
/**
|
||||
* Parses the {@link WasteType} from the given Value from the Web-page.
|
||||
*/
|
||||
public static WasteType parseValue(String value) {
|
||||
switch (value) {
|
||||
case "Restabfall":
|
||||
return GENERAL_WASTE;
|
||||
case "Bioabfall":
|
||||
return BIO_WASTE;
|
||||
case "Papier":
|
||||
return PAPER;
|
||||
case "Leichtverpackungen":
|
||||
return LIGHT_PACKAGES;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown waste type: " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final WasteType type;
|
||||
private final List<Date> dates;
|
||||
|
||||
/**
|
||||
* Creates an new {@link CollectionDate}.
|
||||
*/
|
||||
public CollectionDate(final WasteType type, final List<Date> dates) {
|
||||
this.type = type;
|
||||
this.dates = dates;
|
||||
Collections.sort(this.dates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (non empty list) of next collection dates, for the given {@link WasteType}, ordered ascending.
|
||||
*/
|
||||
public List<Date> getDates() {
|
||||
return this.dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link WasteType} that is collected at the given times.
|
||||
*/
|
||||
public WasteType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("waste type: %s, collection dates: %s", this.type, this.dates);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="ahawastecollection" 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>aha WasteCollection Binding</name>
|
||||
<description>This binding provides information about the upcoming waste collection dates for places, that are served by
|
||||
aha,
|
||||
the waste collection company of the region Hannover. The values are retrieved from the online
|
||||
aha waste collection
|
||||
schedule available at: https://www.aha-region.de/abholtermine/abfuhrkalender.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,36 @@
|
||||
binding.ahawastecollection.name = aha Müllabführ
|
||||
binding.ahawastecollection.description = Stellt die Abfuhrtermine des aha Zweckverband Abfallwirtschaft Region Hannover zur Verfügung.
|
||||
|
||||
# thing types
|
||||
thing-type.ahawastecollection.collectionSchedule.label = aha Abfuhrkalender
|
||||
thing-type.ahawastecollection.collectionSchedule.description = aha Abfuhrkalender, der die nächsten Abfuhrtermine von https://www.aha-region.de/abholtermine/abfuhrkalender abfragt.
|
||||
|
||||
# thing type config description
|
||||
thing-type.config.ahawastecollection.collectionSchedule.commune.label = Gemeinde
|
||||
thing-type.config.ahawastecollection.collectionSchedule.commune.description = Gemeinde von der Formular-Eingabe
|
||||
|
||||
thing-type.config.ahawastecollection.collectionSchedule.street.label = Straße
|
||||
thing-type.config.ahawastecollection.collectionSchedule.street.description = Wert für die selektierte Straße: z.B. "02095@Oesterleystr.+/+Südstadt@Südstadt"
|
||||
|
||||
thing-type.config.ahawastecollection.collectionSchedule.houseNumber.label = Hausnummer
|
||||
thing-type.config.ahawastecollection.collectionSchedule.houseNumber.description = Hausnummer von der Formular-Eingabe
|
||||
|
||||
thing-type.config.ahawastecollection.collectionSchedule.houseNumberAddon.label = Adresszusatz
|
||||
thing-type.config.ahawastecollection.collectionSchedule.houseNumberAddon.description = Adresszusatzvon der Formular-Eingabe
|
||||
|
||||
thing-type.config.ahawastecollection.collectionSchedule.collectionPlace.label = Abholplatz
|
||||
thing-type.config.ahawastecollection.collectionSchedule.collectionPlace.description = Abholplatz von der Formular-Eingabe, z.B. "02095-0010+"
|
||||
|
||||
# channel types
|
||||
channel-type.ahawastecollection.collectionDateGeneralWaste.label = Abholtag Restabfall
|
||||
channel-type.ahawastecollection.collectionDateGeneralWaste.description = Nächster Abholtag für Restabfall
|
||||
|
||||
channel-type.ahawastecollection.collectionDateLeightweightPackaging.label = Abholtag Leichtverpackungen
|
||||
channel-type.ahawastecollection.collectionDateLeightweightPackaging.description = Nächster Abholtag für Leichtverpackungen
|
||||
|
||||
channel-type.ahawastecollection.collectionDateBioWaste.label = Abholtag Biomüll
|
||||
channel-type.ahawastecollection.collectionDateBioWaste.description = Nächster Abholtag für Biomüll
|
||||
|
||||
channel-type.ahawastecollection.collectionDatePaper.label = Abholtag Papier
|
||||
channel-type.ahawastecollection.collectionDatePaper.description = Nächster Abholtag für Papier
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="ahawastecollection"
|
||||
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="collectionSchedule">
|
||||
<label>aha Waste Collection Schedule</label>
|
||||
<description>aha Waste Collection Schedule from http://www.aha-region.de/abholtermine/abfuhrkalender</description>
|
||||
|
||||
<channels>
|
||||
<channel id="generalWaste" typeId="collectionDateGeneralWaste"/>
|
||||
<channel id="leightweightPackaging" typeId="collectionDateLeightweightPackaging"/>
|
||||
<channel id="bioWaste" typeId="collectionDateBioWaste"/>
|
||||
<channel id="paper" typeId="collectionDatePaper"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="commune" type="text" required="true">
|
||||
<label>Commune</label>
|
||||
<description>The selected commune, taken from the form field 'gemeinde'. </description>
|
||||
</parameter>
|
||||
<parameter name="street" type="text" required="true">
|
||||
<label>Street</label>
|
||||
<description>The selected street, taken from the form field 'strasse'. This value must look like
|
||||
67269@Rosmarinweg+/+Kirchhorst@Kirchhorst</description>
|
||||
</parameter>
|
||||
<parameter name="houseNumber" type="text" required="true">
|
||||
<label>House Number</label>
|
||||
<description>The selected house number, taken from the form field 'hausnr'.</description>
|
||||
</parameter>
|
||||
<parameter name="houseNumberAddon" type="text" required="false">
|
||||
<label>House Number Addon</label>
|
||||
<description>The selected house number addon, taken from the form field 'hausnraddon'.</description>
|
||||
</parameter>
|
||||
<parameter name="collectionPlace" type="text" required="true">
|
||||
<label>Collection Place</label>
|
||||
<description>Form value for the collection place, taken from the form field `ladeort`. This value must look like
|
||||
67269-0010+</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Channel type that represents the collection time of an waste type. -->
|
||||
<channel-type id="collectionDateGeneralWaste">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>General Waste</label>
|
||||
<description>Next collection day for general waste</description>
|
||||
<state readOnly="true" pattern="%1$tF"/>
|
||||
</channel-type>
|
||||
<channel-type id="collectionDateLeightweightPackaging">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Leightweight Packaging</label>
|
||||
<description>Next collection day for leightweight packaging</description>
|
||||
<state readOnly="true" pattern="%1$tF"/>
|
||||
</channel-type>
|
||||
<channel-type id="collectionDateBioWaste">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Bio Waste</label>
|
||||
<description>Next collection day for bio waste</description>
|
||||
<state readOnly="true" pattern="%1$tF"/>
|
||||
</channel-type>
|
||||
<channel-type id="collectionDatePaper">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Paper</label>
|
||||
<description>Next collection day for paper</description>
|
||||
<state readOnly="true" pattern="%1$tF"/>
|
||||
</channel-type>
|
||||
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
|
||||
|
||||
/**
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class AhaCollectionScheduleStub implements AhaCollectionSchedule {
|
||||
|
||||
public static final Date GENERAL_WASTE_DATE = new GregorianCalendar(2021, 2, 19).getTime();
|
||||
public static final Date LEIGHTWEIGHT_PACKAGING_DATE = new GregorianCalendar(2021, 2, 20).getTime();
|
||||
public static final Date BIO_WASTE_DATE = new GregorianCalendar(2021, 2, 21).getTime();
|
||||
public static final Date PAPER_DATE = new GregorianCalendar(2021, 2, 22).getTime();
|
||||
|
||||
@Override
|
||||
public Map<WasteType, CollectionDate> getCollectionDates() throws IOException {
|
||||
final Map<WasteType, CollectionDate> result = new LinkedHashMap<>(4);
|
||||
result.put(WasteType.GENERAL_WASTE,
|
||||
new CollectionDate(WasteType.GENERAL_WASTE, Arrays.asList(GENERAL_WASTE_DATE)));
|
||||
result.put(WasteType.LIGHT_PACKAGES,
|
||||
new CollectionDate(WasteType.GENERAL_WASTE, Arrays.asList(LEIGHTWEIGHT_PACKAGING_DATE)));
|
||||
result.put(WasteType.BIO_WASTE, new CollectionDate(WasteType.GENERAL_WASTE, Arrays.asList(BIO_WASTE_DATE)));
|
||||
result.put(WasteType.PAPER, new CollectionDate(WasteType.GENERAL_WASTE, Arrays.asList(PAPER_DATE)));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class AhaCollectionScheduleStubFactory implements AhaCollectionScheduleFactory {
|
||||
|
||||
@Override
|
||||
public AhaCollectionSchedule create(final String commune, final String street, final String houseNumber,
|
||||
final String houseNumberAddon, final String collectionPlace) {
|
||||
return new AhaCollectionScheduleStub();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.ahawastecollection.internal.CollectionDate.WasteType;
|
||||
|
||||
/**
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AhaCollectionScheduleTest {
|
||||
|
||||
@Test
|
||||
public void testGetValuesForHannoverCity() throws Exception {
|
||||
final AhaCollectionScheduleImpl schedule = new AhaCollectionScheduleImpl("Hannover",
|
||||
"02095@Oesterleystr.+/+Südstadt@Südstadt", "10", "", "02095-0010+");
|
||||
final Map<WasteType, CollectionDate> dates = schedule.getCollectionDates();
|
||||
// No bio waste is collected here
|
||||
assertEquals(3, dates.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetValuesForRegion() throws Exception {
|
||||
final AhaCollectionScheduleImpl schedule = new AhaCollectionScheduleImpl("Lehrte",
|
||||
"70185@Haselnussweg+/+Hämelerwald@Hämelerwald", "12", "", "70185-0012+");
|
||||
final Map<WasteType, CollectionDate> dates = schedule.getCollectionDates();
|
||||
// All four waste types are collected here.
|
||||
assertEquals(4, dates.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.ahawastecollection.internal;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.scheduler.CronJob;
|
||||
import org.openhab.core.scheduler.CronScheduler;
|
||||
import org.openhab.core.scheduler.ScheduledCompletableFuture;
|
||||
import org.openhab.core.scheduler.SchedulerRunnable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* @author Sönke Küper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AhaWasteCollectionHandlerTest {
|
||||
|
||||
private static final Configuration CONFIG = createConfig();
|
||||
|
||||
private static Configuration createConfig() {
|
||||
final Configuration config = new Configuration();
|
||||
config.put("commune", "Hannover");
|
||||
config.put("collectionPlace", "02095-0010+");
|
||||
config.put("houseNumber", "10");
|
||||
config.put("houseNumberAddon", "");
|
||||
config.put("street", "02095@Oesterleystr.+/+Südstadt@Südstadt");
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception indicating that the execution of an script within the stub-Scheduler failed.
|
||||
*/
|
||||
private static class SchedulerRuntimeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -1262671065082256315L;
|
||||
|
||||
public SchedulerRuntimeException(@Nullable final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link CronScheduler} that executes all commands synchronous.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static CronScheduler createStubScheduler() {
|
||||
return new CronScheduler() {
|
||||
|
||||
@Override
|
||||
public final ScheduledCompletableFuture<Void> schedule(final CronJob cronJob,
|
||||
final Map<String, Object> config, final String cronExpression) {
|
||||
try {
|
||||
cronJob.run(config);
|
||||
} catch (final Exception e) {
|
||||
throw new SchedulerRuntimeException(e);
|
||||
}
|
||||
return Mockito.mock(ScheduledCompletableFuture.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ScheduledCompletableFuture<Void> schedule(final SchedulerRunnable runnable,
|
||||
final String cronExpression) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (final Exception e) {
|
||||
throw new SchedulerRuntimeException(e);
|
||||
}
|
||||
return Mockito.mock(ScheduledCompletableFuture.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Thing mockThing(final Configuration config) {
|
||||
final Thing thing = mock(Thing.class);
|
||||
when(thing.getUID())
|
||||
.thenReturn(new ThingUID(AhaWasteCollectionBindingConstants.THING_TYPE_SCHEDULE, "collectionCalendar"));
|
||||
when(thing.getConfiguration()).thenReturn(config);
|
||||
|
||||
final Channel channelBioWaste = mockChannel(thing.getUID(), AhaWasteCollectionBindingConstants.BIOWASTE);
|
||||
final Channel channelGeneralWaste = mockChannel(thing.getUID(),
|
||||
AhaWasteCollectionBindingConstants.GENERAL_WASTE);
|
||||
final Channel channelPaper = mockChannel(thing.getUID(), AhaWasteCollectionBindingConstants.PAPER);
|
||||
final Channel channelLeightweightPackaging = mockChannel(thing.getUID(),
|
||||
AhaWasteCollectionBindingConstants.LEIGHTWEIGHT_PACKAGING);
|
||||
|
||||
when(thing.getChannels()).thenReturn(
|
||||
Arrays.asList(channelBioWaste, channelGeneralWaste, channelLeightweightPackaging, channelPaper));
|
||||
return thing;
|
||||
}
|
||||
|
||||
private static Channel mockChannel(final ThingUID thingId, final String channelId) {
|
||||
final Channel channel = Mockito.mock(Channel.class);
|
||||
when(channel.getUID()).thenReturn(new ChannelUID(thingId, channelId));
|
||||
return channel;
|
||||
}
|
||||
|
||||
private static AhaWasteCollectionHandler createAndInitHandler(final ThingHandlerCallback callback,
|
||||
final Thing thing) {
|
||||
final AhaWasteCollectionHandler handler = new AhaWasteCollectionHandler(thing, createStubScheduler(),
|
||||
ZoneId::systemDefault, new AhaCollectionScheduleStubFactory());
|
||||
handler.setCallback(callback);
|
||||
handler.initialize();
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static State getDateTime(final Date day) {
|
||||
final ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(day.toInstant(), ZoneId.systemDefault());
|
||||
return new DateTimeType(zonedDateTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateChannels() {
|
||||
final Thing thing = mockThing(CONFIG);
|
||||
final ThingHandlerCallback callback = mock(ThingHandlerCallback.class);
|
||||
final AhaWasteCollectionHandler handler = createAndInitHandler(callback, thing);
|
||||
|
||||
try {
|
||||
verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.UNKNOWN)));
|
||||
verify(callback, timeout(1000)).statusUpdated(eq(thing),
|
||||
argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
|
||||
verify(callback, timeout(1000)).stateUpdated(
|
||||
new ChannelUID(thing.getUID(), AhaWasteCollectionBindingConstants.BIOWASTE),
|
||||
getDateTime(AhaCollectionScheduleStub.BIO_WASTE_DATE));
|
||||
verify(callback, timeout(1000)).stateUpdated(
|
||||
new ChannelUID(thing.getUID(), AhaWasteCollectionBindingConstants.GENERAL_WASTE),
|
||||
getDateTime(AhaCollectionScheduleStub.GENERAL_WASTE_DATE));
|
||||
verify(callback, timeout(1000)).stateUpdated(
|
||||
new ChannelUID(thing.getUID(), AhaWasteCollectionBindingConstants.LEIGHTWEIGHT_PACKAGING),
|
||||
getDateTime(AhaCollectionScheduleStub.LEIGHTWEIGHT_PACKAGING_DATE));
|
||||
verify(callback, timeout(1000)).stateUpdated(
|
||||
new ChannelUID(thing.getUID(), AhaWasteCollectionBindingConstants.PAPER),
|
||||
getDateTime(AhaCollectionScheduleStub.PAPER_DATE));
|
||||
} finally {
|
||||
handler.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user