001package jmri.jmrix.roco.z21;
002
003import java.util.Locale;
004
005import jmri.JmriException;
006import jmri.Sensor;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import javax.annotation.Nonnull;
011
012/**
013 * Manage the Z21Specific Sensor implementation.
014 * <p>
015 * for RM Bus sensors, System names are "ZSnnn", where Z is the
016 * user-configurable system prefix and nnn is the sensor number without padding.
017 * <p>
018 * for CAN Bus sensors, System names are "ZSmm:pp" where Z is the
019 * user-configurable system prefix, mm is the CAN bus module id and pp is the
020 * contact number.
021 *
022 * @author Paul Bender Copyright (C) 2003-2018
023 * @navassoc 1 - * jmri.jmrix.lenz.Z21RMBusSensor
024 * @navassoc 1 - * jmri.jmrix.lenz.Z21CanSensor
025 */
026public class Z21SensorManager extends jmri.managers.AbstractSensorManager implements Z21Listener {
027
028    // ctor has to register for Z21 events
029    public Z21SensorManager(Z21SystemConnectionMemo memo) {
030        super(memo);
031        // register for messages
032        memo.getTrafficController().addz21Listener(this);
033        // make sure we are going to get can detector and RMBus data from 
034        // the command station
035        // set the broadcast flags so we get messages we may want to hear
036        memo.getRocoZ21CommandStation().setCanDetectorFlag(true);
037        memo.getRocoZ21CommandStation().setRMBusMessagesFlag(true);
038        // and forward the flags to the command station.
039        memo.getTrafficController().sendz21Message(Z21Message.getLanSetBroadcastFlagsRequestMessage(
040                memo.getRocoZ21CommandStation().getZ21BroadcastFlags()), null);
041    }
042
043    /**
044     * {@inheritDoc}
045     */
046    @Override
047    @Nonnull
048    public Z21SystemConnectionMemo getMemo() {
049        return (Z21SystemConnectionMemo) memo;
050    }
051
052    // to free resources when no longer used
053    @Override
054    public void dispose() {
055        getMemo().getTrafficController().removez21Listener(this);
056        super.dispose();
057    }
058
059    // Z21 specific methods
060
061    /**
062     * {@inheritDoc}
063     * <p>
064     * Assumes calling method has checked that a Sensor with this system
065     * name does not already exist.
066     *
067     * @throws IllegalArgumentException if the system name is not in a valid format
068     */
069    @Override
070    @Nonnull
071    protected Sensor createNewSensor(@Nonnull String systemName, String userName)  throws IllegalArgumentException {
072        if (systemName.contains(":")) {
073            // check for CAN format.
074            int bitNum = Z21CanBusAddress.getBitFromSystemName(systemName, getSystemPrefix());
075            if (bitNum != -1) {
076                return new Z21CanSensor(systemName, userName, getMemo());
077            } else {
078                throw new IllegalArgumentException("Invalid Sensor name: " + systemName);
079            }
080        } else {
081            // check if the output bit is available
082            int bitNum = Z21RMBusAddress.getBitFromSystemName(systemName, getSystemPrefix());
083            if (bitNum != -1) {
084                // create the new RMBus Sensor object
085                return new Z21RMBusSensor(systemName, userName,
086                        getMemo().getTrafficController(), getSystemPrefix());
087            } else {
088                throw new IllegalArgumentException("Invalid Sensor name: " + systemName);
089            }
090        }
091    }
092
093    /**
094     * {@inheritDoc}
095     */
096    @Override
097    public void reply(Z21Reply msg) {
098        log.debug("received message: {}", msg);
099        // LAN_CAN_DETECTOR message are related to CAN reporters/sensors.
100        if (msg.isCanDetectorMessage()) {
101            int type = (msg.getElement(9) & 0xFF);
102            log.debug("Sensor message type {}", type);
103            if (type == 0x01) {
104                log.debug("Received LAN_CAN_DETECTOR message");
105                int netID = (msg.getElement(4) & 0xFF) + ((msg.getElement(5) & 0xFF) << 8);
106                int msgPort = (msg.getElement(8) & 0xFF);
107                int address = (msg.getElement(6) & 0xFF) + ((msg.getElement(7) & 0xFF) << 8);
108                String systemName = Z21CanBusAddress.buildDecimalSystemNameFromParts(getSystemPrefix(),typeLetter(),address,msgPort);
109                Z21CanSensor r = (Z21CanSensor) getBySystemName(systemName);
110                if (null == r) {
111                    // try with the module's CAN network ID
112                    systemName = Z21CanBusAddress.buildHexSystemNameFromParts(getSystemPrefix(),typeLetter(),netID, msgPort);
113                    r = (Z21CanSensor) getBySystemName(systemName);
114                    if (null == r) {
115                        log.debug("Creating reporter {}", systemName);
116                        // need to create a new one, and send the message on 
117                        // to the newly created object.
118                        ((Z21CanSensor) provideSensor(systemName)).reply(msg);
119                    }
120                }
121            }
122        } else if (msg.isRMBusDataChangedReply()) {
123            log.debug("Received RM Bus Data Changed message");
124            // we could create sensors here automatically, but the 
125            // feed response contains data for 80 sensors, with no way
126            // to tell which of the 80 are actually connected.
127        }
128    }
129
130    /**
131     * {@inheritDoc}
132     */
133    @Override
134    public void message(Z21Message l) {
135        // no processing of outgoing messages.
136    }
137
138    /**
139     * {@inheritDoc}
140     */
141    @Override
142    @Nonnull
143    public String validateSystemNameFormat(@Nonnull String name, @Nonnull Locale locale) {
144        name = validateSystemNamePrefix(name, locale);
145        if (name.substring(getSystemNamePrefix().length()).contains(":")) {
146            return Z21CanBusAddress.validateSystemNameFormat(name, this, locale);
147        } else {
148            return Z21RMBusAddress.validateSystemNameFormat(name, this, locale);
149        }
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    @Override
156    public NameValidity validSystemNameFormat(@Nonnull String systemName) {
157        return Z21RMBusAddress.validSystemNameFormat(systemName, 'S', getSystemPrefix()) == NameValidity.VALID
158                ? NameValidity.VALID
159                : Z21CanBusAddress.validSystemNameFormat(systemName, 'S', getSystemPrefix());
160    }
161
162    @Override
163    public boolean allowMultipleAdditions(@Nonnull String systemName) {
164        return true;
165    }
166
167    @Override
168    @Nonnull
169    public synchronized String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
170        int encoderAddress;
171        int input;
172
173        if (curAddress.contains(":")) {
174            // This is a CAN Bus sensor address passed in the form of encoderAddress:input
175            int seperator = curAddress.indexOf(':');
176            try {
177                encoderAddress = Integer.parseInt(curAddress.substring(0, seperator));
178                input = Integer.parseInt(curAddress.substring(seperator + 1));
179                return Z21CanBusAddress.buildDecimalSystemNameFromParts(getSystemPrefix(),typeLetter(),encoderAddress,input);
180            } catch (NumberFormatException ex) {
181                // system name may include hex values for CAN sensors.
182                try {
183                    encoderAddress = Integer.parseInt(curAddress.substring(0, seperator), 16);
184                    input = Integer.parseInt(curAddress.substring(seperator + 1));
185                    return Z21CanBusAddress.buildHexSystemNameFromParts(getSystemPrefix(),typeLetter(),encoderAddress,input);
186                } catch (NumberFormatException ex1) {
187                    throw new JmriException("Unable to convert "+curAddress+" into the cab and input format of nn:xx");
188                }
189            }
190        } else {
191            // This is an RMBus Sensor address.
192            try {
193                iName = Integer.parseInt(curAddress);
194                return getSystemPrefix() + typeLetter() + iName;
195            } catch (NumberFormatException ex) {
196                throw new JmriException("Hardware Address "+curAddress+" passed should be a number or the cab and input format of nn:xx");
197            }
198        }
199    }
200
201    int iName; // must synchronize to avoid race conditions.
202
203    /**
204     * Does not enforce any rules on the encoder or input values.
205     */
206    @Override
207    public synchronized String getNextValidAddress(@Nonnull String curAddress, @Nonnull String prefix, boolean ignoreInitialExisting) throws JmriException{
208
209        String tmpSName = createSystemName(curAddress, prefix);
210        //Check to determine if the systemName is in use, return null if it is,
211        //otherwise return the next valid address.
212        Sensor s = getBySystemName(tmpSName);
213        if (s != null || ignoreInitialExisting) {
214            for (int x = 1; x < 10; x++) {
215                iName = iName + 1;
216                s = getBySystemName(prefix + typeLetter() + iName);
217                if (s == null) {
218                    return Integer.toString(iName);
219                }
220            }
221            log.warn(Bundle.getMessage("InvalidNextValidTenInUse",getBeanTypeHandled(true),curAddress,iName));
222            throw new JmriException(Bundle.getMessage("InvalidNextValidTenInUse",getBeanTypeHandled(true),curAddress,iName));
223        } else {
224            return Integer.toString(iName);
225        }
226    }
227
228    /**
229     * {@inheritDoc}
230     */
231    @Override
232    public Sensor getBySystemName(@Nonnull String sName){
233        Z21SystemNameComparator comparator = new Z21SystemNameComparator(getSystemPrefix(),typeLetter());
234        return getBySystemName(sName,comparator);
235    }
236
237    /**
238     * Provide a manager-specific tooltip for the Add new item beantable pane.
239     */
240    @Override
241    public String getEntryToolTip() {
242        return Bundle.getMessage("AddInputEntryToolTip");
243    }
244
245    private static final Logger log = LoggerFactory.getLogger(Z21SensorManager.class);
246
247}