001package jmri.jmrix.acela;
002
003import java.util.Locale;
004import javax.annotation.Nonnull;
005import jmri.Sensor;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Manage the Acela-specific Sensor implementation.
011 * <p>
012 * System names are "ASnnnn", where A is the user configurable system prefix,
013 * nnnn is the sensor number without padding.
014 * <p>
015 * Sensors are numbered from 0.
016 * <p>
017 * This is an AcelaListener to handle the replies to poll messages. Those are
018 * forwarded to the specific AcelaNode object corresponding to their origin for
019 * processing of the data.
020 *
021 * @author Bob Jacobsen Copyright (C) 2003, 2007
022 * @author Dave Duchamp, multi node extensions, 2004
023 * @author Bob Coleman Copyright (C) 2007, 2008 Based on CMRI serial example,
024 * modified to establish Acela support.
025 */
026public class AcelaSensorManager extends jmri.managers.AbstractSensorManager
027        implements AcelaListener {
028
029    public AcelaSensorManager(AcelaSystemConnectionMemo memo) {
030        super(memo);
031    }
032
033    /**
034     * {@inheritDoc}
035     */
036    @Override
037    @Nonnull
038    public AcelaSystemConnectionMemo getMemo() {
039        return (AcelaSystemConnectionMemo) memo;
040    }
041
042    /**
043     * {@inheritDoc}
044     * <p>
045     * System name is normalized to ensure uniqueness.
046     * @throws IllegalArgumentException when SystemName can't be converted
047     */
048    @Override
049    @Nonnull
050    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
051        // Validate the systemName
052        if (AcelaAddress.validSystemNameFormat(systemName, 'S', getSystemPrefix()) == NameValidity.INVALID) {
053            log.error("Invalid Sensor system Name format: {}", systemName);
054            throw new IllegalArgumentException("Invalid Sensor System Name format: " + systemName);
055        }
056        Sensor s;
057        String sName = systemName;
058        if (sName.isEmpty()) {
059            // system name is not valid
060            throw new IllegalArgumentException("Invalid Acela Sensor system name - " +  // NOI18N
061                    systemName);
062        }
063        // does this Sensor already exist
064        s = getBySystemName(sName);
065        if (s != null) {
066            throw new IllegalArgumentException("Acela Sensor with this name already exists - " +  // NOI18N
067                    systemName);
068        }
069        // check under alternate name
070        String altName = AcelaAddress.convertSystemNameToAlternate(sName, getSystemPrefix());
071        s = getBySystemName(altName);
072        if (s != null) {
073            throw new IllegalArgumentException("Acela Sensor with name  " +  // NOI18N
074                    systemName + " already exists as " + altName);
075        }
076        // check bit number
077        int bit = AcelaAddress.getBitFromSystemName(sName, getSystemPrefix());
078        if ((bit < AcelaAddress.MINSENSORADDRESS) || (bit > AcelaAddress.MAXSENSORADDRESS)) {
079            log.error("Sensor bit number {} is outside the supported range {}-{}", bit, AcelaAddress.MINSENSORADDRESS, AcelaAddress.MAXSENSORADDRESS);
080            throw new IllegalArgumentException("Sensor bit number " +  // NOI18N
081                    Integer.toString(bit) + " is outside the supported range " + // NOI18N
082                    Integer.toString(AcelaAddress.MAXSENSORADDRESS) + "-" +
083                    Integer.toString(AcelaAddress.MAXSENSORADDRESS));
084        }
085        // Sensor system name is valid and Sensor doesn't exist, make a new one
086        if (userName == null) {
087            s = new AcelaSensor(sName);
088        } else {
089            s = new AcelaSensor(sName, userName);
090        }
091
092        // ensure that a corresponding Acela Node exists
093        AcelaNode node = AcelaAddress.getNodeFromSystemName(sName, getMemo());
094        if (node == null) {
095            log.warn("Sensor: {} refers to an undefined Acela Node.", sName);
096            return s;
097        }
098        if (!node.hasActiveSensors) {
099            int newNodeAddress;
100            newNodeAddress = node.getNodeAddress();
101            log.warn("We got the wrong node: {}", newNodeAddress);
102            return s;
103        }
104        // register this sensor with the Acela Node
105        node.registerSensor(s, bit);
106        return s;
107    }
108
109    /**
110     * {@inheritDoc}
111     * <p>
112     * Verifies system name has valid prefix and is an integer from
113     * {@value AcelaAddress#MINSENSORADDRESS} to
114     * {@value AcelaAddress#MAXSENSORADDRESS}.
115     */
116    @Override
117    @Nonnull
118    public String validateSystemNameFormat(@Nonnull String systemName, @Nonnull Locale locale) {
119        return super.validateIntegerSystemNameFormat(systemName,
120                AcelaAddress.MINSENSORADDRESS,
121                AcelaAddress.MAXSENSORADDRESS,
122                locale);
123    }
124
125    /**
126     * {@inheritDoc}
127     */
128    @Override
129    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
130        return (AcelaAddress.validSystemNameFormat(systemName, 'S', getSystemPrefix()));
131    }
132
133    /**
134     * Dummy routine
135     */
136    @Override
137    public void message(AcelaMessage r) {
138        log.warn("unexpected message");
139    }
140
141    /**
142     * Process a reply to a poll of Sensors of one node.
143     */
144    @Override
145    public void reply(AcelaReply r) {
146        // Determine which state we are in: Initializing Acela Network or Polling Sensors
147        boolean currentstate = getMemo().getTrafficController().getAcelaTrafficControllerState();
148        //  Flag to indicate which state we are in: 
149        //  false == Initializing Acela Network
150        //  true == Polling Sensors
151        if (!currentstate) {
152            int replysize = r.getNumDataElements();
153            if (replysize == 0) {
154                // The Acela Online command seems to return an empty message
155                log.warn("We got an empty reply of size: {}", replysize);
156            } else {
157                if (replysize == 1) {
158                    byte replyvalue = (byte) (r.getElement(0));
159                    if (replyvalue == 0x00) {
160                        //  Everything is OK.
161                    } else {  //  Assume this is the response to the pollnodes
162                        log.warn("We got a bad return code: {}", replyvalue);
163                    }
164                } else {
165                    for (int i = 0; i < replysize; i++) {
166                        byte replynodetype = (byte) (r.getElement(i));
167
168                        int nodetype;
169                        switch (replynodetype) {
170                            case 0x00: {
171                                nodetype = AcelaNode.AC;  // Should never get this
172                                break;
173                            }
174                            case 0x01: {
175                                nodetype = AcelaNode.TB;
176                                break;
177                            }
178                            case 0x02: {
179                                nodetype = AcelaNode.D8;
180                                break;
181                            }
182                            case 0x03: {
183                                nodetype = AcelaNode.WM;
184                                break;
185                            }
186                            case 0x04: {
187                                nodetype = AcelaNode.SM;
188                                break;
189                            }
190                            case 0x05: {
191                                nodetype = AcelaNode.SC;
192                                break;
193                            }
194                            case 0x06: {
195                                nodetype = AcelaNode.SW;
196                                break;
197                            }
198                            case 0x07: {
199                                nodetype = AcelaNode.YM;
200                                break;
201                            }
202                            case 0x08: {
203                                nodetype = AcelaNode.SY;
204                                break;
205                            }
206                            default: {
207                                nodetype = AcelaNode.UN;  // Should never get this
208                            }
209                        }
210                        int tempaddr = i + 1;
211                        new AcelaNode(tempaddr, nodetype, getMemo().getTrafficController());
212                        log.info("Created a new Acela Node [{}] as a result of Acela network Poll of type: {}", tempaddr, replynodetype);
213                    }
214                    getMemo().getTrafficController().setAcelaTrafficControllerState(true);
215                }
216            }
217        } else {
218            int replysize = r.getNumDataElements();
219            if (replysize > 1) {  // Bob C: not good if only one sensor module !!
220                getMemo().getTrafficController().updateSensorsFromPoll(r);
221            }
222        }
223    }
224
225    /**
226     * Register any orphan Sensors when a new Acela Node is created.
227     * @param node which node to search for sensors.
228     */
229    public void registerSensorsForNode(AcelaNode node) {
230        // get list containing all Sensors
231        log.info("Trying to register sensor from Manager 2: {}Sxx", getSystemPrefix()); // multichar prefix
232        // Iterate through the sensors
233        AcelaNode tNode;
234        for (Sensor s : getNamedBeanSet()) {
235            String sName = s.getSystemName();
236            log.debug("system Name is {}", sName);
237            if (sName.startsWith(getSystemNamePrefix())) { // multichar prefix
238                // This is an Acela Sensor
239                tNode = AcelaAddress.getNodeFromSystemName(sName, getMemo());
240                if (tNode == node) {
241                    // This sensor is for this new Acela Node - register it
242                    node.registerSensor(s,
243                            AcelaAddress.getBitFromSystemName(sName, getSystemPrefix()));
244                }
245            }
246        }
247    }
248
249    @Override
250    public boolean allowMultipleAdditions(@Nonnull String systemName) {
251        return true;
252    }
253
254    private final static Logger log = LoggerFactory.getLogger(AcelaSensorManager.class);
255
256}