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 require the new "J" commands for turnout definitions?
204     * @return true if yes or false if no
205     */
206    public boolean isTurnoutIDsMessageRequired() {
207        boolean ret = false;
208        try {
209            ret = (jmri.Version.compareCanonicalVersions(version, "5.0.0") >= 0);
210        } catch (IllegalArgumentException ignore) {
211        }
212        return ret;  
213    }
214
215    /**
216     * Does this command station need the throttle register to be sent?
217     * @return true if yes or false if no
218     */
219    public boolean isThrottleRegisterRequired() {
220        boolean ret = true;
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) function message format?
230     * @return true if yes or false if no
231     */
232    public boolean isFunctionV4Supported() {
233        boolean ret = false;
234        try {
235            ret = (jmri.Version.compareCanonicalVersions(version, "4.0.0") >= 0);
236        } catch (IllegalArgumentException ignore) {
237        }
238        return ret;  
239    }
240
241    /**
242     * Can this command station handle the newer (V4) program message formats?
243     * @return true if yes or false if no
244     */
245    public boolean isProgramV4Supported() {
246        boolean ret = false;
247        try {
248            ret = (jmri.Version.compareCanonicalVersions(version, "4.0.1") >= 0);
249        } catch (IllegalArgumentException ignore) {
250        }
251        return ret;  
252    }
253
254    // A few utility functions
255    /**
256     * Get the Lower byte of a locomotive address from the decimal locomotive
257     * address.
258     * @param address loco address.
259     * @return loco address byte lo.
260     */
261    public static int getDCCAddressLow(int address) {
262        /* For addresses below 128, we just return the address, otherwise,
263         we need to return the upper byte of the address after we add the
264         offset 0xC000. The first address used for addresses over 127 is 0xC080*/
265        if (address < 128) {
266            return (address);
267        } else {
268            int temp = address + 0xC000;
269            temp = temp & 0x00FF;
270            return temp;
271        }
272    }
273
274    /**
275     * Get the Upper byte of a locomotive address from the decimal locomotive
276     * address.
277     * @param address loco address.
278     * @return high byte of address.
279     */
280    public static int getDCCAddressHigh(int address) {
281        /* this isn't actually the high byte, For addresses below 128, we
282         just return 0, otherwise, we need to return the upper byte of the
283         address after we add the offset 0xC000 The first address used for
284         addresses over 127 is 0xC080*/
285        if (address < 128) {
286            return (0x00);
287        } else {
288            int temp = address + 0xC000;
289            temp = temp & 0xFF00;
290            temp = temp / 256;
291            return temp;
292        }
293    }
294
295    /* To implement the CommandStation Interface, we have to define the
296     sendPacket function */
297    /**
298     * Send a specific packet to the rails.
299     *
300     * @param packet  Byte array representing the packet, including the
301     *                error-correction byte. Must not be null.
302     * @param repeats Number of times to repeat the transmission.
303     */
304    @Override
305    public boolean sendPacket(@Nonnull byte [] packet, int repeats) {
306
307        if (_tc == null) {
308            log.error("Send Packet Called without setting traffic controller");
309            return false;
310        }
311
312        int reg = 0;  // register 0, so this doesn't repeat
313        //  DCC++ BaseStation code appends its own error-correction byte.
314        // So we have to omit the JMRI-generated one.
315        DCCppMessage msg = DCCppMessage.makeWriteDCCPacketMainMsg(reg, packet.length - 1, packet);
316        assert msg != null;
317        log.debug("sendPacket:'{}'", msg);
318
319        for (int i = 0; i < repeats; i++) {
320            _tc.sendDCCppMessage(msg, null);
321        }
322        return true;
323    }
324
325    /*
326     * For the command station interface, we need to set the traffic 
327     * controller.
328     */
329    public void setTrafficController(DCCppTrafficController tc) {
330        _tc = tc;
331    }
332
333    private DCCppTrafficController _tc = null;
334
335    public void setSystemConnectionMemo(DCCppSystemConnectionMemo memo) {
336        adaptermemo = memo;
337    }
338
339    public DCCppSystemConnectionMemo getSystemConnectionMemo() {
340        return adaptermemo;
341    }
342
343    private DCCppSystemConnectionMemo adaptermemo;
344
345    private void creatermgr() {
346        if (rmgr == null) {
347            rmgr = new DCCppRegisterManager(maxNumSlots);
348        }
349    }
350
351    @Override
352    public String getUserName() {
353        if (adaptermemo == null) {
354            return "DCC++";
355        }
356        return adaptermemo.getUserName();
357    }
358
359    @Override
360    @Nonnull
361    public String getSystemPrefix() {
362        if (adaptermemo == null) {
363            return "D";
364        }
365        return adaptermemo.getSystemPrefix();
366    }
367
368    public int requestNewRegister(int addr) {
369        creatermgr();
370        return (rmgr.requestRegister(addr));
371    }
372
373    public void releaseRegister(int addr) {
374        creatermgr();
375        rmgr.releaseRegister(addr);
376    }
377
378    // Return DCCppConstants.NO_REGISTER_FREE if address is not in list
379    public int getRegisterNum(int addr) {
380        creatermgr();
381        return (rmgr.getRegisterNum(addr));
382    }
383
384    // Return DCCppConstants.REGISTER_UNALLOCATED if register is unused.
385    public int getRegisterAddress(int num) {
386        creatermgr();
387        return (rmgr.getRegisterAddress(num));
388    }
389
390    /*
391     * We need to register for logging
392     */
393    private final static Logger log = LoggerFactory.getLogger(DCCppCommandStation.class);
394
395}