001package jmri.jmrix.dccpp;
002
003import javax.annotation.Nonnull;
004
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Defines the standard/common routines used in multiple classes related to the
010 * DCC++ Command Station, on a DCC++ network.
011 *
012 * @author Bob Jacobsen Copyright (C) 2001
013 * @author Portions by Paul Bender Copyright (C) 2003
014 * @author Mark Underwood Copyright (C) 2015
015 * @author Harald Barth Copyright (C) 2019
016 *
017 * Based on LenzCommandStation by Bob Jacobsen and Paul Bender
018 */
019public class DCCppCommandStation implements jmri.CommandStation {
020
021    /* The First group of routines is for obtaining the Software and
022     hardware version of the Command station */
023    /**
024     * We need to add a few data members for saving the version information we
025     * get from the layout.
026     *
027     */
028    @Nonnull private String stationType = "Unknown";
029    @Nonnull private String build       = "Unknown";
030    @Nonnull private String version     = "0.0.0";
031    private DCCppRegisterManager rmgr = null;
032    private int maxNumSlots = DCCppConstants.MAX_MAIN_REGISTERS; //default to register size
033
034    public DCCppCommandStation() {
035        super();
036    }
037
038    public DCCppCommandStation(DCCppSystemConnectionMemo memo) {
039        super();
040        adaptermemo = memo;
041    }
042
043    public void setStationType(String s) {
044        if (!stationType.equals(s)) {
045            log.info("Station Type set to '{}'", s);
046            stationType = s;            
047        }
048    }
049    
050    /**
051     * Get the Station Type of the connected Command Station
052     * it is populated by response from the CS, initially "Unknown" 
053     * @return StationType
054     */
055    @Nonnull
056    public String getStationType() {
057        return stationType;
058    }
059
060    public void setBuild(String s) {
061        if (!build.equals(s)) {
062            log.info("Build set to '{}'", s);
063            build = s;            
064        }
065    }
066
067    /**
068     * Get the Build of the connected Command Station
069     * it is populated by response from the CS, initially "Unknown" 
070     * @return Build
071     */
072    @Nonnull
073    public String getBuild() {
074        return build;
075    }
076
077    public void setVersion(String s) {
078        if (!version.equals(s)) {
079            if (jmri.Version.isCanonicalVersion(s)) {
080                log.info("Version set to '{}'", s);
081                version = s;
082            } else {
083                log.warn("'{}' is not a canonical version, version not changed", s);
084            }
085        }
086    }
087
088    /**
089     * Get the canonical version of the connected Command Station
090     * it is populated by response from the CS, so initially '0.0.0' 
091     * @return Version
092     */
093    @Nonnull
094    public String getVersion() {
095        return version;
096    }
097
098    /**
099     * Parse the DCC++ CS status response to pull out the base station version
100     * and software version.
101     * @param l status response to query.
102     */
103    protected void setCommandStationInfo(DCCppReply l) {
104 // V1.0 Syntax
105 //String syntax = "iDCC\\+\\+\\s+BASE\\s+STATION\\s+v([a-zA-Z0-9_.]+):\\s+BUILD\\s+((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))";
106 // V1.1 Syntax
107 //String syntax = "iDCC\\+\\+BASE STATION FOR ARDUINO \\b(\\w+)\\b \\/ (ARDUINO|POLOLU\\sMC33926) MOTOR SHIELD: BUILD ((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))";
108 // V1.0/V1.1 Simplified
109 //String syntax = "iDCC\\+\\+(.*): BUILD (.*)";
110        // V1.2.1 Syntax
111        // String syntax = "iDCC++ BASE STATION FOR ARDUINO \\b(\\w+)\\b \\/ (ARDUINO|POLOLU\\sMC33926) MOTOR SHIELD: ((\\d+\\s\\w+\\s\\d+)\\s+(\\d+:\\d+:\\d+))";
112        // Changes from v1.1: space between "DCC++" and "BASE", and "BUILD" is removed.
113        // V1.0/V1.1/V1.2 Simplified
114        // String syntax = "iDCC\\+\\+\\s?(.*):\\s?(?:BUILD)? (.*)";
115
116        setStationType(l.getStationType());
117        setBuild(l.getBuildString());
118        setVersion(l.getVersion());
119    }
120
121    protected void setCommandStationMaxNumSlots(DCCppReply l) {
122        int newNumSlots = l.getValueInt(1);
123        setCommandStationMaxNumSlots(newNumSlots);
124    }
125    protected void setCommandStationMaxNumSlots(int newNumSlots) {
126        if (newNumSlots < maxNumSlots) {
127            log.warn("Command Station maxNumSlots cannot be reduced from {} to {}", maxNumSlots, newNumSlots);
128            return;
129        }
130        if (newNumSlots != maxNumSlots) {
131            log.info("changing maxNumSlots from {} to {}", maxNumSlots, newNumSlots);
132            maxNumSlots = newNumSlots;
133        }
134    }
135    protected int getCommandStationMaxNumSlots() {
136        return maxNumSlots;
137    }
138
139    /**
140     * Provide the version string returned during the initial check.
141     * @return version string.
142     */
143    public String getVersionString() {
144        return(stationType + ": BUILD " + build);
145    }
146
147    /**
148     * Remember whether or not in service mode.
149     */
150    boolean mInServiceMode = false;
151
152    /**
153     * DCC++ command station does provide Ops Mode.
154     * @return always true.
155     */
156    public boolean isOpsModePossible() {
157        return true;
158    }
159
160    /**
161     * Does this command station require JMRI to send periodic function refresh packets?
162     * @return true if required, false if not
163     */
164    public boolean isFunctionRefreshRequired() {
165        boolean ret = true;
166        try {
167            //command stations starting with 3 handle their own function refresh
168            ret = (jmri.Version.compareCanonicalVersions(version, "3.0.0") < 0);
169        } catch (IllegalArgumentException ignore) {
170        }
171        return ret;  
172    }
173
174    /**
175     * Can this command station handle the Read with a starting value ('V'erify)
176     * @return true if yes or false if no
177     */
178    public boolean isReadStartValSupported() {
179        boolean ret = false;
180        try {
181            //command stations starting with 3 can handle reads with startVals
182            ret = (jmri.Version.compareCanonicalVersions(version, "3.0.0") >= 0);
183        } catch (IllegalArgumentException ignore) {
184        }
185        return ret;  
186    }
187 
188    /**
189     * Can this command station handle the Servo and Vpin Turnout creation message formats?
190     * @return true if yes or false if no
191     */
192    public boolean isServoTurnoutCreationSupported() {
193        boolean ret = false;
194        try {
195            // SERVO and VPIN turnout commands added at 3.2.0
196            ret = (jmri.Version.compareCanonicalVersions(version, "3.2.0") >= 0);
197        } catch (IllegalArgumentException ignore) {
198        }
199        return ret;  
200    }
201
202    /**
203     * Does this command station need the throttle register to be sent?
204     * @return true if yes or false if no
205     */
206    public boolean isThrottleRegisterRequired() {
207        boolean ret = true;
208        try {
209            ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") < 0);
210        } catch (IllegalArgumentException ignore) {
211        }
212        return ret;  
213    }
214
215    /**
216     * Can this command station handle the newer (V4) function message format?
217     * @return true if yes or false if no
218     */
219    public boolean isFunctionV4Supported() {
220        boolean ret = false;
221        try {
222            ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") >= 0);
223        } catch (IllegalArgumentException ignore) {
224        }
225        return ret;  
226    }
227
228    /**
229     * Can this command station handle the newer (V4) program message formats?
230     * @return true if yes or false if no
231     */
232    public boolean isProgramV4Supported() {
233        boolean ret = false;
234        try {
235            ret = (jmri.Version.compareCanonicalVersions(version, "4.0.1") >= 0);
236        } catch (IllegalArgumentException ignore) {
237        }
238        return ret;  
239    }
240
241    // A few utility functions
242    /**
243     * Get the Lower byte of a locomotive address from the decimal locomotive
244     * address.
245     * @param address loco address.
246     * @return loco address byte lo.
247     */
248    public static int getDCCAddressLow(int address) {
249        /* For addresses below 128, we just return the address, otherwise,
250         we need to return the upper byte of the address after we add the
251         offset 0xC000. The first address used for addresses over 127 is 0xC080*/
252        if (address < 128) {
253            return (address);
254        } else {
255            int temp = address + 0xC000;
256            temp = temp & 0x00FF;
257            return temp;
258        }
259    }
260
261    /**
262     * Get the Upper byte of a locomotive address from the decimal locomotive
263     * address.
264     * @param address loco address.
265     * @return high byte of address.
266     */
267    public static int getDCCAddressHigh(int address) {
268        /* this isn't actually the high byte, For addresses below 128, we
269         just return 0, otherwise, we need to return the upper byte of the
270         address after we add the offset 0xC000 The first address used for
271         addresses over 127 is 0xC080*/
272        if (address < 128) {
273            return (0x00);
274        } else {
275            int temp = address + 0xC000;
276            temp = temp & 0xFF00;
277            temp = temp / 256;
278            return temp;
279        }
280    }
281
282    /* To implement the CommandStation Interface, we have to define the
283     sendPacket function */
284    /**
285     * Send a specific packet to the rails.
286     *
287     * @param packet  Byte array representing the packet, including the
288     *                error-correction byte. Must not be null.
289     * @param repeats Number of times to repeat the transmission.
290     */
291    @Override
292    public boolean sendPacket(@Nonnull byte [] packet, int repeats) {
293
294        if (_tc == null) {
295            log.error("Send Packet Called without setting traffic controller");
296            return false;
297        }
298
299        int reg = 0;  // register 0, so this doesn't repeat
300        //  DCC++ BaseStation code appends its own error-correction byte.
301        // So we have to omit the JMRI-generated one.
302        DCCppMessage msg = DCCppMessage.makeWriteDCCPacketMainMsg(reg, packet.length - 1, packet);
303        assert msg != null;
304        log.debug("sendPacket:'{}'", msg);
305
306        for (int i = 0; i < repeats; i++) {
307            _tc.sendDCCppMessage(msg, null);
308        }
309        return true;
310    }
311
312    /*
313     * For the command station interface, we need to set the traffic 
314     * controller.
315     */
316    public void setTrafficController(DCCppTrafficController tc) {
317        _tc = tc;
318    }
319
320    private DCCppTrafficController _tc = null;
321
322    public void setSystemConnectionMemo(DCCppSystemConnectionMemo memo) {
323        adaptermemo = memo;
324    }
325
326    public DCCppSystemConnectionMemo getSystemConnectionMemo() {
327        return adaptermemo;
328    }
329
330    private DCCppSystemConnectionMemo adaptermemo;
331
332    private void creatermgr() {
333        if (rmgr == null) {
334            rmgr = new DCCppRegisterManager(maxNumSlots);
335        }
336    }
337
338    @Override
339    public String getUserName() {
340        if (adaptermemo == null) {
341            return "DCC++";
342        }
343        return adaptermemo.getUserName();
344    }
345
346    @Override
347    @Nonnull
348    public String getSystemPrefix() {
349        if (adaptermemo == null) {
350            return "D";
351        }
352        return adaptermemo.getSystemPrefix();
353    }
354
355    public int requestNewRegister(int addr) {
356        creatermgr();
357        return (rmgr.requestRegister(addr));
358    }
359
360    public void releaseRegister(int addr) {
361        creatermgr();
362        rmgr.releaseRegister(addr);
363    }
364
365    // Return DCCppConstants.NO_REGISTER_FREE if address is not in list
366    public int getRegisterNum(int addr) {
367        creatermgr();
368        return (rmgr.getRegisterNum(addr));
369    }
370
371    // Return DCCppConstants.REGISTER_UNALLOCATED if register is unused.
372    public int getRegisterAddress(int num) {
373        creatermgr();
374        return (rmgr.getRegisterAddress(num));
375    }
376
377    /*
378     * We need to register for logging
379     */
380    private final static Logger log = LoggerFactory.getLogger(DCCppCommandStation.class);
381
382}