]> git.basschouten.com Git - openhab-addons.git/blob
738da9c62775e0ffb8b64f6204357540e9e1ef44
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.minecraft.internal.discovery;
14
15 import java.util.HashMap;
16 import java.util.Map;
17 import java.util.Set;
18
19 import org.openhab.binding.minecraft.internal.MinecraftBindingConstants;
20 import org.openhab.binding.minecraft.internal.MinecraftHandlerFactory;
21 import org.openhab.binding.minecraft.internal.config.ServerConfig;
22 import org.openhab.binding.minecraft.internal.handler.MinecraftServerHandler;
23 import org.openhab.binding.minecraft.internal.message.data.PlayerData;
24 import org.openhab.binding.minecraft.internal.message.data.SignData;
25 import org.openhab.binding.minecraft.internal.server.ServerConnection;
26 import org.openhab.binding.minecraft.internal.util.Pair;
27 import org.openhab.core.config.discovery.AbstractDiscoveryService;
28 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
29 import org.openhab.core.config.discovery.DiscoveryService;
30 import org.openhab.core.thing.ThingUID;
31 import org.osgi.service.component.annotations.Component;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import rx.Observable;
36 import rx.Subscription;
37 import rx.functions.Func1;
38 import rx.schedulers.Schedulers;
39 import rx.subscriptions.CompositeSubscription;
40
41 /**
42  * Handles discovery of Minecraft server, players and signs.
43  *
44  * @author Mattias Markehed - Initial contribution
45  */
46 @Component(service = DiscoveryService.class, configurationPid = "discovery.minecraft")
47 public class MinecraftDiscoveryService extends AbstractDiscoveryService {
48
49     private final Logger logger = LoggerFactory.getLogger(MinecraftDiscoveryService.class);
50
51     private static final int DISCOVER_TIMEOUT_SECONDS = 60;
52
53     private CompositeSubscription subscription;
54
55     public MinecraftDiscoveryService() {
56         super(Set.of(MinecraftBindingConstants.THING_TYPE_SERVER), DISCOVER_TIMEOUT_SECONDS, false);
57     }
58
59     @Override
60     protected void startScan() {
61         logger.debug("Starting Minecraft discovery scan");
62         discoverServers();
63     }
64
65     @Override
66     protected synchronized void stopScan() {
67         logger.debug("Stopping Minecraft discovery scan");
68         stopDiscovery();
69         super.stopScan();
70     }
71
72     /**
73      * Start scanning for players and signs on servers.
74      */
75     private void discoverServers() {
76         subscription = new CompositeSubscription();
77
78         Observable<ServerConnection> serverRx = serversConnectRx().cache();
79
80         Subscription playerSubscription = subscribePlayersRx(serverRx);
81         Subscription signsSubscription = subscribeSignsRx(serverRx);
82
83         subscription.add(playerSubscription);
84         subscription.add(signsSubscription);
85     }
86
87     /**
88      * Subscribe for sign updates
89      *
90      * @param serverRx server stream
91      * @return subscription for listening to sign events.
92      */
93     private Subscription subscribeSignsRx(Observable<ServerConnection> serverRx) {
94         return serverRx.flatMap(connection -> connection.getSocketHandler().getSignsRx().distinct(),
95                 (connection, signs) -> new Pair<>(connection, signs)).subscribe(conectionSignPair -> {
96                     for (SignData sign : conectionSignPair.second) {
97                         submitSignDiscoveryResults(conectionSignPair.first.getThingUID(), sign);
98                     }
99                 }, e -> logger.error("Error while scanning for signs", e));
100     }
101
102     /**
103      * Subscribe to player updates
104      *
105      * @param serverRx server stream
106      * @return subscription for listening to player events.
107      */
108     private Subscription subscribePlayersRx(Observable<ServerConnection> serverRx) {
109         return serverRx
110                 .flatMap(socketHandler -> socketHandler.getSocketHandler().getPlayersRx().distinct(),
111                         (connection, players) -> new Pair<>(connection, players))
112                 .subscribeOn(Schedulers.newThread()).subscribe(conectionPlayerPair -> {
113                     for (PlayerData player : conectionPlayerPair.second) {
114                         submitPlayerDiscoveryResults(conectionPlayerPair.first.getThingUID(), player.getName());
115                     }
116                 }, e -> logger.error("Error while scanning for players", e));
117     }
118
119     /**
120      * Teardown subscribers and stop searching for players and signs.
121      */
122     private void stopDiscovery() {
123         if (subscription != null && !subscription.isUnsubscribed()) {
124             subscription.unsubscribe();
125         }
126     }
127
128     /**
129      * Get all servers that have been added as observable.
130      *
131      * @return observable emitting server objects.
132      */
133     private Observable<ServerConnection> serversConnectRx() {
134         return Observable.from(MinecraftHandlerFactory.getMinecraftServers())
135                 .flatMap(new Func1<MinecraftServerHandler, Observable<ServerConnection>>() {
136                     @Override
137                     public Observable<ServerConnection> call(MinecraftServerHandler server) {
138                         ServerConfig config = server.getServerConfig();
139                         return ServerConnection.create(server.getThing().getUID(), config.getHostname(),
140                                 config.getPort());
141                     }
142                 });
143     }
144
145     /**
146      * Submit the discovered Devices to the inbox.
147      *
148      * @param bridgeUID
149      * @param name name of the player
150      */
151     private void submitPlayerDiscoveryResults(ThingUID bridgeUID, String name) {
152         String id = deviceNameToId(name);
153         ThingUID uid = new ThingUID(MinecraftBindingConstants.THING_TYPE_PLAYER, bridgeUID, id);
154
155         Map<String, Object> properties = new HashMap<>();
156         properties.put(MinecraftBindingConstants.PARAMETER_PLAYER_NAME, name);
157         thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withBridge(bridgeUID)
158                 .withLabel(name).build());
159     }
160
161     /**
162      * Submit the discovered Signs to the inbox.
163      *
164      * @param bridgeUID
165      * @param sign data describing sign
166      */
167     private void submitSignDiscoveryResults(ThingUID bridgeUID, SignData sign) {
168         String id = deviceNameToId(sign.getName());
169         ThingUID uid = new ThingUID(MinecraftBindingConstants.THING_TYPE_SIGN, bridgeUID, id);
170
171         Map<String, Object> properties = new HashMap<>();
172         properties.put(MinecraftBindingConstants.PARAMETER_SIGN_NAME, sign.getName());
173         thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withBridge(bridgeUID)
174                 .withLabel(sign.getName()).build());
175     }
176
177     /**
178      * Cleanup device name so it can be used as id.
179      *
180      * @param name the name of device.
181      * @return id of device.
182      */
183     private String deviceNameToId(String name) {
184         if (name == null) {
185             return "";
186         }
187         return name.replaceAll("[^a-zA-Z0-9]+", "");
188     }
189 }