diff --git a/addons/binding/org.openhab.binding.ftpupload/.classpath b/addons/binding/org.openhab.binding.ftpupload/.classpath new file mode 100644 index 0000000000000..c9d8a81b5173f --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.ftpupload/.project b/addons/binding/org.openhab.binding.ftpupload/.project new file mode 100644 index 0000000000000..aad3a6997b495 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/.project @@ -0,0 +1,33 @@ + + + org.openhab.binding.ftpupload + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/addons/binding/org.openhab.binding.ftpupload/ESH-INF/binding/binding.xml b/addons/binding/org.openhab.binding.ftpupload/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..1cc2058021475 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/ESH-INF/binding/binding.xml @@ -0,0 +1,22 @@ + + + + FTP Upload Binding + This binding is for receiving files via FTP. + Pauli Anttila + + + + + TCP port of the FTP server + 2121 + + + + The number of seconds before an inactive client is disconnected. If this value is set to 0, the idle time is disabled. + 60 + + + diff --git a/addons/binding/org.openhab.binding.ftpupload/ESH-INF/thing/thing-types.xml b/addons/binding/org.openhab.binding.ftpupload/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..8a28bd28ea34c --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/ESH-INF/thing/thing-types.xml @@ -0,0 +1,59 @@ + + + + + + Receive image files via FTP. + + + + + + + + + + Username + + + + Password + password + + + + + + Image + + Image received via FTP + + + + + Filename to match received files. Supports regular expression patterns. + .* + + + + + trigger + + + + + + + + + + Filename to match received files. Supports regular expression patterns. + .* + + + + + diff --git a/addons/binding/org.openhab.binding.ftpupload/META-INF/MANIFEST.MF b/addons/binding/org.openhab.binding.ftpupload/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000..e32ebf403435c --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/META-INF/MANIFEST.MF @@ -0,0 +1,29 @@ +Manifest-Version: 1.0 +Bundle-ActivationPolicy: lazy +Bundle-ManifestVersion: 2 +Bundle-Name: FTP Upload Binding +Bundle-SymbolicName: org.openhab.binding.ftpupload;singleton:=true +Bundle-Vendor: openHAB +Bundle-Version: 2.3.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ClassPath: ., + lib/ftplet-api-1.1.0.jar, + lib/ftpserver-core-1.1.0.jar, + lib/mina-core-2.0.16.jar +Export-Package: + org.openhab.binding.ftpupload, + org.openhab.binding.ftpupload.handler +Import-Package: + org.apache.commons.lang, + org.eclipse.jdt.annotation;resolution:=optional, + org.eclipse.smarthome.config.core, + org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.thing, + org.eclipse.smarthome.core.thing.binding, + org.eclipse.smarthome.core.thing.binding.builder, + org.eclipse.smarthome.core.thing.type, + org.eclipse.smarthome.core.types, + org.eclipse.smarthome.io.net.http, + org.osgi.service.component, + org.slf4j +Service-Component: OSGI-INF/*.xml diff --git a/addons/binding/org.openhab.binding.ftpupload/OSGI-INF/.gitignore b/addons/binding/org.openhab.binding.ftpupload/OSGI-INF/.gitignore new file mode 100644 index 0000000000000..6722cd96e785a --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/OSGI-INF/.gitignore @@ -0,0 +1 @@ +*.xml diff --git a/addons/binding/org.openhab.binding.ftpupload/README.md b/addons/binding/org.openhab.binding.ftpupload/README.md new file mode 100644 index 0000000000000..9df77d9ad8388 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/README.md @@ -0,0 +1,174 @@ +# FTP Upload Binding + +This binding can be used to receive image files from FTP clients. +The binding acts as a FTP server. +Images stored on the FTP server are not saved to the file system, therefore the binding shouldn't cause any problems on flash based openHAB installations. + +## Supported Things + +This binding supports Things of type ```ftpupload```. +Every Thing is identified by FTP user name. +Therefore, every thing should use unique user name to login FTP server. + +## Discovery + +Automatic discovery is not supported. + +## Binding Configuration + +The binding has the following configuration options: + +| Parameter | Name | Description | Required | Default value | +|-------------|--------------|------------------------------------------------------------------------------------------------------------------------|----------|---------------| +| port | TCP Port | TCP port of the FTP server | no | 2121 | +| idleTimeout | Idle timeout | The number of seconds before an inactive client is disconnected. If this value is set to 0, the idle time is disabled. | no | 60 | + +## Channels + +This binding currently supports the following channels: + +| Channel Type ID | Item Type | Description | +|-----------------|--------------|----------------------------------------------------------------------------------------| +| image | Image | Image file received via FTP. | + +When an image file is uploaded to FTP server, the binding tries to find the channel whose filename matches the uploaded image filename. +If no match is found, no channel is updated. +The filename parameter supports regular expression patterns. +See more details in the Things example. + +Image channel supports following options: + +| Parameter | Name | Description | Required | Default value | +|-------------|--------------|--------------------------------------------------------------------------|----------|---------------| +| filename | Filename | Filename to match received files. Supports regular expression patterns. | yes | .* | + + +### Trigger Channels + +| Channel Type ID | Options | Description | +|-----------------|------------------------|-----------------------------------------------------| +| image-received | IMAGE_RECEIVED | Triggered when image file received from FTP client. | + +When an image file is uploaded to FTP server, the binding tries to find the trigger channel whose filename matches the upload image filename. +If no match is found, no channel is updated. +The filename parameter supports regular expression patterns. +See more details in the Things example. + +Trigger channels supports following options: + +| Parameter | Name | Description | Required | Default value | +|-------------|--------------|--------------------------------------------------------------------------|----------|---------------| +| filename | Filename | Filename to match received files. Supports regular expression patterns. | yes | .* | + +## Full Example + +Things: + +``` +Thing ftpupload:imagereceiver:images1 [ userName="test1", password="12345" ] { + +Thing ftpupload:imagereceiver:images2 [ userName="test2", password="12345" ] { + Channels: + Type image-channel : my_image1 "My Image channel 1" [ + filename="test12[0-9]{2}.png" // match to filename test12xx.png, where xx can be numbers between 00-99 + ] + Type image-channel : my_image2 "My Image channel 2" [ + filename="test.jpg" + ] + Trigger String : my_image_trigger1 [ + filename="test12[0-9]{2}.png" + ] + Trigger String : my_image_trigger2 [ + filename="test.jpg" + ] +} +``` + +Items: + +``` +Image Image1 { channel="ftpupload:imagereceiver:images1:image" } +Image Image2 { channel="ftpupload:imagereceiver:images2:my_image1" } +``` + +Rules: + +``` +rule "example trigger rule 1" +when + Channel 'ftpupload:imagereceiver:images1:image-received' triggered IMAGE_RECEIVED +then + logInfo("Test","Image received") +end + +rule "example trigger rule 2" +when + Channel 'ftpupload:imagereceiver:images2:my_image_trigger1' triggered IMAGE_RECEIVED +then + logInfo("Test","Image received") +end + +``` + +Sitemap: + +``` +Frame label="FTP images" { + Image item=Image1 + Image item=Image2 +} +``` + +## Use case example + +The binding can be used to receive images from network cameras that send images to a FTP server when motion or sound is detected. + +Things: + +``` +Thing ftpupload:imagereceiver:garagecamera [ userName="garage", password="12345" ] +``` + +Items: + +``` +Image Garage_NetworkCamera_Motion_Image { channel="ftpupload:imagereceiver:garagecamera:image" } +``` + +Rules: + +``` +rule "example trigger rule" +when + Channel 'ftpupload:imagereceiver:garagecamera:image-received' triggered IMAGE_RECEIVED +then + logInfo("Test","Garage motion detected") +end +``` + +Sitemap: + +``` +Frame label="Garage network camera" icon="camera" { + Image item=Garage_NetworkCamera_Motion_Image +} +``` + +## Logging and Problem Solving + +For problem solving, if binding logging is not enough, Apache FTP server logging can also be enabled by the following command in the karaf console: + +``` +log:set DEBUG org.apache.ftpserver +``` + +and set back to default level: + +``` +log:set DEFAULT org.apache.ftpserver +``` + +If you meet any problems to receive images from the network cameras, you could test connection to binding with any FTP client. +You can send image files via FTP client and thing channels should be updated accordingly. + + \ No newline at end of file diff --git a/addons/binding/org.openhab.binding.ftpupload/about.html b/addons/binding/org.openhab.binding.ftpupload/about.html new file mode 100644 index 0000000000000..34f40eecb27d6 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/about.html @@ -0,0 +1,49 @@ + + + + +About + + +

About This Content

+ +

Nov 10, 2016

+

License

+ +

The openHAB community makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at http://www.eclipse.org/legal/epl-v10.html. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the openHAB community, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at openhab.org.

+ + +

Third Party Content

+

The Content includes items that have been sourced from third parties as set out below. If you + did not receive this Content directly from the openHAB community, the following is provided + for informational purposes only, and you should look to the Redistributor's license for + terms and conditions of use.

+

+ Apache MINA

+ Apache MINA under + Apache License. +

+

+ Apache FtpServer

+ Apache FtpServer under + Apache License. +

+

+ Apache Ftplet

+ Apache Ftplet under + Apache License. +

+ + diff --git a/addons/binding/org.openhab.binding.ftpupload/build.properties b/addons/binding/org.openhab.binding.ftpupload/build.properties new file mode 100644 index 0000000000000..2e82976f5b510 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/build.properties @@ -0,0 +1,10 @@ +source.. = src/main/java/ +output.. = target/classes +bin.includes = META-INF/,\ + .,\ + OSGI-INF/,\ + ESH-INF/,\ + about.html,\ + lib/ftplet-api-1.1.0.jar,\ + lib/ftpserver-core-1.1.0.jar,\ + lib/mina-core-2.0.16.jar diff --git a/addons/binding/org.openhab.binding.ftpupload/lib/ftplet-api-1.1.0.jar b/addons/binding/org.openhab.binding.ftpupload/lib/ftplet-api-1.1.0.jar new file mode 100644 index 0000000000000..36784e9744c2b Binary files /dev/null and b/addons/binding/org.openhab.binding.ftpupload/lib/ftplet-api-1.1.0.jar differ diff --git a/addons/binding/org.openhab.binding.ftpupload/lib/ftpserver-core-1.1.0.jar b/addons/binding/org.openhab.binding.ftpupload/lib/ftpserver-core-1.1.0.jar new file mode 100644 index 0000000000000..00bf2a16d87a6 Binary files /dev/null and b/addons/binding/org.openhab.binding.ftpupload/lib/ftpserver-core-1.1.0.jar differ diff --git a/addons/binding/org.openhab.binding.ftpupload/lib/mina-core-2.0.16.jar b/addons/binding/org.openhab.binding.ftpupload/lib/mina-core-2.0.16.jar new file mode 100644 index 0000000000000..0e6d9883e6917 Binary files /dev/null and b/addons/binding/org.openhab.binding.ftpupload/lib/mina-core-2.0.16.jar differ diff --git a/addons/binding/org.openhab.binding.ftpupload/pom.xml b/addons/binding/org.openhab.binding.ftpupload/pom.xml new file mode 100644 index 0000000000000..4adc2e36a2ba9 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/pom.xml @@ -0,0 +1,20 @@ + + + + 4.0.0 + + + org.openhab.binding + pom + 2.3.0-SNAPSHOT + + + org.openhab.binding + org.openhab.binding.ftpupload + 2.3.0-SNAPSHOT + + FTP Upload Binding + eclipse-plugin + + diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/FtpUploadBindingConstants.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/FtpUploadBindingConstants.java new file mode 100644 index 0000000000000..4d635ed6bbfa3 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/FtpUploadBindingConstants.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +/** + * The {@link FtpUploadBinding} class defines common constants, which are + * used across the whole binding. + * + * @author Pauli Anttila - Initial contribution + */ +public class FtpUploadBindingConstants { + + public static final String BINDING_ID = "ftpupload"; + + // List of all Thing Type UIDs + public final static ThingTypeUID THING_TYPE_IMAGERECEIVER = new ThingTypeUID(BINDING_ID, "imagereceiver"); + + // List of all Channel ids + public final static String IMAGE = "image"; + public final static String IMAGE_RECEIVED_TRIGGER = "image-received"; + + // List of all channel parameters + public final static String PARAM_FILENAME_PATTERN = "filename"; + + public final static String EVENT_IMAGE_RECEIVED = "IMAGE_RECEIVED"; +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/handler/FtpUploadHandler.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/handler/FtpUploadHandler.java new file mode 100644 index 0000000000000..8e8c520023648 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/handler/FtpUploadHandler.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.handler; + +import static org.openhab.binding.ftpupload.FtpUploadBindingConstants.*; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.smarthome.core.library.types.RawType; +import org.eclipse.smarthome.core.thing.Channel; +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.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.io.net.http.HttpUtil; +import org.openhab.binding.ftpupload.internal.config.FtpUploadConfig; +import org.openhab.binding.ftpupload.internal.ftp.FtpServer; +import org.openhab.binding.ftpupload.internal.ftp.FtpServerEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link FtpUploadHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Pauli Anttila - Initial contribution + */ +public class FtpUploadHandler extends BaseThingHandler implements FtpServerEventListener { + + private Logger logger = LoggerFactory.getLogger(FtpUploadHandler.class); + + private FtpUploadConfig configuration; + private FtpServer ftpServer; + + public FtpUploadHandler(Thing thing, FtpServer ftpServer) { + super(thing); + this.ftpServer = ftpServer; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("handleCommand for channel {}: {}", channelUID.getId(), command.toString()); + logger.debug("Command sending not supported by this binding"); + + if (command.equals(RefreshType.REFRESH)) { + ftpServer.printStats(); + } + } + + @Override + public void initialize() { + logger.debug("Initializing handler for FTP Upload Binding"); + configuration = getConfigAs(FtpUploadConfig.class); + logger.debug("Using configuration: {}", configuration.toString()); + + ftpServer.addEventListener(this); + try { + ftpServer.addAuthenticationCredentials(configuration.userName, configuration.password); + } catch (IllegalArgumentException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void dispose() { + ftpServer.removeAuthenticationCredentials(configuration.userName); + ftpServer.removeEventListener(this); + } + + @Override + public void fileReceived(String userName, String filename, byte[] data) { + if (configuration.userName.equals(userName)) { + updateStatus(ThingStatus.ONLINE); + updateChannels(filename, data); + updateTriggers(filename); + } + } + + private String guessMimeTypeFromData(byte[] data) { + String mimeType = HttpUtil.guessContentTypeFromData(data); + logger.debug("Mime type guess from content: {}", mimeType); + if (mimeType == null) { + mimeType = RawType.DEFAULT_MIME_TYPE; + } + logger.debug("Mime type: {}", mimeType); + return mimeType; + } + + private void updateChannels(String filename, byte[] data) { + for (Channel channel : thing.getChannels()) { + String channelConf = (String) channel.getConfiguration().get(PARAM_FILENAME_PATTERN); + if (channelConf != null) { + if (filenameMatch(filename, channelConf)) { + if ("Image".equals(channel.getAcceptedItemType())) { + updateState(channel.getUID().getId(), new RawType(data, guessMimeTypeFromData(data))); + } + } + } + } + } + + private void updateTriggers(String filename) { + for (Channel channel : thing.getChannels()) { + String channelConf = (String) channel.getConfiguration().get(PARAM_FILENAME_PATTERN); + if (channelConf != null) { + if (filenameMatch(filename, channelConf)) { + if ("TRIGGER".equals(channel.getKind().toString())) { + triggerChannel(channel.getUID().getId(), EVENT_IMAGE_RECEIVED); + } + } + } + } + } + + private boolean filenameMatch(String filename, String pattern) { + try { + return Pattern.compile(pattern).matcher(filename).find(); + } catch (PatternSyntaxException e) { + logger.warn("Invalid filename pattern '{}', reason: {}", pattern, e.getMessage()); + } + return false; + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java new file mode 100644 index 0000000000000..af606f85ff9cc --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal; + +import static org.openhab.binding.ftpupload.FtpUploadBindingConstants.THING_TYPE_IMAGERECEIVER; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.ftpserver.FtpServerConfigurationException; +import org.apache.ftpserver.ftplet.FtpException; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.ThingStatusInfo; +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.ftpupload.handler.FtpUploadHandler; +import org.openhab.binding.ftpupload.internal.ftp.FtpServer; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link FtpUploadHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Pauli Anttila - Initial contribution + */ +@Component(service = ThingHandlerFactory.class, immediate = true, configurationPid = "binding.ftpupload") +public class FtpUploadHandlerFactory extends BaseThingHandlerFactory { + private final Logger logger = LoggerFactory.getLogger(FtpUploadHandlerFactory.class); + + private final static Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_IMAGERECEIVER); + + private final int DEFAULT_PORT = 2121; + private final int DEFAULT_IDLE_TIMEOUT = 60; + + private FtpServer ftpServer; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(THING_TYPE_IMAGERECEIVER)) { + if (ftpServer.getStartUpErrorReason() != null) { + thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + ftpServer.getStartUpErrorReason())); + } + return new FtpUploadHandler(thing, ftpServer); + } + + return null; + } + + @Override + protected synchronized void activate(ComponentContext componentContext) { + super.activate(componentContext); + ftpServer = new FtpServer(); + modified(componentContext); + } + + @Override + protected synchronized void deactivate(ComponentContext componentContext) { + stopFtpServer(); + ftpServer = null; + super.deactivate(componentContext); + } + + protected synchronized void modified(ComponentContext componentContext) { + stopFtpServer(); + Dictionary properties = componentContext.getProperties(); + + int port = DEFAULT_PORT; + int idleTimeout = DEFAULT_IDLE_TIMEOUT; + + if (properties.get("port") != null) { + String strPort = properties.get("port").toString(); + if (StringUtils.isNotEmpty(strPort)) { + try { + port = Integer.valueOf(strPort); + } catch (NumberFormatException e) { + logger.warn("Invalid port number '{}', using default port {}", strPort, port); + } + } + } + + if (properties.get("idleTimeout") != null) { + String strIdleTimeout = properties.get("idleTimeout").toString(); + if (StringUtils.isNotEmpty(strIdleTimeout)) { + try { + idleTimeout = Integer.valueOf(strIdleTimeout); + } catch (NumberFormatException e) { + logger.warn("Invalid idle timeout '{}', using default timeout {}", strIdleTimeout, idleTimeout); + } + } + } + + try { + logger.info("Starting FTP server, port={}, idleTimeout={}", port, idleTimeout); + ftpServer.startServer(port, idleTimeout); + } catch (FtpException | FtpServerConfigurationException e) { + logger.warn("FTP server starting failed, reason: {}", e.getMessage()); + } + } + + private void stopFtpServer() { + logger.info("Stopping FTP server"); + ftpServer.stopServer(); + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/config/FtpUploadConfig.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/config/FtpUploadConfig.java new file mode 100644 index 0000000000000..648da874d2568 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/config/FtpUploadConfig.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.config; + +/** + * Configuration class for {@link FtpUploadBinding} device. + * + * @author Pauli Anttila - Initial contribution + */ + +public class FtpUploadConfig { + + public String userName; + public String password; + + @Override + public String toString() { + String str = ""; + + str += "userName = " + userName; + str += ", password = *****"; + + return str; + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUser.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUser.java new file mode 100644 index 0000000000000..cecafc4d35d3a --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUser.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import java.util.List; + +import org.apache.ftpserver.ftplet.Authority; +import org.apache.ftpserver.ftplet.AuthorizationRequest; +import org.apache.ftpserver.ftplet.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple FTP user implementation. + * + * + * @author Pauli Anttila - Initial contribution + */ +public class FTPUser implements User { + private static Logger logger = LoggerFactory.getLogger(FTPUser.class); + + private final String login; + private int idleTimeout; + + public FTPUser(String login, int idleTimeout) { + this.login = login; + this.idleTimeout = idleTimeout; + } + + @Override + public AuthorizationRequest authorize(final AuthorizationRequest authRequest) { + logger.trace("authorize: {}", authRequest); + return authRequest; + } + + @Override + public boolean getEnabled() { + logger.trace("getEnabled"); + return true; + } + + @Override + public String getHomeDirectory() { + logger.trace("getHomeDirectory"); + return "/"; + } + + @Override + public int getMaxIdleTime() { + logger.trace("getMaxIdleTime"); + return idleTimeout; + } + + @Override + public String getName() { + logger.trace("getName"); + return this.login; + } + + @Override + public List getAuthorities() { + logger.trace("getAuthorities"); + return null; + } + + @Override + public List getAuthorities(Class arg0) { + logger.trace("getAuthorities: {}", arg0); + return null; + } + + @Override + public String getPassword() { + logger.trace("getPassword"); + return null; + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUserManager.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUserManager.java new file mode 100644 index 0000000000000..d1b4373a66985 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FTPUserManager.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import java.util.HashMap; + +import org.apache.ftpserver.ftplet.Authentication; +import org.apache.ftpserver.ftplet.AuthenticationFailedException; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.User; +import org.apache.ftpserver.ftplet.UserManager; +import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple FTP user manager implementation. + * + * + * @author Pauli Anttila - Initial contribution + */ +public class FTPUserManager implements UserManager { + private final Logger logger = LoggerFactory.getLogger(FTPUserManager.class); + + private int idleTimeout; + private HashMap authenticationData = new HashMap(); + + @Override + public User authenticate(final Authentication inAuth) throws AuthenticationFailedException { + logger.trace("authenticate: {}", inAuth); + + UsernamePasswordAuthentication upa = (UsernamePasswordAuthentication) inAuth; + String login = upa.getUsername(); + String password = upa.getPassword(); + + if (!autheticate(login, password)) { + throw new AuthenticationFailedException(); + } + return new FTPUser(login, idleTimeout); + } + + private boolean autheticate(String login, String password) { + boolean result = false; + + if (login != null && password != null) { + UsernamePassword credential = authenticationData.get(login); + + if (credential != null) { + if (login.equals(credential.getUsername()) && password.equals(credential.getPassword())) { + return true; + } + } + } + return result; + } + + public void setIdleTimeout(int idleTimeout) { + this.idleTimeout = idleTimeout; + } + + public synchronized void addAuthenticationCredentials(String username, String password) + throws IllegalArgumentException { + + if (authenticationData.containsKey(username)) { + throw new IllegalArgumentException("Credentials for user '" + username + "' already exists!"); + } + authenticationData.put(username, new UsernamePassword(username, password)); + } + + public synchronized void removeAuthenticationCredentials(String username) { + authenticationData.remove(username); + } + + @Override + public User getUserByName(final String login) throws FtpException { + logger.trace("getUserByName: {}", login); + return new FTPUser(login, idleTimeout); + } + + @Override + public void delete(String arg0) throws FtpException { + logger.trace("delete: {}", arg0); + } + + @Override + public boolean doesExist(String arg0) throws FtpException { + logger.trace("doesExist: {}", arg0); + return false; + } + + @Override + public String getAdminName() throws FtpException { + logger.trace("getAdminName"); + return null; + } + + @Override + public String[] getAllUserNames() throws FtpException { + logger.trace("getAllUserNames"); + return null; + } + + @Override + public boolean isAdmin(String arg0) throws FtpException { + logger.trace("isAdmin: {}", arg0); + return false; + } + + @Override + public void save(User arg0) throws FtpException { + logger.trace("save: {}", arg0); + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServer.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServer.java new file mode 100644 index 0000000000000..648eadb22772e --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServer.java @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.ftpserver.FtpServerConfigurationException; +import org.apache.ftpserver.FtpServerFactory; +import org.apache.ftpserver.ftplet.DefaultFtplet; +import org.apache.ftpserver.ftplet.FileSystemFactory; +import org.apache.ftpserver.ftplet.FileSystemView; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.FtpRequest; +import org.apache.ftpserver.ftplet.FtpSession; +import org.apache.ftpserver.ftplet.FtpStatistics; +import org.apache.ftpserver.ftplet.Ftplet; +import org.apache.ftpserver.ftplet.FtpletContext; +import org.apache.ftpserver.ftplet.FtpletResult; +import org.apache.ftpserver.ftplet.User; +import org.apache.ftpserver.listener.Listener; +import org.apache.ftpserver.listener.ListenerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple FTP server implementation to receive files via FTP. + * + * + * @author Pauli Anttila - Initial contribution + */ +public class FtpServer { + + private final Logger logger = LoggerFactory.getLogger(FtpServer.class); + + private int port; + int idleTimeout; + + private org.apache.ftpserver.FtpServer server; + private List listeners; + private MyFTPLet myFTPLet; + private FTPUserManager FTPUserManager; + private String ftpStartUpErrorReason; + + public FtpServer() { + listeners = new ArrayList<>(); + FTPUserManager = new FTPUserManager(); + } + + public void startServer(int port, int idleTimeout) throws FtpException { + stopServer(); + this.port = port; + this.idleTimeout = idleTimeout; + FTPUserManager.setIdleTimeout(idleTimeout); + initServer(); + } + + public void stopServer() { + if (server != null) { + server.stop(); + } + } + + public String getStartUpErrorReason() { + return ftpStartUpErrorReason; + } + + public synchronized void addEventListener(FtpServerEventListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + public synchronized void addAuthenticationCredentials(String username, String password) + throws IllegalArgumentException { + + FTPUserManager.addAuthenticationCredentials(username, password); + } + + public synchronized void removeAuthenticationCredentials(String username) { + FTPUserManager.removeAuthenticationCredentials(username); + } + + public synchronized void removeEventListener(FtpServerEventListener listener) { + listeners.remove(listener); + } + + private void sendMsgToListeners(String userName, String filename, byte[] data) { + Iterator iterator = listeners.iterator(); + + while (iterator.hasNext()) { + try { + iterator.next().fileReceived(userName, filename, data); + } catch (Exception e) { + // catch all exceptions give all handlers a fair chance of handling the messages + logger.debug("Event listener invoking error: {}", e.getMessage()); + } + } + } + + public void printStats() { + FtpStatistics ftpStats = myFTPLet.getStats(); + + logger.debug("TotalConnectionNumber: {}", ftpStats.getTotalConnectionNumber()); + logger.debug("TotalLoginNumber: {}", ftpStats.getTotalLoginNumber()); + logger.debug("TotalFailedLoginNumber: {}", ftpStats.getTotalFailedLoginNumber()); + logger.debug("TotalUploadNumber: {}", ftpStats.getTotalUploadNumber()); + logger.debug("TotalUploadSize: {}", ftpStats.getTotalUploadSize()); + + logger.debug("CurrentConnectionNumber: {}", ftpStats.getCurrentConnectionNumber()); + logger.debug("CurrentLoginNumber: {}", ftpStats.getCurrentLoginNumber()); + } + + private void initServer() throws FtpException { + FtpServerFactory serverFactory = new FtpServerFactory(); + ListenerFactory listenerFactory = new ListenerFactory(); + listenerFactory.setPort(port); + listenerFactory.setIdleTimeout(idleTimeout); + + Listener listener = listenerFactory.createListener(); + + serverFactory.addListener("default", listener); + + Map ftplets = new LinkedHashMap<>(); + myFTPLet = new MyFTPLet(); + + ftplets.put("ftplet", myFTPLet); + + serverFactory.setFtplets(ftplets); + serverFactory.setFileSystem(new FileSystemFactory() { + @Override + public FileSystemView createFileSystemView(User user) throws FtpException { + logger.debug("createFileSystemView: {}", user.getName()); + return new SimpleFileSystemView(); + } + }); + + // set the user manager + serverFactory.setUserManager(FTPUserManager); + server = serverFactory.createServer(); + + try { + server.start(); + ftpStartUpErrorReason = null; + } catch (FtpException | FtpServerConfigurationException e) { + ftpStartUpErrorReason = "Failed to start FTP server"; + if (!e.getMessage().isEmpty()) { + ftpStartUpErrorReason += ": " + e.getMessage(); + } + throw e; + } + } + + private class MyFTPLet extends DefaultFtplet { + FtpletContext ftpletContext; + + public FtpStatistics getStats() { + return ftpletContext.getFtpStatistics(); + } + + @Override + public void init(FtpletContext ftpletContext) throws FtpException { + this.ftpletContext = ftpletContext; + } + + @Override + public void destroy() { + logger.trace("destroy"); + } + + @Override + public FtpletResult onConnect(FtpSession session) throws FtpException, IOException { + logger.debug("User connected to FtpServer"); + return super.onConnect(session); + } + + @Override + public FtpletResult onUploadEnd(final FtpSession session, final FtpRequest request) + throws FtpException, IOException { + + String userRoot = session.getUser().getHomeDirectory(); + String currDir = session.getFileSystemView().getWorkingDirectory().getAbsolutePath(); + String fileName = request.getArgument(); + + logger.debug("File {} upload to FTP server", userRoot + currDir + "/" + fileName); + + SimpleFtpFile file = (SimpleFtpFile) session.getFileSystemView().getFile(fileName); + byte[] data = file.getData(); + + sendMsgToListeners(session.getUser().getName(), fileName, data); + return FtpletResult.SKIP; + } + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServerEventListener.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServerEventListener.java new file mode 100644 index 0000000000000..ecf69aae24541 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/FtpServerEventListener.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * This interface defines interface to receive data from FTP server. + * + * @author Pauli Anttila - Initial contribution + */ +public interface FtpServerEventListener { + + /** + * Procedure for receive raw data from FTP server. + * + * @param userName User name. + * @param filename Received filename. + * @param data Received raw data. + */ + void fileReceived(@NonNull String userName, @NonNull String filename, byte[] data); + +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFileSystemView.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFileSystemView.java new file mode 100644 index 0000000000000..732bf18dfa285 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFileSystemView.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import org.apache.ftpserver.ftplet.FileSystemView; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.FtpFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple FTP file system view implementation. + * + * + * @author Pauli Anttila - Initial contribution + */ +public class SimpleFileSystemView implements FileSystemView { + private Logger logger = LoggerFactory.getLogger(SimpleFileSystemView.class); + + SimpleFtpFile file = new SimpleFtpFile(); + + @Override + public boolean changeWorkingDirectory(String arg0) throws FtpException { + logger.trace("changeWorkingDirectory: {}", arg0); + return true; + } + + @Override + public void dispose() { + logger.trace("dispose"); + } + + @Override + public FtpFile getFile(String arg0) throws FtpException { + logger.trace("getFile: {}", arg0); + return file; + } + + @Override + public FtpFile getHomeDirectory() throws FtpException { + logger.trace("getHomeDirectory"); + return new SimpleFtpFile(); + } + + @Override + public FtpFile getWorkingDirectory() throws FtpException { + logger.trace("getWorkingDirectory"); + return new SimpleFtpFile(); + } + + @Override + public boolean isRandomAccessible() throws FtpException { + logger.trace("isRandomAccessible"); + return false; + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFtpFile.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFtpFile.java new file mode 100644 index 0000000000000..19cb68fc47958 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/SimpleFtpFile.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.ftpserver.ftplet.FtpFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple FTP file implementation. + * + * + * @author Pauli Anttila - Initial contribution + */ +public class SimpleFtpFile implements FtpFile { + private Logger logger = LoggerFactory.getLogger(SimpleFtpFile.class); + + MyOutputStream file; + + public byte[] getData() { + return file.getData(); + } + + @Override + public InputStream createInputStream(long arg0) throws IOException { + logger.trace("createInputStream: {}", arg0); + return null; + } + + @Override + public OutputStream createOutputStream(long arg0) throws IOException { + logger.trace("createOutputStream: {}", arg0); + file = new MyOutputStream(); + return file; + } + + @Override + public boolean delete() { + logger.trace("delete"); + return false; + } + + @Override + public boolean doesExist() { + logger.trace("doesExist"); + return false; + } + + @Override + public String getAbsolutePath() { + logger.trace("getAbsolutePath"); + return "/"; + } + + @Override + public String getGroupName() { + logger.trace("getGroupName"); + return null; + } + + @Override + public long getLastModified() { + logger.trace("getLastModified"); + return 0; + } + + @Override + public int getLinkCount() { + logger.trace("getLinkCount"); + return 0; + } + + @Override + public String getName() { + logger.trace("getName"); + return ""; + } + + @Override + public String getOwnerName() { + logger.trace("getOwnerName"); + return null; + } + + @Override + public long getSize() { + logger.trace("getSize"); + return 0; + } + + @Override + public boolean isDirectory() { + logger.trace("isDirectory"); + return false; + } + + @Override + public boolean isFile() { + logger.trace("isFile"); + return false; + } + + @Override + public boolean isHidden() { + logger.trace("isHidden"); + return false; + } + + @Override + public boolean isReadable() { + logger.trace("isReadable"); + return false; + } + + @Override + public boolean isRemovable() { + logger.trace("isRemovable"); + return false; + } + + @Override + public boolean isWritable() { + logger.trace("isWritable"); + return true; + } + + @Override + public List listFiles() { + logger.trace("listFiles"); + return null; + } + + @Override + public boolean mkdir() { + logger.trace("mkdir"); + return false; + } + + @Override + public boolean move(FtpFile arg0) { + logger.trace("move: {}", arg0); + return false; + } + + @Override + public boolean setLastModified(long arg0) { + logger.trace("setLastModified: {}", arg0); + return false; + } + + @Override + public Object getPhysicalFile() { + logger.trace("getPhysicalFile"); + return null; + } + + private class MyOutputStream extends OutputStream { + private StringBuilder data = new StringBuilder(); + + @Override + public void write(int b) throws IOException { + data.append(String.format("%02X", (byte) b)); + } + + public byte[] getData() { + try { + byte[] d = DatatypeConverter.parseHexBinary(data.toString()); + logger.debug("File len: {}", d.length); + return d; + } catch (IllegalArgumentException e) { + logger.debug("Exception occured during data conversion: {}", e.getMessage()); + } + return null; + } + } +} diff --git a/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/UsernamePassword.java b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/UsernamePassword.java new file mode 100644 index 0000000000000..8cf1f0018daa5 --- /dev/null +++ b/addons/binding/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/ftp/UsernamePassword.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.ftpupload.internal.ftp; + +/** + * Simple wrapper class to store user name and password pairs. + * + * @author Pauli Anttila - Initial contribution + */ +class UsernamePassword { + private String username; + private String password; + + UsernamePassword(String username, String password) { + this.setUsername(username); + this.setPassword(password); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/addons/binding/pom.xml b/addons/binding/pom.xml index 99e7bd44e6191..0183b1f7f45e9 100644 --- a/addons/binding/pom.xml +++ b/addons/binding/pom.xml @@ -38,6 +38,7 @@ org.openhab.binding.folding org.openhab.binding.freebox org.openhab.binding.fronius + org.openhab.binding.ftpupload org.openhab.binding.gardena org.openhab.binding.harmonyhub org.openhab.binding.hdanywhere diff --git a/features/openhab-addons/src/main/feature/feature.xml b/features/openhab-addons/src/main/feature/feature.xml index cfc97abc0e1c8..23bfbf3950959 100644 --- a/features/openhab-addons/src/main/feature/feature.xml +++ b/features/openhab-addons/src/main/feature/feature.xml @@ -112,10 +112,17 @@ openhab-transport-mdns mvn:org.openhab.binding/org.openhab.binding.freebox/${project.version} + openhab-runtime-base mvn:org.openhab.binding/org.openhab.binding.fronius/${project.version} + + + openhab-runtime-base + mvn:org.openhab.binding/org.openhab.binding.ftpupload/${project.version} + + openhab-runtime-base mvn:org.openhab.binding/org.openhab.binding.gardena/${project.version}