001package jmri.jmrix.loconet.pr2;
002
003import jmri.DccLocoAddress;
004import jmri.InstanceManager;
005import jmri.JmriException;
006import jmri.jmrix.loconet.LnConstants;
007import jmri.jmrix.loconet.LnOpsModeProgrammer;
008import jmri.jmrix.loconet.LnPowerManager;
009import jmri.jmrix.loconet.LnPr2ThrottleManager;
010import jmri.jmrix.loconet.LnTrafficController;
011import jmri.jmrix.loconet.LocoNetMessage;
012import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
013
014/**
015 * PowerManager implementation for controlling layout power via PR2.
016 * <p>
017 * Some of the message formats used in this class are Copyright Digitrax, Inc.
018 * and used with permission as part of the JMRI project. That permission does
019 * not extend to uses in other software products. If you wish to use this code,
020 * algorithm or these message formats outside of JMRI, please contact Digitrax
021 * Inc for separate permission.
022 *
023 * @author Bob Jacobsen Copyright (C) 2001
024 */
025public class LnPr2PowerManager extends LnPowerManager {
026
027    public LnPr2PowerManager(LocoNetSystemConnectionMemo memo) {
028        super(memo);
029        this.tc = memo.getLnTrafficController();
030    }
031
032    LnTrafficController tc;
033
034    @Override
035    public void setPower(int v) throws JmriException {
036        int old = power;
037        power = UNKNOWN;
038
039        // Instead of GPON/GPOFF, PR2 uses ops-mode writes to CV 128 for control
040        if (v == ON) {
041            // get current active address
042            DccLocoAddress activeAddress = ((LnPr2ThrottleManager) InstanceManager.throttleManagerInstance()).getActiveAddress();
043            if (activeAddress != null) {
044                pm = new LnOpsModeProgrammer(memo, activeAddress.getNumber(), activeAddress.isLongAddress());
045                checkOpsProg();
046
047                // set bit 1 in CV 128
048                pm.writeCV("128", 1, null);
049                power = ON;
050                firePowerPropertyChange(old, power);
051                // start making sure that the power is refreshed
052                if (timer == null) {
053                    timer = new javax.swing.Timer(2 * 1000, e -> refresh());
054                    timer.setInitialDelay(2 * 1000);
055                    timer.setRepeats(true);     // in case we run by
056                }
057                timer.start();
058            }
059        } else if (v == OFF) {
060            if (timer != null) {
061                timer.stop();
062            }
063
064            // get current active address
065            DccLocoAddress activeAddress = ((LnPr2ThrottleManager) InstanceManager.throttleManagerInstance()).getActiveAddress();
066            if (activeAddress != null) {
067                pm = new LnOpsModeProgrammer(memo, activeAddress.getNumber(), activeAddress.isLongAddress());
068                checkOpsProg();
069
070                // reset bit 1 in CV 128
071                pm.writeCV("128", 0, null);
072                power = OFF;
073            }
074        }
075        // notify of change
076        firePowerPropertyChange(old, power);
077    }
078
079    void refresh() {
080        // send inquiry message to keep power alive
081        LocoNetMessage msg = new LocoNetMessage(2);
082        msg.setOpCode(LnConstants.OPC_GPBUSY);
083        tc.sendLocoNetMessage(msg);
084    }
085
086    LnOpsModeProgrammer pm = null;
087
088    private void checkOpsProg() throws JmriException {
089        if (pm == null) {
090            throw new JmriException("Use PR2 power manager after dispose"); // NOI18N
091        }
092    }
093
094    // to listen for status changes from LocoNet
095    @Override
096    public void message(LocoNetMessage m) {
097        int old = power;
098        if (m.getOpCode() == LnConstants.OPC_GPON) {
099            power = ON;
100        } else if (m.getOpCode() == LnConstants.OPC_GPOFF) {
101            power = OFF;
102            if (timer != null) {
103                // Protect against uninitialized timer, for case where some other
104                // LocoNet agent issues OPC_GPOFF before JMRI initializes its timer.
105                // A NPE was seen, before protected added, with the DCS52.
106                timer.stop();
107            }
108        } else if (m.getOpCode() == LnConstants.OPC_WR_SL_DATA) {
109            // if this is a service mode write, drop out of power on mode
110            if ((m.getElement(1) == 0x0E)
111                    && (m.getElement(2) == 0x7C)
112                    && ((m.getElement(3) & 0x04) == 0x00)) {
113                // go to power off due to service mode op
114                if (power == ON) {
115                    power = OFF;
116                    if (timer != null) {
117                        timer.stop();
118                    }
119                }
120            }
121        } else if ( // check for status showing going off
122                (m.getOpCode() == LnConstants.OPC_PEER_XFER)
123                && (m.getElement(1) == 0x10)
124                && (m.getElement(2) == 0x22)
125                && (m.getElement(3) == 0x22)
126                && (m.getElement(4) == 0x01)) {  // PR2 form
127            int[] data = m.getPeerXfrData();
128            if ((data[2] & 0x40) != 0x40) {
129                // dropped off
130                if (power == ON) {
131                    power = OFF;
132                    if (timer != null) {
133                        timer.stop();
134                    }
135                }
136            }
137        }
138        firePowerPropertyChange(old, power);
139    }
140    
141    /**
142     * Returns false to indicate PR2 does not implement an "IDLE" power state.
143     * @return false
144     */
145    @Override
146    public boolean implementsIdle() {
147        return false;
148    }
149
150    // timer support to send updates & keep power alive
151    javax.swing.Timer timer = null;
152}
153