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}