diff --git a/CODEOWNERS b/CODEOWNERS index b3b89cf06e8..36b397fb6c8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -250,6 +250,7 @@ /bundles/org.openhab.binding.plugwiseha/ @lsiepel /bundles/org.openhab.binding.powermax/ @lolodomo /bundles/org.openhab.binding.proteusecometer/ @2chilled +/bundles/org.openhab.binding.prowl/ @octa22 /bundles/org.openhab.binding.publictransportswitzerland/ @jeremystucki /bundles/org.openhab.binding.pulseaudio/ @peuter /bundles/org.openhab.binding.pushbullet/ @hakan42 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index bb4e528761e..f24bf336a44 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1241,6 +1241,11 @@ org.openhab.binding.proteusecometer ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.prowl + ${project.version} + org.openhab.addons.bundles org.openhab.binding.publictransportswitzerland diff --git a/bundles/org.openhab.binding.prowl/NOTICE b/bundles/org.openhab.binding.prowl/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/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.prowl/README.md b/bundles/org.openhab.binding.prowl/README.md new file mode 100644 index 00000000000..34ac1981df0 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/README.md @@ -0,0 +1,43 @@ +# Prowl Binding + +This is the binding for the [Prowl](https://www.prowlapp.com) iOS push service. +It has been written from scratch and therefore it is not based on the original 1.x Prowl binding. +It has no other purpose than sending push messages to iOS devices. + +## Binding Configuration + +The binding does not require any manual configuration on the binding level. + +## Thing Configuration + +This binding has only one thing called _Broker_. If you want to use this binding, just add a broker instance and configure the API key, which you can generate on the Prowl website. +You can also modify the _application_ property, which identifies the originator of these push messages. +If you want to have specific refresh time for the remaining free push messages channel, you can edit the _refresh_ property. +Anyway beware - every check consumes one free push message you can send in an hour. + +## Channels + +The broker thing has only one channel keeping the number of free push messages, which can be sent. + +| channel | type | description | +|------------|--------|--------------------------------------------------------| +| remaining | Number | This channel provides the number of free push messages | + +## Example + +_*.things_ + +``` +Thing prowl:broker:mybroker "Prowl Broker" [ apiKey="0000000000000000000000000000000000000000" ] +``` + +_*.rules_ + +Once you have created the broker thing with a valid API key, you can use the Prowl service in your rules. +First you need to create an instance of the broker just before any call or on the top rules level. (replace the _mybroker_ with the right name of your instance). +Then you can call method _pushNotification_, which requires two parameters - _event_ and _description_. + +``` +val prowl = getActions("prowl","prowl:broker:mybroker") +prowl.pushNotification("Event", "This is the description of the event") +``` diff --git a/bundles/org.openhab.binding.prowl/pom.xml b/bundles/org.openhab.binding.prowl/pom.xml new file mode 100644 index 00000000000..89ba32c553b --- /dev/null +++ b/bundles/org.openhab.binding.prowl/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.3.0-SNAPSHOT + + + org.openhab.binding.prowl + + openHAB Add-ons :: Bundles :: Prowl Binding + + diff --git a/bundles/org.openhab.binding.prowl/src/main/feature/feature.xml b/bundles/org.openhab.binding.prowl/src/main/feature/feature.xml new file mode 100644 index 00000000000..8e30f3333e5 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/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.prowl/${project.version} + + diff --git a/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlBindingConstants.java b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlBindingConstants.java new file mode 100644 index 00000000000..5c868e89321 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlBindingConstants.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2022 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.prowl.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link ProwlBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Ondrej Pecta - Initial contribution + */ +@NonNullByDefault +public class ProwlBindingConstants { + + private static final String BINDING_ID = "prowl"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_BROKER = new ThingTypeUID(BINDING_ID, "broker"); + + // List of all Channel ids + public static final String CHANNEL_REMAINING = "remaining"; + + // constants + public static final String PROWL_ADD_URI = "https://api.prowlapp.com/publicapi/add"; + public static final String PROWL_VERIFY_URI = "https://api.prowlapp.com/publicapi/verify"; +} diff --git a/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlConfiguration.java b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlConfiguration.java new file mode 100644 index 00000000000..8711eb59ebf --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlConfiguration.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2022 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.prowl.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ProwlConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Ondrej Pecta - Initial contribution + */ +@NonNullByDefault +public class ProwlConfiguration { + + /** + * Prowl configuration parameters. + */ + public String apiKey = ""; + public String application = "openHAB"; + public int refresh = 30; +} diff --git a/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandler.java b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandler.java new file mode 100644 index 00000000000..6ba26fb0476 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandler.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2010-2022 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.prowl.internal; + +import static org.openhab.binding.prowl.internal.ProwlBindingConstants.*; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +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.eclipse.jetty.client.util.StringContentProvider; +import org.openhab.binding.prowl.internal.action.ProwlActions; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ProwlHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Ondrej Pecta - Initial contribution + */ +@NonNullByDefault +public class ProwlHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(ProwlHandler.class); + + private ProwlConfiguration config = new ProwlConfiguration(); + final private HttpClient httpClient; + + /** + * Future to poll for status + */ + private @Nullable ScheduledFuture statusFuture; + + public ProwlHandler(Thing thing, HttpClient client) { + super(thing); + this.httpClient = client; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + @Override + public void initialize() { + config = getConfigAs(ProwlConfiguration.class); + updateStatus(ThingStatus.UNKNOWN); + + statusFuture = scheduler.scheduleWithFixedDelay(() -> updateStatus(), 0, config.refresh, TimeUnit.MINUTES); + } + + private void updateStatus() { + if (keyVerificationSucceeded(config.apiKey)) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + } + + @Override + public void dispose() { + ScheduledFuture localPollFuture = statusFuture; + if (localPollFuture != null && !localPollFuture.isCancelled()) { + localPollFuture.cancel(true); + } + super.dispose(); + } + + private boolean keyVerificationSucceeded(String apiKey) { + try { + ContentResponse response = httpClient.GET(PROWL_VERIFY_URI + "?apikey=" + apiKey); + String resp = response.getContentAsString(); + logger.trace("verify response: {}", resp); + if (resp.contains("> getServices() { + return Collections.singletonList(ProwlActions.class); + } + + public void pushNotification(@Nullable String event, @Nullable String description) { + if (event == null || description == null) { + logger.debug("Cannot push message with null event or null description"); + return; + } + + logger.debug("Pushing an event: {} with desc: {}", event, description); + try { + ContentResponse response = httpClient.POST(PROWL_ADD_URI).timeout(5, TimeUnit.SECONDS) + .content( + new StringContentProvider("apikey=" + config.apiKey + "&application=" + config.application + + "&event=" + event + "&description=" + description), + "application/x-www-form-urlencoded; charset=UTF-8") + .send(); + String resp = response.getContentAsString(); + updateFreeMessages(resp); + logger.trace("add response: {}", resp); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + logger.debug("error during calling uri: {}", PROWL_ADD_URI, e); + } catch (TimeoutException e) { + logger.debug("timeout during calling uri: {}", PROWL_ADD_URI, e); + } + } + + private void updateFreeMessages(String resp) { + final String str = "remaining=\""; + + // trying to simply parse the simple xml rather than using XPATH + int start = resp.indexOf(str) + str.length(); + int end = resp.indexOf("\"", start + 1); + + try { + String messages = resp.substring(start, end); + logger.debug("remaining messages parsed: {}", messages); + int freeMessages = Integer.parseInt(messages); + updateState(CHANNEL_REMAINING, new DecimalType(freeMessages)); + } catch (StringIndexOutOfBoundsException | NumberFormatException ex) { + logger.debug("Error parsing remaining messages", ex); + } + } +} diff --git a/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandlerFactory.java b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandlerFactory.java new file mode 100644 index 00000000000..88c94c130db --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/ProwlHandlerFactory.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2022 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.prowl.internal; + +import static org.openhab.binding.prowl.internal.ProwlBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.io.net.http.HttpClientFactory; +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 ProwlHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Ondrej Pecta - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.prowl", service = ThingHandlerFactory.class) +public class ProwlHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BROKER); + + private final HttpClientFactory httpClientFactory; + + @Activate + public ProwlHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + this.httpClientFactory = httpClientFactory; + } + + @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_BROKER.equals(thingTypeUID)) { + return new ProwlHandler(thing, httpClientFactory.getCommonHttpClient()); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/action/ProwlActions.java b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/action/ProwlActions.java new file mode 100644 index 00000000000..a535caf52e6 --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/java/org/openhab/binding/prowl/internal/action/ProwlActions.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2022 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.prowl.internal.action; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.prowl.internal.ProwlHandler; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.RuleAction; +import org.openhab.core.thing.binding.ThingActions; +import org.openhab.core.thing.binding.ThingActionsScope; +import org.openhab.core.thing.binding.ThingHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ProwlActions} class contains methods for use in DSL. + * + * @author Ondrej Pecta - Initial contribution + */ +@ThingActionsScope(name = "prowl") +@NonNullByDefault +public class ProwlActions implements ThingActions { + private final Logger logger = LoggerFactory.getLogger(ProwlActions.class); + private @Nullable ProwlHandler handler; + + @Override + public void setThingHandler(ThingHandler thingHandler) { + this.handler = (ProwlHandler) thingHandler; + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } + + @RuleAction(label = "@text/pushNotificationActionLabel", description = "@text/pushNotificationActionDescription") + public void pushNotification( + @ActionInput(name = "event", label = "@text/pushNotificationActionEventLabel", description = "@text/pushNotificationActionEventDescription") @Nullable String event, + @ActionInput(name = "message", label = "@text/pushNotificationActionMessageLabel", description = "@text/pushNotificationActionMessageDescription") @Nullable String message) { + ProwlHandler clientHandler = handler; + if (clientHandler == null) { + logger.warn("Prowl ThingHandler is null"); + return; + } + + handler.pushNotification(event, message); + } + + public static void pushNotification(@Nullable ThingActions actions, @Nullable String event, + @Nullable String description) { + if (actions instanceof ProwlActions) { + ((ProwlActions) actions).pushNotification(event, description); + } else { + throw new IllegalArgumentException("Instance is not a ProwlActions class."); + } + } +} diff --git a/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 00000000000..8da6916486d --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + Prowl Binding + This is the binding for Prowl. + + diff --git a/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/i18n/prowl.properties b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/i18n/prowl.properties new file mode 100644 index 00000000000..08d7fb8c6bb --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/i18n/prowl.properties @@ -0,0 +1,32 @@ +# binding + +binding.prowl.name = Prowl Binding +binding.prowl.description = This is the binding for Prowl. + +# thing types + +thing-type.prowl.broker.label = Broker +thing-type.prowl.broker.description = A broker thing for the Prowl Binding + +# thing types config + +thing-type.config.prowl.broker.apiKey.label = API key +thing-type.config.prowl.broker.apiKey.description = API key created in the ProwlApp +thing-type.config.prowl.broker.application.label = Application name +thing-type.config.prowl.broker.application.description = Application name used in every push message +thing-type.config.prowl.broker.refresh.label = Refresh +thing-type.config.prowl.broker.refresh.description = Specifies the refresh time in minutes for checking for remaining free messages + +# channel types + +channel-type.prowl.remaining.label = Remaining Messages +channel-type.prowl.remaining.description = Remaining free push messages for Prowl Binding + +# actions + +pushNotificationActionLabel = push a notification +pushNotificationActionDescription = Send a push message using ProwlApp. +pushNotificationActionEventLabel = Event +pushNotificationActionEventDescription = Event name. +pushNotificationActionMessageLabel = Message +pushNotificationActionMessageDescription = Message text. diff --git a/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 00000000000..017950c7a0c --- /dev/null +++ b/bundles/org.openhab.binding.prowl/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,42 @@ + + + + + + + A broker thing for the Prowl Binding + + + + + + + + + Application name used in every push message + openHAB + + + + API key created in the ProwlApp + + + + Specifies the refresh time in minutes for checking for remaining free messages + 30 + + + + + + + + Number + + Remaining free push messages for Prowl Binding + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 43b52c4f89d..cdf7906f980 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -282,6 +282,7 @@ org.openhab.binding.plugwiseha org.openhab.binding.powermax org.openhab.binding.proteusecometer + org.openhab.binding.prowl org.openhab.binding.publictransportswitzerland org.openhab.binding.pulseaudio org.openhab.binding.pushbullet