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,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>