001package jmri.jmrix.loconet; 002 003import jmri.implementation.DefaultSignalHead; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007/** 008 * Extend jmri.SignalHead for signals implemented by an SE8C. 009 * <p> 010 * This implementation writes out to the physical signal when it's commanded to 011 * change appearance, and updates its internal state when it hears commands from 012 * other places. 013 * <p> 014 * To get a complete set of aspects, we assume that the SE8C board has been 015 * configured such that the 4th aspect is "dark". We then do flashing aspects by 016 * commanding the lit appearance to change. 017 * <p> 018 * This is a grandfathered implementation that is specific to LocoNet systems. A 019 * more general implementation, which can work with any system(s), is available 020 * in {@link jmri.implementation.SE8cSignalHead}. This package is maintained so 021 * that existing XML files can continue to be read. In particular, it only works 022 * with the first LocoNet connection (names LHnnn, not L2Hnnn etc). 023 * <p> 024 * The algorithms in this class are a collaborative effort of Digitrax, Inc and 025 * Bob Jacobsen. 026 * <p> 027 * Some of the message formats used in this class are Copyright Digitrax, Inc. 028 * and used with permission as part of the JMRI project. That permission does 029 * not extend to uses in other software products. If you wish to use this code, 030 * algorithm or these message formats outside of JMRI, please contact Digitrax 031 * Inc for separate permission. 032 * 033 * @author Bob Jacobsen Copyright (C) 2002 034 */ 035public class SE8cSignalHead extends DefaultSignalHead implements LocoNetListener { 036 037 public SE8cSignalHead(int pNumber, String userName) { 038 // create systemname 039 super("LH" + pNumber, userName); // NOI18N 040 init(pNumber); 041 } 042 043 public SE8cSignalHead(int pNumber) { 044 // create systemname 045 super("LH" + pNumber); // NOI18N 046 init(pNumber); 047 } 048 049 void init(int pNumber) { 050 tc = jmri.InstanceManager.getDefault(LnTrafficController.class); 051 mNumber = pNumber; 052 mAppearance = DARK; // start turned off 053 // At construction, register for messages 054 tc.addLocoNetListener(~0, this); 055 updateOutput(); 056 } 057 058 LnTrafficController tc; 059 060 public int getNumber() { 061 return mNumber; 062 } 063 064 // Handle a request to change state by sending a LocoNet command 065 @Override 066 protected void updateOutput() { 067 // send SWREQ for close 068 LocoNetMessage l = new LocoNetMessage(4); 069 l.setOpCode(LnConstants.OPC_SW_REQ); 070 071 int address = 0; 072 boolean closed = false; 073 if (!mLit) { 074 address = mNumber + 1; 075 closed = true; 076 } else if (!mFlashOn 077 && ((mAppearance == FLASHGREEN) 078 || (mAppearance == FLASHYELLOW) 079 || (mAppearance == FLASHRED))) { 080 // flash says to make output dark; 081 // flashing-but-lit is handled below 082 address = mNumber + 1; 083 closed = true; 084 } else { 085 // which of the four states? 086 switch (mAppearance) { 087 case FLASHRED: 088 case RED: 089 address = mNumber; 090 closed = false; 091 break; 092 case FLASHYELLOW: 093 case YELLOW: 094 address = mNumber + 1; 095 closed = false; 096 break; 097 case FLASHGREEN: 098 case GREEN: 099 address = mNumber; 100 closed = true; 101 break; 102 case DARK: 103 address = mNumber + 1; 104 closed = true; 105 break; 106 default: 107 log.error("Invalid state request: {}", mAppearance); 108 return; 109 } 110 } 111 // compute address fields 112 int hiadr = (address - 1) / 128; 113 int loadr = (address - 1) - hiadr * 128; 114 if (closed) { 115 hiadr |= 0x20; 116 } 117 118 // set "on" bit 119 hiadr |= 0x10; 120 121 // store and send 122 l.setElement(1, loadr); 123 l.setElement(2, hiadr); 124 tc.sendLocoNetMessage(l); 125 } 126 127 // implementing classes will typically have a function/listener to get 128 // updates from the layout, which will then call 129 // public void firePropertyChange(String propertyName, 130 // Object oldValue, 131 // Object newValue) 132 // _once_ if anything has changed state (or set the commanded state directly) 133 @Override 134 public void message(LocoNetMessage l) { 135 int oldAppearance = mAppearance; 136 // parse message type 137 switch (l.getOpCode()) { 138 case LnConstants.OPC_SW_REQ: { /* page 9 of LocoNet PE */ 139 140 int sw1 = l.getElement(1); 141 int sw2 = l.getElement(2); 142 if (myAddress(sw1, sw2)) { 143 if ((sw2 & LnConstants.OPC_SW_REQ_DIR) != 0) { 144 // was set CLOSED 145 if (mAppearance != FLASHGREEN) { 146 mAppearance = GREEN; 147 } 148 } else { 149 // was set THROWN 150 if (mAppearance != FLASHRED) { 151 mAppearance = RED; 152 } 153 } 154 } 155 if (myAddressPlusOne(sw1, sw2)) { 156 if ((sw2 & LnConstants.OPC_SW_REQ_DIR) != 0) { 157 // was set CLOSED, which means DARK 158 // don't change if one of the possibilities already 159 if (!(mAppearance == FLASHYELLOW || mAppearance == DARK 160 || mAppearance == FLASHGREEN || mAppearance == FLASHRED 161 || !mLit || !mFlashOn)) { 162 mAppearance = DARK; // that's the setting by default 163 } 164 } else { 165 // was set THROWN 166 if (mAppearance != FLASHYELLOW) { 167 mAppearance = YELLOW; 168 } 169 } 170 } 171 break; 172 } 173 case LnConstants.OPC_SW_REP: { /* page 9 of LocoNet PE */ 174 175 int sw1 = l.getElement(1); 176 int sw2 = l.getElement(2); 177 if (myAddress(sw1, sw2)) { 178 // see if its a turnout state report 179 if ((sw2 & LnConstants.OPC_SW_REP_INPUTS) == 0) { 180 // sort out states 181 if ((sw2 & LnConstants.OPC_SW_REP_CLOSED) != 0) { 182 // was set CLOSED 183 if (mAppearance != FLASHGREEN) { 184 mAppearance = GREEN; 185 } 186 } 187 if ((sw2 & LnConstants.OPC_SW_REP_THROWN) != 0) { 188 // was set THROWN 189 if (mAppearance != FLASHRED) { 190 mAppearance = RED; 191 } 192 } 193 } 194 } 195 if (myAddressPlusOne(sw1, sw2)) { 196 // see if its a turnout state report 197 if ((sw2 & LnConstants.OPC_SW_REP_INPUTS) == 0) { 198 // was set CLOSED, which means DARK 199 // don't change if one of the possibilities already 200 if (!(mAppearance == FLASHYELLOW || mAppearance == DARK 201 || mAppearance == FLASHGREEN || mAppearance == FLASHRED 202 || !mLit || !mFlashOn)) { 203 mAppearance = DARK; // that's the setting by default 204 } 205 } 206 if ((sw2 & LnConstants.OPC_SW_REP_THROWN) != 0) { 207 // was set THROWN 208 if (mAppearance != FLASHYELLOW) { 209 mAppearance = YELLOW; 210 } 211 } 212 } 213 return; 214 } 215 default: 216 return; 217 } 218 // reach here if the state has updated 219 if (oldAppearance != mAppearance) { 220 firePropertyChange("Appearance", Integer.valueOf(oldAppearance), Integer.valueOf(mAppearance)); // NOI18N 221 } 222 } 223 224 @Override 225 public void dispose() { 226 tc.removeLocoNetListener(~0, this); 227 super.dispose(); 228 } 229 230 // data members 231 int mNumber; // LocoNet Turnout number with lower address (0 based) 232 233 private boolean myAddress(int a1, int a2) { 234 // the "+ 1" in the following converts to throttle-visible numbering 235 return (((a2 & 0x0f) * 128) + (a1 & 0x7f) + 1) == mNumber; 236 } 237 238 private boolean myAddressPlusOne(int a1, int a2) { 239 // the "+ 1" in the following converts to throttle-visible numbering 240 return (((a2 & 0x0f) * 128) + (a1 & 0x7f) + 1) == mNumber + 1; 241 } 242 private final static Logger log = LoggerFactory.getLogger(SE8cSignalHead.class); 243 244}