added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.minecraft</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,25 @@
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
== Third-party Content
tubesock
* License: MIT License
* Project: https://github.com/FirebaseExtended/TubeSock
* Source: https://github.com/FirebaseExtended/TubeSock
rxjava
* License: Apache 2.0 License
* Project: https://github.com/ReactiveX/RxJava
* Source: https://github.com/ReactiveX/RxJava/tree/1.x

View File

@@ -0,0 +1,79 @@
# Minecraft Binding
This binding integrates Minecraft with openHAB through the [spigot plugin](https://github.com/ibaton/bukkit-openhab-plugin/releases/download/1.5/OHMinecraft.jar) ([sources](https://github.com/ibaton/bukkit-openhab-plugin/tree/master)).
The binding allows reading of server and player data.
It furthermore keeps track of redstone power going below signs and links them to Switch items.
## Youtube Videos
[![Minecraft Binding 1](https://img.youtube.com/vi/TdvkTorzkXU/0.jpg)](https://youtu.be/TdvkTorzkXU)
[![Minecraft Binding 2](https://img.youtube.com/vi/zAyNWmr7byE/0.jpg)](https://youtu.be/zAyNWmr7byE)
## Discovery
The Minecraft binding automatically finds all Minecraft servers running [this plugin](https://github.com/ibaton/bukkit-openhab-plugin/releases/download/1.9/OHMinecraft.jar) on the local network.
Servers can be added manually if they are not found automatically.
::: tip Note
Discovery uses zeroconf, which may not work if you host a Minecraft server in a virtual machine or container.
:::
## Channels
Depending on the thing type, different channels are provided:
### Server
| Channel Type ID | Item Type | Description |
|-----------------|-----------|-----------------------------------------|
| name | String | Name of Minecraft server |
| online | Switch | Online status |
| bukkitVersion | String | The bukkit version running on server |
| version | String | The Minecraft version running on server |
| players | Number | The number of players on server |
| maxPlayers | Number | The maximum number of players on server |
### Player
| Channel Type ID | Item Type | Description |
|----------------------------|-----------|------------------------------------------------------------|
| playerName | String | The name of the player |
| playerOnline | Switch | Is the player connected to the server |
| playerLevel | Number | The current level of the player |
| playerTotalExperience | Number | The total experience of the player |
| playerExperiencePercentage | Number | The percentage of the experience bar filled for next level |
| playerHealth | Number | The health of the player |
| playerWalkSpeed | Number | The speed of the player |
| playerLocation | Location | The player location |
| playerGameMode | Number | The players game mode |
### Sign
| Channel Type ID | Item Type | Description |
|-----------------|-----------|----------------------------------------------|
| signActive | Switch | Does the sign have powered redstone below it |
#### Active switch (Controllable from openHAB)
<a href="https://drive.google.com/uc?export=view&id=0B3UO0c11-Q6hMkNZSjJidGk4b28"><img src="https://drive.google.com/uc?export=view&id=0B3UO0c11-Q6hMkNZSjJidGk4b28" style="width: 500px; max-width: 100%; height: auto" title="Click for the larger version." /></a>
#### Passive sensor
<a href="https://drive.google.com/uc?export=view&id=0B3UO0c11-Q6hUG1wd3h0MDUzUzQ"><img src="https://drive.google.com/uc?export=view&id=0B3UO0c11-Q6hUG1wd3h0MDUzUzQ" style="width: 500px; max-width: 100%; height: auto" title="Click for the larger version." /></a>
### Example Thing Definition
The easiest method to add Minecraft servers, players, and signs is use the automatic discovery through Paper UI. However, you can manualy define the objects using thing configuration files. Players and signs are connected through Minecraft server [bridges](https://www.openhab.org/docs/configuration/things.html#defining-bridges-using-files).
```xtend
Bridge minecraft:server:myminecraftserver "Minecraft server for Friends" @ "Minecraft" [ hostname="192.168.1.100", port=10692 ] {
Thing player my_name "My Minecraft User" @ "Minecraft" [ playerName="minecraft_username" ]
Thing player friends_name "My Friend's Minecraft User" @ "Minecraft" [ playerName="friends_username" ]
Thing sign sign_name "Example Sign" @ "Minecraft" [ signName="sensor" ]
}
```

View File

@@ -0,0 +1,36 @@
<?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.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.minecraft</artifactId>
<name>openHAB Add-ons :: Bundles :: Minecraft Binding</name>
<properties>
<bnd.importpackage>org.apache.http.conn.ssl;resolution:="optional"</bnd.importpackage>
</properties>
<dependencies>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.1.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.firebase</groupId>
<artifactId>tubesock</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link MinecraftBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Mattias Markehed - Initial contribution
*/
@NonNullByDefault
public class MinecraftBindingConstants {
public static final String BINDING_ID = "minecraft";
public static final int SERVER_RECONNECT = 30; // Seconds to wait before reconnect
public static final String PARAMETER_HOSTNAME = "hostname";
public static final String PARAMETER_PORT = "port";
public static final String TYPE_ID_SERVER = "server";
public static final String TYPE_ID_PLAYER = "player";
public static final String TYPE_ID_SIGN = "redstoneSign";
public static final String CHANNEL_PLAYERS = "players";
public static final String CHANNEL_MAX_PLAYERS = "maxPlayers";
public static final String CHANNEL_ONLINE = "online";
public static final String CHANNEL_PLAYER_ONLINE = "playerOnline";
public static final String CHANNEL_PLAYER_LEVEL = "playerLevel";
public static final String CHANNEL_PLAYER_LEVEL_PERCENTAGE = "playerExperiencePercentage";
public static final String CHANNEL_PLAYER_TOTAL_EXPERIENCE = "playerTotalExperience";
public static final String CHANNEL_PLAYER_HEALTH = "playerHealth";
public static final String CHANNEL_PLAYER_WALK_SPEED = "playerWalkSpeed";
public static final String CHANNEL_PLAYER_LOCATION = "playerLocation";
public static final String CHANNEL_PLAYER_GAME_MODE = "playerGameMode";
public static final String CHANNEL_SIGN_ACTIVE = "signActive";
public static final String PARAMETER_PLAYER_NAME = "playerName";
public static final String PARAMETER_SIGN_NAME = "signName";
public static final ThingTypeUID THING_TYPE_SERVER = new ThingTypeUID(BINDING_ID, TYPE_ID_SERVER);
public static final ThingTypeUID THING_TYPE_PLAYER = new ThingTypeUID(BINDING_ID, TYPE_ID_PLAYER);
public static final ThingTypeUID THING_TYPE_SIGN = new ThingTypeUID(BINDING_ID, TYPE_ID_SIGN);
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openhab.binding.minecraft.internal.handler.MinecraftPlayerHandler;
import org.openhab.binding.minecraft.internal.handler.MinecraftServerHandler;
import org.openhab.binding.minecraft.internal.handler.MinecraftSignHandler;
import org.openhab.core.thing.Bridge;
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.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MinecraftHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Mattias Markehed - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.minecraft")
public class MinecraftHandlerFactory extends BaseThingHandlerFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftHandlerFactory.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>();
static {
SUPPORTED_THING_TYPES_UIDS.add(MinecraftBindingConstants.THING_TYPE_SERVER);
SUPPORTED_THING_TYPES_UIDS.add(MinecraftBindingConstants.THING_TYPE_PLAYER);
SUPPORTED_THING_TYPES_UIDS.add(MinecraftBindingConstants.THING_TYPE_SIGN);
}
private static List<MinecraftServerHandler> minecraftServers = new ArrayList<>();
@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(MinecraftBindingConstants.THING_TYPE_SERVER)) {
MinecraftServerHandler serverHandler = new MinecraftServerHandler((Bridge) thing);
minecraftServers.add(serverHandler);
return serverHandler;
} else if (thingTypeUID.equals(MinecraftBindingConstants.THING_TYPE_PLAYER)) {
MinecraftPlayerHandler playerHandler = new MinecraftPlayerHandler(thing);
return playerHandler;
} else if (thingTypeUID.equals(MinecraftBindingConstants.THING_TYPE_SIGN)) {
MinecraftSignHandler signHandler = new MinecraftSignHandler(thing);
return signHandler;
}
return null;
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
minecraftServers.remove(thingHandler);
super.removeHandler(thingHandler);
}
/**
* Get all Minecraft handlers created.
*
* @return the Minecraft handlers created,
*/
public static List<MinecraftServerHandler> getMinecraftServers() {
LOGGER.debug("getMinecraftServers {}", minecraftServers.size());
return new ArrayList<>(minecraftServers);
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.config;
/**
* Configuration settings for a {@link org.openhab.binding.minecraft.internal.handler.MinecraftPlayerHandler}.
*
* @author Mattias Markehed - Initial contribution
*/
public class PlayerConfig {
private String playerName = "";
/**
* Get name of player.
*
* @return player name
*/
public String getName() {
return playerName;
}
/**
* Set the player name.
*
* @param player name
*/
public void setName(String name) {
this.playerName = name;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.config;
/**
* Configuration settings for a {@link org.openhab.binding.minecraft.internal.handler.MinecraftServerHandler}.
*
* @author Mattias Markehed - Initial contribution
*/
public class ServerConfig {
private int port = 10692;
private String hostname = "127.0.0.1";
/**
* Get port used to connect to server.
*
* @return server port
*/
public int getPort() {
return port;
}
/**
* Set the port used to connect to server.
*
* @param port server port
*/
public void setPort(int port) {
this.port = port;
}
/**
* Get host used to connect to server.
*
* @return server host
*/
public String getHostname() {
return hostname;
}
/**
* Set the host used to connect to server.
*
* @param port host port
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.config;
/**
* Configuration settings for a {@link org.openhab.binding.minecraft.internal.handler.MinecraftSignHandler}.
*
* @author Mattias Markehed - Initial contribution
*/
public class SignConfig {
private String signName = "";
/**
* Get text of sign to monitor.
*
* @return sign text
*/
public String getName() {
return signName;
}
/**
* Set the sign text to listen for.
*
* @param sign text.
*/
public void setName(String name) {
this.signName = name;
}
}

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.discovery;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
import org.openhab.binding.minecraft.internal.MinecraftHandlerFactory;
import org.openhab.binding.minecraft.internal.config.ServerConfig;
import org.openhab.binding.minecraft.internal.handler.MinecraftServerHandler;
import org.openhab.binding.minecraft.internal.message.data.PlayerData;
import org.openhab.binding.minecraft.internal.message.data.SignData;
import org.openhab.binding.minecraft.internal.server.ServerConnection;
import org.openhab.binding.minecraft.internal.util.Pair;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscription;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;
/**
* Handles discovery of Minecraft server, players and signs.
*
* @author Mattias Markehed - Initial contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.minecraft")
public class MinecraftDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(MinecraftDiscoveryService.class);
private static final int DISCOVER_TIMEOUT_SECONDS = 60;
private CompositeSubscription subscription;
public MinecraftDiscoveryService() {
super(Collections.singleton(MinecraftBindingConstants.THING_TYPE_SERVER), DISCOVER_TIMEOUT_SECONDS, false);
}
@Override
protected void startScan() {
logger.debug("Starting Minecraft discovery scan");
discoverServers();
}
@Override
protected synchronized void stopScan() {
logger.debug("Stopping Minecraft discovery scan");
stopDiscovery();
super.stopScan();
}
/**
* Start scanning for players and signs on servers.
*/
private void discoverServers() {
subscription = new CompositeSubscription();
Observable<ServerConnection> serverRx = serversConnectRx().cache();
Subscription playerSubscription = subscribePlayersRx(serverRx);
Subscription signsSubscription = subscribeSignsRx(serverRx);
subscription.add(playerSubscription);
subscription.add(signsSubscription);
}
/**
* Subscribe for sign updates
*
* @param serverRx server stream
* @return subscription for listening to sign events.
*/
private Subscription subscribeSignsRx(Observable<ServerConnection> serverRx) {
return serverRx
.flatMap(connection -> connection.getSocketHandler().getSignsRx().distinct(), (connection, signs) -> {
return new Pair<>(connection, signs);
}).subscribe(conectionSignPair -> {
for (SignData sign : conectionSignPair.second) {
submitSignDiscoveryResults(conectionSignPair.first.getThingUID(), sign);
}
}, e -> logger.error("Error while scanning for signs", e));
}
/**
* Subscribe to player updates
*
* @param serverRx server stream
* @return subscription for listening to player events.
*/
private Subscription subscribePlayersRx(Observable<ServerConnection> serverRx) {
return serverRx
.flatMap(socketHandler -> socketHandler.getSocketHandler().getPlayersRx().distinct(),
(connection, players) -> new Pair<>(connection, players))
.subscribeOn(Schedulers.newThread()).subscribe(conectionPlayerPair -> {
for (PlayerData player : conectionPlayerPair.second) {
submitPlayerDiscoveryResults(conectionPlayerPair.first.getThingUID(), player.getName());
}
}, e -> logger.error("Error while scanning for players", e));
}
/**
* Teardown subscribers and stop searching for players and signs.
*/
private void stopDiscovery() {
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
/**
* Get all servers that have been added as observable.
*
* @return observable emitting server objects.
*/
private Observable<ServerConnection> serversConnectRx() {
return Observable.from(MinecraftHandlerFactory.getMinecraftServers())
.flatMap(new Func1<MinecraftServerHandler, Observable<ServerConnection>>() {
@Override
public Observable<ServerConnection> call(MinecraftServerHandler server) {
ServerConfig config = server.getServerConfig();
return ServerConnection.create(server.getThing().getUID(), config.getHostname(),
config.getPort());
}
});
}
/**
* Submit the discovered Devices to the Smarthome inbox,
*
* @param bridgeUID
* @param name name of the player
*/
private void submitPlayerDiscoveryResults(ThingUID bridgeUID, String name) {
String id = deviceNameToId(name);
ThingUID uid = new ThingUID(MinecraftBindingConstants.THING_TYPE_PLAYER, bridgeUID, id);
Map<String, Object> properties = new HashMap<>();
properties.put(MinecraftBindingConstants.PARAMETER_PLAYER_NAME, name);
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withBridge(bridgeUID)
.withLabel(name).build());
}
/**
* Submit the discovered Signs to the Smarthome inbox,
*
* @param bridgeUID
* @param sign data describing sign
*/
private void submitSignDiscoveryResults(ThingUID bridgeUID, SignData sign) {
String id = deviceNameToId(sign.getName());
ThingUID uid = new ThingUID(MinecraftBindingConstants.THING_TYPE_SIGN, bridgeUID, id);
Map<String, Object> properties = new HashMap<>();
properties.put(MinecraftBindingConstants.PARAMETER_SIGN_NAME, sign.getName());
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withBridge(bridgeUID)
.withLabel(sign.getName()).build());
}
/**
* Cleanup device name so it can be used as id.
*
* @param name the name of device.
* @return id of device.
*/
private String deviceNameToId(String name) {
if (name == null) {
return "";
}
return name.replaceAll("[^a-zA-Z0-9]+", "");
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.discovery;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
/**
* The {@link MinecraftMDNSDiscoveryParticipant} is responsible for discovering Minecraft servers
* {@link MDNSDiscoveryService}.
*
* @author Mattias Markehed - Initial contribution
*/
@Component(immediate = true)
public class MinecraftMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(MinecraftBindingConstants.THING_TYPE_SERVER);
}
@Override
public String getServiceType() {
return "_http._tcp.local.";
}
@Override
public DiscoveryResult createResult(ServiceInfo service) {
if (service.getName().equals("wc-minecraft")) {
ThingUID uid = getThingUID(service);
if (uid != null) {
Map<String, Object> properties = new HashMap<>();
int port = service.getPort();
String host = service.getInetAddresses()[0].getHostAddress();
properties.put(MinecraftBindingConstants.PARAMETER_HOSTNAME, host);
properties.put(MinecraftBindingConstants.PARAMETER_PORT, port);
return DiscoveryResultBuilder.create(uid).withProperties(properties)
.withRepresentationProperty(uid.getId()).withLabel("Minecraft Server (" + host + ")").build();
}
}
return null;
}
/**
* Check if service is a minecraft server.
*
* @param service the service to check.
* @return true if minecraft server, else false.
*/
private boolean isMinecraftServer(ServiceInfo service) {
return (service != null && service.getType() != null && service.getType().equals(getServiceType()));
}
@Override
public ThingUID getThingUID(ServiceInfo service) {
if (isMinecraftServer(service) && service.getInetAddresses().length > 0) {
String host = service.getInetAddresses()[0].getHostAddress();
host = host.replace('.', '_');
return new ThingUID(MinecraftBindingConstants.THING_TYPE_SERVER, host);
}
return null;
}
}

View File

@@ -0,0 +1,195 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.handler;
import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
import org.openhab.binding.minecraft.internal.config.PlayerConfig;
import org.openhab.binding.minecraft.internal.message.OHMessage;
import org.openhab.binding.minecraft.internal.message.data.PlayerData;
import org.openhab.binding.minecraft.internal.message.data.commands.PlayerCommandData;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
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.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import rx.Observable;
import rx.Subscription;
/**
* The {@link MinecraftPlayerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mattias Markehed - Initial contribution
*/
public class MinecraftPlayerHandler extends BaseThingHandler {
private Logger logger = LoggerFactory.getLogger(MinecraftPlayerHandler.class);
private Subscription playerSubscription;
private MinecraftServerHandler bridgeHandler;
private PlayerConfig config;
private Gson gson = new GsonBuilder().create();
public MinecraftPlayerHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
this.bridgeHandler = getBridgeHandler();
this.config = getThing().getConfiguration().as(PlayerConfig.class);
if (bridgeHandler == null || getThing().getBridgeUID() == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
return;
}
updateStatus(ThingStatus.ONLINE);
hookupListeners(bridgeHandler);
}
@Override
public void dispose() {
super.dispose();
if (!playerSubscription.isUnsubscribed()) {
playerSubscription.unsubscribe();
}
}
private void hookupListeners(MinecraftServerHandler bridgeHandler) {
playerSubscription = bridgeHandler.getPlayerRx().doOnNext(players -> {
boolean playerOnline = false;
for (PlayerData player : players) {
if (config.getName().equals(player.getName())) {
playerOnline = true;
break;
}
}
State onlineState = playerOnline ? OnOffType.ON : OnOffType.OFF;
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_ONLINE, onlineState);
}).flatMap(players -> Observable.from(players)).filter(player -> config.getName().equals(player.getName()))
.subscribe(player -> updatePlayerState(player));
}
/**
* Updates the state of player
*
* @param player the player to update
*/
private void updatePlayerState(PlayerData player) {
State playerLevel = new DecimalType(player.getLevel());
State playerLevelPercentage = new DecimalType(player.getExperience());
State playerTotalExperience = new DecimalType(player.getTotalExperience());
State playerHealth = new DecimalType(player.getHealth());
State playerWalkSpeed = new DecimalType(player.getWalkSpeed());
DecimalType longitude = new DecimalType(player.getLocation().getX());
DecimalType latitude = new DecimalType(player.getLocation().getY());
DecimalType altitude = new DecimalType(player.getLocation().getY());
State playerLocation = new PointType(latitude, longitude, altitude);
State playerGameMode = new StringType(player.getGameMode());
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_LEVEL_PERCENTAGE, playerLevelPercentage);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_TOTAL_EXPERIENCE, playerTotalExperience);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_LEVEL, playerLevel);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_HEALTH, playerHealth);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_WALK_SPEED, playerWalkSpeed);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_LOCATION, playerLocation);
updateState(MinecraftBindingConstants.CHANNEL_PLAYER_GAME_MODE, playerGameMode);
}
private String getPlayerName() {
return config.getName();
}
private synchronized MinecraftServerHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device {}.", getThing().getUID());
return null;
} else {
return getBridgeHandler(bridge);
}
}
private synchronized MinecraftServerHandler getBridgeHandler(Bridge bridge) {
MinecraftServerHandler bridgeHandler = null;
ThingHandler handler = bridge.getHandler();
if (handler instanceof MinecraftServerHandler) {
bridgeHandler = (MinecraftServerHandler) handler;
} else {
logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
bridgeHandler = null;
}
return bridgeHandler;
}
@Override
public void updateState(String channelID, State state) {
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelID);
updateState(channelUID, state);
}
/**
* Send a player command to server.
*
* @param type the type of command to send
* @param playerName the name of the player to target
* @param value the related to command
*/
private void sendPlayerCommand(String type, String playerName, String value) {
PlayerCommandData playerCommand = new PlayerCommandData(type, playerName, value);
JsonElement serializedCommand = gson.toJsonTree(playerCommand);
logger.debug("Command: {}", serializedCommand);
bridgeHandler.sendMessage(new OHMessage(OHMessage.MESSAGE_TYPE_PLAYER_COMMANDS, serializedCommand));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case MinecraftBindingConstants.CHANNEL_PLAYER_HEALTH:
sendPlayerCommand(PlayerCommandData.COMMAND_PLAYER_HEALTH, getPlayerName(), command.toString());
break;
case MinecraftBindingConstants.CHANNEL_PLAYER_LEVEL:
sendPlayerCommand(PlayerCommandData.COMMAND_PLAYER_LEVEL, getPlayerName(), command.toString());
break;
case MinecraftBindingConstants.CHANNEL_PLAYER_WALK_SPEED:
sendPlayerCommand(PlayerCommandData.COMMAND_PLAYER_WALK_SPEED, getPlayerName(), command.toString());
break;
case MinecraftBindingConstants.CHANNEL_PLAYER_GAME_MODE:
sendPlayerCommand(PlayerCommandData.COMMAND_PLAYER_GAME_MODE, getPlayerName(), command.toString());
break;
case MinecraftBindingConstants.CHANNEL_PLAYER_LOCATION:
sendPlayerCommand(PlayerCommandData.COMMAND_PLAYER_LOCATION, getPlayerName(), command.toString());
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.handler;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
import org.openhab.binding.minecraft.internal.config.ServerConfig;
import org.openhab.binding.minecraft.internal.message.OHMessage;
import org.openhab.binding.minecraft.internal.message.data.PlayerData;
import org.openhab.binding.minecraft.internal.message.data.ServerData;
import org.openhab.binding.minecraft.internal.message.data.SignData;
import org.openhab.binding.minecraft.internal.server.ServerConnection;
import org.openhab.binding.minecraft.internal.util.RetryWithDelay;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscription;
import rx.subscriptions.CompositeSubscription;
/**
* The {@link MinecraftServerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mattias Markehed - Initial contribution
*/
public class MinecraftServerHandler extends BaseBridgeHandler {
private Logger logger = LoggerFactory.getLogger(MinecraftServerHandler.class);
private ServerConfig config;
private Observable<ServerConnection> serverConnectionRX;
private ServerConnection connection;
private CompositeSubscription subscription;
public MinecraftServerHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
subscription = new CompositeSubscription();
config = getConfigAs(ServerConfig.class);
logger.info("Initializing MinecraftHandler");
connectToServer();
updateStatus(ThingStatus.ONLINE);
}
/**
* Get server configuration
*
* @return
*/
public ServerConfig getServerConfig() {
return config;
}
/**
* Directly connect to server.
* Reconnects when connection is lost
*/
private void connectToServer() {
String host = config.getHostname();
int port = config.getPort();
serverConnectionRX = ServerConnection.create(getThing().getUID(), host, port)
.doOnNext(item -> updateOnlineState(true)).doOnError(e -> updateOnlineState(false))
.retryWhen(new RetryWithDelay(1, TimeUnit.MINUTES)).repeat().replay(1).refCount();
Subscription serverUpdateSubscription = serverConnectionRX
.flatMap(connection -> connection.getSocketHandler().getServerRx())
.subscribe(serverData -> updateServerState(serverData));
Subscription serverConnectionSubscription = serverConnectionRX.subscribe(connection -> {
this.connection = connection;
});
subscription.add(serverUpdateSubscription);
subscription.add(serverConnectionSubscription);
}
public Observable<List<SignData>> getSignsRx() {
return serverConnectionRX.switchMap(connection -> connection.getSocketHandler().getSignsRx());
}
public Observable<List<PlayerData>> getPlayerRx() {
return serverConnectionRX.switchMap(connection -> connection.getSocketHandler().getPlayersRx());
}
/**
* Update online state of server
*
* @param isOnline true if server is online
*/
private void updateOnlineState(boolean isOnline) {
State onlineState = isOnline ? OnOffType.ON : OnOffType.OFF;
updateState(MinecraftBindingConstants.CHANNEL_ONLINE, onlineState);
}
/**
* Update state of minecraft server
*
* @param serverData
*/
private void updateServerState(ServerData serverData) {
State playersState = new DecimalType(serverData.getPlayers());
State maxPlayersState = new DecimalType(serverData.getMaxPlayers());
updateState(MinecraftBindingConstants.CHANNEL_PLAYERS, playersState);
updateState(MinecraftBindingConstants.CHANNEL_MAX_PLAYERS, maxPlayersState);
}
/**
* Send message to server.
* Does nothing if no connection is established.
*
* @param message the message to send
* @return true if message was sent.
*/
public boolean sendMessage(OHMessage message) {
if (connection != null) {
connection.sendMessage(message);
return true;
}
return false;
}
@Override
public void dispose() {
logger.debug("Disposing minecraft server thing");
if (subscription != null) {
subscription.unsubscribe();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
}

View File

@@ -0,0 +1,152 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.handler;
import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
import org.openhab.binding.minecraft.internal.config.SignConfig;
import org.openhab.binding.minecraft.internal.message.OHMessage;
import org.openhab.binding.minecraft.internal.message.data.SignData;
import org.openhab.binding.minecraft.internal.message.data.commands.SignCommandData;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
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.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import rx.Observable;
import rx.Subscription;
/**
* The {@link MinecraftSignHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mattias Markehed - Initial contribution
*/
public class MinecraftSignHandler extends BaseThingHandler {
private Logger logger = LoggerFactory.getLogger(MinecraftSignHandler.class);
private MinecraftServerHandler bridgeHandler;
private Subscription signSubscription;
private SignConfig config;
private Gson gson = new GsonBuilder().create();
public MinecraftSignHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
this.bridgeHandler = getBridgeHandler();
this.config = getThing().getConfiguration().as(SignConfig.class);
if (getThing().getBridgeUID() == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
return;
}
updateStatus(ThingStatus.ONLINE);
hookupListeners(bridgeHandler);
}
@Override
public void dispose() {
super.dispose();
if (!signSubscription.isUnsubscribed()) {
signSubscription.unsubscribe();
}
}
private String getSignName() {
return config.getName();
}
private void hookupListeners(MinecraftServerHandler bridgeHandler) {
signSubscription = bridgeHandler.getSignsRx().flatMap(signs -> Observable.from(signs))
.filter(sign -> config.getName().equals(sign.getName())).subscribe(sign -> updateSignState(sign));
}
/**
* Updates sign state of player.
*
* @param sign the sign to update
*/
private void updateSignState(SignData sign) {
State activeState = sign.getState() ? OnOffType.ON : OnOffType.OFF;
updateState(MinecraftBindingConstants.CHANNEL_SIGN_ACTIVE, activeState);
}
private synchronized MinecraftServerHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device {}.", getThing().getUID());
return null;
} else {
return getBridgeHandler(bridge);
}
}
private synchronized MinecraftServerHandler getBridgeHandler(Bridge bridge) {
MinecraftServerHandler bridgeHandler = null;
ThingHandler handler = bridge.getHandler();
if (handler instanceof MinecraftServerHandler) {
bridgeHandler = (MinecraftServerHandler) handler;
} else {
logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
bridgeHandler = null;
}
return bridgeHandler;
}
@Override
public void updateState(String channelID, State state) {
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelID);
updateState(channelUID, state);
}
/**
* Send a sign command to server.
*
* @param type the type of command to send
* @param signName the sign that the command targets
* @param value the value related to command
*/
private void sendSignCommand(String type, String signName, String value) {
SignCommandData signCommand = new SignCommandData(type, signName, value);
JsonElement serializedCommand = gson.toJsonTree(signCommand);
bridgeHandler.sendMessage(new OHMessage(OHMessage.MESSAGE_TYPE_SIGN_COMMANDS, serializedCommand));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case MinecraftBindingConstants.CHANNEL_SIGN_ACTIVE:
Boolean activeState = command == OnOffType.ON ? true : false;
sendSignCommand(SignCommandData.COMMAND_SIGN_ACTIVE, getSignName(), activeState.toString());
}
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message;
import com.google.gson.JsonElement;
/**
* Message used for communicating with Minecraft server.
* Used both for sending and receiving messages.
*
* @author Mattias Markehed - Initial contribution
*/
public class OHMessage {
public static final int MESSAGE_TYPE_PLAYERS = 1;
public static final int MESSAGE_TYPE_SERVERS = 2;
public static final int MESSAGE_TYPE_SIGNS = 4;
public static final int MESSAGE_TYPE_PLAYER_COMMANDS = 3;
public static final int MESSAGE_TYPE_SIGN_COMMANDS = 5;
private int messageType;
private JsonElement message;
/**
* Creates a message of type.
*
* @param messageType the message type.
* @param message message data.
*/
public OHMessage(int messageType, JsonElement message) {
this.messageType = messageType;
this.message = message;
}
/**
* Get the type of message
*
* @return type of message
*/
public int getMessageType() {
return messageType;
}
/**
* Set message type.
*
* @param messageType the type of message
*/
public void setMessageType(int messageType) {
this.messageType = messageType;
}
/**
* Get messsage data.
*
* @return
*/
public JsonElement getMessage() {
return message;
}
/**
* Set the message to send.
*
* @param message
*/
public void setMessage(JsonElement message) {
this.message = message;
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data;
/**
* Holds location data for Minecraft objects.
*
* @author Mattias Markehed - Initial contribution
*/
public class LocationData {
private double x;
private double y;
private double z;
private float pitch;
private float yaw;
/**
* Get x position.
*
* @return x position
*/
public double getX() {
return x;
}
/**
* Get y position.
*
* @return y position
*/
public double getY() {
return y;
}
/**
* Get z position.
*
* @return z position
*/
public double getZ() {
return z;
}
/**
* Get pitch of object
*
* @return pitch of object.
*/
public float getPitch() {
return pitch;
}
/**
* Get yaw of object
*
* @return yaw of object
*/
public float getYaw() {
return yaw;
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data;
/**
* Object representing Minecraft player.
*
* @author Mattias Markehed - Initial contribution
*/
public class PlayerData {
protected String displayName;
protected String name;
protected int level;
protected int totalExperience;
protected float experience;
protected double health;
protected float walkSpeed;
protected LocationData location;
protected String gameMode;
/**
* Get the display name of player.
*
* @return display name
*/
public String getDisplayName() {
return displayName;
}
/**
* Get the name of player
*
* @return name of player.
*/
public String getName() {
return name;
}
/**
* Get the player level.
*
* @return level of player
*/
public int getLevel() {
return level;
}
/**
* Get the total experience of player.
*
* @return total experience
*/
public int getTotalExperience() {
return totalExperience;
}
/**
* Get player experiance.
*
* @return experiance of player
*/
public float getExperience() {
return experience;
}
/**
* Get health of player.
*
* @return player health
*/
public double getHealth() {
return health;
}
/**
* Get the walk speed of player.
*
* @return walk speed of player
*/
public float getWalkSpeed() {
return walkSpeed;
}
/**
* Get location of player.
*
* @return location of player
*/
public LocationData getLocation() {
return location;
}
/**
* Get the players game mode.
*
* @return game mode
*/
public String getGameMode() {
return gameMode;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PlayerData other = (PlayerData) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data;
/**
* Object representing Minecraft server.
*
* @author Mattias Markehed - Initial contribution
*/
public class ServerData {
private int maxPlayers;
private int players;
/**
* Get max number of players.
*
* @return max number of players.
*/
public int getMaxPlayers() {
return maxPlayers;
}
/**
* Get the number of players.
*
* @return number of players.
*/
public int getPlayers() {
return players;
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data;
/**
* Object representing a tracked Minecraft sign.
*
* @author Mattias Markehed - Initial contribution
*/
public class SignData {
private String name;
private boolean state;
/**
* Creates a representation of sign.
*
* @param name text on sign.
* @param state true if powered by redstone else false.
*/
public SignData(String name, boolean state) {
this.name = name;
this.state = state;
}
/**
* The text on sign.
*
* @return text name
*/
public String getName() {
return name;
}
/**
* The active sign of state.
*
* @return true if powered by redstone
*/
public boolean getState() {
return state;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SignData other = (SignData) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data.commands;
/**
* Object representing Minecraft server.
*
* @author Mattias Markehed - Initial contribution
*/
public class PlayerCommandData {
public static final String COMMAND_PLAYER_HEALTH = "PLAYER_HEALTH";
public static final String COMMAND_PLAYER_LEVEL = "PLAYER_LEVEL";
public static final String COMMAND_PLAYER_WALK_SPEED = "PLAYER_WALK_SPEED";
public static final String COMMAND_PLAYER_GAME_MODE = "PLAYER_GAME_MODE";
public static final String COMMAND_PLAYER_LOCATION = "PLAYER_LOCATION";
private String type;
private String playerName;
private String value;
public PlayerCommandData() {
}
public PlayerCommandData(String type, String playerName, String value) {
this.type = type;
this.playerName = playerName;
this.value = value;
}
/**
* Get the type of command.
*
* @return the type of command.
*/
public String getType() {
return type;
}
/**
* The name of the player that the command targets.
*
* @return name of player
*/
public String getPlayerName() {
return playerName;
}
/**
* The command value sent.
*
* @return command value.
*/
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.message.data.commands;
/**
* A command that targets a sign.
*
* @author Mattias Markehed - Initial contribution
*/
public class SignCommandData {
public static final String COMMAND_SIGN_ACTIVE = "COMMAND_SIGN_ACTIVE";
private String type;
private String signName;
private String value;
public SignCommandData() {
}
public SignCommandData(String type, String signName, String value) {
this.type = type;
this.signName = signName;
this.value = value;
}
/**
* Get the type of command.
*
* @return the type of command.
*/
public String getType() {
return type;
}
/**
* The name of the sign that the command targets.
*
* @return name of sign
*/
public String getSignName() {
return signName;
}
/**
* The command value sent.
*
* @return command value.
*/
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.server;
import java.util.ArrayList;
import java.util.List;
import org.openhab.binding.minecraft.internal.message.OHMessage;
import org.openhab.binding.minecraft.internal.message.data.PlayerData;
import org.openhab.binding.minecraft.internal.message.data.ServerData;
import org.openhab.binding.minecraft.internal.message.data.SignData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.firebase.tubesock.WebSocketEventHandler;
import com.firebase.tubesock.WebSocketException;
import com.firebase.tubesock.WebSocketMessage;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import rx.Observable;
import rx.subjects.BehaviorSubject;
/**
* Handles sending and receiving messages from Minecraft server.
*
* @author Mattias Markehed - Initial contribution
*/
public class MinecraftSocketHandler implements WebSocketEventHandler {
private Logger logger = LoggerFactory.getLogger(MinecraftSocketHandler.class);
private BehaviorSubject<ServerData> serverRx = BehaviorSubject.create();
private BehaviorSubject<List<SignData>> signsRx = BehaviorSubject.<List<SignData>> create();
private BehaviorSubject<List<PlayerData>> playersRx = BehaviorSubject.<List<PlayerData>> create();
private Gson gson = new GsonBuilder().create();
@Override
public void onClose() {
logger.info("Connection to minecraft server closed");
}
@Override
public void onError(WebSocketException e) {
logger.error("Server error {}", e.getMessage(), e);
}
@Override
public void onOpen() {
logger.info("Connection to minecraft server opened");
}
@Override
public void onLogMessage(String s) {
logger.info("Log message: {}", s);
}
@Override
public void onMessage(WebSocketMessage message) {
String msg = message.getText();
if (msg != null) {
OHMessage ohMessage = gson.fromJson(msg, OHMessage.class);
int messageType = ohMessage.getMessageType();
if (OHMessage.MESSAGE_TYPE_SERVERS == messageType) {
ServerData serverData = gson.fromJson(ohMessage.getMessage(), ServerData.class);
serverRx.onNext(serverData);
} else if (OHMessage.MESSAGE_TYPE_PLAYERS == messageType) {
List<PlayerData> playerData = gson.fromJson(ohMessage.getMessage(),
new TypeToken<ArrayList<PlayerData>>() {
}.getType());
playersRx.onNext(playerData);
} else if (OHMessage.MESSAGE_TYPE_SIGNS == messageType) {
List<SignData> signsData = gson.fromJson(ohMessage.getMessage(), new TypeToken<ArrayList<SignData>>() {
}.getType());
signsRx.onNext(signsData);
}
}
}
/**
* Get observable emitting server items.
*
* @return observable emitting server items.
*/
public Observable<ServerData> getServerRx() {
return serverRx.asObservable();
}
/**
* Get observable emitting sign items.
*
* @return observable emitting sign items.
*/
public Observable<List<SignData>> getSignsRx() {
return signsRx.asObservable();
}
/**
* Get observable emitting player items.
*
* @return observable emitting player items.
*/
public Observable<List<PlayerData>> getPlayersRx() {
return playersRx.asObservable();
}
}

View File

@@ -0,0 +1,226 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.server;
import java.net.URI;
import java.net.URISyntaxException;
import org.openhab.binding.minecraft.internal.message.OHMessage;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.firebase.tubesock.WebSocket;
import com.firebase.tubesock.WebSocketException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
/**
* Holds information about connection to Minecraft server.
*
* @author Mattias Markehed - Initial contribution
*/
public class ServerConnection {
private String host;
private int port;
private ThingUID thingUID;
private Gson gson = new GsonBuilder().create();
private MinecraftSocketHandler socketHandler;
private WebSocket webSocket;
private ServerConnection(ThingUID thingUID, String host, int port) {
this.host = host;
this.port = port;
this.thingUID = thingUID;
}
public class ServerData {
public String host;
public int port;
public ServerConnection connection;
public ServerData(String host, int port, ServerConnection connection) {
this.host = host;
this.port = port;
this.connection = connection;
}
}
/**
* Get host address of server.
*
* @return server host address.
*/
public String getHost() {
return host;
}
/**
* Get port used to communicate to Minecraft server.
*
* @return
*/
public int getPort() {
return port;
}
/**
* Get UID of server
*
* @return server uid.
*/
public ThingUID getThingUID() {
return thingUID;
}
public void sendMessage(OHMessage message) {
String serializedMessage = gson.toJson(message);
webSocket.send(serializedMessage);
}
/**
* Add the handler used to handle messages state updates web socket.
*
* @param handler the websocket handler to use for new connections.
*/
private void setSocketHandler(MinecraftSocketHandler handler) {
socketHandler = handler;
}
private void setWebSocket(WebSocket webSocket) {
this.webSocket = webSocket;
}
/**
* Get the object used to handle state changes and messages from web socket.
*
* @return handler for web socket.
*/
public MinecraftSocketHandler getSocketHandler() {
return socketHandler;
}
/**
* Directly connect to server.
* Reconnects when connection is lost
*/
public static Observable<ServerConnection> create(final ThingUID thingUID, final String host, final int port) {
final String serverUrl = String.format("ws://%s:%d/stream", host, port);
return Observable.<ServerConnection> create(new OnSubscribe<ServerConnection>() {
private final Logger logger = LoggerFactory.getLogger(ServerConnection.class);
@Override
public void call(final Subscriber<? super ServerConnection> subscriber) {
logger.info("Start connecting to Minecraft server at: {}", serverUrl);
if (!subscriber.isUnsubscribed()) {
ServerConnection serverConnection = new ServerConnection(thingUID, host, port);
MinecraftSocketHandler socketHandler = new MinecraftSocketHandler() {
@Override
public void onError(WebSocketException e) {
subscriber.onError(e);
}
@Override
public void onClose() {
logger.info("Connection to Minecraft server stopped");
subscriber.onCompleted();
}
};
URI destUri = null;
try {
destUri = new URI(serverUrl);
} catch (URISyntaxException e) {
subscriber.onError(e);
}
final WebSocket websocket = new WebSocket(destUri);
websocket.setEventHandler(socketHandler);
websocket.connect();
serverConnection.setSocketHandler(socketHandler);
serverConnection.setWebSocket(websocket);
subscriber.onNext(serverConnection);
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
subscriber.unsubscribe();
websocket.close();
}
}));
}
}
});
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + port;
result = prime * result + ((socketHandler == null) ? 0 : socketHandler.hashCode());
result = prime * result + ((thingUID == null) ? 0 : thingUID.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ServerConnection other = (ServerConnection) obj;
if (host == null) {
if (other.host != null) {
return false;
}
} else if (!host.equals(other.host)) {
return false;
}
if (port != other.port) {
return false;
}
if (socketHandler == null) {
if (other.socketHandler != null) {
return false;
}
} else if (!socketHandler.equals(other.socketHandler)) {
return false;
}
if (thingUID == null) {
if (other.thingUID != null) {
return false;
}
} else if (!thingUID.equals(other.thingUID)) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.util;
import java.util.Objects;
/**
* Generic pair object.
*
* @author Mattias Markehed - Initial contribution
*/
public class Pair<T, R> {
public final T first;
public final R second;
public Pair(T first, R second) {
this.first = first;
this.second = second;
}
@Override
public int hashCode() {
return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Pair)) {
return false;
}
Pair<?, ?> p = (Pair<?, ?>) obj;
return Objects.equals(p.first, first) && Objects.equals(p.second, second);
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.minecraft.internal.util;
import java.util.concurrent.TimeUnit;
import rx.Observable;
import rx.functions.Func1;
/**
* RX operator subscribing to observable with a delay after it has finished.
*
* @author Mattias Markehed - Initial contribution
*/
public class RetryWithDelay implements Func1<Observable<? extends Throwable>, Observable<?>> {
private final int maxRetries;
private final long retryDelayMillis;
private int retryCount;
public RetryWithDelay(final long retryDelay, TimeUnit unit) {
this(-1, TimeUnit.MILLISECONDS.convert(retryDelay, unit));
}
public RetryWithDelay(final int maxRetries, final long retryDelay, TimeUnit unit) {
this(maxRetries, TimeUnit.MILLISECONDS.convert(retryDelay, unit));
}
private RetryWithDelay(final int maxRetries, final long retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
@Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts.flatMap(new Func1<Throwable, Observable<?>>() {
@Override
public Observable<?> call(Throwable throwable) {
if (maxRetries < 0 || ++retryCount < maxRetries) {
return Observable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);
}
return Observable.error(throwable);
}
});
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="minecraft" 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>Minecraft Binding</name>
<description>This binding can integrate Minecraft servers.</description>
<author>Mattias Markehed</author>
</binding:binding>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="minecraft"
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">
<bridge-type id="server">
<label>Minecraft Bukkit Server</label>
<description>Server from which data is being fetched.</description>
<channels>
<channel id="online" typeId="online"/>
<channel id="players" typeId="players"/>
<channel id="maxPlayers" typeId="maxPlayers"/>
</channels>
<config-description>
<parameter name="hostname" type="text" required="true">
<label>Hostname or IP</label>
<context>network-address</context>
<description>Hostname or IP of the server.</description>
</parameter>
<parameter name="port" type="integer">
<label>Port</label>
<description>The port on which the server can be accessed.</description>
<default>10692</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="player">
<supported-bridge-type-refs>
<bridge-type-ref id="server"/>
</supported-bridge-type-refs>
<label>Player</label>
<description>A connected player connected to the Minecraft server.</description>
<channels>
<channel id="playerOnline" typeId="online">
<label>Online</label>
<description>Online status of the player.</description>
</channel>
<channel id="playerLevel" typeId="playerLevel"/>
<channel id="playerTotalExperience" typeId="playerTotalExperience"/>
<channel id="playerExperiencePercentage" typeId="playerExperiencePercentage"/>
<channel id="playerHealth" typeId="playerHealth"/>
<channel id="playerWalkSpeed" typeId="playerWalkSpeed"/>
<channel id="playerLocation" typeId="location"/>
<channel id="playerGameMode" typeId="playerGameMode"/>
</channels>
<config-description>
<parameter name="playerName" type="text" required="true">
<label>Player Name</label>
<description>The name of the player.</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="redstoneSign">
<supported-bridge-type-refs>
<bridge-type-ref id="server"/>
</supported-bridge-type-refs>
<label>Sign</label>
<description>A sign with a redstone path under it.</description>
<channels>
<channel id="signActive" typeId="signActive"/>
</channels>
<config-description>
<parameter name="signName" type="text" required="true">
<label>Sign Name</label>
<description>The text on the sign.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="players">
<item-type>Number</item-type>
<label>Players</label>
<description>The number of players on the server.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="maxPlayers">
<item-type>Number</item-type>
<label>Max Players</label>
<description>The maxumum number of players on the server.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="online">
<item-type>Switch</item-type>
<label>Online</label>
<description>Online status of the thing.</description>
<category>Switch</category>
<state readOnly="true">
<options>
<option value="ON">Online</option>
<option value="OFF">Offline</option>
</options>
</state>
</channel-type>
<channel-type id="location">
<item-type>Location</item-type>
<label>Location</label>
<description>The location of the player.</description>
</channel-type>
<channel-type id="playerLevel">
<item-type>Number</item-type>
<label>Level</label>
<description>The players current level.</description>
</channel-type>
<channel-type id="playerTotalExperience" advanced="true">
<item-type>Number</item-type>
<label>Total Experience</label>
<description>The total experience of the player.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="playerExperiencePercentage" advanced="true">
<item-type>Number</item-type>
<label>Experience</label>
<description>Percentage of the experience bar filled for the next level.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="playerHealth">
<item-type>Number</item-type>
<label>Health</label>
<description>The health of player.</description>
</channel-type>
<channel-type id="playerWalkSpeed" advanced="true">
<item-type>Number</item-type>
<label>Speed</label>
<description>The speed of player.</description>
<state min="0" max="1" step="0.05" pattern="%.2f"/>
</channel-type>
<channel-type id="playerGameMode" advanced="true">
<item-type>String</item-type>
<label>Game Mode</label>
<description>The players current game mode.</description>
<state readOnly="false">
<options>
<option value="CREATIVE">creative</option>
<option value="SURVIVAL">survival</option>
<option value="ADVENTURE">adventure</option>
<option value="SPECTATOR">spectator</option>
</options>
</state>
</channel-type>
<channel-type id="signActive">
<item-type>Switch</item-type>
<label>Online</label>
<description>Shows if the sign has powered redstone below it.</description>
<category>Switch</category>
</channel-type>
</thing:thing-descriptions>