mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[ahawastecollection] Initial Contribution (#10387)
Signed-off-by: Sönke Küper <soenkekueper@gmx.de>
This commit is contained in:
parent
7fb7c65306
commit
710aa760d0
@ -9,6 +9,7 @@
|
||||
/bundles/org.openhab.automation.jythonscripting/ @openhab/add-ons-maintainers
|
||||
/bundles/org.openhab.automation.pidcontroller/ @fwolter
|
||||
/bundles/org.openhab.binding.adorne/ @theiding
|
||||
/bundles/org.openhab.binding.ahawastecollection/ @soenkekueper
|
||||
/bundles/org.openhab.binding.airq/ @aurelio1
|
||||
/bundles/org.openhab.binding.airquality/ @kubawolanin
|
||||
/bundles/org.openhab.binding.airvisualnode/ @3cky
|
||||
|
@ -36,6 +36,11 @@
|
||||
<artifactId>org.openhab.binding.adorne</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.ahawastecollection</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.airq</artifactId>
|
||||
|
21
bundles/org.openhab.binding.ahawastecollection/NOTICE
Normal file
21
bundles/org.openhab.binding.ahawastecollection/NOTICE
Normal file
@ -0,0 +1,21 @@
|
||||
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
|
||||
|
||||
|
||||
== Third-party Content
|
||||
|
||||
jsoup
|
||||
* License: MIT License
|
||||
* Project: https://jsoup.org/
|
||||
* Source: https://github.com/jhy/jsoup
|
139
bundles/org.openhab.binding.ahawastecollection/README.md
Normal file
139
bundles/org.openhab.binding.ahawastecollection/README.md
Normal file
@ -0,0 +1,139 @@
|
||||
# aha Waste Collection Binding
|
||||
|
||||
This binding provides information about the upcoming waste collection dates for places, that are served by aha, the waste collection company for the region of Hannover. The values are retrieved from the online aha waste collection schedule available at: [aha Abfuhrkalender](https://www.aha-region.de/abholtermine/abfuhrkalender).
|
||||
|
||||
## Supported Things
|
||||
|
||||
- **collectionSchedule:** Represents the connection to the **aha Waste Collection Schedule** with four channels for the different waste types.
|
||||
|
||||
## Discovery
|
||||
|
||||
Discovery is not possible, due some form input values from the website above are required.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
For configuration of the **collectionSchedule** thing, you need the form inputs from the aha collections schedule web page. Follow the steps below to get the required configuration parameters from the form input values.
|
||||
|
||||
|
||||
1. Open [aha Abfuhrkalender](https://www.aha-region.de/abholtermine/abfuhrkalender) in your favorite brower with developer-console.
|
||||
2. Open the developer console and switch to network tab (for example press F12 in chrome / edge / firefox).
|
||||
3. Fill in the form: Select your commune, Street and house number and hit "Suchen".
|
||||
4. Select the first request to https://www.aha-region.de/abholtermine/abfuhrkalender (see first screenshot below)
|
||||
5. Check the form data at the end of the request for the form values (see second screenshot below)
|
||||
5. Fill in the values from the form input in thing configuration (see examples below)
|
||||
|
||||
![Chrome Developer Console Top](doc/images/ChromeDevconsoleTop.png "Chrome Developer Console showing request URL")
|
||||
|
||||
*Check if you've selected the correct request, that contains the form data*
|
||||
|
||||
![Chrome Developer Console Bottom](doc/images/ChromeDevconsoleBottom.png "Chrome Developer Console showing form inputs")
|
||||
|
||||
*Grab the values for the configuration parameters from the form data section at the end of the request*
|
||||
|
||||
**collectionSchedule** parameters:
|
||||
|
||||
| Property | Default | Required | Description |
|
||||
|-|-|-|-|
|
||||
| `commune` | | Yes | The selected commune, taken from the form field `gemeinde`. |
|
||||
| `street` | | Yes | The selected street, taken from the form field `strasse`. This value must look like 67269@Rosmarinweg+/+Kirchhorst@Kirchhorst |
|
||||
| `houseNumber` | | Yes | The selected house number, taken from the form field `hausnr`. |
|
||||
| `houseNumberAddon` | | No | The selected house number addon, taken from the form field `hausnraddon`, may be empty. |
|
||||
| `collectionPlace` | | Yes | Form value for the collection place, taken from the form field `ladeort`. This value must look like 67269-0010+ |
|
||||
|
||||
## Channels
|
||||
|
||||
The thing **aha Waste Collection Schedule** provides four channels for the upcoming day of waste collection for the different waste types.
|
||||
|
||||
|
||||
| channel | type | description |
|
||||
|----------|--------|------------------------------|
|
||||
| generalWaste | DateTime | Next collection day for general waste |
|
||||
| leightweightPackaging | DateTime | Next collection day for leightweight packaging |
|
||||
| bioWaste | DateTime | Next collection day for bio waste |
|
||||
| paper | DateTime | Next collection day for paper |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
wasteCollection.things
|
||||
|
||||
```
|
||||
Thing ahawastecollection:collectionSchedule:wasteCollectionSchedule "aha Abfuhrkalender" [ commune="Isernhagen", street="67269@Rosmarinweg+/+Kirchhorst@Kirchhorst", houseNumber="10", houseNumberAddon="", collectionPlace="67269-0010+" ]
|
||||
```
|
||||
|
||||
wasteCollection.items
|
||||
|
||||
```
|
||||
DateTime collectionDay_generalWaste "Next general waste collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:generalWaste"}
|
||||
DateTime collectionDay_leightweightPackaging "Next lightweight packaging collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:leightweightPackaging"}
|
||||
DateTime collectionDay_bioWaste "Next bio waste collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:bioWaste"}
|
||||
DateTime collectionDay_paper "Next paper collection" {channel="ahawastecollection:collectionSchedule:wasteCollectionSchedule:paper"}
|
||||
```
|
||||
|
||||
|
||||
Example for rule that sends an notification with collected waste types on day before collection
|
||||
|
||||
```
|
||||
triggers:
|
||||
- id: "1"
|
||||
configuration:
|
||||
time: 18:00
|
||||
type: timer.TimeOfDayTrigger
|
||||
conditions: []
|
||||
actions:
|
||||
- inputs: {}
|
||||
id: "2"
|
||||
configuration:
|
||||
type: application/javascript
|
||||
script: >-
|
||||
// Determine next day with time 00:00:00
|
||||
var today = items['LokaleZeit_DatumundZeit'];
|
||||
|
||||
var tomorrow = today
|
||||
.getZonedDateTime()
|
||||
.plusDays(1)
|
||||
.withHour(0)
|
||||
.withMinute(0)
|
||||
.withSecond(0)
|
||||
.withNano(0);
|
||||
|
||||
// Get next collection dates from items
|
||||
var biomuellDate = items['collectionDay_bioWaste'].getZonedDateTime();
|
||||
var leichtverpackungDate = items['collectionDay_leightweightPackaging'].getZonedDateTime();
|
||||
var papierDate = items['collectionDay_paper'].getZonedDateTime();
|
||||
var restmuellDate = items['collectionDay_generalWaste'].getZonedDateTime();
|
||||
|
||||
|
||||
// Check which waste types are collected on the next day
|
||||
var biomuellCollection = biomuellDate.equals(tomorrow);
|
||||
var leichtverpackungCollection = leichtverpackungDate.equals(tomorrow);
|
||||
var papierCollection = papierDate.equals(tomorrow);
|
||||
var restmuellCollection = restmuellDate.equals(tomorrow);
|
||||
|
||||
// Transfer booleans to waste type names
|
||||
var toBeCollected = [];
|
||||
|
||||
if (biomuellCollection) {
|
||||
toBeCollected.push('bio waste');
|
||||
}
|
||||
|
||||
if (leichtverpackungCollection) {
|
||||
toBeCollected.push('leihtweight packaging');
|
||||
}
|
||||
|
||||
if (papierCollection) {
|
||||
toBeCollected.push('paper');
|
||||
}
|
||||
|
||||
if (restmuellCollection) {
|
||||
toBeCollected.push('general waste');
|
||||
}
|
||||
|
||||
|
||||
// Send message (or something else) if at least one waste type is collected
|
||||
if (toBeCollected.length > 0) {
|
||||
var message = "Tomorrow the following waste will be collected:\n" + toBeCollected.join(', ');
|
||||
events.sendCommand('SignalSmartHome_Eingabewert', message);
|
||||
}
|
||||
type: script.ScriptAction
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 178 KiB |
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
26
bundles/org.openhab.binding.ahawastecollection/pom.xml
Normal file
26
bundles/org.openhab.binding.ahawastecollection/pom.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.ahawastecollection</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: aha Waste Collection Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.8.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@
|
||||
<module>org.openhab.transform.xslt</module>
|
||||
<!-- bindings -->
|
||||
<module>org.openhab.binding.adorne</module>
|
||||
<module>org.openhab.binding.ahawastecollection</module>
|
||||
<module>org.openhab.binding.airq</module>
|
||||
<module>org.openhab.binding.airquality</module>
|
||||
<module>org.openhab.binding.airvisualnode</module>
|
||||
|
Loading…
Reference in New Issue
Block a user