From 69572b1623d004267356c520669182fe81784fd9 Mon Sep 17 00:00:00 2001 From: Stewart Cossey Date: Tue, 8 Sep 2020 06:13:41 +1200 Subject: [PATCH] [nzwateralerts] New Zealand Water Alerts Binding initial contribution (#7899) Signed-off-by: Stewart Cossey --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../.classpath | 49 +++++++ .../.project | 23 ++++ .../org.openhab.binding.nzwateralerts/NOTICE | 13 ++ .../README.md | 73 ++++++++++ .../org.openhab.binding.nzwateralerts/pom.xml | 17 +++ .../src/main/feature/feature.xml | 9 ++ .../NZWaterAlertsBindingConstants.java | 45 ++++++ .../internal/NZWaterAlertsConfiguration.java | 27 ++++ .../internal/api/BeWaterWise.java | 77 +++++++++++ .../internal/api/NapierCityCouncil.java | 84 +++++++++++ .../internal/api/SmartWater.java | 78 +++++++++++ .../internal/api/TaupoDistrictCouncil.java | 91 ++++++++++++ .../internal/api/WaterAlertWebClient.java | 90 ++++++++++++ .../internal/api/WaterWebService.java | 30 ++++ .../internal/binder/NZWaterAlertsBinder.java | 130 ++++++++++++++++++ .../binder/NZWaterAlertsBinderListener.java | 31 +++++ .../handler/NZWaterAlertsHandler.java | 111 +++++++++++++++ .../handler/NZWaterAlertsHandlerFactory.java | 66 +++++++++ .../resources/ESH-INF/binding/binding.xml | 10 ++ .../resources/ESH-INF/thing/thing-types.xml | 63 +++++++++ bundles/pom.xml | 1 + 23 files changed, 1124 insertions(+) create mode 100644 bundles/org.openhab.binding.nzwateralerts/.classpath create mode 100644 bundles/org.openhab.binding.nzwateralerts/.project create mode 100644 bundles/org.openhab.binding.nzwateralerts/NOTICE create mode 100644 bundles/org.openhab.binding.nzwateralerts/README.md create mode 100644 bundles/org.openhab.binding.nzwateralerts/pom.xml create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsBindingConstants.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsConfiguration.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/BeWaterWise.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/NapierCityCouncil.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/SmartWater.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/TaupoDistrictCouncil.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterAlertWebClient.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterWebService.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinder.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinderListener.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandler.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandlerFactory.java create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 0b7d3a1968a3a..f6a3d79739bd6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -154,6 +154,7 @@ /bundles/org.openhab.binding.ntp/ @marcelrv /bundles/org.openhab.binding.nuki/ @mkatter /bundles/org.openhab.binding.nuvo/ @mlobstein +/bundles/org.openhab.binding.nzwateralerts/ @cossey /bundles/org.openhab.binding.oceanic/ @kgoderis /bundles/org.openhab.binding.ojelectronics/ @EvilPingu /bundles/org.openhab.binding.omnikinverter/ @hansbogert diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 09bd596c41ee5..d3786db8ddfd4 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -756,6 +756,11 @@ org.openhab.binding.nuvo ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.nzwateralerts + ${project.version} + org.openhab.addons.bundles org.openhab.binding.oceanic diff --git a/bundles/org.openhab.binding.nzwateralerts/.classpath b/bundles/org.openhab.binding.nzwateralerts/.classpath new file mode 100644 index 0000000000000..39abf1c5e9102 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/.classpath @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.nzwateralerts/.project b/bundles/org.openhab.binding.nzwateralerts/.project new file mode 100644 index 0000000000000..46031aa79d5a7 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.nzwateralerts + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.nzwateralerts/NOTICE b/bundles/org.openhab.binding.nzwateralerts/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.nzwateralerts/README.md b/bundles/org.openhab.binding.nzwateralerts/README.md new file mode 100644 index 0000000000000..02b1bbdbf56a2 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/README.md @@ -0,0 +1,73 @@ +# NZ Water Alerts Binding + +Get Water Alert Levels for cities in New Zealand. +Getting this alert level can help you script and automate smarter tasks for water and avoid getting penalized from your distract or local council. + +> Example: Disable automated spinklers based on a level 3 or 4 water alert level + +This Binding scrapes multiple websites for Water Levels: + +* Northland's [BeWaterWise Website](https://bewaterwise.org.nz/) +* Waikato's [Smart Water Website](https://www.smartwater.org.nz/) +* Napier's [Council Website](https://www.napier.govt.nz) + +## Thing Configuration + +The binding and thing ID is `nzwateralerts:wateralert`. + +### Configuration Values + +| Value | Type | Description | +| --------------- | ------------ | -------------------------------------- | +| location | string | The location to get water data from. Refer to the list below for values. | +| refreshInterval | number | The time interval (in hours) to refresh the data. + +### Supported city/area list + +| City | Config Value | +| ------------------------ | ------------------------------------------ | +| Bream Bay | bewaterwise:whangarei:breambay | +| Dargaville & Baylys | bewaterwise:kaipara:dargavilleampbaylys | +| Glinks Gully | bewaterwise:kaipara:glinksgully | +| Hamilton City | smartwater:hamilton:hamilton | +| Kaikohe / Ngawha | bewaterwise:farnorth:kaikohengawha | +| Kaitaia | bewaterwise:farnorth:kaitaia | +| Kerikeri / Waipapa | bewaterwise:farnorth:kerikeriwaipapa | +| Mangapai | bewaterwise:whangarei:mangapai | +| Mangawhai | bewaterwise:kaipara:mangawhai | +| Maungakaramea | bewaterwise:whangarei:maungakaramea | +| Maungaturoto | bewaterwise:kaipara:maungaturoto | +| Moerewa / Kawakawa | bewaterwise:farnorth:moerewakawakawa | +| Napier | napiercitycouncil:napier:napier | +| Okaihau | bewaterwise:farnorth:okaihau | +| Opononi / Omapere | bewaterwise:farnorth:opononiomapere | +| Rawene | bewaterwise:farnorth:rawene | +| Ruawai | bewaterwise:kaipara:ruawai | +| Russell | bewaterwise:farnorth:russell | +| Waipa District | smartwater:waipa:waipa | +| Waikato District | smartwater:waikato:waikato | +| Waitangi / Paihia / Opua | bewaterwise:farnorth:waitangipaihiaopua | +| Whangarei | bewaterwise:whangarei:whangarei | + +### Example + +``` +Thing nzwateralerts:wateralert "HCC" [ location="smartwater:hamilton:hamilton", refreshInterval="4" ] +``` + +The above gets the Water Alert level for Hamilton and refreshes this data every 4 hours. + +## Channels + +There is only one channel with this binding labelled `alertlevel` which contains a Number 0-4 to represent the alert level. + +Depending on your region, either Alert Level 0 or 1 can represent _No Water Restrictions_. +Check with your regional council for further details. + +## Other Cities + +At present the supported cities were implemented by scraping the web page on the respective website which contains the restriction information. + +**No councils have this data in a programmatic format easily accessible to software.** +Most won't have pages which contain the current alert level and only offer alerts via twitter or text. +If you can convince your council to always have a page displaying the current alert level (even if none is in effect) then I can attempt to parse the page for inclusion in this Binding. diff --git a/bundles/org.openhab.binding.nzwateralerts/pom.xml b/bundles/org.openhab.binding.nzwateralerts/pom.xml new file mode 100644 index 0000000000000..d8dd1869e3eeb --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.9-SNAPSHOT + + + org.openhab.binding.nzwateralerts + + openHAB Add-ons :: Bundles :: NZ Water Alerts Binding + + diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/feature/feature.xml b/bundles/org.openhab.binding.nzwateralerts/src/main/feature/feature.xml new file mode 100644 index 0000000000000..1c2807ad22bdf --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.nzwateralerts/${project.version} + + diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsBindingConstants.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsBindingConstants.java new file mode 100644 index 0000000000000..b715fd639ccff --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsBindingConstants.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.openhab.binding.nzwateralerts.internal.api.BeWaterWise; +import org.openhab.binding.nzwateralerts.internal.api.NapierCityCouncil; +import org.openhab.binding.nzwateralerts.internal.api.SmartWater; +import org.openhab.binding.nzwateralerts.internal.api.WaterWebService; + +/** + * The {@link NZWaterAlertsBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class NZWaterAlertsBindingConstants { + + private static final String BINDING_ID = "nzwateralerts"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_WATERALERT = new ThingTypeUID(BINDING_ID, "wateralert"); + + // List of all Channel ids + public static final String CHANNEL_ALERTLEVEL = "alertlevel"; + + // List of all supported services + public static final List WATER_WEB_SERVICES = Arrays + .asList(new WaterWebService[] { new SmartWater(), new BeWaterWise(), new NapierCityCouncil() }); +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsConfiguration.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsConfiguration.java new file mode 100644 index 0000000000000..fb299ad2cbaed --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/NZWaterAlertsConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link NZWaterAlertsConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class NZWaterAlertsConfiguration { + public @Nullable String location; + public int refreshInterval; +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/BeWaterWise.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/BeWaterWise.java new file mode 100644 index 0000000000000..867c1f8571843 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/BeWaterWise.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link BeWaterWise} class contains the logic to get data the + * bewaterwise.org.nz website. + * + * Northland Regional Council + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class BeWaterWise implements WaterWebService { + private final Logger logger = LoggerFactory.getLogger(BeWaterWise.class); + + private static final String HOSTNAME = "https://bewaterwise.org.nz"; + private static final String REGION_FARNORTH = "/current-water-levels_far-north/"; + private static final String REGION_WHANGAREI = "/current-water-levels_whangarei/"; + private static final String REGION_KAIPARA = "/current-water-levels_kaipara/"; + + private static final String PATTERN = "vc_text_separator.*?(.*?)<\\/span>.*?water-level-([0-4]).*?"; + private static final Pattern REGEX = Pattern.compile(PATTERN, + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + @Override + public String service() { + return "bewaterwise"; + } + + @Override + public String endpoint(final String region) { + switch (region.toLowerCase()) { + case "farnorth": + return HOSTNAME + REGION_FARNORTH; + + case "whangarei": + return HOSTNAME + REGION_WHANGAREI; + + case "kaipara": + return HOSTNAME + REGION_KAIPARA; + } + return ""; + } + + @Override + public int findWaterLevel(final String data, final String area) { + final Matcher matches = REGEX.matcher(data); + + while (matches.find()) { + final String dataArea = matches.group(1).replaceAll("\\W", ""); + final String level = matches.group(2); + logger.debug("Data Area {} Level {}", dataArea, level); + if (dataArea.equalsIgnoreCase(area)) { + return Integer.valueOf(level); + } + } + return -1; + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/NapierCityCouncil.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/NapierCityCouncil.java new file mode 100644 index 0000000000000..00d77bb7d3fb7 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/NapierCityCouncil.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link NapierCityCouncil} class contains the logic to get data the + * www.napier.govt.nz website. + * + * Napier City Council + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class NapierCityCouncil implements WaterWebService { + private final Logger logger = LoggerFactory.getLogger(NapierCityCouncil.class); + + private static final String HOSTNAME = "https://www.napier.govt.nz"; + private static final String REGION_NAPIER = "/services/water/water-restrictions/"; + + private static final String PATTERN = "\"waterstat\".*?

.*?at (.*?) Restrictions.*?"; + private static final Pattern REGEX = Pattern.compile(PATTERN, + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + @Override + public String service() { + return "napiercitycouncil"; + } + + @Override + public String endpoint(final String region) { + switch (region.toLowerCase()) { + case "napier": + return HOSTNAME + REGION_NAPIER; + + } + return ""; + } + + @Override + public int findWaterLevel(final String data, final String area) { + final Matcher matches = REGEX.matcher(data); + + while (matches.find()) { + final String level = matches.group(1); + logger.debug("Data Level {}", level); + + switch (level.toLowerCase()) { + case "no": + return 0; + + case "level one": + return 1; + + case "level two": + return 2; + + case "level three": + return 3; + + case "level four": + return 4; + } + + } + return -1; + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/SmartWater.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/SmartWater.java new file mode 100644 index 0000000000000..0dd489810ff64 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/SmartWater.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SmartWater} class contains the logic to get data the + * SmartWater.org.nz website. + * + * Waikato Regional Council + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class SmartWater implements WaterWebService { + private final Logger logger = LoggerFactory.getLogger(SmartWater.class); + + private static final String HOSTNAME = "http://www.smartwater.org.nz"; + private static final String REGION_HAMILTON = "/alert-levels/hamilton-city"; + private static final String REGION_WAIKATO = "/alert-levels/waikato-district-council"; + private static final String REGION_WAIPA = "/alert-levels/waipa-district-council"; + + private static final String PATTERN = "/assets/Alert-Level-Images/water-alert-([1-4]|no)-large.svg.*?"; + private static final Pattern REGEX = Pattern.compile(PATTERN, + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + @Override + public String service() { + return "smartwater"; + } + + @Override + public String endpoint(final String region) { + switch (region.toLowerCase()) { + case "hamilton": + return HOSTNAME + REGION_HAMILTON; + + case "waikato": + return HOSTNAME + REGION_WAIKATO; + + case "waipa": + return HOSTNAME + REGION_WAIPA; + } + return ""; + } + + @Override + public int findWaterLevel(final String data, final String area) { + final Matcher matches = REGEX.matcher(data); + + while (matches.find()) { + String level = matches.group(1); + logger.debug("Data Level {}", level); + if (level.equalsIgnoreCase("no")) { + logger.debug("Convert Data Level to 0"); + level = "0"; + } + return Integer.valueOf(level); + } + return -1; + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/TaupoDistrictCouncil.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/TaupoDistrictCouncil.java new file mode 100644 index 0000000000000..64a043345a769 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/TaupoDistrictCouncil.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TaupoDistrictCouncil} class contains the logic to get data the + * www.taupodc.govt.nz website. + * + * Taupo District Council + * + * NOTE: This is currently a placeholder. This region needs to go into restrictions + * first so I can parse the correct output. -Stewart Cossey + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class TaupoDistrictCouncil implements WaterWebService { + private final Logger logger = LoggerFactory.getLogger(TaupoDistrictCouncil.class); + + private static final String HOSTNAME = "https://www.taupodc.govt.nz"; + private static final String REGION_TAUPO = "/transport-and-water/water-conservation"; + + private static final String PATTERN = "div class=\"rc\".*?.*?.*?(.*?) restrictions"; + private static final Pattern REGEX = Pattern.compile(PATTERN, + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + + @Override + public String service() { + return "taupodistrictcouncil"; + } + + @Override + public String endpoint(final String region) { + switch (region.toLowerCase()) { + case "taupo": + return HOSTNAME + REGION_TAUPO; + + } + return ""; + } + + @Override + public int findWaterLevel(final String data, final String area) { + final Matcher matches = REGEX.matcher(data); + + while (matches.find()) { + final String level = matches.group(1); + logger.debug("Data Level {}", level); + + switch (level.toLowerCase()) { + case "no": + return 0; + + case "level one": + case "level 1": + return 1; + + case "level two": + case "level 2": + return 2; + + case "level three": + case "level 3": + return 3; + + case "level four": + case "level 4": + return 4; + } + + } + return -1; + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterAlertWebClient.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterAlertWebClient.java new file mode 100644 index 0000000000000..1c1fe71c42d1d --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterAlertWebClient.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import static org.openhab.binding.nzwateralerts.internal.NZWaterAlertsBindingConstants.*; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WebClient} class contains the logic to get data from a URL. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class WaterAlertWebClient { + private final Logger logger = LoggerFactory.getLogger(WaterAlertWebClient.class); + + private static final int REQUEST_TIMEOUT = 10; + + private final HttpClient httpClient; + private final String webService; + private final String region; + private final String area; + + private @Nullable WaterWebService service = null; + + public WaterAlertWebClient(final HttpClient httpClient, final String location) { + this.httpClient = httpClient; + + final String[] locationSegmented = location.split(":", 3); + webService = locationSegmented[0]; + region = locationSegmented[1]; + area = locationSegmented[2]; + + for (final WaterWebService srv : WATER_WEB_SERVICES) { + logger.trace("Checking service {}", srv.service()); + if (locationSegmented[0].equalsIgnoreCase(srv.service())) { + logger.trace("Found service {}", srv.service()); + service = srv; + } + } + + if (service == null) { + logger.debug("Service could not be found for {}", locationSegmented[0]); + } + } + + public @Nullable Integer getLevel() { + ContentResponse response; + final WaterWebService localService = service; + try { + if (localService != null) { + logger.debug("Getting Water Level from service {} region {} area {}", webService, region, area); + + final String endpoint = localService.endpoint(region); + logger.trace("Getting data from endpoint {} with timeout {}", endpoint, REQUEST_TIMEOUT); + response = httpClient.newRequest(endpoint).timeout(REQUEST_TIMEOUT, TimeUnit.SECONDS).send(); + + final int waterLevel = localService.findWaterLevel(response.getContentAsString(), area); + logger.debug("Got water level {}", waterLevel); + return waterLevel; + } else { + logger.debug("Service, region is null"); + return null; + } + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.debug("Error when attempting to get Water Level {}", e.getMessage()); + return null; + } + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterWebService.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterWebService.java new file mode 100644 index 0000000000000..c726b34a52f80 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/api/WaterWebService.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WebService} class contains the common interfaces for the different + * services. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public interface WaterWebService { + String service(); + + String endpoint(String region); + + int findWaterLevel(String data, String area); +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinder.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinder.java new file mode 100644 index 0000000000000..9ca10b8aa9c6c --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinder.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.binder; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.openhab.binding.nzwateralerts.internal.NZWaterAlertsConfiguration; +import org.openhab.binding.nzwateralerts.internal.api.WaterAlertWebClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link NZWaterAlertsController} is responsible for handling the connection + * between the handler and API. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class NZWaterAlertsBinder { + private @Nullable WaterAlertWebClient webClient; + + private final Logger logger = LoggerFactory.getLogger(NZWaterAlertsBinder.class); + + private final Set listeners = new CopyOnWriteArraySet<>(); + private @Nullable ScheduledFuture future; + private final ScheduledExecutorService scheduler; + + private int refreshInterval = 5; + + public NZWaterAlertsBinder(final HttpClient httpClient, @Nullable final NZWaterAlertsConfiguration config, + final ScheduledExecutorService scheduler) { + this.scheduler = scheduler; + + if (config != null) { + final String localLocation = config.location; + if (localLocation == null) { + for (final NZWaterAlertsBinderListener listener : listeners) { + listener.updateBindingStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Location is not set."); + } + } else { + this.webClient = new WaterAlertWebClient(httpClient, localLocation); + refreshInterval = config.refreshInterval; + } + } else { + for (final NZWaterAlertsBinderListener listener : listeners) { + listener.updateBindingStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Could not create webClient, a parameter is null"); + } + logger.debug("Create Binder failed due to null config item"); + } + } + + public void update() { + final WaterAlertWebClient localWebClient = webClient; + if (localWebClient != null) { + final Integer waterLevel = localWebClient.getLevel(); + + for (final NZWaterAlertsBinderListener listener : listeners) { + if (waterLevel == null) { + listener.updateBindingStatus(ThingStatus.OFFLINE); + } else { + listener.updateBindingStatus(ThingStatus.ONLINE); + listener.updateWaterLevel(waterLevel); + } + } + } + } + + /** + * Registers the given {@link NZWaterAlertsBinderListener}. If it is already + * registered, this method returns immediately. + * + * @param alertsBinderInterface The {@link NZWaterAlertsBinderListener} to be + * registered. + */ + public void registerListener(final NZWaterAlertsBinderListener alertsBinderInterface) { + final boolean isAdded = listeners.add(alertsBinderInterface); + if (isAdded) { + updatePollingState(); + } + } + + /** + * Unregisters the given {@link NZWaterAlertsBinderListener}. If it is already + * unregistered, this method returns immediately. + * + * @param alertsBinderInterface The {@link NZWaterAlertsBinderListener} to be + * unregistered. + */ + public void unregisterListener(final NZWaterAlertsBinderListener alertsBinderInterface) { + final boolean isRemoved = listeners.remove(alertsBinderInterface); + if (isRemoved) { + updatePollingState(); + } + } + + private void updatePollingState() { + final ScheduledFuture localFuture = future; + + if (localFuture != null && listeners.isEmpty()) { + localFuture.cancel(true); + future = null; + return; + } + + if (localFuture == null && !listeners.isEmpty()) { + future = scheduler.scheduleWithFixedDelay(this::update, 0, refreshInterval, TimeUnit.HOURS); + } + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinderListener.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinderListener.java new file mode 100644 index 0000000000000..c686660f688f3 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/binder/NZWaterAlertsBinderListener.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.binder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; + +/** + * The {@link NZWaterAlertsControllerListener} is responsible for handling the events from the WebClient and Handler. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public interface NZWaterAlertsBinderListener { + void updateWaterLevel(int level); + + void updateBindingStatus(ThingStatus thingStatus); + + void updateBindingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String description); +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandler.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandler.java new file mode 100644 index 0000000000000..9052d30d2c22c --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandler.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.handler; + +import static org.openhab.binding.nzwateralerts.internal.NZWaterAlertsBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.nzwateralerts.internal.NZWaterAlertsConfiguration; +import org.openhab.binding.nzwateralerts.internal.binder.NZWaterAlertsBinder; +import org.openhab.binding.nzwateralerts.internal.binder.NZWaterAlertsBinderListener; + +/** + * The {@link NZWaterAlertsHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +public class NZWaterAlertsHandler extends BaseThingHandler implements NZWaterAlertsBinderListener { + + private @Nullable NZWaterAlertsConfiguration config = null; + private HttpClient httpClient; + private @Nullable NZWaterAlertsBinder binder = null; + + public NZWaterAlertsHandler(Thing thing, HttpClient httpClient) { + super(thing); + + this.httpClient = httpClient; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + final NZWaterAlertsBinder localBinder = binder; + if (CHANNEL_ALERTLEVEL.equals(channelUID.getId())) { + if (command instanceof RefreshType) { + if (localBinder != null) + localBinder.update(); + } + } + } + + @Override + public void initialize() { + config = getConfigAs(NZWaterAlertsConfiguration.class); + + NZWaterAlertsBinder localBinder = binder = new NZWaterAlertsBinder(httpClient, config, scheduler); + + updateStatus(ThingStatus.UNKNOWN); + localBinder.registerListener(this); + } + + @Override + public void dispose() { + NZWaterAlertsBinder localBinder = binder; + if (localBinder != null) { + localBinder.unregisterListener(this); + } + + super.dispose(); + } + + @Override + public void handleRemoval() { + NZWaterAlertsBinder localBinder = binder; + if (localBinder != null) { + localBinder.unregisterListener(this); + } + + super.handleRemoval(); + } + + @Override + public void updateWaterLevel(int level) { + if (level == -1) { + updateState(new ChannelUID(getThing().getUID(), CHANNEL_ALERTLEVEL), UnDefType.UNDEF); + } else { + updateState(new ChannelUID(getThing().getUID(), CHANNEL_ALERTLEVEL), new DecimalType(level)); + } + } + + @Override + public void updateBindingStatus(ThingStatus thingStatus) { + updateStatus(thingStatus); + } + + @Override + public void updateBindingStatus(ThingStatus thingStatus, ThingStatusDetail thingStatusDetail, String description) { + updateStatus(thingStatus, thingStatusDetail, description); + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandlerFactory.java b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandlerFactory.java new file mode 100644 index 0000000000000..9e98fdcf55ffa --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/java/org/openhab/binding/nzwateralerts/internal/handler/NZWaterAlertsHandlerFactory.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.nzwateralerts.internal.handler; + +import static org.openhab.binding.nzwateralerts.internal.NZWaterAlertsBindingConstants.*; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.eclipse.smarthome.io.net.http.HttpClientFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link NZWaterAlertsHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Stewart Cossey - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.nzwateralerts", service = ThingHandlerFactory.class) +public class NZWaterAlertsHandlerFactory extends BaseThingHandlerFactory { + private final HttpClient httpClient; + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_WATERALERT); + + @Activate + public NZWaterAlertsHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_WATERALERT.equals(thingTypeUID)) { + return new NZWaterAlertsHandler(thing, httpClient); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..306967e9d8dc8 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + NZ Water Alerts Binding + Water Alert Levels for New Zealand water supply. + Stewart Cossey + + diff --git a/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..f1799ad113b07 --- /dev/null +++ b/bundles/org.openhab.binding.nzwateralerts/src/main/resources/ESH-INF/thing/thing-types.xml @@ -0,0 +1,63 @@ + + + + + + Water Alert Levels for New Zealand water supply. + + + + + + + + + The location to get the Water Alert level for. + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + The interval (in hours) to refresh the data. + + + + + + + Number + + The alert level for the location. + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 552c6be3d75b3..ec5165810e42a 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -192,6 +192,7 @@ org.openhab.binding.ntp org.openhab.binding.nuki org.openhab.binding.nuvo + org.openhab.binding.nzwateralerts org.openhab.binding.oceanic org.openhab.binding.ojelectronics org.openhab.binding.omnikinverter