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}