[prowl] Initial contribution (#10967)

Signed-off-by: Ondrej Pecta <opecta@gmail.com>
This commit is contained in:
Ondrej Pecta 2022-03-13 17:37:59 +01:00 committed by GitHub
parent 4f384419c8
commit 43fe75ba2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 534 additions and 0 deletions

View File

@ -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

View File

@ -1241,6 +1241,11 @@
<artifactId>org.openhab.binding.proteusecometer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.prowl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.publictransportswitzerland</artifactId>

View File

@ -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

View File

@ -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")
```

View File

@ -0,0 +1,17 @@
<?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.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.prowl</artifactId>
<name>openHAB Add-ons :: Bundles :: Prowl Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.prowl-${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-prowl" description="Prowl Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.prowl/${project.version}</bundle>
</feature>
</features>

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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("<success code=\"200\"")) {
updateFreeMessages(resp);
return true;
} else {
return false;
}
} 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);
}
return false;
}
@Override
public Collection<Class<? extends ThingHandlerService>> 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);
}
}
}

View File

@ -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<ThingTypeUID> 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;
}
}

View File

@ -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.");
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="prowl" 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>Prowl Binding</name>
<description>This is the binding for Prowl.</description>
</binding:binding>

View File

@ -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.

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="prowl"
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">
<!-- Prowl Thing Type -->
<thing-type id="broker">
<label>Broker</label>
<description>A broker thing for the Prowl Binding</description>
<channels>
<channel id="remaining" typeId="remaining"/>
</channels>
<config-description>
<parameter name="application" type="text" required="false">
<label>Application name</label>
<description>Application name used in every push message</description>
<default>openHAB</default>
</parameter>
<parameter name="apiKey" type="text" required="true">
<label>API key</label>
<description>API key created in the ProwlApp</description>
</parameter>
<parameter name="refresh" type="integer" required="false" min="1">
<label>Refresh</label>
<description>Specifies the refresh time in minutes for checking for remaining free messages</description>
<default>30</default>
</parameter>
</config-description>
</thing-type>
<!-- remaining messages -->
<channel-type id="remaining">
<item-type>Number</item-type>
<label>Remaining Messages</label>
<description>Remaining free push messages for Prowl Binding</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -282,6 +282,7 @@
<module>org.openhab.binding.plugwiseha</module>
<module>org.openhab.binding.powermax</module>
<module>org.openhab.binding.proteusecometer</module>
<module>org.openhab.binding.prowl</module>
<module>org.openhab.binding.publictransportswitzerland</module>
<module>org.openhab.binding.pulseaudio</module>
<module>org.openhab.binding.pushbullet</module>