001package jmri.jmrix.ecos;
002
003import java.util.Hashtable;
004import javax.annotation.Nonnull;
005import jmri.Sensor;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Implement sensor manager for ECoS systems. The Manager handles all the state
011 * changes.
012 * <p>
013 * System names are "USnnn:yy", Dcc4PcBoardManager nnn is the ECoS Object Number for a given
014 * s88 Bus Module and yy is the port on that module.
015 *
016 * @author Kevin Dickerson Copyright (C) 2009
017 */
018public class EcosSensorManager extends jmri.managers.AbstractSensorManager
019        implements EcosListener {
020
021    public EcosSensorManager(@Nonnull EcosSystemConnectionMemo memo) {
022        super(memo);
023        tc = memo.getTrafficController();
024        init();
025    }
026        
027    private void init() {
028        // listen for sensor creation
029        // connect to the TrafficManager
030        tc.addEcosListener(this);
031
032        // ask to be notified
033        //Not sure if we need to worry about newly created sensors on the layout.
034        /*EcosMessage m = new EcosMessage("request(26, view)");
035         tc.sendEcosMessage(m, this);*/
036        // get initial state
037        EcosMessage m = new EcosMessage("queryObjects(26, ports)");
038        tc.sendEcosMessage(m, this);
039    }
040
041    private final EcosTrafficController tc;
042    //The hash table simply holds the object number against the EcosSensor ref.
043    private final Hashtable<Integer, EcosSensor> _tecos = new Hashtable<>();   // stores known Ecos Object ids to DCC
044    private final Hashtable<Integer, Integer> _sport = new Hashtable<>();   // stores known Ecos Object ids to DCC
045
046    /**
047     * {@inheritDoc}
048     */
049    @Override
050    @Nonnull
051    public EcosSystemConnectionMemo getMemo() {
052        return (EcosSystemConnectionMemo) memo;
053    }
054
055    /**
056     * {@inheritDoc}
057     */
058    @Override
059    @Nonnull
060    protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException {
061        //int ports = Integer.parseInt(systemName.substring(getSystemPrefix().length() + 1));
062        return new EcosSensor(systemName, userName);
063    }
064
065    // to listen for status changes from Ecos system
066    @Override
067    public void reply(EcosReply m) {
068        // is this a list of sensors?
069        EcosSensor es;
070        if (m.getResultCode() == 0) {
071            int ecosObjectId = m.getEcosObjectId();
072            if ((ecosObjectId != 26) && ((ecosObjectId < 100) || (ecosObjectId > 300))) {
073                log.debug("message receieved that is not within the valid Sensor object range");
074                return;
075            }
076            if (m.isUnsolicited() || m.getReplyType().equals("get")) { //<Event Messages are unsolicited
077                String[] lines = m.getContents();
078                for (int i = 0; i < lines.length; i++) {
079                    int start = lines[i].indexOf("[") + 1;
080                    int end = lines[i].indexOf("]");
081                    if (lines[i].contains("state")) {
082                        start = start + 2;
083                        if (start > 0 && end > 0) {
084                            String val = lines[i].substring(start, end);
085                            int intState = Integer.parseInt(val, 16);
086                            decodeSensorState(ecosObjectId, intState);
087                        }
088                    }
089                    if (lines[i].contains("railcom")) {
090                        //int newstate = UNKNOWN;
091                        if (start > 0 && end > 0) {
092                            String val = lines[i].substring(start, lines[i].indexOf(",")).trim();
093                            int j = Integer.parseInt(val);
094                            j++;
095                            StringBuilder sb = new StringBuilder();
096                            sb.append(getSystemPrefix());
097                            sb.append("R");
098                            sb.append(ecosObjectId);
099                            sb.append(":");
100                            //Little work around to pad single digit address out.
101                            if (j < 10) {
102                                sb.append("0");
103                            }
104                            sb.append(j);
105                            try {
106                                EcosReporter rp = (EcosReporter) getMemo().getReporterManager().provideReporter(sb.toString());
107                                rp.decodeDetails(lines[i]);
108                            } catch (IllegalArgumentException ex) {
109                                log.warn("Failed to provide Reporter \"{}\" in reply", sb.toString());
110                            }
111                        }
112
113                    }
114                }
115                //With the sensor manager we don't keep an eye on the manager, which we proabaly need to do.
116            } else {
117                if (m.getReplyType().equals("queryObjects") && ecosObjectId == 26) {
118                    String[] lines = m.getContents();
119                    for (int i = 0; i < lines.length; i++) {
120                        if (lines[i].contains("ports[")) { // skip odd lines
121                            int start = 0;
122                            int end = lines[i].indexOf(' ');
123                            int object = Integer.parseInt(lines[i].substring(start, end));
124
125                            if ((100 <= object) && (object < 300)) { // only physical sensors
126                                start = lines[i].indexOf('[') + 1;
127                                end = lines[i].indexOf(']');
128                                int ports = Integer.parseInt(lines[i].substring(start, end));
129                                log.debug("Found sensor object {} ports {}", object, ports);
130
131                                if ((ports == 8) || (ports == 16)) {
132                                    Sensor s;
133                                    String sensorprefix = getSystemPrefix() + "S" + object + ":";
134                                    _sport.put(object, ports);
135                                    //ports 1, 5, 9 13 on a ECoS detector are railcom enabled., but value in messages is returned 0, 4, 8, 12
136                                    for (int j = 1; j <= ports; j++) {
137                                        StringBuilder sb = new StringBuilder();
138                                        sb.append(sensorprefix);
139                                        //Little work around to pad single digit address out.
140                                        if (j < 10) {
141                                            sb.append("0");
142                                        }
143                                        sb.append(j);
144                                        s = getSensor(sb.toString());
145                                        if (s == null) {
146                                            es = (EcosSensor) provideSensor(sb.toString());
147                                            es.setObjectNumber(object);
148                                            _tecos.put(object, es);
149                                            if (object >= 200 && (j == 1 || j == 5 || j == 9 || j == 13)) {
150                                                sb = new StringBuilder();
151                                                sb.append(getSystemPrefix());
152                                                sb.append("R");
153                                                sb.append(object);
154                                                sb.append(":");
155                                                //Little work around to pad single digit address out.
156                                                if (j < 10) {
157                                                    sb.append("0");
158                                                }
159                                                sb.append(j);
160                                                try {
161                                                    EcosReporter rp = (EcosReporter) getMemo().getReporterManager().provideReporter(sb.toString());
162                                                    rp.setObjectPort(object, (j - 1));
163                                                    es.setReporter(rp);
164                                                    EcosMessage em = new EcosMessage("get(" + object + ", railcom[" + (j - 1) + "])");
165                                                    tc.sendEcosMessage(em, this);
166                                                } catch (IllegalArgumentException ex) {
167                                                    log.warn("Failed to provide Reporter \"{}\"", sb.toString());
168                                                }
169                                            }
170                                        }
171                                    }
172                                    EcosMessage em = new EcosMessage("request(" + object + ", view)");
173                                    tc.sendEcosMessage(em, this);
174
175                                    em = new EcosMessage("get(" + object + ",state)");
176                                    tc.sendEcosMessage(em, this);
177                                } else {
178                                    log.debug("Invalid number of ports returned for Module {}", object);
179                                }
180                            }
181                        }
182                    }
183                }
184            }
185        }
186    }
187
188    @Override
189    public void message(EcosMessage m) {
190        // messages are ignored
191    }
192
193    private void decodeSensorState(int object, int intState) {
194        EcosSensor es;
195        int k = 1;
196        int result;
197        String sensorprefix = getSystemPrefix() + "S" + object + ":";
198        for (int port = 1; port <= _sport.get(object); port++) {
199            result = intState & k;
200            //Little work around to pad single digit address out.
201            StringBuilder sb = new StringBuilder();
202            sb.append(sensorprefix);
203            //Little work around to pad single digit address out.
204            if (port < 10) {
205                sb.append("0");
206            }
207            sb.append(port);
208            es = (EcosSensor) provideSensor(sb.toString());
209            if (result == 0) {
210                es.setOwnState(Sensor.INACTIVE);
211            } else {
212                es.setOwnState(Sensor.ACTIVE);
213            }
214            k = k * 2;
215        }
216    }
217
218    public void refreshItems() {
219        /*ask to be notified about newly created sensors on the layout.
220         Doing the request to view the list, will also kick off a request to 
221         view each individual sensor*/
222        EcosMessage m = new EcosMessage("queryObjects(26, ports)");
223        tc.sendEcosMessage(m, this);
224    }
225    
226    /**
227     * Validates to contain at least 1 number . . .
228     * <p>
229     * TODO: Custom validation for EcosSensorManager could be improved.
230     * {@inheritDoc}
231     */
232    @Override
233    @Nonnull
234    public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException {
235        return validateTrimmedMin1NumberSystemNameFormat(name,locale);
236    }
237
238    @Override
239    public void dispose() {
240        tc.removeEcosListener(this);
241        super.dispose();
242    }
243
244    private final static Logger log = LoggerFactory.getLogger(EcosSensorManager.class);
245
246}