[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
15 changed files with 534 additions and 0 deletions

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>