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}