[pushsafer] Initial Contribution of Pushsafer Binding (#10790)

* Pushsafer binding

Signed-off-by: Pushsafer.com (Kevin Siml) <info@appzer.de>

* Improvements and comments from code review

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

* Incorporated comments from review

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>

Co-authored-by: Pushsafer.com (Kevin Siml) <info@appzer.de>
This commit is contained in:
Christoph Weitkamp 2021-07-11 21:45:23 +02:00 committed by GitHub
parent 6fc24e4aa4
commit fc9864f434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2034 additions and 1 deletions

View File

@ -237,6 +237,7 @@
/bundles/org.openhab.binding.pulseaudio/ @peuter
/bundles/org.openhab.binding.pushbullet/ @hakan42
/bundles/org.openhab.binding.pushover/ @cweitkamp
/bundles/org.openhab.binding.pushsafer/ @appzer @cweitkamp
/bundles/org.openhab.binding.qbus/ @QbusKoen
/bundles/org.openhab.binding.radiothermostat/ @mlobstein
/bundles/org.openhab.binding.regoheatpump/ @crnjan

View File

@ -1166,6 +1166,11 @@
<artifactId>org.openhab.binding.pushover</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.pushsafer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.radiothermostat</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,54 @@
# Pushsafer Binding
The Pushsafer binding allows you to notify mobile devices of a message using the [Pushsafer API](https://www.pushsafer.com/pushapi).
To get started you first need to register (a free process) to get a Private Key.
Initially you have to register a device with one of the [client apps](https://www.pushsafer.com/apps), to get a device id.
## Supported Things
There is only one Thing available - the `pushsafer-account`.
You are able to create multiple instances of this Thing to broadcast to different devices or groups with push-notification content and setting.
## Thing Configuration
| Configuration Parameter | Type | Description |
|-------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| `apikey` | text | Your private-key to access the Pushsafer [Message API](https://www.pushsafer.com/pushapi). **mandatory** |
| `user` | text | Your username or email address to validate against the Pushsafer Message API. **mandatory** |
| `device` | text | Your device or group id to which device(s) you want to push notifications. **mandatory** |
| `title` | text | The default title of a message (default: `"openHAB"`). |
| `format` | text | The default format (`"none"`, `"HTML"` or `"monospace"`) of a message (default: `none`). |
| `sound` | text | The default notification sound on target device (default: `1`) (see [supported notification sounds](https://www.pushsafer.com/pushapi#api-sound)). |
| `vibration` | text | How often the device should vibrate. empty=device default or a number 1-3. |
| `icon` | text | The default notification icon on target device (default: `1`) (see [supported notification icons](https://www.pushsafer.com/pushapi#api-icon)). |
| `color` | text | The color (hexadecimal) of notification icon (e.g. #FF0000). |
| `url` | text | URL or [URL Scheme](https://www.pushsafer.com/url_schemes) send with notification. |
| `urlTitle` | text | Title of URL. |
| `retry` | integer | The retry parameter specifies how often (in seconds) the Pushsafer servers will send the same notification to the user (default: `300`). **advanced** |
| `expire` | integer | The expire parameter specifies how long (in seconds) your notification will continue to be retried (default: `3600`). **advanced** |
| `confirm` | integer | Integer 10-10800 (10s steps) Time in seconds after which a message should be sent again before it is confirmed. (default: `0`). **advanced** |
| `time2live` | integer | Time in minutes, after a message automatically gets purged (default: `0`). **advanced** |
| `answer` | integer | 1 = enables reply to push notifications (default: `0`). **advanced** |
The `retry` and `expire` parameters are only used for emergency-priority notifications.
## Channels
Currently the binding does not support any Channels.
## Thing Actions
All actions return a `Boolean` value to indicate if the message was sent successfully or not.
The parameter `message` is **mandatory**, the `title` parameter defaults to whatever value you defined in the `title` related configuration parameter.
- `sendPushsaferMessage(String message, @Nullable String title)` - This method is used to send a plain text message.
- `sendPushsaferHtmlMessage(String message, @Nullable String title)` - This method is used to send a HTML message.
- `sendPushsaferMonospaceMessage(String message, @Nullable String title)` - This method is used to send a monospace message.
- `sendPushsaferAttachmentMessage(String message, @Nullable String title, String attachment, @Nullable String contentType, @Nullable String authentication)` - This method is used to send a message with an image attachment. It takes a local path or url to the image attachment (parameter `attachment` **mandatory**), an optional `contentType` to define the content-type of the attachment (default: `"jpeg"`, possible values: `"jpeg"`, `"png"`, `"gif"`) and an optional `authentication` for the given URL to define the authentication if needed (default: `""`, example: `"user:password"`).
- `sendPushsaferURLMessage(String message, @Nullable String title, String url, @Nullable String urlTitle)` - This method is used to send a message with an URL. A supplementary `url` to show with the message and a `urlTitle` for the URL, otherwise just the URL is shown.
- `sendPushsaferPriorityMessage(String message, @Nullable String title, @Nullable Integer priority)` - This method is used to send a priority message. Parameter `priority` is the priority (`-2`, `-1`, `0`, `1`, `2`) to be used (default: `2`).

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.pushsafer</artifactId>
<name>openHAB Add-ons :: Bundles :: Pushsafer Binding</name>
</project>

View File

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

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PushsaferBindingConstants} class defines common constants, which are used across the whole binding.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferBindingConstants {
private static final String BINDING_ID = "pushsafer";
public static final ThingTypeUID PUSHSAFER_ACCOUNT = new ThingTypeUID(BINDING_ID, "pushsafer-account");
public static final String CONFIG_SOUND = "sound";
public static final String CONFIG_ICON = "icon";
public static final String ALL_DEVICES = "a";
public static final String DEFAULT_SOUND = "";
public static final String DEFAULT_ICON = "1";
public static final String DEFAULT_COLOR = "";
public static final String DEFAULT_URL = "";
public static final String DEFAULT_URLTITLE = "";
public static final String DEFAULT_VIBRATION = "1";
public static final int DEFAULT_CONFIRM = 0;
public static final boolean DEFAULT_ANSWER = false;
public static final int DEFAULT_TIME2LIVE = 0;
public static final String DEFAULT_TITLE = "openHAB";
}

View File

@ -0,0 +1,234 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.actions;
import static org.openhab.binding.pushsafer.internal.PushsaferBindingConstants.DEFAULT_TITLE;
import static org.openhab.binding.pushsafer.internal.connection.PushsaferMessageBuilder.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pushsafer.internal.connection.PushsaferConfigurationException;
import org.openhab.binding.pushsafer.internal.connection.PushsaferMessageBuilder;
import org.openhab.binding.pushsafer.internal.handler.PushsaferAccountHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
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;
/**
* Some automation actions to be used with a {@link PushsaferAccountHandler}.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@ThingActionsScope(name = "pushsafer")
@NonNullByDefault
public class PushsaferActions implements ThingActions {
private static final String DEFAULT_EMERGENCY_PRIORITY = "2";
private final Logger logger = LoggerFactory.getLogger(PushsaferActions.class);
private @NonNullByDefault({}) PushsaferAccountHandler accountHandler;
@RuleAction(label = "@text/sendPushsaferMessageActionLabel", description = "@text/sendPushsaferMessageActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) {
logger.trace("ThingAction 'sendPushsaferMessage' called with value(s): message='{}', title='{}'", message,
title);
return send(getDefaultPushsaferMessageBuilder(message), title);
}
public static Boolean sendPushsaferMessage(ThingActions actions, String message, @Nullable String title) {
return ((PushsaferActions) actions).sendPushsaferMessage(message, title);
}
@RuleAction(label = "@text/sendPushsaferURLMessageActionLabel", description = "@text/sendPushsaferURLMessageActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferURLMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title,
@ActionInput(name = "url", label = "@text/sendPushsaferMessageActionInputURLLabel", description = "@text/sendPushsaferMessageActionInputURLDescription", type = "java.lang.String", required = true) String url,
@ActionInput(name = "urlTitle", label = "@text/sendPushsaferMessageActionInputURLTitleLabel", description = "@text/sendPushsaferMessageActionInputURLTitleDescription", type = "java.lang.String") @Nullable String urlTitle) {
logger.trace(
"ThingAction 'sendPushsaferURLMessage' called with value(s): message='{}', url='{}', title='{}', urlTitle='{}'",
message, url, title, urlTitle);
if (url == null) {
throw new IllegalArgumentException("Skip sending message as 'url' is null.");
}
PushsaferMessageBuilder builder = getDefaultPushsaferMessageBuilder(message).withUrl(url);
if (urlTitle != null) {
builder.withUrl(urlTitle);
}
return send(builder, title);
}
public static Boolean sendPushsaferURLMessage(ThingActions actions, String message, @Nullable String title,
String url, @Nullable String urlTitle) {
return ((PushsaferActions) actions).sendPushsaferURLMessage(message, title, url, urlTitle);
}
@RuleAction(label = "@text/sendHTMLMessageActionLabel", description = "@text/sendHTMLMessageActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferHtmlMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) {
logger.trace("ThingAction 'sendPushsaferHtmlMessage' called with value(s): message='{}', title='{}'", message,
title);
return send(getDefaultPushsaferMessageBuilder(message).withHtmlFormatting(), title);
}
public static Boolean sendPushsaferHtmlMessage(ThingActions actions, String message, @Nullable String title) {
return ((PushsaferActions) actions).sendPushsaferHtmlMessage(message, title);
}
@RuleAction(label = "@text/sendPushsaferMonospaceMessageActionLabel", description = "@text/sendPushsaferMonospaceMessageActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferMonospaceMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) {
logger.trace("ThingAction 'sendPushsaferMonospaceMessage' called with value(s): message='{}', title='{}'",
message, title);
return send(getDefaultPushsaferMessageBuilder(message).withMonospaceFormatting(), title);
}
public static Boolean sendPushsaferMonospaceMessage(ThingActions actions, String message, @Nullable String title) {
return ((PushsaferActions) actions).sendPushsaferMonospaceMessage(message, title);
}
@RuleAction(label = "@text/sendPushsaferAttachmentMessageActionLabel", description = "@text/sendPushsaferAttachmentMessageActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferAttachmentMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title,
@ActionInput(name = "attachment", label = "@text/sendPushsaferMessageActionInputAttachmentLabel", description = "@text/sendPushsaferMessageActionInputAttachmentDescription", type = "java.lang.String", required = true) String attachment,
@ActionInput(name = "contentType", label = "@text/sendPushsaferMessageActionInputContentTypeLabel", description = "@text/sendPushsaferMessageActionInputContentTypeDescription", type = "java.lang.String", defaultValue = DEFAULT_CONTENT_TYPE) @Nullable String contentType,
@ActionInput(name = "authentication", label = "@text/sendPushsaferMessageActionInputAuthenticationLabel", description = "@text/sendPushsaferMessageActionInputAuthenticationDescription", type = "java.lang.String", defaultValue = DEFAULT_AUTH) @Nullable String authentication) {
logger.trace(
"ThingAction 'sendPushsaferAttachmentMessage' called with value(s): message='{}', title='{}', attachment='{}', contentType='{}', authentication='{}'",
message, title, attachment, contentType, authentication);
if (attachment == null) {
throw new IllegalArgumentException("Skip sending message as 'attachment' is null.");
}
PushsaferMessageBuilder builder = getDefaultPushsaferMessageBuilder(message).withAttachment(attachment);
if (contentType != null) {
builder.withContentType(contentType);
}
if (authentication != null) {
builder.withAuthentication(authentication);
}
return send(builder, title);
}
public static Boolean sendPushsaferAttachmentMessage(ThingActions actions, String message, @Nullable String title,
String attachment, @Nullable String contentType, @Nullable String authentication) {
return ((PushsaferActions) actions).sendPushsaferAttachmentMessage(message, title, attachment, contentType,
authentication);
}
@RuleAction(label = "@text/sendPushsaferPriorityMessageActionLabel", description = "@text/sendPushsaferPriorityMessageActionDescription")
public @ActionOutput(name = "receipt", label = "@text/sendPushsaferPriorityMessageActionOutputLabel", description = "@text/sendPushsaferPriorityMessageActionOutputDescription", type = "java.lang.String") String sendPushsaferPriorityMessage(
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title,
@ActionInput(name = "priority", label = "@text/sendPushsaferMessageActionInputPriorityLabel", description = "@text/sendPushsaferMessageActionInputPriorityDescription", type = "java.lang.Integer", defaultValue = DEFAULT_EMERGENCY_PRIORITY) @Nullable Integer priority) {
logger.trace(
"ThingAction 'sendPushsaferPriorityMessage' called with value(s): message='{}', title='{}', priority='{}'",
message, title, priority);
PushsaferMessageBuilder builder = getDefaultPushsaferMessageBuilder(message)
.withPriority(priority == null ? EMERGENCY_PRIORITY : priority.intValue());
if (title != null) {
builder.withTitle(title);
}
return accountHandler.sendPushsaferPriorityMessage(builder);
}
public static String sendPushsaferPriorityMessage(ThingActions actions, String message, @Nullable String title,
@Nullable Integer priority) {
return ((PushsaferActions) actions).sendPushsaferPriorityMessage(message, title, priority);
}
@RuleAction(label = "@text/cancelPushsaferPriorityMessageActionLabel", description = "@text/cancelPushsaferPriorityMessageActionDescription")
public @ActionOutput(name = "canceled", label = "@text/cancelPushsaferPriorityMessageActionOutputLabel", description = "@text/cancelPushsaferPriorityMessageActionOutputDescription", type = "java.lang.Boolean") Boolean cancelPushsaferPriorityMessage(
@ActionInput(name = "receipt", label = "@text/cancelPushsaferPriorityMessageActionInputReceiptLabel", description = "@text/cancelPushsaferPriorityMessageActionInputReceiptDescription", type = "java.lang.String", required = true) String receipt) {
logger.trace("ThingAction 'cancelPushsaferPriorityMessage' called with value(s): '{}'", receipt);
if (accountHandler == null) {
throw new RuntimeException("PushsaferAccountHandler is null!");
}
if (receipt == null) {
throw new IllegalArgumentException("Skip sending message as 'receipt' is null.");
}
return accountHandler.cancelPushsaferPriorityMessage(receipt);
}
public static Boolean cancelPushsaferPriorityMessage(ThingActions actions, String receipt) {
return ((PushsaferActions) actions).cancelPushsaferPriorityMessage(receipt);
}
@RuleAction(label = "@text/sendPushsaferMessageToDeviceActionLabel", description = "@text/sendPushsaferMessageToDeviceActionDescription")
public @ActionOutput(name = "sent", label = "@text/sendPushsaferMessageActionOutputLabel", description = "@text/sendPushsaferMessageActionOutputDescription", type = "java.lang.Boolean") Boolean sendPushsaferMessageToDevice(
@ActionInput(name = "device", label = "@text/sendPushsaferMessageActionInputDeviceLabel", description = "@text/sendPushsaferMessageActionInputDeviceDescription", type = "java.lang.String", required = true) String device,
@ActionInput(name = "message", label = "@text/sendPushsaferMessageActionInputMessageLabel", description = "@text/sendPushsaferMessageActionInputMessageDescription", type = "java.lang.String", required = true) String message,
@ActionInput(name = "title", label = "@text/sendPushsaferMessageActionInputTitleLabel", description = "@text/sendPushsaferMessageActionInputTitleDescription", type = "java.lang.String", defaultValue = DEFAULT_TITLE) @Nullable String title) {
logger.trace(
"ThingAction 'sendPushsaferMessageToDevice' called with value(s): device='{}', message='{}', title='{}'",
device, message, title);
if (device == null) {
throw new IllegalArgumentException("Skip sending message as 'device' is null.");
}
return send(getDefaultPushsaferMessageBuilder(message).withDevice(device), title);
}
public static Boolean sendPushsaferMessageToDevice(ThingActions actions, String device, String message,
@Nullable String title) {
return ((PushsaferActions) actions).sendPushsaferMessageToDevice(device, message, title);
}
private PushsaferMessageBuilder getDefaultPushsaferMessageBuilder(String message) {
if (accountHandler == null) {
throw new RuntimeException("PushsaferAccountHandler is null!");
}
if (message == null) {
throw new IllegalArgumentException("Skip sending message as 'message' is null.");
}
try {
return accountHandler.getDefaultPushsaferMessageBuilder(message);
} catch (PushsaferConfigurationException e) {
throw new IllegalArgumentException(e.getCause());
}
}
private Boolean send(PushsaferMessageBuilder builder, @Nullable String title) {
if (title != null) {
builder.withTitle(title);
}
return accountHandler.sendPushsaferMessage(builder);
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.accountHandler = (PushsaferAccountHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return accountHandler;
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.config;
import static org.openhab.binding.pushsafer.internal.PushsaferBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link PushsaferAccountConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferAccountConfiguration {
public @Nullable String apikey;
public @Nullable String user;
public String device = ALL_DEVICES;
public String title = DEFAULT_TITLE;
public String format = "none";
public String sound = DEFAULT_SOUND;
public String icon = DEFAULT_ICON;
public String color = DEFAULT_COLOR;
public String url = DEFAULT_URL;
public String urlTitle = DEFAULT_URLTITLE;
public boolean answer = DEFAULT_ANSWER;
public int confirm = DEFAULT_CONFIRM;
public int time2live = DEFAULT_TIME2LIVE;
public String vibration = DEFAULT_VIBRATION;
public int retry = 300;
public int expire = 3600;
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.config;
import static org.openhab.binding.pushsafer.internal.PushsaferBindingConstants.*;
import java.net.URI;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pushsafer.internal.dto.Icon;
import org.openhab.binding.pushsafer.internal.dto.Sound;
import org.openhab.binding.pushsafer.internal.handler.PushsaferAccountHandler;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ParameterOption;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.osgi.service.component.annotations.Component;
/**
* The {@link PushsaferConfigOptionProvider} class contains fields mapping thing configuration parameters.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@Component(service = ConfigOptionProvider.class)
@NonNullByDefault
public class PushsaferConfigOptionProvider implements ConfigOptionProvider, ThingHandlerService {
private @Nullable PushsaferAccountHandler accountHandler;
@Override
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
@Nullable Locale locale) {
PushsaferAccountHandler localAccountHandler = accountHandler;
if (localAccountHandler != null) {
if (PUSHSAFER_ACCOUNT.getAsString().equals(uri.getSchemeSpecificPart()) && CONFIG_SOUND.equals(param)) {
List<Sound> sounds = localAccountHandler.getSounds();
if (!sounds.isEmpty()) {
return sounds.stream().map(Sound::getAsParameterOption)
.sorted(Comparator.comparing(ParameterOption::getLabel))
.collect(Collectors.toUnmodifiableList());
}
}
if (PUSHSAFER_ACCOUNT.getAsString().equals(uri.getSchemeSpecificPart()) && CONFIG_ICON.equals(param)) {
List<Icon> icons = localAccountHandler.getIcons();
if (!icons.isEmpty()) {
return icons.stream().map(Icon::getAsParameterOption)
.sorted(Comparator.comparing(ParameterOption::getLabel))
.collect(Collectors.toUnmodifiableList());
}
}
}
return null;
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.accountHandler = (PushsaferAccountHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return accountHandler;
}
}

View File

@ -0,0 +1,223 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.connection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.pushsafer.internal.config.PushsaferAccountConfiguration;
import org.openhab.binding.pushsafer.internal.dto.Icon;
import org.openhab.binding.pushsafer.internal.dto.Sound;
import org.openhab.core.cache.ExpiringCacheMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* The {@link PushsaferAPIConnection} is responsible for handling the connections to Pushsafer Messages API.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferAPIConnection {
private final Logger logger = LoggerFactory.getLogger(PushsaferAPIConnection.class);
private static final String VALIDATE_URL = "https://www.pushsafer.com/api-k";
private static final String MESSAGE_URL = "https://www.pushsafer.com/api";
private static final String CANCEL_MESSAGE_URL = "https://www.pushsafer.com/api-m";
private static final String SOUNDS_URL = "https://www.pushsafer.com/api-s";
private static final String ICONS_URL = "https://www.pushsafer.com/api-i";
private final HttpClient httpClient;
private final PushsaferAccountConfiguration config;
private final ExpiringCacheMap<String, String> cache = new ExpiringCacheMap<>(TimeUnit.DAYS.toMillis(1));
public PushsaferAPIConnection(HttpClient httpClient, PushsaferAccountConfiguration config) {
this.httpClient = httpClient;
this.config = config;
}
public boolean validateUser() throws PushsaferCommunicationException, PushsaferConfigurationException {
final String localApikey = config.apikey;
if (localApikey == null || localApikey.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
}
final String localUser = config.user;
if (localUser == null || localUser.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-user");
}
final String content = get(buildURL(VALIDATE_URL, Map.of(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey,
PushsaferMessageBuilder.MESSAGE_KEY_USER, localUser)));
final JsonObject json = content == null || content.isBlank() ? null
: JsonParser.parseString(content).getAsJsonObject();
return json == null ? false : getMessageStatus(json);
}
public boolean sendPushsaferMessage(PushsaferMessageBuilder message)
throws PushsaferCommunicationException, PushsaferConfigurationException {
return getMessageStatus(post(MESSAGE_URL, message.build()));
}
public String sendPushsaferPriorityMessage(PushsaferMessageBuilder message)
throws PushsaferCommunicationException, PushsaferConfigurationException {
final JsonObject json = JsonParser.parseString(post(MESSAGE_URL, message.build())).getAsJsonObject();
return getMessageStatus(json) && json.has("receipt") ? json.get("receipt").getAsString() : "";
}
public boolean cancelPushsaferPriorityMessage(String receipt)
throws PushsaferCommunicationException, PushsaferConfigurationException {
return getMessageStatus(post(CANCEL_MESSAGE_URL.replace("{receipt}", receipt),
PushsaferMessageBuilder.getInstance(config.apikey, config.device).build()));
}
public List<Sound> getSounds() throws PushsaferCommunicationException, PushsaferConfigurationException {
final String localApikey = config.apikey;
if (localApikey == null || localApikey.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
}
final Map<String, String> params = new HashMap<>(1);
params.put(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey);
final String content = getFromCache(buildURL(SOUNDS_URL, params));
final JsonObject json = content == null || content.isBlank() ? null
: JsonParser.parseString(content).getAsJsonObject();
final JsonObject sounds = json == null || !json.has("sounds") ? null : json.get("sounds").getAsJsonObject();
return sounds == null ? List.of()
: sounds.entrySet().stream().map(entry -> new Sound(entry.getKey(), entry.getValue().getAsString()))
.collect(Collectors.toUnmodifiableList());
}
public List<Icon> getIcons() throws PushsaferCommunicationException, PushsaferConfigurationException {
final String localApikey = config.apikey;
if (localApikey == null || localApikey.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
}
final Map<String, String> params = new HashMap<>(1);
params.put(PushsaferMessageBuilder.MESSAGE_KEY_TOKEN, localApikey);
final String content = getFromCache(buildURL(ICONS_URL, params));
final JsonObject json = content == null || content.isBlank() ? null
: JsonParser.parseString(content).getAsJsonObject();
final JsonObject icons = json == null || !json.has("icons") ? null : json.get("icons").getAsJsonObject();
return icons == null ? List.of()
: icons.entrySet().stream().map(entry -> new Icon(entry.getKey(), entry.getValue().getAsString()))
.collect(Collectors.toUnmodifiableList());
}
private String buildURL(String url, Map<String, String> requestParams) {
return requestParams.keySet().stream().map(key -> key + "=" + encodeParam(requestParams.get(key)))
.collect(Collectors.joining("&", url + "?", ""));
}
private String encodeParam(@Nullable String value) {
return value == null ? "" : URLEncoder.encode(value, StandardCharsets.UTF_8);
}
private @Nullable String getFromCache(String url)
throws PushsaferCommunicationException, PushsaferConfigurationException {
return cache.putIfAbsentAndGet(url, () -> get(url));
}
private String get(String url) throws PushsaferCommunicationException, PushsaferConfigurationException {
return executeRequest(HttpMethod.GET, url, null);
}
private String post(String url, ContentProvider body)
throws PushsaferCommunicationException, PushsaferConfigurationException {
return executeRequest(HttpMethod.POST, url, body);
}
private String executeRequest(HttpMethod httpMethod, String url, @Nullable ContentProvider body)
throws PushsaferCommunicationException, PushsaferConfigurationException {
logger.trace("Pushsafer request: {} - URL = '{}'", httpMethod, uglifyApikey(url));
try {
final Request request = httpClient.newRequest(url).method(httpMethod).timeout(10, TimeUnit.SECONDS);
if (body != null) {
if (logger.isTraceEnabled()) {
logger.trace("Pushsafer request body: '{}'", body);
}
request.content(body);
}
final ContentResponse contentResponse = request.send();
final int httpStatus = contentResponse.getStatus();
final String content = contentResponse.getContentAsString();
logger.trace("Pushsafer response: status = {}, content = '{}'", httpStatus, content);
switch (httpStatus) {
case HttpStatus.OK_200:
return content;
case 250:
case HttpStatus.BAD_REQUEST_400:
logger.debug("Pushsafer server responded with status code {}: {}", httpStatus, content);
throw new PushsaferConfigurationException(getMessageError(content));
default:
logger.debug("Pushsafer server responded with status code {}: {}", httpStatus, content);
throw new PushsaferCommunicationException(content);
}
} catch (ExecutionException e) {
logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
throw new PushsaferCommunicationException(e.getLocalizedMessage(), e.getCause());
} catch (InterruptedException | TimeoutException e) {
logger.debug("Exception occurred during execution: {}", e.getLocalizedMessage(), e);
throw new PushsaferCommunicationException(e.getLocalizedMessage());
}
}
private String uglifyApikey(String url) {
return url.replaceAll("(k=)+\\w+", "k=*****");
}
private String getMessageError(String content) {
final JsonObject json = JsonParser.parseString(content).getAsJsonObject();
final JsonElement errorsElement = json.get("errors");
if (errorsElement != null && errorsElement.isJsonArray()) {
return errorsElement.getAsJsonArray().toString();
}
return "@text/offline.conf-error-unknown";
}
private boolean getMessageStatus(String content) {
return getMessageStatus(JsonParser.parseString(content).getAsJsonObject());
}
private boolean getMessageStatus(JsonObject json) {
return json.has("status") ? json.get("status").getAsInt() == 1 : false;
}
}

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.connection;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link PushsaferCommunicationException} is a configuration exception for the connections to Pushsafer Messages
* API.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferCommunicationException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message.
*/
public PushsaferCommunicationException() {
super();
}
/**
* Constructs a new exception with the specified detail message.
*
* @param message Detail message
*/
public PushsaferCommunicationException(@Nullable String message) {
super(message);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause The cause
*/
public PushsaferCommunicationException(@Nullable Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message Detail message
* @param cause The cause
*/
public PushsaferCommunicationException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.connection;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link PushsaferConfigurationException} is a configuration exception for the connections to Pushsafer Messages
* API.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferConfigurationException extends IllegalArgumentException {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message.
*/
public PushsaferConfigurationException() {
super();
}
/**
* Constructs a new exception with the specified detail message.
*
* @param message Detail message
*/
public PushsaferConfigurationException(String message) {
super(message);
}
/**
* Constructs a new exception with the specified cause.
*
* @param cause The cause
*/
public PushsaferConfigurationException(Throwable cause) {
super(cause);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message Detail message
* @param cause The cause
*/
public PushsaferConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,355 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.connection;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.util.MultiPartContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PushsaferMessageBuilder} builds the body for Pushsafer Messages API requests.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferMessageBuilder {
private final Logger logger = LoggerFactory.getLogger(PushsaferMessageBuilder.class);
public static final String MESSAGE_KEY_TOKEN = "k";
public static final String MESSAGE_KEY_USER = "u";
private static final String MESSAGE_KEY_MESSAGE = "m";
private static final String MESSAGE_KEY_TITLE = "t";
private static final String MESSAGE_KEY_DEVICE = "d";
private static final String MESSAGE_KEY_ICON = "i";
private static final String MESSAGE_KEY_COLOR = "c";
private static final String MESSAGE_KEY_VIBRATION = "v";
private static final String MESSAGE_KEY_PRIORITY = "pr";
private static final String MESSAGE_KEY_RETRY = "re";
private static final String MESSAGE_KEY_EXPIRE = "ex";
private static final String MESSAGE_KEY_URL = "u";
private static final String MESSAGE_KEY_URL_TITLE = "ut";
private static final String MESSAGE_KEY_SOUND = "s";
private static final String MESSAGE_KEY_TIME2LIVE = "l";
private static final String MESSAGE_KEY_ANSWER = "a";
private static final String MESSAGE_KEY_CONFIRM = "cr";
private static final String MESSAGE_KEY_ATTACHMENT = "p";
public static final String MESSAGE_KEY_HTML = "html";
public static final String MESSAGE_KEY_MONOSPACE = "monospace";
private static final int MAX_MESSAGE_LENGTH = 4096;
private static final int MAX_TITLE_LENGTH = 250;
private static final int MAX_DEVICE_LENGTH = 25;
private static final List<Integer> VALID_PRIORITY_LIST = Arrays.asList(-2, -1, 0, 1, 2);
private static final int DEFAULT_PRIORITY = 0;
public static final int EMERGENCY_PRIORITY = 2;
private static final int MIN_RETRY_SECONDS = 0;
private static final int MAX_EXPIRE_SECONDS = 10800;
private static final int MAX_URL_LENGTH = 512;
private static final int MAX_URL_TITLE_LENGTH = 100;
public static final String DEFAULT_CONTENT_TYPE = "jpeg";
public static final String DEFAULT_AUTH = "";
private final MultiPartContentProvider body = new MultiPartContentProvider();
private @Nullable String message;
private @Nullable String title;
private @Nullable String device;
private int priority = DEFAULT_PRIORITY;
private int retry = 300;
private int expire = 3600;
private @Nullable String url;
private @Nullable String urlTitle;
private @Nullable String sound;
private @Nullable String icon;
private int confirm;
private int time2live;
private boolean answer;
private @Nullable String color;
private @Nullable String vibration;
private @Nullable String attachment;
private String contentType = DEFAULT_CONTENT_TYPE;
private String authentication = DEFAULT_AUTH;
private boolean html = false;
private boolean monospace = false;
private PushsaferMessageBuilder(String apikey, String device) throws PushsaferConfigurationException {
body.addFieldPart(MESSAGE_KEY_TOKEN, new StringContentProvider(apikey), null);
body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null);
}
public static PushsaferMessageBuilder getInstance(@Nullable String apikey, @Nullable String device)
throws PushsaferConfigurationException {
if (apikey == null || apikey.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
}
if (device == null || device.isEmpty()) {
throw new PushsaferConfigurationException("@text/offline.conf-error-missing-device");
}
return new PushsaferMessageBuilder(apikey, device);
}
public PushsaferMessageBuilder withMessage(String message) {
this.message = message;
return this;
}
public PushsaferMessageBuilder withTitle(String title) {
this.title = title;
return this;
}
public PushsaferMessageBuilder withDevice(String device) {
this.device = device;
return this;
}
public PushsaferMessageBuilder withPriority(int priority) {
this.priority = priority;
return this;
}
public PushsaferMessageBuilder withRetry(int retry) {
this.retry = retry;
return this;
}
public PushsaferMessageBuilder withExpire(int expire) {
this.expire = expire;
return this;
}
public PushsaferMessageBuilder withUrl(String url) {
this.url = url;
return this;
}
public PushsaferMessageBuilder withUrlTitle(String urlTitle) {
this.urlTitle = urlTitle;
return this;
}
public PushsaferMessageBuilder withSound(String sound) {
this.sound = sound;
return this;
}
public PushsaferMessageBuilder withIcon(String icon) {
this.icon = icon;
return this;
}
public PushsaferMessageBuilder withColor(String color) {
this.color = color;
return this;
}
public PushsaferMessageBuilder withVibration(String vibration) {
this.vibration = vibration;
return this;
}
public PushsaferMessageBuilder withAnswer(boolean answer) {
this.answer = answer;
return this;
}
public PushsaferMessageBuilder withTime2live(int time2live) {
this.time2live = time2live;
return this;
}
public PushsaferMessageBuilder withConfirm(int confirm) {
this.confirm = confirm;
return this;
}
public PushsaferMessageBuilder withAttachment(String attachment) {
this.attachment = attachment;
return this;
}
public PushsaferMessageBuilder withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public PushsaferMessageBuilder withAuthentication(String authentication) {
this.authentication = authentication;
return this;
}
public PushsaferMessageBuilder withHtmlFormatting() {
this.html = true;
return this;
}
public PushsaferMessageBuilder withMonospaceFormatting() {
this.monospace = true;
return this;
}
public ContentProvider build() throws PushsaferCommunicationException {
if (message != null) {
if (message.length() > MAX_MESSAGE_LENGTH) {
throw new IllegalArgumentException(String.format(
"Skip sending the message as 'message' is longer than %d characters.", MAX_MESSAGE_LENGTH));
}
body.addFieldPart(MESSAGE_KEY_MESSAGE, new StringContentProvider(message), null);
}
if (title != null) {
if (title.length() > MAX_TITLE_LENGTH) {
throw new IllegalArgumentException(String
.format("Skip sending the message as 'title' is longer than %d characters.", MAX_TITLE_LENGTH));
}
body.addFieldPart(MESSAGE_KEY_TITLE, new StringContentProvider(title), null);
}
if (device != null) {
if (device.length() > MAX_DEVICE_LENGTH) {
logger.warn("Skip 'device' as it is longer than {} characters. Got: {}.", MAX_DEVICE_LENGTH, device);
} else {
body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null);
}
}
if (priority != DEFAULT_PRIORITY) {
if (VALID_PRIORITY_LIST.contains(priority)) {
body.addFieldPart(MESSAGE_KEY_PRIORITY, new StringContentProvider(String.valueOf(priority)), null);
if (priority == EMERGENCY_PRIORITY) {
if (retry < MIN_RETRY_SECONDS) {
logger.warn("Retry value of {} is too small. Using default value of {}.", retry,
MIN_RETRY_SECONDS);
body.addFieldPart(MESSAGE_KEY_RETRY,
new StringContentProvider(String.valueOf(MIN_RETRY_SECONDS)), null);
} else {
body.addFieldPart(MESSAGE_KEY_RETRY, new StringContentProvider(String.valueOf(retry)), null);
}
if (0 < expire && expire <= MAX_EXPIRE_SECONDS) {
body.addFieldPart(MESSAGE_KEY_EXPIRE, new StringContentProvider(String.valueOf(expire)), null);
} else {
logger.warn("Expire value of {} is invalid. Using default value of {}.", expire,
MAX_EXPIRE_SECONDS);
body.addFieldPart(MESSAGE_KEY_EXPIRE,
new StringContentProvider(String.valueOf(MAX_EXPIRE_SECONDS)), null);
}
}
} else {
logger.warn("Invalid 'priority', skipping. Expected: {}. Got: {}.",
VALID_PRIORITY_LIST.stream().map(i -> i.toString()).collect(Collectors.joining(",")), priority);
}
}
if (url != null) {
if (url.length() > MAX_URL_LENGTH) {
throw new IllegalArgumentException(String
.format("Skip sending the message as 'url' is longer than %d characters.", MAX_URL_LENGTH));
}
body.addFieldPart(MESSAGE_KEY_URL, new StringContentProvider(url), null);
if (urlTitle != null) {
if (urlTitle.length() > MAX_URL_TITLE_LENGTH) {
throw new IllegalArgumentException(
String.format("Skip sending the message as 'urlTitle' is longer than %d characters.",
MAX_URL_TITLE_LENGTH));
}
body.addFieldPart(MESSAGE_KEY_URL_TITLE, new StringContentProvider(urlTitle), null);
}
}
if (sound != null) {
body.addFieldPart(MESSAGE_KEY_SOUND, new StringContentProvider(sound), null);
}
if (icon != null) {
body.addFieldPart(MESSAGE_KEY_ICON, new StringContentProvider(icon), null);
}
if (color != null) {
body.addFieldPart(MESSAGE_KEY_COLOR, new StringContentProvider(color), null);
}
if (vibration != null) {
body.addFieldPart(MESSAGE_KEY_VIBRATION, new StringContentProvider(vibration), null);
}
body.addFieldPart(MESSAGE_KEY_CONFIRM, new StringContentProvider(String.valueOf(confirm)), null);
body.addFieldPart(MESSAGE_KEY_ANSWER, new StringContentProvider(String.valueOf(answer)), null);
body.addFieldPart(MESSAGE_KEY_TIME2LIVE, new StringContentProvider(String.valueOf(time2live)), null);
if (attachment != null) {
final String encodedString;
try {
if (attachment.startsWith("http")) {
Properties headers = new Properties();
headers.put("User-Agent", "Mozilla/5.0");
if (!authentication.isBlank()) {
headers.put("Authorization", "Basic "
+ Base64.getEncoder().encodeToString(authentication.getBytes(StandardCharsets.UTF_8)));
}
String content = HttpUtil.executeUrl("GET", attachment, headers, null, null, 10);
if (content == null) {
throw new IllegalArgumentException(
String.format("Skip sending the message as content '%s' does not exist.", attachment));
}
encodedString = "data:" + contentType + ";base64," + content;
} else {
File file = new File(attachment);
if (!file.exists()) {
throw new IllegalArgumentException(
String.format("Skip sending the message as file '%s' does not exist.", attachment));
}
byte[] fileContent = Files.readAllBytes(file.toPath());
encodedString = "data:image/" + contentType + ";base64,"
+ Base64.getEncoder().encodeToString(fileContent);
}
body.addFieldPart(MESSAGE_KEY_ATTACHMENT, new StringContentProvider(encodedString), null);
} catch (IOException e) {
logger.debug("IOException occurred - skip sending message: {}", e.getLocalizedMessage(), e);
throw new PushsaferCommunicationException(
String.format("Skip sending the message: %s", e.getLocalizedMessage()), e);
}
}
if (html) {
body.addFieldPart(MESSAGE_KEY_HTML, new StringContentProvider("1"), null);
} else if (monospace) {
body.addFieldPart(MESSAGE_KEY_MONOSPACE, new StringContentProvider("1"), null);
}
body.close();
return body;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ParameterOption;
/**
* The {@link Icons} is the Java class used to map the JSON response to an Pushsafer API request..
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class Icon {
public String icon;
public String label;
public Icon(String icon, String label) {
this.icon = icon;
this.label = label;
}
public ParameterOption getAsParameterOption() {
return new ParameterOption(icon, label);
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ParameterOption;
/**
* The {@link Sound} is the Java class used to map the JSON response to an Pushsafer API request.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class Sound {
public String sound;
public String label;
public Sound(String sound, String label) {
this.sound = sound;
this.label = label;
}
public ParameterOption getAsParameterOption() {
return new ParameterOption(sound, label);
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.factory;
import static org.openhab.binding.pushsafer.internal.PushsaferBindingConstants.PUSHSAFER_ACCOUNT;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.pushsafer.internal.handler.PushsaferAccountHandler;
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 PushsaferHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@Component(configurationPid = "binding.pushsafer", service = ThingHandlerFactory.class)
@NonNullByDefault
public class PushsaferHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(PUSHSAFER_ACCOUNT);
private final HttpClient httpClient;
@Activate
public PushsaferHandlerFactory(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) {
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (PUSHSAFER_ACCOUNT.equals(thingTypeUID)) {
return new PushsaferAccountHandler(thing, httpClient);
}
return null;
}
}

View File

@ -0,0 +1,246 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.handler;
import static org.openhab.binding.pushsafer.internal.PushsaferBindingConstants.*;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.pushsafer.internal.actions.PushsaferActions;
import org.openhab.binding.pushsafer.internal.config.PushsaferAccountConfiguration;
import org.openhab.binding.pushsafer.internal.config.PushsaferConfigOptionProvider;
import org.openhab.binding.pushsafer.internal.connection.PushsaferAPIConnection;
import org.openhab.binding.pushsafer.internal.connection.PushsaferCommunicationException;
import org.openhab.binding.pushsafer.internal.connection.PushsaferConfigurationException;
import org.openhab.binding.pushsafer.internal.connection.PushsaferMessageBuilder;
import org.openhab.binding.pushsafer.internal.dto.Icon;
import org.openhab.binding.pushsafer.internal.dto.Sound;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
/**
* The {@link PushsaferAccountHandler} is responsible for handling commands, which are sent to one of the channels.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@NonNullByDefault
public class PushsaferAccountHandler extends BaseThingHandler {
private static final Collection<Class<? extends ThingHandlerService>> SUPPORTED_THING_ACTIONS = Set
.of(PushsaferActions.class, PushsaferConfigOptionProvider.class);
private final HttpClient httpClient;
private PushsaferAccountConfiguration config = new PushsaferAccountConfiguration();
private @Nullable PushsaferAPIConnection connection;
public PushsaferAccountHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.httpClient = httpClient;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// nothing
}
@Override
public void initialize() {
config = getConfigAs(PushsaferAccountConfiguration.class);
boolean configValid = true;
final String apikey = config.apikey;
if (apikey == null || apikey.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-missing-apikey");
configValid = false;
}
final String user = config.user;
if (user == null || user.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-missing-user");
configValid = false;
}
if (configValid) {
updateStatus(ThingStatus.UNKNOWN);
connection = new PushsaferAPIConnection(httpClient, config);
scheduler.submit(this::asyncValidateUser);
}
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return SUPPORTED_THING_ACTIONS;
}
/**
* Retrieves the list of current sounds and their descriptions from the Pushsafer API.
*
* @return a list of {@link Sound}s
*/
public List<Sound> getSounds() {
try {
return connection != null ? connection.getSounds() : List.of();
} catch (PushsaferCommunicationException e) {
// do nothing, causing exception is already logged
} catch (PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
return List.of();
}
/**
* Retrieves the list of current icons from the Pushsafer API.
*
* @return a list of {@link Icon}s
*/
public List<Icon> getIcons() {
try {
return connection != null ? connection.getIcons() : List.of();
} catch (PushsaferCommunicationException e) {
// do nothing, causing exception is already logged
} catch (PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
return List.of();
}
/**
* Returns a preconfigured {@link PushsaferMessageBuilder}.
*
* @param message the message
* @return a {@link PushsaferMessageBuilder} instance
*/
public PushsaferMessageBuilder getDefaultPushsaferMessageBuilder(String message)
throws PushsaferConfigurationException {
PushsaferMessageBuilder builder = PushsaferMessageBuilder.getInstance(config.apikey, config.device)
.withMessage(message) //
.withTitle(config.title) //
.withRetry(config.retry) //
.withExpire(config.expire);
// specify format if defined
switch (config.format) {
case PushsaferMessageBuilder.MESSAGE_KEY_HTML:
builder.withHtmlFormatting();
break;
case PushsaferMessageBuilder.MESSAGE_KEY_MONOSPACE:
builder.withMonospaceFormatting();
default:
break;
}
// add sound if defined
if (!DEFAULT_SOUND.equals(config.sound)) {
builder.withSound(config.sound);
}
// add icon if defined
if (!DEFAULT_ICON.equals(config.icon)) {
builder.withIcon(config.icon);
}
// add color if defined
if (!DEFAULT_COLOR.equals(config.color)) {
builder.withColor(config.color);
}
// add vibration if defined
if (!DEFAULT_VIBRATION.equals(config.vibration)) {
builder.withVibration(config.vibration);
}
// add url if defined
if (!DEFAULT_URL.equals(config.url)) {
builder.withUrl(config.url);
}
// add urlTitle if defined
if (!DEFAULT_URLTITLE.equals(config.urlTitle)) {
builder.withUrlTitle(config.urlTitle);
}
// add confirm if defined
if (DEFAULT_CONFIRM != config.confirm) {
builder.withConfirm(config.confirm);
}
// add answer if defined
if (DEFAULT_ANSWER != config.answer) {
builder.withAnswer(config.answer);
}
// add time2live if defined
if (DEFAULT_TIME2LIVE != config.time2live) {
builder.withTime2live(config.time2live);
}
return builder;
}
public boolean sendPushsaferMessage(PushsaferMessageBuilder messageBuilder) {
if (connection != null) {
try {
return connection.sendPushsaferMessage(messageBuilder);
} catch (PushsaferCommunicationException e) {
// do nothing, causing exception is already logged
} catch (PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
return false;
} else {
throw new IllegalArgumentException("PushsaferAPIConnection is null!");
}
}
public String sendPushsaferPriorityMessage(PushsaferMessageBuilder messageBuilder) {
if (connection != null) {
try {
return connection.sendPushsaferPriorityMessage(messageBuilder);
} catch (PushsaferCommunicationException e) {
// do nothing, causing exception is already logged
} catch (PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
return "";
} else {
throw new IllegalArgumentException("PushsaferAPIConnection is null!");
}
}
public boolean cancelPushsaferPriorityMessage(String receipt) {
if (connection != null) {
try {
return connection.cancelPushsaferPriorityMessage(receipt);
} catch (PushsaferCommunicationException e) {
// do nothing, causing exception is already logged
} catch (PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
return false;
} else {
throw new IllegalArgumentException("PushsaferAPIConnection is null!");
}
}
@SuppressWarnings("null")
private void asyncValidateUser() {
try {
connection.validateUser();
updateStatus(ThingStatus.ONLINE);
} catch (PushsaferCommunicationException | PushsaferConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="pushsafer" 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>Pushsafer Binding</name>
<description>With Pushsafer you can send &amp; receive push notifications in real time, easily and securely on your
iPhone, iPad, Android, Windows mobile or Windows desktop device as well as on your browser (Chrome, Firefox, Opera
&amp; Yandex)!</description>
</binding:binding>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:pushsafer:pushsafer-account">
<parameter name="apikey" type="text" required="true">
<context>password</context>
<label>Private or Alias Key</label>
<description>Your Private or Alias to access the Pushsafer Message API.</description>
</parameter>
<parameter name="user" type="text" required="true">
<label>Username</label>
<description>Your username or email address to validate against the Pushsafer Message API.</description>
</parameter>
<parameter name="device" type="text">
<label>Device ID</label>
<description>Device ID or Device Group ID to which devices you want to send push-notifications ("a" for all available
devices).</description>
<default>a</default>
</parameter>
<parameter name="title" type="text">
<label>Title</label>
<description>The default title of a message.</description>
<default>openHAB</default>
</parameter>
<parameter name="format" type="text">
<label>Format</label>
<description>The default format of a message.</description>
<default>none</default>
<options>
<option value="none">None</option>
<option value="html">HTML</option>
<option value="monospace">monospace</option>
</options>
</parameter>
<parameter name="sound" type="text">
<label>Notification Sound</label>
<description>The default notification sound on target device.</description>
<default>1</default>
</parameter>
<parameter name="vibration" type="text">
<label>Vibration</label>
<description>How often the device should vibrate. empty=device default or a number 1-3</description>
<default>1</default>
</parameter>
<parameter name="icon" type="text">
<label>Notification Icon</label>
<description>The default notification icon on target device.</description>
<default>1</default>
</parameter>
<parameter name="color" type="text">
<label>Icon Color</label>
<description>The color (hexadecimal) of notification icon (e.g. #FF0000).</description>
<default></default>
</parameter>
<parameter name="url" type="text">
<label>URL</label>
<description>URL or URL Scheme send with notification.</description>
</parameter>
<parameter name="urlTitle" type="text">
<label>URL Title</label>
<description>Title of URL.</description>
</parameter>
<parameter name="retry" type="integer" min="0" max="43200" step="1" unit="m">
<advanced>true</advanced>
<label>Retry</label>
<description>Integer 0-43200: Time in minutes, after a message automatically gets purged.</description>
<default>0</default>
</parameter>
<parameter name="expire" type="integer" min="0" max="10800" step="60" unit="s">
<advanced>true</advanced>
<label>Expire</label>
<description>Integer 60-10800 (60s steps): Time in seconds, after the retry/resend should stop.</description>
<default>0</default>
</parameter>
<parameter name="confirm" type="integer" min="0" max="10800" step="10" unit="s">
<advanced>true</advanced>
<label>Confirm</label>
<description>Integer 10-10800 (10s steps): Time in seconds after which a message should be sent again before it is
confirmed.</description>
<default>0</default>
</parameter>
<parameter name="time2live" type="integer" min="0" max="43200" step="1" unit="m">
<advanced>true</advanced>
<label>Time to Live</label>
<description>Time in minutes, after a message automatically gets purged.</description>
<default>0</default>
</parameter>
<parameter name="answer" type="boolean">
<advanced>true</advanced>
<label>Answer</label>
<description>true = Enable reply to push notifications, false otherwise.</description>
<default>false</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,56 @@
# user defined messages
offline.conf-error-missing-apikey = The 'Private Key' parameter must be configured.
offline.conf-error-missing-user = The 'Username' parameter must be configured.
offline.conf-error-missing-device = The 'Device ID' parameter must be configured.
offline.conf-error-unknown = An unknown error occurred.
# actions
sendPushsaferMessageActionLabel = send a plain text message
sendPushsaferMessageActionDescription = This method is used to send a plain text message.
sendPushsaferMessageActionOutputLabel = Sent
sendPushsaferMessageActionOutputDescription = true, if message has been sent successfully
sendPushsaferMessageActionInputMessageLabel = Message
sendPushsaferMessageActionInputMessageDescription = Message to be sent.
sendPushsaferMessageActionInputTitleLabel = Title
sendPushsaferMessageActionInputTitleDescription = The title of the message.
sendPushsaferURLMessageActionLabel = send a plain text message with an URL
sendPushsaferURLMessageActionDescription = This method is used to send a message with an URL.
sendPushsaferMessageActionInputURLLabel = URL
sendPushsaferMessageActionInputURLDescription = A supplementary URL to show with the message.
sendPushsaferMessageActionInputURLTitleLabel = URL Title
sendPushsaferMessageActionInputURLTitleDescription = A title for the URL, otherwise just the URL is shown.
sendHTMLMessageActionLabel = send a HTML message
sendHTMLMessageActionDescription = This method is used to send a HTML message.
sendPushsaferMonospaceMessageActionLabel = send a monospace message
sendPushsaferMonospaceMessageActionDescription = This method is used to send a monospace message.
sendPushsaferAttachmentMessageActionLabel = send a plain text message with an image attachment
sendPushsaferAttachmentMessageActionDescription = This method is used to send a message with an image attachment.
sendPushsaferMessageActionInputAttachmentLabel = Image Attachment
sendPushsaferMessageActionInputAttachmentDescription = A local path or url to the image.
sendPushsaferMessageActionInputContentTypeLabel = Image Type
sendPushsaferMessageActionInputContentTypeDescription = The image type of the attachment. Defaults to "jpeg", possible values "jpeg,png,gif".
sendPushsaferMessageActionInputAuthenticationLabel = Authentication
sendPushsaferMessageActionInputAuthenticationDescription = Basic access authentication for HTTP(S) requests. Default: "", Example: "user:password".
sendPushsaferPriorityMessageActionLabel = send a priority message
sendPushsaferPriorityMessageActionDescription = This method is used to send a priority message.
sendPushsaferPriorityMessageActionOutputLabel = Receipt
sendPushsaferPriorityMessageActionOutputDescription = Receipt, if priority message sent successfully.
sendPushsaferMessageActionInputPriorityLabel = Priority
sendPushsaferMessageActionInputPriorityDescription = Priority to be used. Defaults to 2.
cancelPushsaferPriorityMessageActionLabel = cancel a priority message
cancelPushsaferPriorityMessageActionDescription = This method is used to cancel a priority message.
cancelPushsaferPriorityMessageActionOnputLabel = Cancelled
cancelPushsaferPriorityMessageActionOnputDescription = true, if message has been cancelled successfully.
cancelPushsaferPriorityMessageActionInputReceiptLabel = Receipt
cancelPushsaferPriorityMessageActionInputReceiptDescription = Receipt of the message to be canceled.
sendPushsaferMessageToDeviceActionLabel = send a plain text message to a specific device
sendPushsaferMessageToDeviceActionDescription = This method is used to send a message to a specific device.
sendPushsaferMessageActionInputDeviceLabel = Device
sendPushsaferMessageActionInputDeviceDescription = The name of a specific device (multiple devices may be separated by a comma).

View File

@ -0,0 +1,97 @@
# binding
binding.pushsafer.description = Mit Pushsafer kannst du in Echtzeit, einfach &amp; sicher, Push-Benachrichtigungen auf dein iPhone, iPad, Android, Windows mobile oder Windows Desktop Gerät sowie an deinen Browser (Chrome, Firefox, Opera &amp; Yandex) senden & empfangen!.
# thing types
thing-type.pushsafer.pushsafer-account.label = Pushsafer Konto
thing-type.pushsafer.pushsafer-account.description = Ermöglicht den Zugriff auf die Pushsafer Message API.
# thing type config description
thing-type.config.pushsafer.pushsafer-account.apikey.label = Privater oder Alias Schlüssel
thing-type.config.pushsafer.pushsafer-account.apikey.description = Privater oder Alias Schlüssel für den Zugriff auf die Pushsafer Message API.
thing-type.config.pushsafer.pushsafer-account.user.label = Benutzername oder E-mAil Adresse für den Zugriff auf die Pushsafer Message API.
thing-type.config.pushsafer.pushsafer-account.user.description = Benutzername oder E-mAil Adresse .
thing-type.config.pushsafer.pushsafer-account.device.label = Geräte ID oder Geräte-Gruppen ID
thing-type.config.pushsafer.pushsafer-account.device.description = Geräte ID oder Geräte-Gruppen ID an welche Geräte die Nachrichten gesendet werden sollen.
thing-type.config.pushsafer.pushsafer-account.title.label = Titel
thing-type.config.pushsafer.pushsafer-account.title.description = Standardtitel der Nachricht.
thing-type.config.pushsafer.pushsafer-account.format.label = Format
thing-type.config.pushsafer.pushsafer-account.format.description = Standardformat der Nachricht.
thing-type.config.pushsafer.pushsafer-account.sound.label = Benachrichtigungs-Ton
thing-type.config.pushsafer.pushsafer-account.sound.description = Standardbenachrichtigungs-Ton auf dem Endgerät.
thing-type.config.pushsafer.pushsafer-account.vibration.label = Vibration
thing-type.config.pushsafer.pushsafer-account.vibration.description = Wie oft das Gerät vibrieren soll. Leer=Geräte-Standard oder 0-3
thing-type.config.pushsafer.pushsafer-account.icon.label = Benachrichtigungs-Icon
thing-type.config.pushsafer.pushsafer-account.icon.description = Standardbenachrichtigungs-Icon auf dem Endgerät.
thing-type.config.pushsafer.pushsafer-account.color.label = Icon Farbe
thing-type.config.pushsafer.pushsafer-account.color.description = Standard Farbe des Icons (hexadezimal z.B. #FF0000)
thing-type.config.pushsafer.pushsafer-account.url.label = URL
thing-type.config.pushsafer.pushsafer-account.url.description = URL oder URL Schema welche mit der Benachrichtigung versendet wird.
thing-type.config.pushsafer.pushsafer-account.urlTitle.label = URL Titel
thing-type.config.pushsafer.pushsafer-account.urlTitle.description = Titel der URL.
thing-type.config.pushsafer.pushsafer-account.retry.label = Wiederholungen
thing-type.config.pushsafer.pushsafer-account.retry.description = Ganzzahl 60-10800 (60er Schritte): Zeit in Sekunden, nach der die Nachricht erneut versendet werden soll.
thing-type.config.pushsafer.pushsafer-account.expire.label = Verfall
thing-type.config.pushsafer.pushsafer-account.expire.description = Ganzzahl 60-10800: Zeit in Sekunden, nach der das erneute Versenden der Nachrichten gestoppt werden soll.
thing-type.config.pushsafer.pushsafer-account.confirm.label = Bestätigung
thing-type.config.pushsafer.pushsafer-account.confirm.description = Ganzzahl 10-10800 (10s Schritte) Zeit in Sekunden, nachdem eine Nachricht erneut gesendet werden soll, bis diese bestätigt wird.
thing-type.config.pushsafer.pushsafer-account.answer.label = Antworten
thing-type.config.pushsafer.pushsafer-account.answer.description = Ermöglicht das Antworten auf Push-Benachrichtigungen. 1 = auf diese Nachricht kann geantwortet werden, 0 = auf diese Nachricht kann nicht geantwortet werden.
thing-type.config.pushsafer.pushsafer-account.time2live.label = Time to Live
thing-type.config.pushsafer.pushsafer-account.time2live.description = Ganzzahl 0-43200: Zeit in Minuten, nach der die Nachricht automatisch gelöscht wird. 0 oder leer = nicht automatisch löschen.
# user defined messages
offline.conf-error-missing-apikey = Der Parameter 'apikey' muss konfiguriert werden.
offline.conf-error-missing-user = Der Parameter 'user' muss konfiguriert werden.
offline.conf-error-missing-device = Der Parameter 'device' muss konfiguriert werden.
offline.conf-error-unknown = Ein unbekannter Fehler ist aufgetreten.
# actions
sendPushsaferMessageActionLabel = eine Textnachricht senden
sendPushsaferMessageActionDescription = Action zum Versenden einer Textnachricht.
sendPushsaferMessageActionOutputLabel = Gesendet
sendPushsaferMessageActionOutputDescription = true, wenn die Nachricht erfolgreich versendet wurde.
sendPushsaferMessageActionInputMessageLabel = Nachricht
sendPushsaferMessageActionInputMessageDescription = Die Nachricht.
sendPushsaferMessageActionInputTitleLabel = Titel
sendPushsaferMessageActionInputTitleDescription = Titel der Nachricht.
sendPushsaferURLMessageActionLabel = eine Textnachricht mit URL senden
sendPushsaferURLMessageActionDescription = Action zum Versenden einer Textnachricht mit einer URL.
sendPushsaferMessageActionInputURLLabel = URL
sendPushsaferMessageActionInputURLDescription = Eine zusätzliche URL, die mit der Nachricht angezeigt werden soll.
sendPushsaferMessageActionInputURLTitleLabel = URL Title
sendPushsaferMessageActionInputURLTitleDescription = Ein Titel für die URL, andernfalls wird nur die URL angezeigt.
sendHTMLMessageActionLabel = eine HTML-Nachricht senden
sendHTMLMessageActionDescription = Action zum Versenden einer HTML-Nachricht.
sendPushsaferMonospaceMessageActionLabel = eine monospace-Nachricht senden
sendPushsaferMonospaceMessageActionDescription = Action zum Versenden einer monospace-Nachricht.
sendPushsaferAttachmentMessageActionLabel = eine Textnachricht mit Bild-Anhang senden
sendPushsaferAttachmentMessageActionDescription = Action zum Versenden einer Textnachricht mit Bild-Anhang.
sendPushsaferMessageActionInputAttachmentLabel = Bild-Anhang
sendPushsaferMessageActionInputAttachmentDescription = Lokaler Pfad oder URL zum Anhang.
sendPushsaferMessageActionInputContentTypeLabel = Bild-Typ
sendPushsaferMessageActionInputContentTypeDescription = Der Bild-Typ für den Anhang. Default: "jpeg", mögliche Werte "jpeg,png,gif".
sendPushsaferMessageActionInputAuthenticationLabel = Authentifizierung
sendPushsaferMessageActionInputAuthenticationDescription = Basisauthentifizierung für HTTP(S) Aufrufe. Default: "", Beispiel: "user:passwort".
sendPushsaferPriorityMessageActionLabel = eine Prioritätsnachricht senden
sendPushsaferPriorityMessageActionDescription = Action zum Versenden einer Prioritätsnachricht.
sendPushsaferPriorityMessageActionOutputLabel = Receipt
sendPushsaferPriorityMessageActionOutputDescription = ID der Prioritätsnachricht, wenn diese erfolgreich versendet wurde.
sendPushsaferMessageActionInputPriorityLabel = Priorität
sendPushsaferMessageActionInputPriorityDescription = Die Priorität. Default: 2.
cancelPushsaferPriorityMessageActionLabel = eine Prioritätsnachricht annullieren
cancelPushsaferPriorityMessageActionDescription = Action zum Annullieren einer Prioritätsnachricht.
cancelPushsaferPriorityMessageActionOnputLabel = Annulliert
cancelPushsaferPriorityMessageActionOnputDescription = true, wenn die Prioritätsnachricht erfolgreich annulliert wurde.
cancelPushsaferPriorityMessageActionInputReceiptLabel = Receipt
cancelPushsaferPriorityMessageActionInputReceiptDescription = Die ID der Prioritätsnachricht.
sendPushsaferMessageToDeviceActionLabel = eine Nachricht an ein Endgerät
sendPushsaferMessageToDeviceActionDescription = Action zum Versenden einer Nachricht an ein Endgerät.
sendPushsaferMessageActionInputDeviceLabel = Endgerät
sendPushsaferMessageActionInputDeviceDescription = Der Name des Endgeräts (mehrere Geräte können durch ein Komma getrennt werden).

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pushsafer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="pushsafer-account">
<label>Pushsafer Account</label>
<description>Provides access to the Pushsafer Messages API.</description>
<representation-property>apikey</representation-property>
<config-description-ref uri="thing-type:pushsafer:pushsafer-account"/>
</thing-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,166 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pushsafer.internal.actions;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.openhab.binding.pushsafer.internal.connection.PushsaferConfigurationException;
import org.openhab.binding.pushsafer.internal.connection.PushsaferMessageBuilder;
import org.openhab.binding.pushsafer.internal.handler.PushsaferAccountHandler;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingHandler;
/**
* Unit tests for {@link PushsaferActions}.
*
* @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.WARN)
public class PushsaferActionsTest {
private static final String MESSAGE = "My Message";
private static final String TITLE = "My Title";
private static final String URL = "https://www.test.com";
private static final String URL_TITLE = "Some Link";
private static final String RECEIPT = "12345";
@NonNullByDefault
private final ThingActions thingActionsStub = new ThingActions() {
@Override
public void setThingHandler(ThingHandler handler) {
}
@Override
public @Nullable ThingHandler getThingHandler() {
return null;
}
};
private @Mock PushsaferAccountHandler mockPushsaferAccountHandler;
private PushsaferActions pushsaferThingActions;
@BeforeEach
public void setUp() throws PushsaferConfigurationException {
pushsaferThingActions = new PushsaferActions();
when(mockPushsaferAccountHandler.getDefaultPushsaferMessageBuilder(any()))
.thenReturn(PushsaferMessageBuilder.getInstance("key", "user"));
when(mockPushsaferAccountHandler.sendPushsaferMessage(any())).thenReturn(Boolean.TRUE);
when(mockPushsaferAccountHandler.sendPushsaferPriorityMessage(any())).thenReturn(RECEIPT);
}
// sendPushsaferMessage
@Test
public void testSendMessageThingActionsIsNotPushsaferThingActions() {
assertThrows(ClassCastException.class,
() -> PushsaferActions.sendPushsaferMessage(thingActionsStub, MESSAGE, TITLE));
}
@Test
public void testSendMessageThingHandlerIsNull() {
assertThrows(RuntimeException.class,
() -> PushsaferActions.sendPushsaferMessage(pushsaferThingActions, MESSAGE, TITLE));
}
@Test
public void testSendMessageWithoutTitle() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
boolean sent = PushsaferActions.sendPushsaferMessage(pushsaferThingActions, MESSAGE, null);
assertThat(sent, is(true));
}
@Test
public void testSendMessage() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
boolean sent = PushsaferActions.sendPushsaferMessage(pushsaferThingActions, MESSAGE, TITLE);
assertThat(sent, is(true));
}
// sendPushsaferURLMessage
@Test
public void testSendURLMessageThingActionsIsNotPushsaferThingActions() {
assertThrows(ClassCastException.class,
() -> PushsaferActions.sendPushsaferURLMessage(thingActionsStub, MESSAGE, TITLE, URL, URL_TITLE));
}
@Test
public void testSendURLMessageThingHandlerIsNull() {
assertThrows(RuntimeException.class,
() -> PushsaferActions.sendPushsaferURLMessage(pushsaferThingActions, MESSAGE, TITLE, URL, URL_TITLE));
}
@Test
public void testSendURLMessageWithoutTitle() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
boolean sent = PushsaferActions.sendPushsaferURLMessage(pushsaferThingActions, MESSAGE, null, URL, URL_TITLE);
assertThat(sent, is(true));
}
@Test
public void testSendURLMessageWithoutURLTitle() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
boolean sent = PushsaferActions.sendPushsaferURLMessage(pushsaferThingActions, MESSAGE, TITLE, URL, null);
assertThat(sent, is(true));
}
@Test
public void testSendURLMessage() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
boolean sent = PushsaferActions.sendPushsaferURLMessage(pushsaferThingActions, MESSAGE, TITLE, URL, URL_TITLE);
assertThat(sent, is(true));
}
// sendPushsaferPriorityMessage
@Test
public void testSendPriorityMessageThingActionsIsNotPushsaferThingActions() {
assertThrows(ClassCastException.class, () -> PushsaferActions.sendPushsaferPriorityMessage(thingActionsStub,
MESSAGE, TITLE, PushsaferMessageBuilder.EMERGENCY_PRIORITY));
}
@Test
public void testSendPriorityMessageThingHandlerIsNull() {
assertThrows(RuntimeException.class, () -> PushsaferActions.sendPushsaferPriorityMessage(pushsaferThingActions,
MESSAGE, TITLE, PushsaferMessageBuilder.EMERGENCY_PRIORITY));
}
@Test
public void testSendPriorityMessageWithoutTitle() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
String receipt = PushsaferActions.sendPushsaferPriorityMessage(pushsaferThingActions, MESSAGE, null,
PushsaferMessageBuilder.EMERGENCY_PRIORITY);
assertThat(receipt, is(RECEIPT));
}
@Test
public void testSendPriorityMessage() {
pushsaferThingActions.setThingHandler(mockPushsaferAccountHandler);
String receipt = PushsaferActions.sendPushsaferPriorityMessage(pushsaferThingActions, MESSAGE, TITLE,
PushsaferMessageBuilder.EMERGENCY_PRIORITY);
assertThat(receipt, is(RECEIPT));
}
}

View File

@ -269,7 +269,7 @@
<module>org.openhab.binding.pulseaudio</module>
<module>org.openhab.binding.pushbullet</module>
<module>org.openhab.binding.pushover</module>
<module>org.openhab.binding.qbus</module>
<module>org.openhab.binding.pushsafer</module>
<module>org.openhab.binding.radiothermostat</module>
<module>org.openhab.binding.regoheatpump</module>
<module>org.openhab.binding.revogi</module>