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