
io.relayr.java.websocket.WebSocketClient Maven / Gradle / Ivy
package io.relayr.java.websocket;
import com.google.gson.Gson;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.relayr.java.api.ChannelApi;
import io.relayr.java.model.DataPackage;
import io.relayr.java.model.Device;
import io.relayr.java.model.action.Action;
import io.relayr.java.model.action.Command;
import io.relayr.java.model.action.Configuration;
import io.relayr.java.model.action.Reading;
import io.relayr.java.model.channel.ChannelDefinition;
import io.relayr.java.model.channel.DataChannel;
import io.relayr.java.model.channel.PublishChannel;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subjects.AsyncSubject;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
@Singleton
public class WebSocketClient {
private final ChannelApi channelApi;
private final WebSocket webSocket;
private final Map subChannels = new HashMap<>();
final Map> subObservers = new HashMap<>();
Observable pubChannel;
final Map pubChannels = new HashMap<>();
final Map> pubObservers = new HashMap<>();
final Map actionObservers = new HashMap<>();
@Inject
public WebSocketClient(ChannelApi api, WebSocketFactory factory) {
channelApi = api;
webSocket = factory.createWebSocket();
}
/**
* Subscribe to get {@link Reading} from device.
* @param device device to subscribe to
*/
public Observable subscribe(Device device) {
if (device == null || device.getId() == null) return Observable.empty();
return subscribe(device.getId());
}
/**
* Subscribe to get {@link Reading} from device.
* @param deviceId to subscribe to
*/
public Observable subscribe(String deviceId) {
if (deviceId == null) return Observable.empty();
if (!subObservers.containsKey(deviceId)) return createSubObserver(deviceId);
else return subObservers.get(deviceId);
}
private synchronized Observable createSubObserver(final String deviceId) {
final PublishSubject subject = PublishSubject.create();
subObservers.put(deviceId, subject);
channelApi.create(new ChannelDefinition(deviceId, "mqtt"))
.flatMap(new Func1>() {
@Override public Observable call(final DataChannel channel) {
return webSocket.createClient(channel);
}
})
.subscribeOn(Schedulers.newThread())
.subscribe(new Subscriber() {
@Override public void onCompleted() {}
@Override public void onError(Throwable e) {
e.printStackTrace();
subObservers.remove(deviceId);
}
@Override public void onNext(DataChannel channel) {
subscribeToChannel(channel, deviceId, subject);
}
});
return subject.doOnError(new Action1() {
@Override public void call(Throwable throwable) {
unSubscribe(deviceId);
}
});
}
private void subscribeToChannel(final DataChannel channel, final String deviceId,
final PublishSubject subject) {
webSocket.subscribe(channel.getTopic(), channel.getId(), new WebSocketCallback() {
@Override public void connectCallback(Object message) {
if (!subChannels.containsKey(deviceId))
subChannels.put(deviceId, channel);
}
@Override public void disconnectCallback(Object message) {
subject.onError((Throwable) message);
subChannels.remove(deviceId);
subObservers.remove(deviceId);
}
@Override public void successCallback(Object message) {
DataPackage dataPackage = new Gson().fromJson(message.toString(), DataPackage.class);
for (DataPackage.Data dataPoint : dataPackage.readings) {
subject.onNext(new Reading(dataPackage.received, dataPoint.recorded,
dataPoint.meaning, dataPoint.path, dataPoint.value));
}
}
@Override
public void errorCallback(Throwable e) {
subject.onError(e);
subChannels.remove(deviceId);
subObservers.remove(deviceId);
}
});
}
public void unSubscribe(final String deviceId) {
if (subObservers.containsKey(deviceId)) {
subObservers.get(deviceId).onCompleted();
subObservers.remove(deviceId);
}
if (!subChannels.isEmpty() && subChannels.containsKey(deviceId))
if (webSocket.unSubscribe(subChannels.get(deviceId).getCredentials().getTopic()))
subChannels.remove(deviceId);
}
public synchronized Observable publish(final String deviceId, final Reading payload) {
final AsyncSubject publishSubject = pubObservers.get(deviceId);
if (publishSubject != null) {
publishData(deviceId, payload, publishSubject);
} else {
final AsyncSubject subject = AsyncSubject.create();
if (pubChannel == null) createPubChannel(deviceId);
pubChannel.subscribeOn(Schedulers.newThread())
.subscribe(new Observer() {
@Override public void onCompleted() {}
@Override public void onError(Throwable e) {
pubChannels.remove(deviceId);
pubObservers.remove(deviceId);
subject.onError(e);
}
@Override public void onNext(DataChannel channel) {
if (!pubObservers.containsKey(deviceId))
pubObservers.put(deviceId, subject);
if (!pubChannels.containsKey(deviceId))
pubChannels.put(deviceId, channel);
publishData(deviceId, payload, subject);
}
});
return clearIfError(subject, deviceId);
}
return publishSubject;
}
private void publishData(String deviceId, Reading payload, AsyncSubject subject) {
try {
webSocket.publish(pubChannels.get(deviceId).getCredentials().getTopic() + "data",
new Gson().toJson(payload));
subject.onNext(null);
subject.onCompleted(); //Called because async observable is used
} catch (MqttException e) {
pubChannels.remove(deviceId);
pubObservers.remove(deviceId);
subject.onError(e);
}
}
/**
* Subscribe to get {@link Action} from device.
* {@link Action} can be {@link Command} or {@link Configuration}
* @param deviceId to subscribe to
*/
public Observable subscribeToCommands(String deviceId) {
if (deviceId == null) return Observable.empty();
if (!actionObservers.containsKey(deviceId)) {
final PublishSubject subject = PublishSubject.create();
return createActionObserver(deviceId, subject, Command.class);
} else {
return actionObservers.get(deviceId);
}
}
/**
* Subscribe to get {@link Action} from device.
* {@link Action} can be {@link Command} or {@link Configuration}
* @param deviceId to subscribe to
*/
public Observable subscribeToConfigurations(String deviceId) {
if (deviceId == null) return Observable.empty();
if (!actionObservers.containsKey(deviceId)) {
final PublishSubject subject = PublishSubject.create();
return createActionObserver(deviceId, subject, Configuration.class);
} else {
return actionObservers.get(deviceId);
}
}
synchronized Observable createActionObserver(final String deviceId,
final PublishSubject subject,
final Class type) {
actionObservers.put(deviceId, subject);
if (pubChannel == null) createPubChannel(deviceId);
pubChannel.subscribeOn(Schedulers.newThread())
.subscribe(new Subscriber() {
@Override public void onCompleted() {}
@Override public void onError(Throwable e) {
e.printStackTrace();
pubChannels.remove(deviceId);
actionObservers.remove(deviceId);
}
@Override public void onNext(DataChannel channel) {
if (!pubChannels.containsKey(deviceId)) pubChannels.put(deviceId, channel);
final String topic = channel.getTopic() + (type == Command.class ? "cmd" : "conf");
webSocket.subscribe(topic, channel.getId(), new WebSocketCallback() {
@Override public void connectCallback(Object message) {}
@Override public void disconnectCallback(Object message) {
subject.onError((Throwable) message);
actionObservers.remove(deviceId);
}
@Override public void successCallback(Object message) {
subject.onNext(new Gson().fromJson(message.toString(), type));
}
@Override
public void errorCallback(Throwable e) {
subject.onError(e);
actionObservers.remove(deviceId);
}
});
}
});
return clearIfError(subject, deviceId);
}
void createPubChannel(String deviceId) {
pubChannel = channelApi
.createForDevice(new ChannelDefinition(deviceId, "mqtt"), deviceId)
.flatMap(new Func1>() {
@Override public Observable call(PublishChannel channel) {
return webSocket.createClient(channel);
}
});
}
private Observable clearIfError(Subject subject, final String deviceId) {
return subject.doOnError(new Action1() {
@Override public void call(Throwable throwable) {
pubChannels.remove(deviceId);
pubObservers.remove(deviceId);
}
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy