001package jmri.jmris.srcp;
002
003import java.beans.PropertyChangeListener;
004import java.io.DataInputStream;
005import java.io.IOException;
006import java.io.OutputStream;
007import java.util.ArrayList;
008
009import jmri.*;
010import jmri.jmris.AbstractThrottleServer;
011import jmri.SystemConnectionMemo;
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015/**
016 * Interface between the JMRI Throttles and an SRCP network connection
017 *
018 * @author Paul Bender Copyright (C) 2016
019 */
020public class JmriSRCPThrottleServer extends AbstractThrottleServer {
021
022    private static final Logger log = LoggerFactory.getLogger(JmriSRCPThrottleServer.class);
023
024    private final OutputStream output;
025
026    private final ArrayList<Integer> busList;
027    private final ArrayList<LocoAddress> addressList;
028
029    public JmriSRCPThrottleServer(DataInputStream inStream, OutputStream outStream) {
030        super();
031        busList = new ArrayList<>();
032        addressList = new ArrayList<>();
033        output = outStream;
034    }
035
036
037    /*
038     * Protocol Specific Functions
039     */
040    @Override
041    public void sendStatus(LocoAddress l) throws IOException {
042        output.write( Bundle.getMessage("Error499").getBytes());
043    }
044
045    /*
046     * send the status of the specified throttle address on the specified bus
047     * @param bus bus number.
048     * @param address locomoitve address.
049     */
050    public void sendStatus(int bus, int address) throws IOException {
051        log.debug("send Status called with bus {} and address {}", bus, address);
052
053        /* translate the bus into a system connection memo */
054        java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class);
055        SystemConnectionMemo memo;
056        try {
057            memo = list.get(bus - 1);
058        } catch (java.lang.IndexOutOfBoundsException obe) {
059            output.write(Bundle.getMessage("Error412").getBytes());
060            return;
061        }
062
063        /* request the throttle for this particular locomotive address */
064        if (memo.provides(jmri.ThrottleManager.class)) {
065            ThrottleManager t = memo.get(jmri.ThrottleManager.class);
066            // we will use getThrottleInfo to request information about the
067            // address, so we need to convert the address to a DccLocoAddress
068            // object first.
069            DccLocoAddress addr = new DccLocoAddress(address, !(t.canBeShortAddress(address)));
070            Boolean isForward = (Boolean) t.getThrottleInfo(addr, Throttle.ISFORWARD);
071            Float speedSetting = (Float) t.getThrottleInfo(addr, Throttle.SPEEDSETTING);
072            SpeedStepMode speedStepMode = (SpeedStepMode) t.getThrottleInfo(addr, Throttle.SPEEDSTEPMODE);
073            Boolean f0 = (Boolean) t.getThrottleInfo(addr, "F0");
074            Boolean f1 = (Boolean) t.getThrottleInfo(addr, "F1");
075            Boolean f2 = (Boolean) t.getThrottleInfo(addr, "F2");
076            Boolean f3 = (Boolean) t.getThrottleInfo(addr, "F3");
077            Boolean f4 = (Boolean) t.getThrottleInfo(addr, "F4");
078            Boolean f5 = (Boolean) t.getThrottleInfo(addr, "F5");
079            Boolean f6 = (Boolean) t.getThrottleInfo(addr, "F6");
080            Boolean f7 = (Boolean) t.getThrottleInfo(addr, "F7");
081            Boolean f8 = (Boolean) t.getThrottleInfo(addr, "F8");
082            Boolean f9 = (Boolean) t.getThrottleInfo(addr, "F9");
083            Boolean f10 = (Boolean) t.getThrottleInfo(addr, "F10");
084            Boolean f11 = (Boolean) t.getThrottleInfo(addr, "F11");
085            Boolean f12 = (Boolean) t.getThrottleInfo(addr, "F12");
086            Boolean f13 = (Boolean) t.getThrottleInfo(addr, "F13");
087            Boolean f14 = (Boolean) t.getThrottleInfo(addr, "F14");
088            Boolean f15 = (Boolean) t.getThrottleInfo(addr, "F15");
089            Boolean f16 = (Boolean) t.getThrottleInfo(addr, "F16");
090            Boolean f17 = (Boolean) t.getThrottleInfo(addr, "F17");
091            Boolean f18 = (Boolean) t.getThrottleInfo(addr, "F18");
092            Boolean f19 = (Boolean) t.getThrottleInfo(addr, "F19");
093            Boolean f20 = (Boolean) t.getThrottleInfo(addr, "F20");
094            Boolean f21 = (Boolean) t.getThrottleInfo(addr, "F21");
095            Boolean f22 = (Boolean) t.getThrottleInfo(addr, "F22");
096            Boolean f23 = (Boolean) t.getThrottleInfo(addr, "F23");
097            Boolean f24 = (Boolean) t.getThrottleInfo(addr, "F24");
098            Boolean f25 = (Boolean) t.getThrottleInfo(addr, "F25");
099            Boolean f26 = (Boolean) t.getThrottleInfo(addr, "F26");
100            Boolean f27 = (Boolean) t.getThrottleInfo(addr, "F27");
101            Boolean f28 = (Boolean) t.getThrottleInfo(addr, "F28");
102            // and now build the output string to send
103            String StatusString = "100 INFO " + bus + " GL " + address + " ";
104            StatusString += isForward ? "1 " : "0 ";
105            {
106                int numSteps = 100;
107                // For NMRA DCC speed step modes, we use the number of steps from that mode.
108                // Non-NMRA modes are not supported, so for those, we fall back to 100 steps.
109                switch(speedStepMode) {
110                    case NMRA_DCC_128:
111                    case NMRA_DCC_28:
112                    case NMRA_DCC_27:
113                    case NMRA_DCC_14:
114                        numSteps = speedStepMode.numSteps;
115                        break;
116                    default:
117                        numSteps = 100;
118                        break;
119                }
120                StatusString += (int) java.lang.Math.ceil(speedSetting * numSteps) + " " + numSteps;
121            }
122            StatusString += f0 ? " 1" : " 0";
123            StatusString += f1 ? " 1" : " 0";
124            StatusString += f2 ? " 1" : " 0";
125            StatusString += f3 ? " 1" : " 0";
126            StatusString += f4 ? " 1" : " 0";
127            StatusString += f5 ? " 1" : " 0";
128            StatusString += f6 ? " 1" : " 0";
129            StatusString += f7 ? " 1" : " 0";
130            StatusString += f8 ? " 1" : " 0";
131            StatusString += f9 ? " 1" : " 0";
132            StatusString += f10 ? " 1" : " 0";
133            StatusString += f11 ? " 1" : " 0";
134            StatusString += f12 ? " 1" : " 0";
135            StatusString += f13 ? " 1" : " 0";
136            StatusString += f14 ? " 1" : " 0";
137            StatusString += f15 ? " 1" : " 0";
138            StatusString += f16 ? " 1" : " 0";
139            StatusString += f17 ? " 1" : " 0";
140            StatusString += f18 ? " 1" : " 0";
141            StatusString += f19 ? " 1" : " 0";
142            StatusString += f20 ? " 1" : " 0";
143            StatusString += f21 ? " 1" : " 0";
144            StatusString += f22 ? " 1" : " 0";
145            StatusString += f23 ? " 1" : " 0";
146            StatusString += f24 ? " 1" : " 0";
147            StatusString += f25 ? " 1" : " 0";
148            StatusString += f26 ? " 1" : " 0";
149            StatusString += f27 ? " 1" : " 0";
150            StatusString += f28 ? " 1" : " 0";
151            StatusString += "\n\r";
152            output.write(StatusString.getBytes());
153        } else {
154            output.write(Bundle.getMessage("Error412").getBytes());
155        }
156    }
157
158    @Override
159    public void sendErrorStatus() throws IOException {
160        output.write(Bundle.getMessage("Error499").getBytes());
161    }
162
163    @Override
164    public void parsecommand(String statusString) throws JmriException, IOException {
165    }
166
167    @Override
168    public void sendThrottleFound(jmri.LocoAddress address) throws IOException {
169        Integer bus;
170        if (addressList.contains(address)) {
171            bus = busList.get(addressList.indexOf(address));
172        } else {
173            // we didn't request this address.
174            return;
175        }
176
177        // Build the output string to send
178        String StatusString = "101 INFO " + bus + " GL " + address.getNumber() + " "; // assume DCC for now.
179        StatusString += address.getProtocol() == jmri.LocoAddress.Protocol.DCC_SHORT ? "N 1 28" : "N 2 28";
180        StatusString += "\n\r";
181        output.write(StatusString.getBytes());
182    }
183
184    @Override
185    public void sendThrottleReleased(jmri.LocoAddress address) throws IOException {
186        Integer bus;
187        if (addressList.contains(address)) {
188            bus = busList.get(addressList.indexOf(address));
189        } else {
190            // we didn't request this address.
191            return;
192        }
193
194        // Build the output string to send
195        String StatusString = "102 INFO " + bus + " GL " + address.getNumber(); // assume DCC for now.
196        StatusString += address.getProtocol() == jmri.LocoAddress.Protocol.DCC_SHORT ? "N 1 28" : "N 2 28";
197        StatusString += "\n\r";
198        output.write(StatusString.getBytes());
199    }
200
201    public void initThrottle(int bus, int address, boolean isLong,
202            int speedsteps, int functions) throws IOException {
203        log.debug("initThrottle called with bus {} and address {}", bus, address);
204
205        /* translate the bus into a system connection memo */
206        java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class);
207        SystemConnectionMemo memo;
208        try {
209            memo = list.get(bus - 1);
210        } catch (java.lang.IndexOutOfBoundsException obe) {
211            output.write(Bundle.getMessage("Error412").getBytes());
212            return;
213        }
214
215        /* request the throttle for this particular locomotive address */
216        if (memo.provides(jmri.ThrottleManager.class)) {
217            ThrottleManager t = memo.get(jmri.ThrottleManager.class);
218            // we will use getThrottleInfo to request information about the
219            // address, so we need to convert the address to a DccLocoAddress
220            // object first.
221            DccLocoAddress addr = new DccLocoAddress(address, isLong);
222            busList.add(bus);
223            addressList.add(addr);
224            t.requestThrottle(addr, this, false);
225        }
226    }
227
228    public void releaseThrottle(int bus, int address) throws IOException {
229        log.debug("releaseThrottle called with bus {} and address {}", bus, address);
230
231        /* translate the bus into a system connection memo */
232        java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class);
233        SystemConnectionMemo memo;
234        try {
235            memo = list.get(bus - 1);
236        } catch (java.lang.IndexOutOfBoundsException obe) {
237            output.write(Bundle.getMessage("Error412").getBytes());
238            return;
239        }
240
241        /* release the throttle for this particular locomotive address */
242        if (memo.provides(jmri.ThrottleManager.class)) {
243            ThrottleManager t = memo.get(jmri.ThrottleManager.class);
244            DccLocoAddress addr = new DccLocoAddress(address, t.canBeLongAddress(address));
245            t.releaseThrottle((DccThrottle) throttleList.get(addressList.indexOf(addr)), this);
246            throttleList.remove(addressList.indexOf(addr));
247            sendThrottleReleased(addr);
248            busList.remove(addressList.indexOf(addr));
249            addressList.remove(addr);
250        }
251    }
252
253    /*
254     * Set Throttle Speed and Direction
255     *
256     * @param bus, bus the throttle is on.
257     * @param l address of the locomotive to change speed of.
258     * @param speed float representing the speed, -1 for emergency stop.
259     * @param isForward boolean, true if forward, false if reverse or
260     * undefined.
261     */
262    public void setThrottleSpeedAndDirection(int bus, int address, float speed, boolean isForward) {
263        log.debug("Setting Speed for address {} bus {} to {} with direction {}",
264                address, bus, speed, isForward ? "forward" : "reverse");
265        java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class);
266        SystemConnectionMemo memo;
267        try {
268            memo = list.get(bus - 1);
269        } catch (java.lang.IndexOutOfBoundsException obe) {
270            try {
271                output.write(Bundle.getMessage("Error412").getBytes());
272            } catch (IOException ioe) {
273                log.error("Error writing to network port");
274            }
275            return;
276        }
277
278        /* request the throttle for this particular locomotive address */
279        if (memo.provides(jmri.ThrottleManager.class)) {
280            ThrottleManager tm = memo.get(jmri.ThrottleManager.class);
281            // we will use getThrottleInfo to request information about the
282            // address, so we need to convert the address to a DccLocoAddress
283            // object first.
284            DccLocoAddress addr = new DccLocoAddress(address, tm.canBeLongAddress(address));
285
286            // get the throttle for the address.
287            if (addressList.contains(addr)) {
288                log.debug("Throttle in throttle list");
289                Throttle t = throttleList.get(addressList.indexOf(addr));
290                // set the speed and direction.
291                t.setSpeedSetting(speed);
292                t.setIsForward(isForward);
293            }
294        }
295    }
296
297    /*
298     * Set Throttle Functions on/off
299     *
300     * @param bus, bus the throttle is on.
301     * @param l address of the locomotive to change speed of.
302     * @param fList an ArrayList of boolean values indicating whether the
303     *         function is active or not.
304     */
305    public void setThrottleFunctions(int bus, int address, ArrayList<Boolean> fList) {
306        log.debug("Setting Functions for address {} bus {}",
307                address, bus);
308        java.util.List<SystemConnectionMemo> list = InstanceManager.getList(SystemConnectionMemo.class);
309        SystemConnectionMemo memo;
310        try {
311            memo = list.get(bus - 1);
312        } catch (java.lang.IndexOutOfBoundsException obe) {
313            try {
314                output.write(Bundle.getMessage("Error412").getBytes());
315            } catch (IOException ioe) {
316                log.error("Error writing to network port");
317            }
318            return;
319        }
320
321        /* request the throttle for this particular locomotive address */
322        if (memo.provides(jmri.ThrottleManager.class)) {
323            ThrottleManager tm = memo.get(jmri.ThrottleManager.class);
324            // we will use getThrottleInfo to request information about the
325            // address, so we need to convert the address to a DccLocoAddress
326            // object first.
327            DccLocoAddress addr = new DccLocoAddress(address, tm.canBeLongAddress(address));
328
329            // get the throttle for the address.
330            if (addressList.contains(addr)) {
331                log.debug("Throttle in throttle list");
332                Throttle t = throttleList.get(addressList.indexOf(addr));
333                setFunctionsByThrottle(t,fList);
334
335            }
336        }
337    }
338
339    // implementation of ThrottleListener
340    @Override
341    public void notifyThrottleFound(DccThrottle t) {
342        log.debug("notified throttle found");
343        throttleList.add(t);
344        try {
345            sendThrottleFound(t.getLocoAddress());
346            t.addPropertyChangeListener(new SRCPThrottlePropertyChangeListener(this, t, busList.get(addressList.indexOf(t.getLocoAddress()))));
347        } catch (java.io.IOException ioe) {
348            //Something failed writing data to the port.
349        }
350    }
351
352    static class SRCPThrottlePropertyChangeListener implements PropertyChangeListener {
353
354        int bus;
355        int address;
356        JmriSRCPThrottleServer clientServer = null;
357
358        SRCPThrottlePropertyChangeListener(JmriSRCPThrottleServer ts, Throttle t,
359                int bus) {
360            log.debug("property change listener created");
361            clientServer = ts;
362            this.bus = bus;
363            address = t.getLocoAddress().getNumber();
364        }
365
366        // update the state of this throttle if any of the properties change
367        @Override
368        public void propertyChange(java.beans.PropertyChangeEvent e) {
369            if (log.isDebugEnabled()) {
370                log.debug("Property change event received {} / {}", e.getPropertyName(), e.getNewValue());
371            }
372            switch (e.getPropertyName()) {
373                case Throttle.SPEEDSETTING:
374                case Throttle.SPEEDSTEPS:
375                case Throttle.ISFORWARD:
376                    try {
377                        clientServer.sendStatus(bus, address);
378                    } catch (IOException ioe) {
379                        log.error("Error writing to network port");
380                    }
381                    break;
382                default:
383                    for (int i = 0; i <= 28; i++) {
384                        if (e.getPropertyName().equals("F" + i)) {
385                            try {
386                                clientServer.sendStatus(bus, address);
387                            } catch (IOException ioe) {
388                                log.error("Error writing to network port");
389                            }
390                            break; // stop the loop, only one function property
391                            // will be matched.
392                        } else if (e.getPropertyName().equals("F" + i + "Momentary")) {
393                            try {
394                                clientServer.sendStatus(bus, address);
395                            } catch (IOException ioe) {
396                                log.error("Error writing to network port");
397                            }
398                            break; // stop the loop, only one function property
399                            // will be matched.
400                        }
401                    }
402                    break;
403            }
404
405        }
406
407    }
408
409}