001package jmri.jmris;
002
003import java.beans.PropertyChangeListener;
004import java.io.IOException;
005import java.util.ArrayList;
006
007import jmri.*;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Abstract interface between the JMRI Throttles and a network connection
013 *
014 * @author Paul Bender Copyright (C) 2015
015 */
016abstract public class AbstractThrottleServer implements ThrottleListener {
017
018    private static final Logger log = LoggerFactory.getLogger(AbstractThrottleServer.class);
019    protected ArrayList<Throttle> throttleList;
020
021    public AbstractThrottleServer(){
022        throttleList = new ArrayList<>();
023    }
024
025    /*
026     * Protocol Specific Abstract Functions
027     */
028    abstract public void sendStatus(LocoAddress address) throws IOException;
029
030    abstract public void sendErrorStatus() throws IOException;
031
032    abstract public void sendThrottleFound(LocoAddress address) throws IOException;
033
034    abstract public void sendThrottleReleased(LocoAddress address) throws IOException;
035
036    abstract public void parsecommand(String statusString) throws JmriException, IOException;
037
038    /*
039     * Set Throttle Speed and Direction
040     *
041     * @param l LocoAddress of the locomotive to change speed of.
042     * @param speed float representing the speed, -1 for emergency stop.
043     * @param isForward boolean, true if forward, false if reverse or
044     * undefined.
045     */
046    public void setThrottleSpeedAndDirection(LocoAddress l, float speed, boolean isForward) {
047        // get the throttle for the address.
048        throttleList.forEach(t -> {
049            if (t.getLocoAddress() == l) {
050                // set the speed and direction.
051                t.setSpeedSetting(speed);
052                t.setIsForward(isForward);
053            }
054        });
055    }
056
057    /**
058     * Set Throttle Functions on/off.
059     *
060     * @param l LocoAddress of the locomotive to change speed of.
061     * @param fList an ArrayList of boolean values indicating whether the
062     *         function is active or not.
063     */
064    public void setThrottleFunctions(LocoAddress l, ArrayList<Boolean> fList) {
065        // get the throttle for the address.
066        throttleList.forEach(t -> {
067            if (t.getLocoAddress() == l) {
068                setFunctionsByThrottle(t,fList);
069            }
070        });
071    }
072    
073    /**
074     * Set Throttle Functions on/off.
075     *
076     * @param t Throttle to change speed of.
077     * @param fList an ArrayList of boolean values indicating whether the
078     *         function is active or not.
079     */
080    protected void setFunctionsByThrottle(Throttle t, ArrayList<Boolean> fList){
081        for (int i = 0; i < fList.size(); i++) {
082            if ( i > t.getFunctions().length-1) {
083                log.error("Unable to set Function {} on Throttle {}",i,t.getLocoAddress());
084                try {
085                    sendErrorStatus();
086                } catch (IOException ioe) {
087                    log.error("Error writing to network port");
088                }
089            } else {
090                t.setFunction(i, fList.get(i));
091            }
092        }
093    }
094
095    /*
096     * Request a throttle for the specified address from the default
097     * Throttle Manager.
098     *
099     * @param l LocoAddress of the locomotive to request.
100     */
101    public void requestThrottle(LocoAddress l) {
102        ThrottleManager t = InstanceManager.throttleManagerInstance();
103        boolean result;
104        result = t.requestThrottle(l, this, false); 
105        if (!result) {
106            try {
107                sendErrorStatus();
108            } catch (IOException ioe) {
109                log.error("Error writing to network port");
110            }
111        }
112    }
113
114    /*
115     * Release a throttle for the specified address from the default
116     * Throttle Manager.
117     *
118     * @param l LocoAddress of the locomotive to request.
119     */
120    public void releaseThrottle(LocoAddress l) {
121        ThrottleManager t = InstanceManager.throttleManagerInstance();
122        t.cancelThrottleRequest(l, this);
123        if (l instanceof DccLocoAddress) {
124            throttleList.forEach(throttle -> {
125                if (throttle.getLocoAddress() == l) {
126                    t.releaseThrottle((DccThrottle) throttle, this);
127                    throttleList.remove(throttle);
128                    try {
129                        sendThrottleReleased(l);
130                    } catch (IOException ioe) {
131                        log.error("Error writing to network port");
132                    }
133                }
134            });
135        }
136    }
137
138    /**
139     * {@inheritDoc}
140     */
141    @Override
142    public void notifyThrottleFound(DccThrottle t) {
143        throttleList.add(t);
144        t.addPropertyChangeListener(new ThrottlePropertyChangeListener(this, t));
145        try {
146            sendThrottleFound(t.getLocoAddress());
147        } catch (java.io.IOException ioe) {
148            //Something failed writing data to the port.
149        }
150    }
151
152    /**
153     * {@inheritDoc}
154     */
155    @Override
156    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
157        try {
158            sendErrorStatus();
159        } catch (java.io.IOException ioe) {
160            //Something failed writing data to the port.
161        }
162    }
163
164    /**
165     * No steal or share decisions made locally
166     * <p>
167     * {@inheritDoc}
168     */
169    @Override
170    public void notifyDecisionRequired(LocoAddress address, DecisionType question) {
171    }
172
173    // internal class used to propagate back end throttle changes
174    // to the clients.
175    static class ThrottlePropertyChangeListener implements PropertyChangeListener {
176
177        protected AbstractThrottleServer clientserver = null;
178        protected Throttle throttle = null;
179
180        ThrottlePropertyChangeListener(AbstractThrottleServer ts, Throttle t) {
181            clientserver = ts;
182            throttle = t;
183        }
184
185        // update the state of this throttle if any of the properties change
186        @Override
187        public void propertyChange(java.beans.PropertyChangeEvent e) {
188            switch (e.getPropertyName()) {
189                case Throttle.SPEEDSETTING:
190                case Throttle.SPEEDSTEPS:
191                case Throttle.ISFORWARD:
192                    try {
193                        clientserver.sendStatus(throttle.getLocoAddress());
194                    } catch (IOException ioe) {
195                        log.error("Error writing to network port");
196                    }
197                    break;
198                default:
199                    for (int i = 0; i <= 28; i++) {
200                        if (e.getPropertyName().equals("F" + i)) {
201                            try {
202                                clientserver.sendStatus(throttle.getLocoAddress());
203                            } catch (IOException ioe) {
204                                log.error("Error writing to network port");
205                            }
206                            break; // stop the loop, only one function property will be matched.
207                        } else if (e.getPropertyName().equals("F" + i + "Momentary")) {
208                            try {
209                                clientserver.sendStatus(throttle.getLocoAddress());
210                            } catch (IOException ioe) {
211                                log.error("Error writing to network port");
212                            }
213                            break; // stop the loop, only one function property will be matched.
214                        }
215                    }
216                    break;
217            }
218
219            log.debug("Property change event received {} / {}", e.getPropertyName(), e.getNewValue());
220        }
221    }
222
223}