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}