001package jmri.jmrix.roco.z21.simulator;
002
003import jmri.jmrix.lenz.XNetConstants;
004import jmri.jmrix.lenz.XNetMessage;
005import jmri.jmrix.lenz.XNetReply;
006import jmri.jmrix.roco.z21.Z21Constants;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Provide access to a simulated z21 XpressNet sub-system.
012 * <p>
013 * This shares some code with the XpressNet simulator, but it's
014 * not a derived class because it isn't a real connection.
015 *
016 * @author Paul Bender, Copyright (C) 2015
017 */
018public class Z21XNetSimulatorAdapter {
019
020    private int csStatus;
021    // status values from the z21 Documentation.
022    private final static int csEmergencyStop = 0x01;
023    // 0x00 means normal mode.
024    private final static int csNormalMode = 0x00;
025
026    // package protected array of Z21SimulatorLocoData objects.
027    Z21SimulatorLocoData[] locoData;
028    int locoCount; // counter for locoData array.
029    int locoPosition; // Position for locoData array.
030
031    public Z21XNetSimulatorAdapter() {
032       csStatus = csNormalMode;
033       locoData = new Z21SimulatorLocoData[20];
034    }
035
036    // generateReply is the heart of the simulation.  It translates an
037    // incoming XNetMessage into an outgoing XNetReply.
038    XNetReply generateReply(XNetMessage m) {
039        log.debug("Generating Reply");
040        XNetReply reply;
041        switch (m.getElement(0)&0xff) {
042            case XNetConstants.CS_REQUEST:
043                log.debug("CS Request Received");
044                switch (m.getElement(1)) {
045                    case XNetConstants.CS_VERSION:
046                        reply=xNetVersionReply();
047                        break;
048                    case XNetConstants.RESUME_OPS:
049                        csStatus = csNormalMode;
050                        reply=normalOpsReply();
051                        break;
052                    case XNetConstants.EMERGENCY_OFF:
053                        csStatus = csEmergencyStop;
054                        reply=everythingOffReply();
055                        break;
056                    case XNetConstants.CS_STATUS:
057                        reply=csStatusReply();
058                        break;
059                    case XNetConstants.SERVICE_MODE_CSRESULT:
060                    default:
061                        log.debug("Unsupported requested received: {}", m.toString());
062                        reply=notSupportedReply();
063                }
064                break;
065            case XNetConstants.LI_VERSION_REQUEST:
066                log.debug("LI Version Request Received");
067                reply=new XNetReply();
068                reply.setOpCode(XNetConstants.LI_VERSION_RESPONSE);
069                reply.setElement(1, 0x00);  // set the hardware type to 0
070                reply.setElement(2, 0x00);  // set the firmware version to 0
071                reply.setElement(3, 0x00);  // set the parity byte to 0
072                reply.setParity();
073                break;
074            case XNetConstants.LOCO_OPER_REQ:
075                log.debug("Locomotive Operations Request received");
076                switch(m.getElement(1)&0xff) {
077                    case XNetConstants.LOCO_SPEED_14:
078                        // z21 specific locomotive information reply is expected.
079                        reply = new XNetReply();
080                        reply.setOpCode(Z21Constants.LAN_X_LOCO_INFO_RESPONSE);
081                        reply.setElement(1, m.getElement(2)); // address msb from
082                        // message.
083                        reply.setElement(2, m.getElement(3)); // address lsb from
084                        // message.
085                        reply.setElement(3, 0x00);  // set speed step mode to 14
086                        reply.setElement(4, m.getElement(4) & 0xff);  // set the speed and direction to the sent value.
087                        reply.setElement(5, 0x00);  // set function group a off
088                        reply.setElement(6, 0x00);  // set function group b off
089                        reply.setElement(7, 0x00);  // set F13-F20 off
090                        reply.setElement(8, 0x00);  // set F21-F28 off
091                        reply.setElement(9, 0x00);  // filler
092                        reply.setElement(10, 0x00);  // filler
093                        reply.setElement(11, 0x00);  // filler
094                        reply.setElement(12, 0x00);  // filler
095                        reply.setElement(13, 0x00);  // filler
096                        reply.setElement(14, 0x00);  // filler
097                        reply.setElement(15, 0x00);  // filler
098                        reply.setElement(16, 0x00);  // set the parity byte to 0
099                        reply.setParity();         // set the parity correctly.
100                        // save the address and speed information for
101                        // the simulator's RailCom values.
102                        locoData[locoPosition] = new Z21SimulatorLocoData((byte) (m.getElement(2) & 0xff), (byte) (m.getElement(3) & 0xff), (byte) (m.getElement(4) & 0xff));
103                        locoCount = (locoCount + 1) % 19;
104                        if (locoCount < 19) // 19 is the limit, set by the protocol.
105                            locoCount++;
106                        locoPosition = (locoPosition + 1) % 19;
107                        break;
108                    case XNetConstants.LOCO_SPEED_28:
109                        // z21 specific locomotive information reply is expected.
110                        reply = new XNetReply();
111                        reply.setOpCode(Z21Constants.LAN_X_LOCO_INFO_RESPONSE);
112                        reply.setElement(1, m.getElement(2)); // address msb from
113                        // message.
114                        reply.setElement(2, m.getElement(3)); // address lsb from
115                        // message.
116                        reply.setElement(3, 0x02);  // set speed step mode to 28
117                        reply.setElement(4, m.getElement(4));  // set the speed and direction to the sent value.
118                        reply.setElement(5, 0x00);  // set function group a off
119                        reply.setElement(6, 0x00);  // set function group b off
120                        reply.setElement(7, 0x00);  // set F13-F20 off
121                        reply.setElement(8, 0x00);  // set F21-F28 off
122                        reply.setElement(9, 0x00);  // filler
123                        reply.setElement(10, 0x00);  // filler
124                        reply.setElement(11, 0x00);  // filler
125                        reply.setElement(12, 0x00);  // filler
126                        reply.setElement(13, 0x00);  // filler
127                        reply.setElement(14, 0x00);  // filler
128                        reply.setElement(15, 0x00);  // filler
129                        reply.setElement(16, 0x00);  // set the parity byte to 0
130                        reply.setParity();         // set the parity correctly.
131                        locoData[locoPosition] = new Z21SimulatorLocoData((byte) (m.getElement(2) & 0xff), (byte) (m.getElement(3) & 0xff), (byte) (m.getElement(4) & 0xff));
132                        if (locoCount < 19) // 19 is the limit, set by the protocol.
133                            locoCount++;
134                        locoPosition = (locoPosition + 1) % 19;
135                        break;
136                    case XNetConstants.LOCO_SPEED_128:
137                        // z21 specific locomotive information reply is expected.
138                        reply = new XNetReply();
139                        reply.setOpCode(Z21Constants.LAN_X_LOCO_INFO_RESPONSE);
140                        reply.setElement(1, m.getElement(2)); // address msb from
141                        // message.
142                        reply.setElement(2, m.getElement(3)); // address lsb from
143                        // message.
144                        reply.setElement(3, 0x04);  // set speed step mode to 128
145                        reply.setElement(4, m.getElement(4));  // set the speed and direction to the sent value.
146                        reply.setElement(5, 0x00);  // set function group a off
147                        reply.setElement(6, 0x00);  // set function group b off
148                        reply.setElement(7, 0x00);  // set F13-F20 off
149                        reply.setElement(8, 0x00);  // set F21-F28 off
150                        reply.setElement(9, 0x00);  // filler
151                        reply.setElement(10, 0x00);  // filler
152                        reply.setElement(11, 0x00);  // filler
153                        reply.setElement(12, 0x00);  // filler
154                        reply.setElement(13, 0x00);  // filler
155                        reply.setElement(14, 0x00);  // filler
156                        reply.setElement(15, 0x00);  // filler
157                        reply.setElement(16, 0x00);  // set the parity byte to 0
158                        reply.setParity();         // set the parity correctly.
159                        reply.setParity();         // set the parity correctly.
160                        locoData[locoPosition] = new Z21SimulatorLocoData((byte) (m.getElement(2) & 0xff), (byte) (m.getElement(3) & 0xff), (byte) (m.getElement(4) & 0xff));
161                        if (locoCount < 19) // 19 is the limit, set by the protocol.
162                            locoCount++;
163                        locoPosition = (locoPosition + 1) % 19;
164                        break;
165                    case Z21Constants.LAN_X_SET_LOCO_FUNCTION:
166                        // z21 specific locomotive information reply is expected.
167                        reply = new XNetReply();
168                        reply.setOpCode(Z21Constants.LAN_X_LOCO_INFO_RESPONSE);
169                        reply.setElement(1, m.getElement(2)); // address msb from
170                        // message.
171                        reply.setElement(2, m.getElement(3)); // address lsb from
172                        // message.
173                        reply.setElement(3, 0x04);  // set speed step mode to 128
174                        reply.setElement(4, 0x00);  // set the speed and direction to the sent value.
175                        reply.setElement(5, 0x00);  // set function group a off
176                        reply.setElement(6, 0x00);  // set function group b off
177                        reply.setElement(7, 0x00);  // set F13-F20 off
178                        reply.setElement(8, 0x00);  // set F21-F28 off
179                        reply.setElement(9, 0x00);  // filler
180                        reply.setElement(10, 0x00);  // filler
181                        reply.setElement(11, 0x00);  // filler
182                        reply.setElement(12, 0x00);  // filler
183                        reply.setElement(13, 0x00);  // filler
184                        reply.setElement(14, 0x00);  // filler
185                        reply.setElement(15, 0x00);  // filler
186                        reply.setElement(16, 0x00);  // set the parity byte to 0
187                        reply.setParity();         // set the parity correctly.
188                        break;
189                    case XNetConstants.LOCO_SET_FUNC_GROUP1:
190                        // XpressNet set Function Group 1.
191                        // We need to find out what a Z21 actually sends in response.
192                    case XNetConstants.LOCO_SET_FUNC_GROUP2:
193                        // XpressNet set Function Group 2.
194                        // We need to find out what a Z21 actually sends in response.
195                    case XNetConstants.LOCO_SET_FUNC_GROUP3:
196                        // XpressNet set Function Group 3.
197                        // We need to find out what a Z21 actually sends in response.
198                    case XNetConstants.LOCO_SET_FUNC_GROUP4:
199                        // XpressNet set Function Group 4.
200                        // We need to find out what a Z21 actually sends in response.
201                    case XNetConstants.LOCO_SET_FUNC_GROUP5:
202                        // XpressNet set Function Group 5.
203                        // We need to find out what a Z21 actually sends in response.
204                    case XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY:
205                        // XpressNet set Function Momentary Group 1.
206                        // We need to find out what a Z21 actually sends in response.
207                    case XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY:
208                        // XpressNet set Function Momentary Group 2.
209                        // We need to find out what a Z21 actually sends in response.
210                    case XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY:
211                        // XpressNet set Function Momentary Group 3.
212                        // We need to find out what a Z21 actually sends in response.
213                    case XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY:
214                        // XpressNet set Function Momentary Group 4.
215                        // We need to find out what a Z21 actually sends in response.
216                    case XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY:
217                        // XpressNet set Function Momentary Group 5.
218                        // We need to find out what a Z21 actually sends in response.
219                        reply = okReply();
220                        break;
221                    case XNetConstants.LOCO_ADD_MULTI_UNIT_REQ:
222                    case XNetConstants.LOCO_REM_MULTI_UNIT_REQ:
223                    case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD:
224                    case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD:
225                    case XNetConstants.LOCO_SPEED_27:
226                    default:
227                        log.debug("Unsupported requested received: {}", m.toString());
228                        reply = notSupportedReply();
229                        break;
230                }
231                break;
232            case XNetConstants.ALL_ESTOP:
233                log.debug("Emergency Stop Received");
234                reply = emergencyStopReply();
235                break;
236            case XNetConstants.EMERGENCY_STOP:
237            case XNetConstants.EMERGENCY_STOP_XNETV1V2:
238                reply = okReply();
239                break;
240           case XNetConstants.ACC_OPER_REQ:
241                log.debug("Accessory Operations Request received");
242                reply = okReply();
243                break;
244            case XNetConstants.LOCO_STATUS_REQ:
245                log.debug("Locomotive Status Request received");
246                switch (m.getElement(1)&0xff) {
247                    case XNetConstants.LOCO_INFO_REQ_V3:
248                        reply=new XNetReply();
249                        reply.setOpCode(XNetConstants.LOCO_INFO_NORMAL_UNIT);
250                        reply.setElement(1, 0x04);  // set to 128 speed step mode
251                        reply.setElement(2, 0x00);  // set the speed to 0
252                        // direction reverse
253                        reply.setElement(3, 0x00);  // set function group a off
254                        reply.setElement(4, 0x00);  // set function group b off
255                        reply.setElement(5, 0x00);  // set the parity byte to 0
256                        reply.setParity();         // set the parity correctly.
257                        break;
258                    case Z21Constants.LAN_X_LOCO_INFO_REQUEST_Z21:
259                        // z21 specific locomotive information request.
260                        reply=new XNetReply();
261                        reply.setOpCode(Z21Constants.LAN_X_LOCO_INFO_RESPONSE);
262                        reply.setElement(1,m.getElement(2)); // address msb from
263                                                            // message.
264                        reply.setElement(2,m.getElement(3)); // address lsb from
265                                                            // message.
266                        reply.setElement(3,0x04);  // set speed step mode to 128
267                        reply.setElement(4,0x00);  // set the speed and direction to 0 and reverse.
268                        reply.setElement(5, 0x00);  // set function group a off
269                        reply.setElement(6, 0x00);  // set function group b off
270                        reply.setElement(7, 0x00);  // set F13-F20 off
271                        reply.setElement(8, 0x00);  // set F21-F28 off
272                        reply.setElement(9, 0x00);  // filler
273                        reply.setElement(10, 0x00);  // filler
274                        reply.setElement(11, 0x00);  // filler
275                        reply.setElement(12, 0x00);  // filler
276                        reply.setElement(13, 0x00);  // filler
277                        reply.setElement(14, 0x00);  // filler
278                        reply.setElement(15, 0x00);  // filler
279                        reply.setElement(16, 0x00);  // set the parity byte to 0
280                        reply.setParity();         // set the parity correctly.
281                        break;
282                    case XNetConstants.LOCO_INFO_REQ_FUNC:
283                    case XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON:
284                    case XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM:
285                    default:
286                        log.debug("Unsupoorted requested received: {}", m.toString());
287                        reply=notSupportedReply();
288                }
289                break;
290            case XNetConstants.ACC_INFO_REQ:
291                log.debug("Accessory Information Request Received");
292                reply=new XNetReply();
293                reply.setOpCode(XNetConstants.ACC_INFO_RESPONSE);
294                reply.setElement(1, m.getElement(1));
295                if (m.getElement(1) < 64) {
296                    // treat as turnout feedback request.
297                    if (m.getElement(2) == 0x80) {
298                        reply.setElement(2, 0x00);
299                    } else {
300                        reply.setElement(2, 0x10);
301                    }
302                } else {
303                    // treat as feedback encoder request.
304                    if (m.getElement(2) == 0x80) {
305                        reply.setElement(2, 0x40);
306                    } else {
307                        reply.setElement(2, 0x50);
308                    }
309                }
310                reply.setElement(3, 0x00);
311                reply.setParity();
312                break;
313            case Z21Constants.LAN_X_GET_TURNOUT_INFO:
314                log.debug("Get Turnout Info Request Received");
315                reply=lanXTurnoutInfoReply(m.getElement(1),m.getElement(2),
316                                true); // always sends "thrown".
317                break;
318            case Z21Constants.LAN_X_SET_TURNOUT:
319                log.debug("Set Turnout Request Received");
320                reply=lanXTurnoutInfoReply(m.getElement(1),m.getElement(2),
321                                (0x01 & m.getElement(3))==0x01);
322                break;
323            case XNetConstants.OPS_MODE_PROG_REQ:
324                if(m.getElement(1) == XNetConstants.OPS_MODE_PROG_WRITE_REQ){
325                    int operation = m.getElement(4) & 0xFC;
326                    switch(operation) {
327                         case 0xEC:
328                           log.debug("Write CV in Ops Mode Request Received");
329                           reply = okReply();
330                           break;
331                         case 0xE4:
332                           log.debug("Verify CV in Ops Mode Request Received");
333                           reply = new XNetReply();
334                           reply.setOpCode(Z21Constants.LAN_X_CV_RESULT_XHEADER);
335                           reply.setElement(1,Z21Constants.LAN_X_CV_RESULT_DB0);
336                           reply.setElement(2,(m.getElement(4)&0x03));
337                           reply.setElement(3,m.getElement(5));
338                           reply.setElement(4,m.getElement(6));
339                           reply.setElement(5,0x00);
340                           reply.setParity();
341                           break;
342                         case 0xE8:
343                           log.debug("Ops Mode Bit Request Received");
344                           reply = okReply();
345                           break;
346                         default:
347                           reply=notSupportedReply();
348                    }
349                } else {
350                    reply=notSupportedReply();
351                }
352                break;
353            case XNetConstants.LI101_REQUEST:
354            case XNetConstants.CS_SET_POWERMODE:
355            //case XNetConstants.PROG_READ_REQUEST:  //PROG_READ_REQUEST
356            //and CS_SET_POWERMODE
357            //have the same value
358            case XNetConstants.PROG_WRITE_REQUEST:
359            case XNetConstants.LOCO_DOUBLEHEAD:
360            default:
361                log.debug("Unsupported requested received: {}", m.toString());
362                reply=notSupportedReply();
363        }
364        log.debug("generated reply {}",reply);
365        return reply;
366    }
367
368    // We have a few canned response messages.
369
370    /**
371     * Create an Unsupported XNetReply message.
372     */
373    private XNetReply notSupportedReply() {
374        XNetReply r = new XNetReply();
375        r.setOpCode(XNetConstants.CS_INFO);
376        r.setElement(1, XNetConstants.CS_NOT_SUPPORTED);
377        r.setElement(2, 0x00); // set the parity byte to 0
378        r.setParity();
379        return r;
380    }
381
382    /**
383     * Create an OK XNetReply message.
384     */
385    private XNetReply okReply() {
386        XNetReply r = new XNetReply();
387        r.setOpCode(XNetConstants.LI_MESSAGE_RESPONSE_HEADER);
388        r.setElement(1, XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS);
389        r.setElement(2, 0x00); // set the parity byte to 0
390        r.setParity();
391        return r;
392    }
393
394    /**
395     * Create a "Normal Operations Resumed" message.
396     */
397    private XNetReply normalOpsReply() {
398        XNetReply r = new XNetReply();
399        r.setOpCode(XNetConstants.CS_INFO);
400        r.setElement(1, XNetConstants.BC_NORMAL_OPERATIONS);
401        r.setElement(2, 0x00); // set the parity byte to 0
402        r.setParity();
403        return r;
404    }
405
406    /**
407     * Create a broadcast "Everything Off" reply.
408     */
409    private XNetReply everythingOffReply() {
410        XNetReply r = new XNetReply();
411        r.setOpCode(XNetConstants.CS_INFO);
412        r.setElement(1, XNetConstants.BC_EVERYTHING_OFF);
413        r.setElement(2, 0x00); // set the parity byte to 0
414        r.setParity();
415        return r;
416    }
417
418    /**
419     * Create a broadcast "Emergecy Stop" reply.
420     */
421    private XNetReply emergencyStopReply() {
422        XNetReply r = new XNetReply();
423        r.setOpCode(XNetConstants.BC_EMERGENCY_STOP);
424        r.setElement(1, XNetConstants.BC_EVERYTHING_STOP);
425        r.setElement(2, 0x00); // set the parity byte to 0
426        r.setParity();
427        return r;
428    }
429
430    /**
431     * Create a reply to a request for the XpressNet Version.
432     */
433    private XNetReply xNetVersionReply(){
434        XNetReply reply=new XNetReply();
435        reply.setOpCode(XNetConstants.CS_SERVICE_MODE_RESPONSE);
436        reply.setElement(1, XNetConstants.CS_SOFTWARE_VERSION);
437        reply.setElement(2, 0x30 & 0xff); // indicate we are version 3.0
438        reply.setElement(3, 0x12 & 0xff); // indicate we are a Z21;
439        reply.setElement(4, 0x00); // set the parity byte to 0
440        reply.setParity();
441        return reply;
442    }
443
444    /**
445     * Create a reply to a request for the Command Station Status.
446     */
447    private XNetReply csStatusReply(){
448        XNetReply reply=new XNetReply();
449        reply.setOpCode(XNetConstants.CS_REQUEST_RESPONSE);
450        reply.setElement(1, XNetConstants.CS_STATUS_RESPONSE);
451        reply.setElement(2, csStatus);
452        reply.setElement(3, 0x00); // set the parity byte to 0
453        reply.setParity();
454        return reply;
455    }
456
457    /**
458     * Create a LAN_X_TURNOUT_INFO reply.
459     */
460    private XNetReply lanXTurnoutInfoReply(int FAdr_MSB,int FAdr_LSB,boolean thrown){
461        XNetReply reply=new XNetReply();
462        reply.setOpCode(Z21Constants.LAN_X_TURNOUT_INFO);
463        reply.setElement(1, FAdr_MSB & 0xff );
464        reply.setElement(2, FAdr_LSB & 0xff );
465        reply.setElement(3, thrown?0x02:0x01); // the turnout direction.
466        reply.setElement(4, 0x00); // set the parity byte to 0.
467        reply.setParity();
468        return reply;
469    }
470
471    private final static Logger log = LoggerFactory.getLogger(Z21XNetSimulatorAdapter.class);
472}