001package jmri.jmrix.bidib; 002 003import jmri.implementation.AbstractVariableLight; 004import jmri.util.MathUtil; 005 006import org.bidib.jbidibc.messages.BidibLibrary; //new 007import org.bidib.jbidibc.messages.LcConfigX; 008import org.bidib.jbidibc.messages.enums.LcOutputType; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Implementation of the Light Control Object for BiDiB. 015 * 016 * @author Paul Bender Copyright (C) 2008-2010 017 * @author Eckart Meyer Copyright (C) 2019-2023 018 */ 019public class BiDiBLight extends AbstractVariableLight implements BiDiBNamedBeanInterface { 020 021 private BiDiBAddress addr; 022 private final char typeLetter; 023 private BiDiBTrafficController tc = null; 024 protected BiDiBOutputMessageHandler messageHandler = null; 025 //private LcConfigX portConfigx; 026 private LcOutputType lcType; //cached type from portConfigX or fixed in type based address 027 028 /** 029 * Create a Light object from system name. 030 * 031 * @param systemName System name of light to be created 032 * @param mgr Light Manager, we get the memo object and the type letter (L) from the manager 033 */ 034 public BiDiBLight(String systemName, BiDiBLightManager mgr) { 035 super(systemName); 036 tc = mgr.getMemo().getBiDiBTrafficController(); 037 log.debug("New Light: {}", systemName); 038 addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo()); 039 log.info("New LIGHT created: {} -> {}", systemName, addr); 040 typeLetter = mgr.typeLetter(); 041 init(); 042 } 043 044 private void init() { 045 // portConfigx = new LcConfigX(addr.makeBidibPort(), new LinkedHashMap<>() ); 046 047 createLightListener(); 048 049 if (addr.isValid() && addr.isPortAddr()) { 050 if (addr.isPortTypeBasedModel()) { 051 lcType = addr.getPortType(); 052 } 053 } 054 055// // DEBUG 056// lcType = LcOutputType.LIGHTPORT; 057// lcType = LcOutputType.SWITCHPORT; 058// lcType = LcOutputType.BACKLIGHTPORT; 059 060 messageHandler.sendQueryConfig(); 061 } 062 063 @Override 064 public BiDiBAddress getAddr() { 065 return addr; 066 } 067 068 /** 069 * Helper function that will be invoked after construction once the type has been 070 * set. Used specifically for preventing double initialization when loading turnouts from XML. 071 */ 072 @Override 073 public void finishLoad() { 074 messageHandler.sendQuery(); 075 } 076 077 @Override 078 public void nodeNew() { 079 //create a new BiDiBAddress 080 addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo()); 081 if (addr.isValid()) { 082 log.info("new light address created: {} -> {}", getSystemName(), addr); 083 messageHandler.sendQueryConfig(); 084 messageHandler.waitQueryConfig(); 085 log.debug("current state is {}", getState()); 086 setState(getCommandedState()); 087 } 088 } 089 090 @Override 091 public void nodeLost() { 092 notifyStateChange(mState, UNKNOWN); 093 } 094 095 /** 096 * Get connection memo from a light object 097 * 098 * @return BiDiB connection memo instance 099 */ 100 public BiDiBSystemConnectionMemo getMemo() { 101 return tc.getSystemConnectionMemo(); 102 } 103 104 /** 105 * Get addres object from a light object 106 * 107 * @return BiDiB address instance 108 */ 109 public BiDiBAddress getAddress() { 110 return addr; 111 } 112 113 /** 114 * Dispose of the light object. 115 * 116 * Remove the Message Listener for this light object 117 */ 118 @Override 119 public void dispose() { 120 if (messageHandler != null) { 121 tc.removeMessageListener(messageHandler); 122 messageHandler = null; 123 } 124 super.dispose(); 125 } 126 127 /** 128 * Check if this object can handle variable intensity. 129 * <p> 130 * @return true for some LC output types, false for others 131 */ 132 public boolean isIntensityVariable() { 133 if (lcType != null) { 134 switch (lcType) { 135 case LIGHTPORT: //we misuse the intensity value for encoding the output state value 136 case SERVOPORT: 137 case BACKLIGHTPORT: 138 case MOTORPORT: //not really supported so far 139 case ANALOGPORT: //not specified! 140 return true; 141 default: 142 // drop through and return false 143 break; 144 } 145 } 146 return false; 147 } 148 149 /** 150 * Can the Light change its intensity setting slowly? 151 * <p> 152 * If true, this Light supports a non-zero value of the transitionTime 153 * property, which controls how long the Light will take to change from one 154 * intensity level to another. 155 * BiDiB LIGHTPORTs have internal dimming via port configuration. 156 * <p> 157 * Unbound property 158 * @return true if isIntensityVariable() is true but for a LIGHTPORT return false. 159 */ 160 @Override 161 public boolean isTransitionAvailable() { 162 return (lcType != LcOutputType.LIGHTPORT && isIntensityVariable()); 163// return isIntensityVariable(); 164 } 165 166 /** 167 * Set the current state of this Light. This routine requests the hardware 168 * to change. 169 * 170 * @param newState new requested state - must be ON or OFF 171 */ 172 @Override 173 synchronized public void setState(int newState) { 174 log.trace("BiDiBLight setState: new: {}, old: {}", newState, mState); 175 if (newState != ON && newState != OFF) { 176 throw new IllegalArgumentException("cannot set state value " + newState); 177 } 178// super.setState(newState); 179 setTargetIntensity(newState == ON ? mMaxIntensity : mMinIntensity); 180 } 181 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public void setTargetIntensity(double intensity) { 188 log.trace("BiDiBLight setTargetIntensity: new: {}", intensity); 189// super.setTargetIntensity(intensity); 190 if (lcType == LcOutputType.LIGHTPORT) { 191 sendIntensity(intensity); 192 // update value and tell listeners 193 notifyTargetIntensityChange(intensity); 194 195 // decide if this is a state change operation 196 int state = (int) (intensity * 100); 197 if (state == BidibLibrary.BIDIB_PORT_DIMM_OFF || state == BidibLibrary.BIDIB_PORT_TURN_OFF) { 198 notifyStateChange(mState, OFF); 199 } else { 200 notifyStateChange(mState, ON); 201 } 202 } 203 else { 204 if (intensity < 0.0 || intensity > 1.0) { 205 throw new IllegalArgumentException("Target intensity value " + intensity + " not in legal range"); 206 } 207 208 // limit 209 if (intensity > mMaxIntensity) { 210 intensity = mMaxIntensity; 211 } 212 if (intensity < mMinIntensity) { 213 intensity = mMinIntensity; 214 } 215 216 // see if there's a transition in use 217 if (getTransitionTime() > 0.0 && lcType != LcOutputType.LIGHTPORT) { 218 startTransition(intensity); 219 } else { 220 // No transition in use, move immediately 221 222 // Set intensity and intermediate state 223 sendIntensity(intensity); 224 // update value and tell listeners 225 notifyTargetIntensityChange(intensity); 226 227 // decide if this is a state change operation 228 if (intensity >= mMaxIntensity) { 229 notifyStateChange(mState, ON); 230 } else if (intensity <= mMinIntensity) { 231 notifyStateChange(mState, OFF); 232 } else { 233 notifyStateChange(mState, INTERMEDIATE); 234 } 235 } 236 } 237 } 238 239 /** 240 * Send request to traffic controller 241 * 242 * @param portstat BiDiB portstat value (see protocol description for valid values) 243 */ 244 protected void sendLcOutput(int portstat) { 245 messageHandler.sendOutput(portstat); 246 } 247 248 /** 249 * Send a Dim/Bright commands to the hardware to reach a specific intensity. 250 * 251 * @param intensity new intensity 252 */ 253 @Override 254 protected void sendIntensity(double intensity) { 255 log.trace("sendIntensity: {}", intensity); 256 if (lcType == LcOutputType.LIGHTPORT) { 257 sendLcOutput( (int) (intensity * 100)); 258 } 259 else if (isIntensityVariable()) { 260 sendLcOutput( (int) MathUtil.pin(intensity * 255.0, 0.0, 255.0)); 261 } 262 else { 263 sendLcOutput(intensity > mMinIntensity ? BidibLibrary.BIDIB_PORT_TURN_ON : BidibLibrary.BIDIB_PORT_TURN_OFF); 264 } 265 } 266 267 /** 268 * Transfer incoming change event to JMRI 269 * 270 * @param portstat BiDiB portstat value (see protocol description for valid values) 271 */ 272 public void receiveIntensity(int portstat) { 273 log.trace("receiveIntensity: {}", portstat); 274 if (lcType == LcOutputType.LIGHTPORT) { 275// notifyTargetIntensityChange( (double)portstat / 100); 276 double intensity = 0; 277 switch(portstat) { 278 case BidibLibrary.BIDIB_PORT_TURN_OFF: 279 intensity = 0; 280 break; 281 case BidibLibrary.BIDIB_PORT_DIMM_OFF: 282 intensity = mMinIntensity; 283 break; 284 case BidibLibrary.BIDIB_PORT_TURN_ON: 285 intensity = 1.0; 286 break; 287 case BidibLibrary.BIDIB_PORT_DIMM_ON: 288 intensity = mMaxIntensity; 289 break; 290 default: 291 intensity = 0; 292 break; 293 } 294 notifyTargetIntensityChange(intensity); 295 if (portstat == BidibLibrary.BIDIB_PORT_DIMM_OFF || portstat == BidibLibrary.BIDIB_PORT_TURN_OFF) { 296 notifyStateChange(mState, OFF); 297 } else { 298 notifyStateChange(mState, ON); 299 } 300 } 301 else if (isIntensityVariable()) { 302 double intensity = MathUtil.pin( (double)portstat / 255, 0.0, 1.0); 303 notifyTargetIntensityChange(intensity); 304 notifyStateChange(mState, intensity <= mMinIntensity ? OFF : ON); 305 } 306 else { 307 notifyTargetIntensityChange( portstat == BidibLibrary.BIDIB_PORT_TURN_OFF ? mMinIntensity : mMaxIntensity); 308 notifyStateChange(mState, portstat == BidibLibrary.BIDIB_PORT_TURN_OFF ? OFF : ON); 309 } 310 } 311 312 @Override 313 protected void sendOnOffCommand(int newState) { 314 // not used, but must be implemented 315 log.trace("sendOnOffCommand: {}", newState); 316 } 317 318 @Override 319 protected int getNumberOfSteps() { 320 return 256; //TODO What is this used for? 321 } 322 323 324 private void createLightListener() { 325 //messageHandler = new BiDiBOutputMessageHandler("LIGHT", addr, tc) { 326 messageHandler = new BiDiBOutputMessageHandler(this, "LIGHT", tc) { 327 @Override 328 public void newOutputState(int state) { 329 log.debug("LIGHT new state: {}", state); 330 //newKnownState( (state == 0) ? CLOSED : THROWN); 331 receiveIntensity(state); 332 } 333 @Override 334 public void outputWait(int time) { 335 log.debug("LIGHT wait: {}", time); 336 if (time > 0) { 337 notifyStateChange(getState(), INCONSISTENT); 338 } 339 } 340 @Override 341 public void errorState(int err) { 342 log.warn("LIGHT error: {} addr: {}", err, addr); 343 notifyStateChange(getState(), INCONSISTENT); 344 } 345 @Override 346 public void newLcConfigX(LcConfigX lcConfigX, LcOutputType lcType) { 347 this.portConfigx = lcConfigX; 348 this.lcType = lcType; 349 } 350 }; 351 tc.addMessageListener(messageHandler); 352 } 353 354 private final static Logger log = LoggerFactory.getLogger(BiDiBLight.class); 355 356}