All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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