001package jmri.jmrix.loconet;
002
003import jmri.JmriException;
004import jmri.managers.AbstractPowerManager;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * PowerManager implementation for controlling layout power.
010 * <p>
011 * Some of the message formats used in this class are Copyright Digitrax, Inc.
012 * and used with permission as part of the JMRI project. That permission does
013 * not extend to uses in other software products. If you wish to use this code,
014 * algorithm or these message formats outside of JMRI, please contact Digitrax
015 * Inc for separate permission.
016 *
017 * @author Bob Jacobsen Copyright (C) 2001
018 * @author B. Milhaupt Copyright (C)
019 */
020public class LnPowerManager extends AbstractPowerManager<LocoNetSystemConnectionMemo> implements LocoNetListener {
021
022    public LnPowerManager(LocoNetSystemConnectionMemo memo) {
023        super(memo);
024        // standard LocoNet - connect
025        if (memo.getLnTrafficController() == null) {
026            log.error("PowerManager Created, yet there is no Traffic Controller");
027            return;
028        }
029        this.tc = memo.getLnTrafficController();
030        tc.addLocoNetListener(~0, this);
031
032        updateTrackPowerStatus();  // this delays a while then reads slot 0 to get current track status
033    }
034
035    @Override
036    public void setPower(int v) throws JmriException {
037        int old = power;
038        power = UNKNOWN;
039
040        checkTC();
041        if (v == ON) {
042            // send GPON
043            LocoNetMessage l = new LocoNetMessage(2);
044            l.setOpCode(LnConstants.OPC_GPON);
045            tc.sendLocoNetMessage(l);
046        } else if (v == OFF) {
047            // send GPOFF
048            LocoNetMessage l = new LocoNetMessage(2);
049            l.setOpCode(LnConstants.OPC_GPOFF);
050            tc.sendLocoNetMessage(l);
051        } else if ((v == IDLE) && (implementsIdle())) {
052            // send OPC_IDLE
053            LocoNetMessage l = new LocoNetMessage(2);
054            l.setOpCode(LnConstants.OPC_IDLE);
055            tc.sendLocoNetMessage(l);
056        }
057
058        firePowerPropertyChange(old, power);
059    }
060
061    // to free resources when no longer used
062    @Override
063    public void dispose() {
064        if (thread != null) {
065            try {
066                thread.interrupt();
067                thread.join();
068            } catch (InterruptedException ex) {
069                log.warn("dispose interrupted");
070            } finally {
071                thread = null;
072            }
073        }
074
075        if (tc != null) {
076            tc.removeLocoNetListener(~0, this);
077        }
078        tc = null;
079    }
080
081    LnTrafficController tc = null;
082
083    private void checkTC() throws JmriException {
084        if (tc == null) {
085            throw new JmriException("Use power manager after dispose"); // NOI18N
086        }
087    }
088
089    // to listen for status changes from LocoNet
090    @Override
091    public void message(LocoNetMessage m) {
092        int old = power;
093        switch (m.getOpCode()) {
094            case LnConstants.OPC_GPON:
095                power = ON;
096                break;
097            case LnConstants.OPC_GPOFF:
098                power = OFF;
099                break;
100            case LnConstants.OPC_IDLE:
101                power = IDLE;
102                break;
103            case LnConstants.OPC_SL_RD_DATA:
104                // grab the track status any time that a slot read of a "normal" slot passes thru.
105                // Ignore "reserved" and "master control" slots in slot numbers 120-127
106                if ((m.getElement(1) == 0x0E) && (m.getElement(2) < 120)) {
107                    switch (m.getElement(7) & (0x03)) {
108                        case LnConstants.GTRK_POWER:
109                            power = IDLE;
110                            break;
111                        case (LnConstants.GTRK_POWER + LnConstants.GTRK_IDLE):
112                            power = ON;
113                            break;
114                        case LnConstants.GTRK_IDLE:
115                            power = OFF;
116                            break;
117                        default:
118                            power = UNKNOWN;
119                            break;
120                    }
121                }   break;
122            default:
123                break;
124        }
125        firePowerPropertyChange(old, power);
126    }
127
128    /**
129     * Creates a thread which delays and then queries slot 0 to get the current
130     * track status. The LnListener will see the slot read data and use the
131     * current track status to update the LnPowerManager's internal track power
132     * state info.
133     */
134    private void updateTrackPowerStatus() {
135        thread = new LnTrackStatusUpdateThread(tc);
136        thread.setName("LnPowerManager LnTrackStatusUpdateThread");
137        thread.start();
138    }
139
140    volatile LnTrackStatusUpdateThread thread;
141
142    /**
143     * Class providing a thread to delay, then query slot 0. The LnPowerManager
144     * can use the resulting OPC_SL_RD_DATA message to update its view of the
145     * current track status.
146     */
147    static class LnTrackStatusUpdateThread extends Thread {
148
149        private LnTrafficController tc;
150
151        /**
152         * Construct the thread.
153         *
154         * @param tc LocoNetTrafficController which can be used to send the
155         *           LocoNet message.
156         */
157        public LnTrackStatusUpdateThread(LnTrafficController tc) {
158            this.tc = tc;
159        }
160
161        /**
162         * Runs the thread - Waits a while (to allow the managers to initialize),
163         * then sends a query of slot 0 so that the PowerManager can inspect
164         * the {@code "<trk>"} byte.
165         */
166        @Override
167        public void run() {
168            // wait a little bit to allow PowerManager to be initialized
169            log.trace("LnTrackStatusUpdateThread start check loop");
170            for (int i = 1; i <=10; i++) {
171                if (tc.status()) break; // TrafficController is reporting ready
172                
173                log.trace("LnTrackStatusUpdateThread waiting {} time", i);
174                // else wait, then try again
175                try {
176                    // Delay 500 mSec to allow init of traffic controller, listeners.
177                    Thread.sleep(500);
178                } catch (InterruptedException e) {
179                    Thread.currentThread().interrupt(); // retain if needed later
180                    return; // and stop work
181                }
182            }
183
184            try {
185                // Delay just a bit more, just in case.  Yes, this shouldn't be needed...
186                Thread.sleep(250);
187            } catch (InterruptedException e) {
188                Thread.currentThread().interrupt(); // retain if needed later
189                return; // and stop work
190            }
191            
192            log.trace("LnTrackStatusUpdateThread sending request");
193            LocoNetMessage msg = new LocoNetMessage(4);
194            msg.setOpCode(LnConstants.OPC_RQ_SL_DATA);
195            msg.setElement(1, 0);
196            msg.setElement(2, 0);
197
198            tc.sendLocoNetMessage(msg);
199            log.debug("LnTrackStatusUpdate sent");
200        }
201    }
202
203    /**
204     * Returns whether command station supports IDLE funcitonality
205     *
206     * @return true if connection's command station supports IDLE state, else false
207     */
208    @Override
209    public boolean implementsIdle() {
210        boolean supportsIdleState = false;
211        if (tc == null) {
212            log.error("TC is null in LnPowerManager");
213            return false;
214        }
215        if (tc.memo == null) {
216            log.error("TC.Memo is null in LnPowerManager");
217            return false;
218        }
219        LnCommandStationType cmdStationType = tc.memo.getSlotManager().getCommandStationType();
220        switch (cmdStationType) {
221            case COMMAND_STATION_DB150:
222            case COMMAND_STATION_DCS100:
223            case COMMAND_STATION_DCS240:
224            case COMMAND_STATION_DCS210:
225            case COMMAND_STATION_DCS200:
226                supportsIdleState = true;
227                break;
228            default:
229                supportsIdleState = false;
230
231        }
232        return supportsIdleState;
233    }
234
235    private final static Logger log = LoggerFactory.getLogger(LnPowerManager.class);
236
237}