001package jmri.jmrix.marklin;
002
003import java.util.Hashtable;
004import javax.annotation.Nonnull;
005import jmri.JmriException;
006import jmri.Sensor;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Implement sensor manager for Marklin systems. The Manager handles all the
012 * state changes.
013 * <p>
014 * System names are "USnnn:yy", where U is the user configurable system prefix,
015 * nnn is the Marklin Object Number for a given s88 Bus Module and
016 * yy is the port on that module.
017 *
018 * @author Kevin Dickerson Copyright (C) 2009
019 */
020public class MarklinSensorManager extends jmri.managers.AbstractSensorManager
021        implements MarklinListener {
022
023    public MarklinSensorManager(MarklinSystemConnectionMemo memo) {
024        super(memo);
025        tc = memo.getTrafficController();
026        // connect to the TrafficManager
027        tc.addMarklinListener(this);
028    }
029
030    MarklinTrafficController tc;
031    //The hash table simply holds the object number against the MarklinSensor ref.
032    private Hashtable<Integer, Hashtable<Integer, MarklinSensor>> _tmarklin = new Hashtable<Integer, Hashtable<Integer, MarklinSensor>>();   // stores known Marklin Obj
033
034    /**
035     * {@inheritDoc}
036     */
037    @Override
038    @Nonnull
039    public MarklinSystemConnectionMemo getMemo() {
040        return (MarklinSystemConnectionMemo) memo;
041    }
042
043    /**
044     * {@inheritDoc}
045     * <p>
046     * System name is normalized to ensure uniqueness.
047     * @throws IllegalArgumentException when SystemName can't be converted
048     */
049    @Override
050    @Nonnull
051    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
052        MarklinSensor s = new MarklinSensor(systemName, userName);
053        if (systemName.contains(":")) {
054            int board = 0;
055            int channel = 0;
056
057            String curAddress = systemName.substring(getSystemPrefix().length() + 1, systemName.length());
058            int seperator = curAddress.indexOf(":");
059            try {
060                board = Integer.parseInt(curAddress.substring(0, seperator));
061                if (!_tmarklin.containsKey(board)) {
062                    _tmarklin.put(board, new Hashtable<>());
063                    MarklinMessage m = MarklinMessage.sensorPollMessage(board);
064                    tc.sendMarklinMessage(m, this);
065                }
066            } catch (NumberFormatException ex) {
067                throw new IllegalArgumentException("Unable to convert " +  // NOI18N
068                        curAddress +
069                        " into the Module and port format of nn:xx"); // NOI18N
070            }
071            Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(board);
072            try {
073                channel = Integer.parseInt(curAddress.substring(seperator + 1));
074                if (!sensorList.containsKey(channel)) {
075                    sensorList.put(channel, s);
076                }
077            } catch (NumberFormatException ex) {
078                throw new IllegalArgumentException("Unable to convert " +  // NOI18N
079                        curAddress +
080                        " into the Module and port format of nn:xx"); // NOI18N
081            }
082        }
083        return s;
084    }
085
086    @Override
087    @Nonnull
088    public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException {
089        if (!curAddress.contains(":")) {
090            throw new JmriException("Hardware Address "+curAddress+"should be passed in the form 'Module:port'");
091        }
092
093        //Address format passed is in the form of board:channel or T:turnout address
094        int seperator = curAddress.indexOf(":");
095        try {
096            board = Integer.parseInt(curAddress.substring(0, seperator));
097        } catch (NumberFormatException ex) {
098            throw new JmriException("First part of "+curAddress+" in front of : should be a number");
099        }
100        try {
101            port = Integer.parseInt(curAddress.substring(seperator + 1));
102        } catch (NumberFormatException ex) {
103            throw new JmriException("Second part of "+curAddress+" after : should be a number");
104        }
105
106        if (port == 0 || port > 16) {
107            throw new JmriException("Port number "+port+" in "+curAddress+" must be between 1 and 16");
108        }
109        StringBuilder sb = new StringBuilder();
110        sb.append(getSystemPrefix());
111        sb.append("S");
112        sb.append(board);
113        sb.append(":");
114        //Little work around to pad single digit address out.
115        padPortNumber(port, sb);
116        return sb.toString();
117    }
118
119    private int board = 0;
120    private int port = 0;
121
122    @Override
123    public boolean allowMultipleAdditions(@Nonnull String systemName) {
124        return true;
125    }
126
127    void padPortNumber(int portNo, StringBuilder sb) {
128        if (portNo < 10) {
129            sb.append("0");
130        }
131        sb.append(portNo);
132    }
133
134    // to listen for status changes from Marklin system
135    @Override
136    public void reply(MarklinReply r) {
137        if (r.getPriority() == MarklinConstants.PRIO_1 && r.getCommand() >= MarklinConstants.FEECOMMANDSTART && r.getCommand() <= MarklinConstants.FEECOMMANDEND) {
138            if (r.getCommand() == MarklinConstants.S88EVENT) {
139                int module = (r.getElement(MarklinConstants.CANADDRESSBYTE1));
140                module = (module << 8) + (r.getElement(MarklinConstants.CANADDRESSBYTE2));
141                int contact = (r.getElement(MarklinConstants.CANADDRESSBYTE3));
142                contact = (contact << 8) + (r.getElement(MarklinConstants.CANADDRESSBYTE4));
143                String sensorprefix = getSystemPrefix() + "S" + module + ":";
144                Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(module);
145                if (sensorList == null) {
146                    //Module does not exist, so add it
147                    sensorList = new Hashtable<Integer, MarklinSensor>();
148                    _tmarklin.put(module, sensorList);
149                    MarklinMessage m = MarklinMessage.sensorPollMessage(module);
150                    tc.sendMarklinMessage(m, this);
151                    if (log.isDebugEnabled()) {
152                        log.debug("New module added {}", module);
153                    }
154                }
155                MarklinSensor ms = sensorList.get(contact);
156                if (ms == null) {
157                    StringBuilder sb = new StringBuilder();
158                    sb.append(sensorprefix);
159                    //Little work around to pad single digit address out.
160                    padPortNumber(contact, sb);
161                    if (log.isDebugEnabled()) {
162                        log.debug("New sensor added {} : {}", contact, sb.toString());
163                    }
164                    ms = (MarklinSensor) provideSensor(sb.toString());
165                }
166                if (r.getElement(9) == 0x01) {
167                    ms.setOwnState(Sensor.INACTIVE);
168                    return;
169                }
170                if (r.getElement(10) == 0x01) {
171                    ms.setOwnState(Sensor.ACTIVE);
172                    return;
173                }
174                log.error("state not found {} {} {}", ms.getDisplayName(), r.getElement(9), r.getElement(10));
175                log.error("for reply {}", r);
176            } else {
177                int s88Module = r.getElement(9);
178                if (_tmarklin.containsKey(s88Module)) {
179                    int status = r.getElement(10);
180                    status = (status << 8) + (r.getElement(11));
181                    decodeSensorState(s88Module, status);
182                    return;
183                }
184                if (log.isDebugEnabled()) {
185                    log.debug("State s88Module not registered {}", s88Module);
186                }
187            }
188        }
189    }
190
191    @Override
192    public void message(MarklinMessage m) {
193        // messages are ignored
194    }
195
196    private void decodeSensorState(int board, int intState) {
197        MarklinSensor ms;
198        int k = 1;
199        int result;
200
201        String sensorprefix = getSystemPrefix() + "S" + board + ":";
202        Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(board);
203        for (int portNo = 1; portNo < 17; portNo++) {
204            result = intState & k;
205            ms = sensorList.get(portNo);
206            if (ms == null) {
207                StringBuilder sb = new StringBuilder();
208                sb.append(sensorprefix);
209                //Little work around to pad single digit address out.
210                padPortNumber(portNo, sb);
211                ms = (MarklinSensor) provideSensor(sb.toString());
212            }
213            if (result == 0) {
214                ms.setOwnState(Sensor.INACTIVE);
215            } else {
216                ms.setOwnState(Sensor.ACTIVE);
217            }
218            k = k * 2;
219        }
220    }
221
222    private final static Logger log = LoggerFactory.getLogger(MarklinSensorManager.class);
223
224}