001package jmri.jmrix.mqtt;
002
003import jmri.Light;
004import jmri.implementation.AbstractLight;
005
006import javax.annotation.Nonnull;
007
008/**
009 * MQTT implementation of the Light interface.
010 *
011 * @author Bob Jacobsen Copyright (C) 2001, 2008, 2020
012 * @author Paul Bender Copyright (C) 2010
013 * @author Fredrik Elestedt  Copyright (C) 2020
014 */
015public class MqttLight extends AbstractLight implements MqttEventListener {
016    private final MqttAdapter mqttAdapter;
017    private final String sendTopic;
018    private final String rcvTopic;
019
020    public MqttLight(MqttAdapter ma, String systemName, String userName, String sendTopic, String rcvTopic) {
021        super(systemName, userName);
022        this.sendTopic = sendTopic;
023        this.rcvTopic = rcvTopic;
024        this.mqttAdapter = ma;
025        this.mqttAdapter.subscribe(rcvTopic, this);
026    }
027
028    public void setParser(MqttContentParser<Light> parser) {
029        this.parser = parser;
030    }
031
032    MqttContentParser<Light> parser = new MqttContentParser<Light>() {
033        private final static String onText = "ON";
034        private final static String offText = "OFF";
035
036        int stateFromString(String payload) {
037            switch (payload) {
038                case onText: return ON;
039                case offText: return OFF;
040                default: return UNKNOWN;
041            }
042        }
043
044        @Override
045        public void beanFromPayload(@Nonnull Light bean, @Nonnull String payload, @Nonnull String topic) {
046            int state = stateFromString(payload);
047
048            boolean couldBeSendMessage = topic.endsWith(sendTopic);
049            boolean couldBeRcvMessage = topic.endsWith(rcvTopic);
050
051            if (couldBeSendMessage) {
052                setCommandedState(state);
053            } else if (couldBeRcvMessage) {
054                setState(state);
055            } else {
056                log.warn("failure to decode topic {} {}", topic, payload);
057            }
058        }
059        
060        @Override
061        public @Nonnull String payloadFromBean(@Nonnull Light bean, int newState){
062            String toReturn = "UNKNOWN";
063            switch (getState()) {
064                case Light.ON:
065                    toReturn = onText;
066                    break;
067                case Light.OFF:
068                    toReturn = offText;
069                    break;
070                default:
071                    log.error("Light has a state which is not supported {}", newState);
072                    break;
073            }
074            return toReturn;
075        }
076    };
077
078    // Handle a request to change state by sending a formatted packet
079    // to the server.
080    @Override
081    protected void doNewState(int oldState, int newState) {
082        log.debug("doNewState with old state {} new state {}", oldState, newState);
083        if (oldState == newState) {
084            return; //no change, just quit.
085        }  // sort out states
086        if ((newState & Light.ON) != 0) {
087            // first look for the double case, which we can't handle
088            if ((newState & Light.OFF) != 0) {
089                // this is the disaster case!
090                log.error("Cannot command both ON and OFF {}", newState);
091                return;
092            } else {
093                // send a ON command
094                sendMessage(true);
095            }
096        } else {
097            // send a OFF command
098            sendMessage(false);
099        }
100    }
101
102    private void sendMessage(boolean on) {
103        this.sendMessage(on ? "ON" : "OFF");
104    }
105
106    private void sendMessage(String c) {
107        jmri.util.ThreadingUtil.runOnLayoutEventually(() -> {
108            mqttAdapter.publish(this.sendTopic, c.getBytes());
109        });
110    }
111
112    @Override
113    public void setState(int newState) {
114        log.debug("setState {} was {}", newState, mState);
115        
116        //int oldState = mState;
117        if (newState != ON && newState != OFF && newState != UNKNOWN) {
118            throw new IllegalArgumentException("cannot set state value " + newState);
119        }
120        
121        // do the state change in the hardware
122        doNewState(mState, newState); // old state, new state
123        // change value and tell listeners
124        notifyStateChange(mState, newState);
125    }
126
127    //request a status update from the layout
128    @Override
129    public void requestUpdateFromLayout() {
130    }
131
132    @Override
133    public void notifyMqttMessage(String receivedTopic, String message) {
134        if (! ( receivedTopic.endsWith(rcvTopic) || receivedTopic.endsWith(sendTopic) ) ) {
135            log.error("Got a message whose topic ({}) wasn't for me ({})", receivedTopic, rcvTopic);
136            return;
137        }        
138        parser.beanFromPayload(this, message, receivedTopic);
139    }
140
141    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MqttLight.class);
142}