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