001package jmri.jmrix.loconet;
002
003import java.util.Map;
004import javax.annotation.Nonnull;
005import jmri.NmraPacket;
006import jmri.implementation.DccSignalMast;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Extend jmri.SignalMast for signals implemented by an LNCP.
012 * <p>
013 * This implementation writes out to the physical signal when it's commanded to
014 * change appearance, and updates its internal state when it hears commands from
015 * other places.
016 * <p>
017 * {@link #setAspect} does not immediately change the local aspect.  Instead, it produces
018 * the message on the network, waiting for that to return and do the local state change,
019 * notification, etc.
020 * <p>
021 * This is a specific implementation for the RR-cirkits LNCP interface board.
022 * A more general implementation, which can work with any system(s), is available
023 * in {@link jmri.implementation.DccSignalMast}.
024 *
025 * @author Kevin Dickerson Copyright (C) 2002
026 */
027public class LNCPSignalMast extends DccSignalMast implements LocoNetListener {
028
029    public LNCPSignalMast(String sys, String user) {
030        super(sys, user, "F$lncpsm"); // NOI18N
031        packetSendCount = 1;
032        configureFromName(sys);
033        init();
034    }
035
036    public LNCPSignalMast(String sys) {
037        super(sys, null, "F$lncpsm"); // NOI18N
038        packetSendCount = 1;
039        configureFromName(sys);
040        init();
041    }
042
043    void init() {
044        if ((c instanceof SlotManager) && (((SlotManager) c).getSystemConnectionMemo() != null)) {
045            tc = ((SlotManager) c).getSystemConnectionMemo().getLnTrafficController();
046        } else {
047            tc = jmri.InstanceManager.getDefault(LnTrafficController.class);
048        }
049
050        //We cheat, and store the two bytes that make up an NMRA packet for later use in decoding a message from the LocoNet
051        int lowAddr = ((dccSignalDecoderAddress - 1) & 0x03);  // Output Pair Address
052        int boardAddr = ((dccSignalDecoderAddress - 1) >> 2); // Board Address
053        int midAddr = boardAddr & 0x3F;
054
055        int highAddr = ((~boardAddr) >> 6) & 0x07;
056
057        dccByteAddr1 = ((byte) (0x80 | midAddr));
058        dccByteAddr2 = ((byte) (0x01 | (highAddr << 4) | (lowAddr << 1)));
059        tc.addLocoNetListener(~0, this);
060    }
061
062    LnTrafficController tc;
063
064    // implementing classes will typically have a function/listener to get
065    // updates from the layout, which will then call
066    //  public void firePropertyChange(String propertyName,
067    //      Object oldValue,
068    //      Object newValue)
069    // _once_ if anything has changed state (or set the commanded state directly)
070    @Override
071    public void message(LocoNetMessage l) {
072        if (l.getOpCode() != LnConstants.OPC_IMM_PACKET) {
073            return;
074        }
075
076        int val7f = l.getElement(2); /* fixed value of 0x7f */
077
078        if (val7f != 0x7f) {
079            return;
080        }
081
082        int reps = l.getElement(3);
083        int len = ((reps & 0x70) >> 4);
084        if (len != 3) {
085            return;
086        }
087        int dhi = l.getElement(4);
088        int im1 = l.getElement(5);
089        int im2 = l.getElement(6);
090        int im3 = l.getElement(7);
091
092        byte[] packet = new byte[len];
093        packet[0] = (byte) (im1 + ((dhi & 0x01) != 0 ? 0x80 : 0));
094        packet[1] = (byte) (im2 + ((dhi & 0x02) != 0 ? 0x80 : 0));
095
096        if (myAddress(packet[0], packet[1])) {
097            packet[2] = (byte) (im3 + ((dhi & 0x04) != 0 ? 0x80 : 0));
098            int aspect = packet[2];
099            for (Map.Entry<String, Integer> entry : appearanceToOutput.entrySet()) {
100                if (entry.getValue() == aspect) {
101                    setKnownState(entry.getKey());
102                    return;
103                }
104            }
105            log.error("Aspect for id {} on signal mast {} not found", aspect, this.getDisplayName());
106        }
107    }
108
109    @Override
110    public void setAspect(@Nonnull String aspect) {
111        if (appearanceToOutput.containsKey(aspect) && appearanceToOutput.get(aspect) != -1) {
112            c.sendPacket(NmraPacket.altAccSignalDecoderPkt(dccSignalDecoderAddress, appearanceToOutput.get(aspect)), packetSendCount);
113        } else {
114            log.warn("Trying to set aspect ({}) that has not been configured on mast {}", aspect, getDisplayName());
115        }
116        // super.setAspect(aspect); // see note in class description
117    }
118
119    public void setKnownState(String aspect) {
120        String oldAspect = this.aspect;
121        this.aspect = aspect;
122        this.speed = (String) getSignalSystem().getProperty(aspect, "speed"); // NOI18N
123        firePropertyChange("Aspect", oldAspect, aspect); // NOI18N
124    }
125
126    @Override
127    public void dispose() {
128        tc.removeLocoNetListener(~0, this);
129        super.dispose();
130    }
131
132    byte dccByteAddr1;
133    byte dccByteAddr2;
134
135    private boolean myAddress(byte a1, byte a2) {
136        if (a1 != dccByteAddr1) {
137            return false;
138        }
139        return (a2 == dccByteAddr2);
140    }
141
142    private final static Logger log = LoggerFactory.getLogger(LNCPSignalMast.class);
143
144}