diff --git a/CODEOWNERS b/CODEOWNERS
index 2ca976d6f5e9f..63afc660f4523 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -122,6 +122,7 @@
/bundles/org.openhab.binding.plugwise/ @wborn
/bundles/org.openhab.binding.powermax/ @lolodomo
/bundles/org.openhab.binding.pulseaudio/ @peuter
+/bundles/org.openhab.binding.pushbullet/ @hakan42
/bundles/org.openhab.binding.regoheatpump/ @crnjan
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
/bundles/org.openhab.binding.rme/ @kgoderis
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index bf733d6844d3b..6f00784adc50a 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -600,6 +600,11 @@
org.openhab.binding.pulseaudio
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.pushbullet
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.regoheatpump
diff --git a/bundles/org.openhab.binding.pushbullet/.classpath b/bundles/org.openhab.binding.pushbullet/.classpath
new file mode 100644
index 0000000000000..a5d95095ccaaf
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.pushbullet/.project b/bundles/org.openhab.binding.pushbullet/.project
new file mode 100644
index 0000000000000..89f11546d5550
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.pushbullet
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.pushbullet/NOTICE b/bundles/org.openhab.binding.pushbullet/NOTICE
new file mode 100644
index 0000000000000..4c20ef446c1e4
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab2-addons
diff --git a/bundles/org.openhab.binding.pushbullet/README.md b/bundles/org.openhab.binding.pushbullet/README.md
new file mode 100644
index 0000000000000..b2d3871dc6df1
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/README.md
@@ -0,0 +1,162 @@
+# Pushbullet Binding
+
+The Pushbullet binding allows you to notify iOS, Android & Windows 10 Phone & Desktop devices of a message using the Pushbullet API web service.
+
+## Supported Things
+
+This binding supports a generic "bot" which is a representation of the client.
+
+## Discovery
+
+This binding provides no discovery.
+The desired bots must be configured manually or via a things file.
+
+## Binding Configuration
+
+The binding has no configuration options itself, all configuration is done at 'Things' level.
+
+## Thing Configuration
+
+### Bot (`bot`)
+
+The bot thing is used to send messages to other recipients.
+It has the following parameters:
+
+| Config | Description | Required | Advanced |
+|------------|------------------------------------------------------------------|----------|----------|
+| token | Pushbullet [API token](#obtaining-an-api-key) to send to devices | Yes | False |
+| name | Explicit Name, for later use when the bot can receive messages | No | True |
+| apiUrlBase | Address of own Pushbullet server, for testing purposes | No | True |
+
+```java
+Thing pushbullet:bot:r2d2 "R2D2" @ "Somewhere" [ token = "verysecretwonttellyou" ]
+
+```
+
+## Channels
+
+| Channel ID | Channel Description | Supported item type | Advanced |
+|------------|-------------------------------------------------|----------------------|----------|
+| recipient | for later use when the bot can receive messages | String | False |
+| title | for later use when the bot can receive messages | String | False |
+| message | for later use when the bot can receive messages | String | False |
+
+## Rule Action
+
+This binding includes rule actions for sending notes.
+Two different actions available:
+
+* `sendPushbulletNote(String recipient, String messsage)`
+* `sendPushbulletNote(String recipient, String title, String messsage)`
+
+Since there is a separate rule action instance for each `bot` thing, this needs to be retrieved through `getActions(scope, thingUID)`.
+The first parameter always has to be `pushbullet` and the second is the full Thing UID of the bot that should be used.
+Once this action instance is retrieved, you can invoke the action method on it.
+
+Examples:
+
+```
+val actions = getActions("pushbullet", "pushbullet:bot:r2d2")
+val result = actions.sendPushbulletNote("someone@example.com", "R2D2 talks here...", "This is the pushed note.")
+```
+
+## Full Example
+
+_Provide a full usage example based on textual configuration files (*.things, *.items, *.sitemap)._
+
+pushbullet.things:
+
+```java
+Thing pushbullet:bot:r2d2 "R2D2" @ "Somewhere" [ token = "verysecretwonttellyou" ]
+
+```
+
+pushbullet.items
+
+```java
+Switch Pushbullet_R2D2_Button "Pushbullet Action bot R2D2"
+```
+
+pushbullet.sitemap
+
+```java
+sitemap pushbullet label="Pushbullet"
+{
+ Switch item=Pushbullet_R2D2_Button
+}
+```
+
+pushbullet.rules
+
+```java
+rule "Pushbullet R2D2 changed"
+when
+ Item Pushbullet_R2D2_Button changed
+then
+ logInfo(filename, "Button R2D2 changed - OH2...")
+
+ if (Pushbullet_R2D2_Button.state == ON)
+ {
+ sendCommand(Pushbullet_R2D2_Button, OFF)
+
+ val actions = getActions("pushbullet", "pushbullet:bot:r2d2")
+ logInfo(filename, "Actions for 'R2D2' are: " + actions)
+
+ if (actions != null)
+ {
+ val result = actions.sendPushbulletNote("someone@example.com", "Title R2D2 OH2", "This has been sent by the new R2D2 bot")
+ logInfo(filename, "Result of send action is: " + result)
+ }
+ }
+end
+```
+
+
+
+
+
+## Creating an account for your bot(s)
+
+The pushbullet accounts are bound to either Google or Facebook accounts.
+
+- Create a bot account with either Facebook or Google
+- Go to ""
+- Chose to either "Sign up with Google" or "Sign up with Facebook".
+- Complete the signup process as guided by the pushbullet web site.
+- Continue with "Obtaining an API key".
+
+## Obtaining an API key
+
+The API keys are bound to the pushbullet account.
+
+- Go to the pushbullet site.
+- Log in with either your personal account or the one you created for your bot.
+- Go to ""
+- Click on "Create Access Token".
+- Copy the token created on the site.
+
+You must at least provide an API token (Private or Alias Key from Pushbullet.com) and a message in some manner before a message can be pushed.
+All other parameters are optional.
+If you use an alias key, the parameters (device, icon, sound, vibration) are overwritten by the alias setting on pushbullet.
+
+## Rate limits
+
+As of 2019, free accounts have a limit of 100 pushes per month.
+This action does not evaluate the rate limiting headers though.
+
+## Translation
+
+This project is being translated on transifex.
+If you want to help, please join the project at the URL:
+
+- https://www.transifex.com/hakan42/openhab-binding-pushbullet/dashboard/
+
+## Libraries
+
+This action has been written without using libraries as jpushbullet or jpushbullet2.
+Both of those libraries use various libraries themselves which makes integrating them into openHAB a challenge.
+
+## pushbullet API
+
+-
+-
diff --git a/bundles/org.openhab.binding.pushbullet/pom.xml b/bundles/org.openhab.binding.pushbullet/pom.xml
new file mode 100644
index 0000000000000..f7d2623e8c3f2
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.0-SNAPSHOT
+
+
+ org.openhab.binding.pushbullet
+
+ openHAB Add-ons :: Bundles :: Pushbullet Binding
+
+
+
+ javax.mail
+ mail
+ 1.4.7
+ provided
+
+
+
+
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/feature/feature.xml b/bundles/org.openhab.binding.pushbullet/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..f0dfc37f14ad8
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${project.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.pushbullet/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletBindingConstants.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletBindingConstants.java
new file mode 100644
index 0000000000000..d60d350de3423
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletBindingConstants.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * The {@link PushbulletBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletBindingConstants {
+
+ private static final String BINDING_ID = "pushbullet";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_BOT = new ThingTypeUID(BINDING_ID, "bot");
+
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BOT);
+
+ // List of all Channel ids
+ public static final String RECIPIENT = "recipient";
+ public static final String TITLE = "title";
+ public static final String MESSAGE = "message";
+
+ // Binding logic constants
+ public static final String API_METHOD_PUSHES = "pushes";
+
+ public static final int TIMEOUT = 30 * 1000; // 30 seconds
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletConfiguration.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletConfiguration.java
new file mode 100644
index 0000000000000..784e4553b99fa
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletConfiguration.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link PushbulletConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletConfiguration {
+
+ private @Nullable String name;
+
+ private String token = "invalid";
+
+ private String apiUrlBase = "invalid";
+
+ public @Nullable String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getApiUrlBase() {
+ return apiUrlBase;
+ }
+
+ public void setApiUrlBase(String apiUrlBase) {
+ this.apiUrlBase = apiUrlBase;
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletHandlerFactory.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletHandlerFactory.java
new file mode 100644
index 0000000000000..4b73f6c093613
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/PushbulletHandlerFactory.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal;
+
+import static org.openhab.binding.pushbullet.internal.PushbulletBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.openhab.binding.pushbullet.internal.handler.PushbulletHandler;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link PushbulletHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.pushbullet", service = ThingHandlerFactory.class)
+public class PushbulletHandlerFactory extends BaseThingHandlerFactory {
+
+ @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_BOT.equals(thingTypeUID)) {
+ return new PushbulletHandler(thing);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/action/PushbulletActions.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/action/PushbulletActions.java
new file mode 100644
index 0000000000000..c8716bae1cdf1
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/action/PushbulletActions.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal.action;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.binding.ThingActions;
+import org.eclipse.smarthome.core.thing.binding.ThingActionsScope;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.openhab.binding.pushbullet.internal.handler.PushbulletHandler;
+import org.openhab.core.automation.annotation.ActionInput;
+import org.openhab.core.automation.annotation.ActionOutput;
+import org.openhab.core.automation.annotation.RuleAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PushbulletActions} class defines rule actions for sending notifications
+ *
+ * @author Hakan Tandogan - Initial contribution
+ */
+@ThingActionsScope(name = "pushbullet")
+@NonNullByDefault
+public class PushbulletActions implements ThingActions {
+
+ private final Logger logger = LoggerFactory.getLogger(PushbulletActions.class);
+
+ private @Nullable PushbulletHandler handler;
+
+ @Override public void setThingHandler(@Nullable ThingHandler handler) {
+ this.handler = (PushbulletHandler) handler;
+ }
+
+ @Override public @Nullable ThingHandler getThingHandler() {
+ return this.handler;
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletNoteLabel", description = "@text/actionSendPushbulletNoteDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletNote(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc") @Nullable String recipient,
+ @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc") @Nullable String title,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc") @Nullable String message) {
+ logger.trace("sendPushbulletNote '{}', '{}', '{}'", recipient, title, message);
+
+ // Use local variable so the SAT check can do proper flow analysis
+ PushbulletHandler localHandler = handler;
+
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPush(recipient, title, message, "note");
+ }
+
+ public static boolean sendPushbulletNote(@Nullable ThingActions actions, @Nullable String recipient,
+ @Nullable String title, @Nullable String message) {
+ if (actions instanceof PushbulletActions) {
+ return ((PushbulletActions) actions).sendPushbulletNote(recipient, title, message);
+ } else {
+ throw new IllegalArgumentException("Instance is not a PushbulletActions class ( " + actions + " )");
+ }
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletNoteLabel", description = "@text/actionSendPushbulletNoteDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletNote(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc") @Nullable String recipient,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc") @Nullable String message) {
+ logger.trace("sendPushbulletNote '{}', '{}'", recipient, message);
+
+ // Use local variable so the SAT check can do proper flow analysis
+ PushbulletHandler localHandler = handler;
+
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPush(recipient, message, "note");
+ }
+
+ public static boolean sendPushbulletNote(@Nullable ThingActions actions, @Nullable String recipient,
+ @Nullable String message) {
+ if (actions instanceof PushbulletActions) {
+ return ((PushbulletActions) actions).sendPushbulletNote(recipient, message);
+ } else {
+ throw new IllegalArgumentException("Instance is not a PushbulletActions class ( " + actions + " )");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/handler/PushbulletHandler.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/handler/PushbulletHandler.java
new file mode 100644
index 0000000000000..c3bb000fe3ef4
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/handler/PushbulletHandler.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal.handler;
+
+import static org.openhab.binding.pushbullet.internal.PushbulletBindingConstants.*;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpResponseException;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.smarthome.core.thing.ThingStatusDetail;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerService;
+import org.eclipse.smarthome.io.net.http.HttpUtil;
+import org.openhab.binding.pushbullet.internal.action.PushbulletActions;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingStatus;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.openhab.binding.pushbullet.internal.PushbulletConfiguration;
+import org.openhab.binding.pushbullet.internal.model.Push;
+import org.openhab.binding.pushbullet.internal.model.PushResponse;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * The {@link PushbulletHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(PushbulletHandler.class);
+
+ private final Gson gson = new GsonBuilder().create();
+
+ private static final Version VERSION = FrameworkUtil.getBundle(PushbulletHandler.class).getVersion();
+
+ private static final Pattern CHANNEL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
+
+ private @Nullable PushbulletConfiguration config;
+
+ public PushbulletHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("About to handle {} on {}", command, channelUID);
+
+ // Future improvement: If recipient is already set, send a push on a command channel change
+ // check reconnect channel of the unifi binding for that
+
+ logger.debug("The Pushbullet binding is a read-only binding and cannot handle command '{}'.", command);
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Start initializing!");
+ config = getConfigAs(PushbulletConfiguration.class);
+
+ // set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
+ // the framework is then able to reuse the resources from the thing handler initialization.
+ // we set this upfront to reliably check status updates in unit tests.
+ updateStatus(ThingStatus.UNKNOWN);
+
+ // Name and Token are both "required", so set the Thing immediately ONLINE.
+ updateStatus(ThingStatus.ONLINE);
+
+ logger.debug("Finished initializing!");
+ }
+
+ @Override
+ public Collection> getServices() {
+ return Collections.singleton(PushbulletActions.class);
+ }
+
+ public boolean sendPush(@Nullable String recipient, @Nullable String message, String type) {
+ return sendPush(recipient, "", message, type);
+ }
+
+ public boolean sendPush(@Nullable String recipient, @Nullable String title, @Nullable String message, String type) {
+ boolean result = false;
+
+ logger.debug("sendPush is called for ");
+ logger.debug("Thing {}", thing);
+ logger.debug("Thing Label: '{}'", thing.getLabel());
+
+ PushbulletConfiguration configuration = getConfigAs(PushbulletConfiguration.class);
+ logger.debug("CFG {}", configuration);
+ logger.debug("CFG Name '{}'", configuration.getName());
+
+ try {
+ logger.debug("Recipient is '{}'", recipient);
+ logger.debug("Title is '{}'", title);
+ logger.debug("Message is '{}'", message);
+
+ Push push = new Push();
+ push.setTitle(title);
+ push.setBody(message);
+ push.setType(type);
+
+ if (recipient != null) {
+ if (isValidEmail(recipient)) {
+ logger.debug("Recipient is an email address");
+ push.setEmail(recipient);
+ } else if (isValidChannel(recipient)) {
+ logger.debug("Recipient is a channel tag");
+ push.setChannel(recipient);
+ } else {
+ logger.warn("Invalid recipient: {}", recipient);
+ logger.warn("Message will be broadcast to all user's devices.");
+ }
+ }
+
+ logger.debug("Push: {}", push);
+
+ String request = gson.toJson(push);
+ logger.debug("Packed Request: {}", request);
+
+ Properties headers = new Properties();
+ headers.put(HttpHeader.USER_AGENT, "openHAB / Pushbullet binding " + VERSION.toString());
+ headers.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString());
+ headers.put("Access-Token", configuration.getToken());
+
+ logger.debug("Headers: {}", headers);
+
+ try (InputStream stream = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))) {
+
+ String pushAPI = configuration.getApiUrlBase() + "/" + API_METHOD_PUSHES;
+
+ String responseString = HttpUtil.executeUrl(HttpMethod.POST.asString(), pushAPI, headers, stream,
+ MimeTypes.Type.APPLICATION_JSON.asString(), TIMEOUT);
+
+ logger.debug("Got Response: {}", responseString);
+ PushResponse response = gson.fromJson(responseString, PushResponse.class);
+
+ logger.debug("Unpacked Response: {}", response);
+
+ stream.close();
+
+ if ((null != response) && (null == response.getPushError())) {
+ result = true;
+ }
+ }
+ catch (IOException e) {
+ logger.warn("IO problems pushing note: {}", e.getMessage());
+ }
+
+ } catch (java.lang.Error e) {
+ if (e.getCause() instanceof HttpResponseException) {
+ logger.debug("Possibly expired token, check on web site", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-httpresponseexception");
+ }
+
+ logger.debug("Error in getResponseString: ", e);
+ }
+
+ return result;
+ }
+
+ /**
+ * helper method checking if channel tag is valid.
+ *
+ * @param channel
+ * @return
+ */
+ private static boolean isValidChannel(String channel) {
+ Matcher m = CHANNEL_PATTERN.matcher(channel);
+ return m.matches();
+ }
+
+ /**
+ * helper method checking if email address is valid.
+ *
+ * @param email
+ * @return
+ */
+ private static boolean isValidEmail(String email) {
+ try {
+ InternetAddress emailAddr = new InternetAddress(email);
+ emailAddr.validate();
+ return true;
+ } catch (AddressException e) {
+ return false;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/Push.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/Push.java
new file mode 100644
index 0000000000000..e32ca0c711359
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/Push.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the push request sent to the API.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
+ */
+public class Push {
+
+ @SerializedName("title")
+ private String title;
+
+ @SerializedName("body")
+ private String body;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("email")
+ private String email;
+
+ @SerializedName("channel_tag")
+ private String channelTag;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getChannel() {
+ return channelTag;
+ }
+
+ public void setChannel(String channelTag) {
+ this.channelTag = channelTag;
+ }
+
+ @Override
+ public String toString() {
+ return "Push {" + "title='" + title + '\'' + ", body='" + body + '\'' + ", type='" + type + '\'' + ", email='"
+ + email + '\'' + ", channelTag='" + channelTag + '\'' + '}';
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushError.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushError.java
new file mode 100644
index 0000000000000..85f963d62daa7
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushError.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents errors in the response fetched from the API.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
+ */
+public class PushError {
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("message")
+ private String message;
+
+ @SerializedName("param")
+ private String param;
+
+ @SerializedName("cat")
+ private String cat;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getParam() {
+ return param;
+ }
+
+ public void setParam(String param) {
+ this.param = param;
+ }
+
+ public String getCat() {
+ return cat;
+ }
+
+ public void setCat(String cat) {
+ this.cat = cat;
+ }
+
+ @Override
+ public String toString() {
+ return "PushError {" + "type='" + type + '\'' + ", message='" + message + '\'' + ", param='" + param + '\''
+ + ", cat='" + cat + '\'' + '}';
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushResponse.java b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushResponse.java
new file mode 100644
index 0000000000000..7183d3b66f26f
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/java/org/openhab/binding/pushbullet/internal/model/PushResponse.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2010-2019 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.pushbullet.internal.model;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the answer to pushes provided by the API.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
+ */
+public class PushResponse {
+
+ @SerializedName("active")
+ private String active;
+
+ @SerializedName("iden")
+ private String iden;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("dismissed")
+ private Boolean dismissed;
+
+ @SerializedName("direction")
+ private String direction;
+
+ @SerializedName("sender_iden")
+ private String senderIdentifier;
+
+ @SerializedName("sender_email")
+ private String senderEmail;
+
+ @SerializedName("sender_email_normalized")
+ private String senderEmailNormalized;
+
+ @SerializedName("sender_name")
+ private String senderName;
+
+ @SerializedName("receiver_iden")
+ private String receiverIdentifier;
+
+ @SerializedName("receiver_email")
+ private String receiverEmail;
+
+ @SerializedName("receiver_email_normalized")
+ private String receiverEmailNormalized;
+
+ @SerializedName("title")
+ private String title;
+
+ @SerializedName("body")
+ private String body;
+
+ @SerializedName("error_code")
+ private String errorCode;
+
+ @SerializedName("error")
+ private PushError pushError;
+
+ public String getActive() {
+ return active;
+ }
+
+ public void setActive(String active) {
+ this.active = active;
+ }
+
+ public String getIden() {
+ return iden;
+ }
+
+ public void setIden(String iden) {
+ this.iden = iden;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public Boolean getDismissed() {
+ return dismissed;
+ }
+
+ public void setDismissed(Boolean dismissed) {
+ this.dismissed = dismissed;
+ }
+
+ public String getDirection() {
+ return direction;
+ }
+
+ public void setDirection(String direction) {
+ this.direction = direction;
+ }
+
+ public String getSenderIdentifier() {
+ return senderIdentifier;
+ }
+
+ public void setSenderIdentifier(String senderIdentifier) {
+ this.senderIdentifier = senderIdentifier;
+ }
+
+ public String getSenderEmail() {
+ return senderEmail;
+ }
+
+ public void setSenderEmail(String senderEmail) {
+ this.senderEmail = senderEmail;
+ }
+
+ public String getSenderEmailNormalized() {
+ return senderEmailNormalized;
+ }
+
+ public void setSenderEmailNormalized(String senderEmailNormalized) {
+ this.senderEmailNormalized = senderEmailNormalized;
+ }
+
+ public String getSenderName() {
+ return senderName;
+ }
+
+ public void setSenderName(String senderName) {
+ this.senderName = senderName;
+ }
+
+ public String getReceiverIdentifier() {
+ return receiverIdentifier;
+ }
+
+ public void setReceiverIdentifier(String receiverIdentifier) {
+ this.receiverIdentifier = receiverIdentifier;
+ }
+
+ public String getReceiverEmail() {
+ return receiverEmail;
+ }
+
+ public void setReceiverEmail(String receiverEmail) {
+ this.receiverEmail = receiverEmail;
+ }
+
+ public String getReceiverEmailNormalized() {
+ return receiverEmailNormalized;
+ }
+
+ public void setReceiverEmailNormalized(String receiverEmailNormalized) {
+ this.receiverEmailNormalized = receiverEmailNormalized;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ public String getErrorCode() {
+ return errorCode;
+ }
+
+ public void setErrorCode(String errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public PushError getPushError() {
+ return pushError;
+ }
+
+ public void setPushError(PushError pushError) {
+ this.pushError = pushError;
+ }
+
+ @Override
+ public String toString() {
+ return "PushResponse {" + "active='" + active + '\'' + ", iden='" + iden + '\'' + ", type='" + type + '\''
+ + ", dismissed=" + dismissed + ", direction='" + direction + '\'' + ", senderIdentifier='"
+ + senderIdentifier + '\'' + ", senderEmail='" + senderEmail + '\'' + ", senderEmailNormalized='"
+ + senderEmailNormalized + '\'' + ", senderName='" + senderName + '\'' + ", receiverIdentifier='"
+ + receiverIdentifier + '\'' + ", receiverEmail='" + receiverEmail + '\'' + ", receiverEmailNormalized='"
+ + receiverEmailNormalized + '\'' + ", title='" + title + '\'' + ", body='" + body + '\''
+ + ", errorCode='" + errorCode + '\'' + ", pushError=" + pushError + '}';
+ }
+}
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..18f0f94dfce51
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ @text/binding.pushbullet.name
+ @text/binding.pushbullet.description
+ Hakan Tandoğan
+
+
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/i18n/pushbullet_en.properties b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/i18n/pushbullet_en.properties
new file mode 100644
index 0000000000000..df79c02a7897b
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/i18n/pushbullet_en.properties
@@ -0,0 +1,17 @@
+# binding
+binding.pushbullet.name = Pushbullet Binding
+binding.pushbullet.description = The Pushbullet binding allows you to send messages to other users of the Pushbullet service.
+
+# action
+actionSendPushbulletNoteLabel = publish an Pushbullet message
+actionSendPushbulletNoteDesc = Publishes a Title to the given Pushbullet Recipient.
+
+actionSendPushbulletNoteInputRecipientLabel = Pushbullet Recipient
+actionSendPushbulletNoteInputRecipientDesc = The Recipient to publish a Title to.
+actionSendPushbulletNoteInputTitleLabel = Title
+actionSendPushbulletNoteInputTitleDesc = The Title to publish
+actionSendPushbulletNoteInputMessageLabel = Message
+actionSendPushbulletNoteInputMessageDesc = The Message to publish
+
+# error texts
+offline.conf-error-httpresponseexception = The pushbullet server reported an error, possibly an expired token. Check on web site
diff --git a/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..ba9c559eecfb7
--- /dev/null
+++ b/bundles/org.openhab.binding.pushbullet/src/main/resources/ESH-INF/thing/thing-types.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Bot to send messages with.
+
+
+
+
+
+
+
+
+
+
+ Explicit Name of Bot, if wanted
+ true
+
+
+
+
+ Token as obtained from the server
+
+
+
+
+ The Pushbullet API Server to use, for local testing
+ https://api.pushbullet.com/v2
+ true
+
+
+
+
+
+
+
+ String
+
+ Mail address or Channel Name
+
+
+
+ String
+
+ Title of the message
+
+
+
+ String
+
+ The text that is to be sent
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index dd57b7d15505c..19dac9996f14d 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -156,6 +156,7 @@
org.openhab.binding.plugwise
org.openhab.binding.powermax
org.openhab.binding.pulseaudio
+ org.openhab.binding.pushbullet
org.openhab.binding.regoheatpump
org.openhab.binding.rfxcom
org.openhab.binding.rme