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 implements MqttEventListener { 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 if (mqttAdapter!= null) mqttAdapter.subscribe(sendTopic, this); // receive back on send topic 031 } 032 033 public MqttSignalMast(String systemName) { 034 super(systemName); 035 configureFromName(systemName); 036 sendTopic = makeSendTopic(systemName); 037 mqttAdapter = jmri.InstanceManager.getDefault(MqttSystemConnectionMemo.class).getMqttAdapter(); 038 if (mqttAdapter!= null) mqttAdapter.subscribe(sendTopic, this); // receive back on send topic 039 } 040 041 @Nonnull 042 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "MS_PKGPROTECT", 043 justification = "Public accessibility for scripts that update the prefixes") 044 public static String sendTopicPrefix = "track/signalmast/"; // default for constructing topic; public for script access, set by config 045 046 public static void setSendTopicPrefix(@Nonnull String prefix) { 047 sendTopicPrefix = prefix; 048 log.debug("sendTopicPrefix set to {}", prefix); 049 } 050 051 protected String makeSendTopic(String systemName) { 052 String[] pieces = systemName.split("\\("); 053 if (pieces.length == 2) { 054 String result = pieces[1].substring(1, pieces[1].length()-1); // starts with ($) 055 return sendTopicPrefix+result; 056 } else { 057 log.warn("not just one '(' in {}", systemName); 058 return sendTopicPrefix+systemName; 059 } 060 } 061 062 private static final String mastType = "IF$mqm"; 063 064 private final String sendTopic; 065 private MqttAdapter mqttAdapter; 066 067 private void configureFromName(String systemName) { 068 // split out the basic information 069 String[] parts = systemName.split(":"); 070 if (parts.length < 3) { 071 log.error("SignalMast system name needs at least three parts: {}", systemName); 072 throw new IllegalArgumentException("System name needs at least three parts: " + systemName); 073 } 074 if (!parts[0].equals(mastType)) { 075 log.warn("SignalMast system name should start with {} but is {}", mastType, systemName); 076 } 077 078 String system = parts[1]; 079 080 String mast = parts[2]; 081 // new style 082 mast = mast.substring(0, mast.indexOf("(")); 083 setMastType(mast); 084 String tmp = parts[2].substring(parts[2].indexOf("($") + 2, parts[2].indexOf(")")); 085 try { 086 log.debug("Parse {} as integer from {}?", tmp, parts[2]); 087 int autoNumber = Integer.parseInt(tmp); 088 synchronized (MqttSignalMast.class) { 089 if (autoNumber > getLastRef()) { 090 setLastRef(autoNumber); 091 } 092 } 093 } catch (NumberFormatException e) { 094 log.debug("Auto generated SystemName {} does not have numeric form, skipping autoincrement", systemName); 095 } 096 configureSignalSystemDefinition(system); 097 configureAspectTable(system, mast); 098 } 099 100 @Override 101 public void setAspect(@Nonnull String aspect) { 102 // check it's a choice 103 if (!map.checkAspect(aspect)) { 104 // not a valid aspect 105 log.warn("attempting to set invalid aspect: {} on mast: {}", aspect, getDisplayName()); 106 throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName()); 107 } else if (disabledAspects.contains(aspect)) { 108 log.warn("attempting to set an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName()); 109 throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName()); 110 } 111 log.debug("Setting aspect {}", aspect); 112 super.setAspect(aspect); 113 report(); 114 } 115 116 @Override 117 public void setHeld(boolean held) { 118 log.debug("Setting held {}", held); 119 super.setHeld(held); 120 report(); 121 } 122 123 @Override 124 public void setLit(boolean lit) { 125 log.debug("Setting lit {}", lit); 126 super.setLit(lit); 127 report(); 128 } 129 130 private void report() { 131 String msg = aspect+"; "; 132 msg = msg+ (getLit()?"Lit; ":"Unlit; "); 133 msg = msg+ (getHeld()?"Held":"Unheld"); 134 sendMessage(msg); 135 } 136 private void sendMessage(String c) { 137 log.debug("publishing \"{}\" on \"{}\"", c, sendTopic); 138 mqttAdapter.publish(sendTopic, c); 139 } 140 141 /** 142 * 143 * @param newVal for ordinal of all MqttSignalMasts in use 144 */ 145 protected static void setLastRef(int newVal) { 146 lastRef = newVal; 147 } 148 149 /** 150 * @return highest ordinal of all MqttSignalMasts in use 151 */ 152 public static int getLastRef() { 153 return lastRef; 154 } 155 156 @Override 157 public void notifyMqttMessage(String receivedTopic, String payload) { 158 if (! ( receivedTopic.endsWith(sendTopic) ) ) { 159 log.error("{} got a message whose topic ({}) wasn't for me ({})", getDisplayName(), receivedTopic, sendTopic); 160 return; 161 } 162 163 // parse and act 164 var parts = payload.split(";"); 165 int length = parts.length; 166 // part 0 is aspect 167 if (length >= 1) { 168 // parts[0] is the aspect name 169 String aspect = parts[0].trim(); 170 // check it's a choice 171 if (!map.checkAspect(aspect)) { 172 // not a valid aspect 173 log.warn("received invalid aspect: {} on mast: {}", aspect, getDisplayName()); 174 throw new IllegalArgumentException("attempting to set invalid aspect: " + aspect + " on mast: " + getDisplayName()); 175 } else if (disabledAspects.contains(aspect)) { 176 log.warn("received an aspect that has been disabled: {} on mast: {}", aspect, getDisplayName()); 177 throw new IllegalArgumentException("attempting to set an aspect that has been disabled: " + aspect + " on mast: " + getDisplayName()); 178 } 179 log.debug("Setting aspect {} from received payload", aspect); 180 super.setAspect(aspect); 181 182 } else { 183 log.error("{} got message with empty payload", getDisplayName()); 184 } 185 if (length >= 2) { 186 // parts[1] is the Lit status 187 if (parts[1].trim().equals("Unlit")) { 188 super.setLit(false); // calling this class's setLit sends report 189 } else { 190 super.setLit(true); // calling this class's setLit sends report 191 } 192 } 193 if (length >= 3) { 194 // parts[2] is the Held status 195 if (parts[2].trim().equals("Held")) { 196 super.setHeld(true); // calling this class's setHeld sends report 197 } else { 198 super.setHeld(false); // calling this class's setHeld sends report 199 } 200 } 201 } 202 203 /** 204 * Ordinal of all MqttSignalMasts to create unique system name. 205 */ 206 private static volatile int lastRef = 0; 207 208 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MqttSignalMast.class); 209 210}