001package jmri.jmrix.mqtt;
002
003import javax.annotation.Nonnull;
004import jmri.implementation.*;
005
006/**
007 * SignalMast implemented via MQTT messages
008 * <p>
009 * System name specifies the creation information:
010 * <pre>
011 * IF$mqm:basic:one-searchlight($0001)
012 * </pre> The name is a colon-separated series of terms:
013 * <ul>
014 * <li>IF$mqm - defines signal masts of this type
015 * <li>basic - name of the signaling system
016 * <li>one-searchlight - name of the particular aspect map
017 * <li>($0001) - small ordinal number for telling various signal masts apart
018 * apart
019 * </ul>
020 *
021 * @author Bob Jacobsen Copyright (C) 2009, 2021
022 */
023public class MqttSignalMast extends AbstractSignalMast {
024
025    public MqttSignalMast(String systemName, String userName) {
026        super(systemName, userName);
027        configureFromName(systemName);
028        sendTopic = makeSendTopic(systemName);
029        mqttAdapter = jmri.InstanceManager.getDefault(MqttSystemConnectionMemo.class).getMqttAdapter();
030    }
031
032    public MqttSignalMast(String systemName) {
033        super(systemName);
034        configureFromName(systemName);
035        sendTopic = makeSendTopic(systemName);
036        mqttAdapter = jmri.InstanceManager.getDefault(MqttSystemConnectionMemo.class).getMqttAdapter();
037    }
038
039    @Nonnull
040    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "MS_PKGPROTECT",
041            justification = "Public accessibility for scripts that update the prefixes")
042    public static String sendTopicPrefix = "track/signalmast/"; // default for constructing topic; public for script access, set by config
043
044    public static void setSendTopicPrefix(@Nonnull String prefix) {
045        sendTopicPrefix = prefix;
046        log.debug("sendTopicPrefix set to {}", prefix);
047    }
048
049    protected String makeSendTopic(String systemName) {
050        String[] pieces = systemName.split("\\(");
051        if (pieces.length == 2) {
052            String result = pieces[1].substring(1, pieces[1].length()-1); // starts with ($)
053            return sendTopicPrefix+result;
054        } else {
055            log.warn("not just one '(' in {}", systemName);
056            return sendTopicPrefix+systemName;
057        }
058    }
059
060    private static final String mastType = "IF$mqm";
061
062    private final String sendTopic;
063    private MqttAdapter mqttAdapter;
064
065    private void configureFromName(String systemName) {
066        // split out the basic information
067        String[] parts = systemName.split(":");
068        if (parts.length < 3) {
069            log.error("SignalMast system name needs at least three parts: {}", systemName);
070            throw new IllegalArgumentException("System name needs at least three parts: " + systemName);
071        }
072        if (!parts[0].equals(mastType)) {
073            log.warn("SignalMast system name should start with {} but is {}", mastType, systemName);
074        }
075
076        String system = parts[1];
077
078        String mast = parts[2];
079        // new style
080        mast = mast.substring(0, mast.indexOf("("));
081        setMastType(mast);
082        String tmp = parts[2].substring(parts[2].indexOf("($") + 2, parts[2].indexOf(")"));
083        try {
084            log.debug("Parse {} as integer from {}?", tmp, parts[2]);
085            int autoNumber = Integer.parseInt(tmp);
086            synchronized (MqttSignalMast.class) {
087                if (autoNumber > getLastRef()) {
088                    setLastRef(autoNumber);
089                }
090            }
091        } catch (NumberFormatException e) {
092            log.debug("Auto generated SystemName {} does not have numeric form, skipping autoincrement", systemName);
093        }
094        configureSignalSystemDefinition(system);
095        configureAspectTable(system, mast);
096    }
097
098    @Override
099    public void setAspect(@Nonnull String aspect) {
100        // check it's a choice
101        if (!map.checkAspect(aspect)) {
102            // not a valid aspect
103            log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName());
104            throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName());
105        } else if (disabledAspects.contains(aspect)) {
106            log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName());
107            throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName());
108        }
109        log.debug("Setting aspect {}", aspect);
110        super.setAspect(aspect);
111        report();
112    }
113
114    @Override
115    public void setHeld(boolean held) {
116        log.debug("Setting held {}", held);
117        super.setHeld(held);
118        report();
119    }
120
121    @Override
122    public void setLit(boolean lit) {
123        log.debug("Setting lit {}", lit);
124        super.setLit(lit);
125        report();
126    }
127
128    private void report() {
129        String msg = aspect+"; ";
130        msg = msg+ (getLit()?"Lit; ":"Unlit; ");
131        msg = msg+ (getHeld()?"Held":"Unheld");
132        sendMessage(msg);
133    }
134    private void sendMessage(String c) {
135        log.debug("publishing \"{}\" on \"{}\"", c, sendTopic);
136        mqttAdapter.publish(sendTopic, c);
137    }
138
139    /**
140     *
141     * @param newVal for ordinal of all MqttSignalMasts in use
142     */
143    protected static void setLastRef(int newVal) {
144        lastRef = newVal;
145    }
146
147    /**
148     * @return highest ordinal of all MqttSignalMasts in use
149     */
150    public static int getLastRef() {
151        return lastRef;
152    }
153
154    /**
155     * Ordinal of all MqttSignalMasts to create unique system name.
156     */
157    private static volatile int lastRef = 0;
158
159    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MqttSignalMast.class);
160
161}