001package jmri.implementation;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Date;
008import java.util.List;
009import java.util.function.Predicate;
010import javax.annotation.Nonnull;
011import javax.swing.Timer;
012import jmri.*;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Each LightControl object is linked to a specific Light, and provides one of
018 * the controls available for switching the Light ON/OFF in response to time or
019 * events occurring on the layout.
020 * <p>
021 * Each LightControl holds the information for one control of the parent Light.
022 * <p>
023 * Each Light may have as many controls as desired by the user. It is the user's
024 * responsibility to ensure that the various control mechanisms do not conflict
025 * with one another.
026 * <p>
027 * Available control types are those defined in the Light.java interface.
028 * Control types: SENSOR_CONTROL FAST_CLOCK_CONTROL TURNOUT_STATUS_CONTROL
029 * TIMED_ON_CONTROL TWO_SENSOR_CONTROL
030 *
031 * <hr>
032 * This file is part of JMRI.
033 * <p>
034 * JMRI is free software; you can redistribute it and/or modify it under the
035 * terms of version 2 of the GNU General Public License as published by the Free
036 * Software Foundation. See the "COPYING" file for a copy of this license.
037 * <p>
038 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
039 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
040 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
041 *
042 * @author Dave Duchamp Copyright (C) 2010
043 */
044public class DefaultLightControl implements LightControl {
045
046    /**
047     * Main constructor methods
048     */
049    public DefaultLightControl() {
050    }
051
052    public DefaultLightControl(jmri.Light l) {
053        _parentLight = l;
054    }
055
056    // instance variables - saved with Light in configuration file
057    private int _controlType = Light.NO_CONTROL;    // control type
058    private String _controlSensorName = "";   // controlling Sensor if SENSOR_CONTROL
059    protected int _controlSensorSense = Sensor.ACTIVE;  // sense of Sensor for Light ON
060    private int _fastClockOnHour = 0;         // on Hour if FAST_CLOCK_CONTROL
061    private int _fastClockOnMin = 0;          // on Minute if FAST_CLOCK_CONTROL
062    private int _fastClockOffHour = 0;        // off Hour if FAST_CLOCK_CONTROL
063    private int _fastClockOffMin = 0;         // off Minute if FAST_CLOCK_CONTROL
064    private String _controlTurnoutName = "";  // turnout whose status is shown if TURNOUT_STATUS_CONTROL
065    private int _turnoutState = Turnout.CLOSED;  // turnout state corresponding to this Light ON
066    private String _timedSensorName = "";     // trigger Sensor if TIMED_ON_CONTROL
067    protected int _timeOnDuration = 0;          // duration (milliseconds) if TIMED_ON_CONTROL
068    private String _controlSensor2Name = ""; // second controlling sensor if TWO_SENSOR_CONTROL
069
070    /**
071     * Create a New LightControl from existing,
072     * for use when editing a LightControl
073     *
074     * @param lc the LightControl to be copied
075     */
076    public DefaultLightControl(@Nonnull LightControl lc) {
077        this._controlType = lc.getControlType();
078        this._controlSensorName = lc.getControlSensorName();
079        this._controlSensorSense = lc.getControlSensorSense();
080        this._fastClockOnHour = lc.getFastClockOnHour();
081        this._fastClockOnMin = lc.getFastClockOnMin();
082        this._fastClockOffHour = lc.getFastClockOffHour();
083        this._fastClockOffMin = lc.getFastClockOffMin();
084        this._controlTurnoutName = lc.getControlTurnoutName();
085        this._turnoutState = lc.getControlTurnoutState();
086        this._timedSensorName = lc.getTimedSensorName();
087        this._timeOnDuration = lc.getTimedOnDuration();
088        this._controlSensor2Name = lc.getControlSensor2Name();
089    }
090
091    /**
092     * Test if a LightControl is equal to this one
093     *
094     * @param o the LightControl object to be checked
095     * @return True if the LightControl is equal, else false
096     */
097    @Override
098    public boolean equals(Object o) {
099        if (!(o instanceof LightControl)) {
100            return false;
101        }
102        LightControl that = (LightControl) o;
103        if (that.getControlType() != this._controlType) return false;
104        boolean _shouldReturn = true;
105        switch(_controlType) {
106            case Light.NO_CONTROL :
107                break;
108            case Light.SENSOR_CONTROL :
109                if ((! that.getControlSensorName().equals(this._controlSensorName)) ||
110                    ( that.getControlSensorSense() != this._controlSensorSense)) _shouldReturn = false;
111                break;
112            case Light.FAST_CLOCK_CONTROL :
113                if ((that.getFastClockOffCombined() != this.getFastClockOffCombined()) ||
114                    (that.getFastClockOnCombined() != this.getFastClockOnCombined())) _shouldReturn = false;
115                break;
116            case Light.TURNOUT_STATUS_CONTROL :
117                if ((! that.getControlTurnoutName().equals(this._controlTurnoutName)) ||
118                    (that.getControlTurnoutState() != this._turnoutState)) _shouldReturn = false;
119                break;
120            case Light.TIMED_ON_CONTROL :
121                if ((! that.getTimedSensorName().equals(this._timedSensorName)) ||
122                    (that.getTimedOnDuration() != this._timeOnDuration)) _shouldReturn = false;
123                break;
124            case Light.TWO_SENSOR_CONTROL :
125                if ((! that.getControlSensorName().equals(this._controlSensorName)) ||
126                    (that.getControlSensorSense() != this._controlSensorSense) ||
127                    (! that.getControlSensor2Name().equals(this._controlSensor2Name))) _shouldReturn = false;
128                break;
129            default:
130                // unexpected _controlType value
131                jmri.util.LoggingUtil.warnOnce(log, "Unexpected _controlType = {}", _controlType);
132        }
133        return _shouldReturn;
134    }
135
136    /** {@inheritDoc} */
137    @Override
138    public int hashCode() {
139        // matches with equals() by contract
140        return _controlType;
141    }
142
143    /** {@inheritDoc} */
144    @Override
145    public int getControlType() {
146        return _controlType;
147    }
148
149    /** {@inheritDoc} */
150    @Override
151    public void setControlType(int type) {
152        _controlType = type;
153    }
154
155    /** {@inheritDoc} */
156    @Override
157    public void setControlSensorName(String sensorName) {
158        _controlSensorName = sensorName;
159    }
160
161    /** {@inheritDoc} */
162    @Override
163    public int getControlSensorSense() {
164        return _controlSensorSense;
165    }
166
167    /** {@inheritDoc} */
168    @Override
169    public String getControlSensorName() {
170        if (_namedControlSensor != null) {
171            return _namedControlSensor.getName();
172        }
173        return _controlSensorName;
174    }
175
176    /** {@inheritDoc} */
177    @Override
178    public void setControlSensorSense(int sense) {
179        if ( sense != Sensor.ACTIVE && sense != Sensor.INACTIVE ) {
180            log.error("Incorrect Sensor State Set");
181        } else {
182            _controlSensorSense = sense;
183        }
184    }
185
186    /** {@inheritDoc} */
187    @Override
188    public int getFastClockOnHour() {
189        return _fastClockOnHour;
190    }
191
192    /** {@inheritDoc} */
193    @Override
194    public int getFastClockOnMin() {
195        return _fastClockOnMin;
196    }
197
198    /** {@inheritDoc} */
199    @Override
200    public int getFastClockOnCombined() {
201        return _fastClockOnHour*60+_fastClockOnMin;
202    }
203
204    /** {@inheritDoc} */
205    @Override
206    public int getFastClockOffHour() {
207        return _fastClockOffHour;
208    }
209
210    /** {@inheritDoc} */
211    @Override
212    public int getFastClockOffMin() {
213        return _fastClockOffMin;
214    }
215
216    /** {@inheritDoc} */
217    @Override
218    public int getFastClockOffCombined() {
219        return _fastClockOffHour*60+_fastClockOffMin;
220    }
221
222    /** {@inheritDoc} */
223    @Override
224    public void setFastClockControlSchedule(int onHour, int onMin, int offHour, int offMin) {
225        _fastClockOnHour = onHour;
226        _fastClockOnMin = onMin;
227        _fastClockOffHour = offHour;
228        _fastClockOffMin = offMin;
229    }
230
231    /** {@inheritDoc} */
232    @Override
233    public String getControlTurnoutName() {
234        return _controlTurnoutName;
235    }
236
237    /** {@inheritDoc} */
238    @Override
239    public void setControlTurnout(String turnoutName) {
240        _controlTurnoutName = turnoutName;
241    }
242
243    /** {@inheritDoc} */
244    @Override
245    public int getControlTurnoutState() {
246        return _turnoutState;
247    }
248
249    /** {@inheritDoc} */
250    @Override
251    public void setControlTurnoutState(int state) {
252        if ( state != Turnout.CLOSED && state != Turnout.THROWN ) {
253            log.error("Incorrect Turnout State Set");
254        } else {
255            _turnoutState = state;
256        }
257    }
258
259    /** {@inheritDoc} */
260    @Override
261    public String getTimedSensorName() {
262        return _timedSensorName;
263    }
264
265    /** {@inheritDoc} */
266    @Override
267    public String getControlTimedOnSensorName() {
268        if (_namedTimedControlSensor != null) {
269            return _namedTimedControlSensor.getName();
270        }
271        return _timedSensorName;
272    }
273
274    /** {@inheritDoc} */
275    @Override
276    public void setControlTimedOnSensorName(String sensorName) {
277        _timedSensorName = sensorName;
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public int getTimedOnDuration() {
283        return _timeOnDuration;
284    }
285
286    /** {@inheritDoc} */
287    @Override
288    public void setTimedOnDuration(int duration) {
289        _timeOnDuration = duration;
290    }
291
292    /** {@inheritDoc} */
293    @Override
294    public String getControlSensor2Name() {
295        if (_namedControlSensor2 != null) {
296            return _namedControlSensor2.getName();
297        }
298        return _controlSensor2Name;
299    }
300
301    /** {@inheritDoc} */
302    @Override
303    public void setControlSensor2Name(String sensorName) {
304        _controlSensor2Name = sensorName;
305    }
306
307    /** {@inheritDoc} */
308    @Override
309    public void setParentLight(Light l) {
310        _parentLight = l;
311    }
312
313    // operational instance variables - not saved between runs
314    private Light _parentLight = null;        // Light that is being controlled
315    private boolean _active = false;
316    private NamedBeanHandle<Sensor> _namedControlSensor = null;
317    private PropertyChangeListener _sensorListener = null;
318    private NamedBeanHandle<Sensor> _namedControlSensor2 = null;
319    private PropertyChangeListener _sensor2Listener = null;
320    private PropertyChangeListener _timebaseListener = null;
321    private Timebase _clock = null;
322    private Turnout _controlTurnout = null;
323    private PropertyChangeListener _turnoutListener = null;
324    private NamedBeanHandle<Sensor> _namedTimedControlSensor = null;
325    private PropertyChangeListener _timedSensorListener = null;
326    private Timer _timedControlTimer = null;
327    private java.awt.event.ActionListener _timedControlListener = null;
328    private int _timeNow;
329    private PropertyChangeListener _parentLightListener = null;
330    private final jmri.NamedBeanHandleManager nbhm = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class);
331
332    /** {@inheritDoc} */
333    @Override
334    public String getDescriptionText(String lightName){
335        StringBuilder name = new StringBuilder(jmri.jmrit.beantable.LightTableAction.lightControlTitle);
336        name.append(" ");
337        name.append(lightName);
338        name.append(" ");
339        name.append(jmri.jmrit.beantable.LightTableAction.getDescriptionText(this, getControlType()));
340        return name.toString();
341    }
342
343    /** {@inheritDoc} */
344    @Override
345    public void activateLightControl() {
346        // skip if Light Control is already active
347        if (_active) {
348            return;
349        }
350
351        if (_parentLight == null){
352            log.error("No Parent Light when activating LightControl");
353            return;
354        }
355
356        // register LightControl with Parent Light to indicate Control
357        // in use if user attempts to delete light
358        _parentLight.addPropertyChangeListener(
359            _parentLightListener = (PropertyChangeEvent e) -> {
360        },_parentLight.toString(), getDescriptionText("") );
361
362        // activate according to control type
363        switch (_controlType) {
364            case Light.SENSOR_CONTROL:
365                _namedControlSensor = null;
366                if (!_controlSensorName.isEmpty()) {
367                    Sensor sen = InstanceManager.sensorManagerInstance().
368                            provideSensor(_controlSensorName);
369                    _namedControlSensor = nbhm.getNamedBeanHandle(_controlSensorName, sen);
370                }
371                if (_namedControlSensor != null) {
372                    // if sensor state is currently known, set light accordingly
373                    oneSensorChanged( _namedControlSensor.getBean().getKnownState() );
374                    // listen for change in sensor state
375                    _namedControlSensor.getBean().addPropertyChangeListener(
376                        _sensorListener = (PropertyChangeEvent e) -> {
377                            if (e.getPropertyName().equals("KnownState")) {
378                                oneSensorChanged( (int) e.getNewValue() );
379                            }
380                    }, _controlSensorName, getDescriptionText(_parentLight.getDisplayName()));
381                    _active = true;
382                } else {
383                    // control sensor does not exist
384                    log.error("Light {} is linked to a Sensor that does not exist: {}",
385                        _parentLight.getSystemName(), _controlSensorName);
386                }
387                break;
388            case Light.FAST_CLOCK_CONTROL:
389                if (areFollowerTimesFaulty(_parentLight.getLightControlList())){
390                    log.error("Light has multiple actions for the same time in {}",
391                        getDescriptionText(_parentLight.getDisplayName()));
392                }
393                if (_clock == null) {
394                    _clock = InstanceManager.getDefault(jmri.Timebase.class);
395                }
396                // initialize light based on current fast time
397                updateClockControlLightFollower();
398                // set up to listen for time changes on a minute basis
399                _clock.addMinuteChangeListener(
400                    _timebaseListener = (PropertyChangeEvent e) -> {
401                        updateClockControlLightFollower();
402                    });
403                _active = true;
404                break;
405            case Light.TURNOUT_STATUS_CONTROL:
406                try {
407                    _controlTurnout = InstanceManager.turnoutManagerInstance().
408                            provideTurnout(_controlTurnoutName);
409                } catch (IllegalArgumentException e) {
410                    // control turnout does not exist
411                    log.error("Light {} is linked to a Turnout that does not exist: {}", _parentLight.getSystemName(), _controlSensorName);
412                    return;
413                }
414                // set light based on current turnout state if known
415                oneTurnoutChanged( _controlTurnout.getKnownState() );
416                // listen for change in turnout state
417                _controlTurnout.addPropertyChangeListener(
418                    _turnoutListener = (PropertyChangeEvent e) -> {
419                        if (e.getPropertyName().equals("KnownState")) {
420                            oneTurnoutChanged( (int) e.getNewValue() );
421                        }
422                    }, _controlTurnoutName, getDescriptionText(_parentLight.getDisplayName()));
423                _active = true;
424                break;
425            case Light.TIMED_ON_CONTROL:
426                if (!_timedSensorName.isEmpty()) {
427                    Sensor sen = InstanceManager.sensorManagerInstance().
428                            provideSensor(_timedSensorName);
429                    _namedTimedControlSensor = nbhm.getNamedBeanHandle(_timedSensorName, sen);
430                }
431                if (_namedTimedControlSensor != null) {
432                    if (_parentLight.getEnabled()) {
433                        // set initial state off
434                        _parentLight.setState(Light.OFF);
435                    }
436
437                    addNamedTimedControlListener();
438                    // listen for change in timed control sensor state
439                    _active = true;
440                } else {
441                    // timed control sensor does not exist
442                    log.error("Light {} is linked to a Sensor that does not exist: {}", _parentLight.getSystemName(), _timedSensorName);
443                }
444                break;
445            case Light.TWO_SENSOR_CONTROL:
446                _namedControlSensor = null;
447                _namedControlSensor2 = null;
448                if (!_controlSensorName.isEmpty()) {
449                    Sensor sen = InstanceManager.sensorManagerInstance().
450                            provideSensor(_controlSensorName);
451                    _namedControlSensor = nbhm.getNamedBeanHandle(_controlSensorName, sen);
452                }
453                if (!_controlSensor2Name.isEmpty()) {
454                    Sensor sen = InstanceManager.sensorManagerInstance().
455                            provideSensor(_controlSensor2Name);
456                    _namedControlSensor2 = nbhm.getNamedBeanHandle(_controlSensor2Name, sen);
457                }
458                if ((_namedControlSensor != null) && (_namedControlSensor2 != null)) {
459                    // if sensor state is currently known, set light accordingly
460                    twoSensorChanged();
461                    // listen for change in sensor states
462                    _sensorListener = addTwoSensorListener(_namedControlSensor.getBean());
463                    _sensor2Listener = addTwoSensorListener(_namedControlSensor2.getBean());
464                    _active = true;
465                } else {
466                    // at least one control sensor does not exist
467                    log.error("Light {} with 2 Sensor Control is linked to a Sensor that does not exist.", _parentLight.getSystemName());
468                }
469                break;
470            default:
471                log.error("Unexpected control type when activating Light: {}", _parentLight);
472        }
473
474    }
475
476    /**
477     * Property Change Listener for Two Sensor.
478     */
479    private PropertyChangeListener addTwoSensorListener(Sensor sensor) {
480        PropertyChangeListener pcl;
481        sensor.addPropertyChangeListener(
482            pcl = (PropertyChangeEvent e) -> {
483                if (e.getPropertyName().equals("KnownState")) {
484                    twoSensorChanged();
485                }
486            }, sensor.getDisplayName(), getDescriptionText(_parentLight.getDisplayName()));
487        return pcl;
488    }
489
490    /**
491     * Add a Timed Control Listener to a Sensor.
492     *
493     */
494    private void addNamedTimedControlListener(){
495        _namedTimedControlSensor.getBean().addPropertyChangeListener(
496            _timedSensorListener = (PropertyChangeEvent e) -> {
497                if (e.getPropertyName().equals("KnownState")
498                    && (int) e.getNewValue() == Sensor.ACTIVE
499                    && _timedControlTimer == null
500                    && _parentLight.getEnabled()) {
501                    // Turn light on
502                    _parentLight.setState(Light.ON);
503                    // Create a timer if one does not exist
504                    _timedControlListener = new TimeLight();
505                    _timedControlTimer = new Timer(_timeOnDuration,
506                        _timedControlListener);
507                    // Start the Timer to turn the light OFF
508                    _timedControlTimer.start();
509                }
510            },
511        _timedSensorName, getDescriptionText(_parentLight.getDisplayName()));
512    }
513
514    /**
515     * Internal routine for handling sensor change or startup
516     * for the 1 Sensor Control Type
517     */
518    private void oneSensorChanged(int newSensorState){
519        if (!_parentLight.getEnabled()) {
520            return;  // ignore property change if user disabled Light
521        }
522        if (newSensorState == Sensor.ACTIVE) {
523            if (_controlSensorSense == Sensor.ACTIVE) {
524                // Turn light on
525                _parentLight.setState(Light.ON);
526            } else {
527                // Turn light off
528                _parentLight.setState(Light.OFF);
529            }
530        } else if (newSensorState == Sensor.INACTIVE) {
531            if (_controlSensorSense == Sensor.INACTIVE) {
532                // Turn light on
533                _parentLight.setState(Light.ON);
534            } else {
535                // Turn light off
536                _parentLight.setState(Light.OFF);
537            }
538        }
539    }
540
541    /**
542     * Internal routine for handling Turnout change or startup
543     * for the TURNOUT_STATUS_CONTROL Control Type
544     */
545    private void oneTurnoutChanged(int newTurnoutState){
546        if (!_parentLight.getEnabled()) {
547            return;  // ignore property change if user disabled light
548        }
549        if (newTurnoutState == Turnout.CLOSED) {
550            if (_turnoutState == Turnout.CLOSED) {
551                // Turn light on
552                _parentLight.setState(Light.ON);
553            } else {
554                // Turn light off
555                _parentLight.setState(Light.OFF);
556            }
557        } else if (newTurnoutState == Turnout.THROWN) {
558            if (_turnoutState == Turnout.THROWN) {
559                // Turn light on
560                _parentLight.setState(Light.ON);
561            } else {
562                // Turn light off
563                _parentLight.setState(Light.OFF);
564            }
565        }
566    }
567
568    /**
569     * Internal routine for handling sensor changes
570     * for the 2 Sensor Control Type
571     */
572    protected void twoSensorChanged() {
573        if (!_parentLight.getEnabled()) {
574            return;  // ignore property change if user disabled Light
575        }
576        int kState = _namedControlSensor.getBean().getKnownState();
577        int kState2 = _namedControlSensor2.getBean().getKnownState();
578        if (_controlSensorSense == Sensor.ACTIVE) {
579            if ((kState == Sensor.ACTIVE) || (kState2 == Sensor.ACTIVE)) {
580                // Turn light on
581                _parentLight.setState(Light.ON);
582            } else {
583                // Turn light off
584                _parentLight.setState(Light.OFF);
585            }
586        } else if (_controlSensorSense == Sensor.INACTIVE) {
587            if ((kState == Sensor.INACTIVE) || (kState2 == Sensor.INACTIVE)) {
588                // Turn light on
589                _parentLight.setState(Light.ON);
590            } else {
591                // Turn light off
592                _parentLight.setState(Light.OFF);
593            }
594        }
595    }
596
597    /**
598     * Internal routine for seeing if we have the latest time to control the FastClock Follower.
599     * <p>
600     * Takes previous day times
601     *
602     * @return True if we have the most recent time ( either on or off ), otherwise False.
603     */
604    private boolean isMasterFastClockFollower(){
605        List<Integer> otherControlTimes= new ArrayList<>();
606        List<Integer> thisControlTimes= new ArrayList<>();
607
608        // put all other times in a single List to compare
609        _parentLight.getLightControlList().forEach((otherLc) -> {
610            if (otherLc!=this && otherLc.getControlType()==Light.FAST_CLOCK_CONTROL) {
611                // by adding 1440 mins to the today times, we can check yesterday in the same list.
612                otherControlTimes.add( otherLc.getFastClockOnCombined() ); // yesterdayOnTime
613                otherControlTimes.add( otherLc.getFastClockOffCombined() ); // yesterdayOffTime
614                otherControlTimes.add( otherLc.getFastClockOnCombined()+1440 ); // todayOnTime
615                otherControlTimes.add( otherLc.getFastClockOffCombined()+1440 ); // todayOffTime
616            }
617        });
618        // log.debug("{} other control times in list {}",otherControlTimes.size(),otherControlTimes);
619
620        thisControlTimes.add( getFastClockOnCombined() ); // yesterdayOnTime
621        thisControlTimes.add( getFastClockOffCombined() ); // yesterdayOffTime
622        thisControlTimes.add( getFastClockOnCombined()+1440 ); // todayOnTime
623        thisControlTimes.add( getFastClockOffCombined()+1440 ); // todayOffTime
624
625        otherControlTimes.removeIf( e -> ( e > ( _timeNow +1440 ) )); // remove future times
626        thisControlTimes.removeIf( e -> ( e > ( _timeNow +1440 ) )); // remove future times
627
628        if (otherControlTimes.isEmpty()){
629            return true;
630        }
631        return Collections.max(thisControlTimes) >= Collections.max(otherControlTimes);
632    }
633
634    /** {@inheritDoc} */
635    @Override
636    public boolean onOffTimesFaulty() {
637        return (getFastClockOnCombined()==getFastClockOffCombined());
638    }
639
640    /**
641     * @param time Combined hours / mins to check against.
642     */
643    private Predicate<LightControl> isFastClockEqual(int time) {
644        return p -> ( !(p==this) && (
645            p.getFastClockOnCombined() == time || p.getFastClockOffCombined() == time ) );
646    }
647
648    /** {@inheritDoc} */
649    @Override
650    public boolean areFollowerTimesFaulty( List<LightControl> compareList ) {
651        if (onOffTimesFaulty()){
652            return true;
653        }
654        return (compareList.stream().anyMatch(isFastClockEqual(getFastClockOnCombined())) ||
655            compareList.stream().anyMatch(isFastClockEqual(getFastClockOffCombined())));
656    }
657
658    /**
659     * Updates the local int of the FastClock Time
660     */
661    @SuppressWarnings("deprecation") // Date.getTime
662    private void setTheTime(){
663        Date now = _clock.getTime();
664        _timeNow = now.getHours() * 60 + now.getMinutes();
665    }
666
667    /**
668     * Updates the status of a Light under FAST_CLOCK_CONTROL. This method is
669     * called every FastClock minute.
670     */
671    private void updateClockControlLightFollower() {
672        if (!_parentLight.getEnabled()) {
673            return;  // ignore property change if user disabled Light
674        }
675        if (_clock != null) {
676            setTheTime();
677            // log.debug("updateClockControl, now is {} master {}",_timeNow,isMasterFastClockFollower());
678            if (!isMasterFastClockFollower()){
679                return;
680            }
681            int state = _parentLight.getState();
682            if (getFastClockOnCombined() <= getFastClockOffCombined()) {
683                // on and off the same day
684                if ((_timeNow < getFastClockOnCombined()) || (_timeNow >= getFastClockOffCombined())) {
685                    // Light should be OFF
686                    if (state == Light.ON) {
687                        logTimeChanges("OFF");
688                        _parentLight.setState(Light.OFF);
689                    }
690                } else {
691                    // Light should be ON
692                    if (state == Light.OFF) {
693                        logTimeChanges("ON");
694                        _parentLight.setState(Light.ON);
695                    }
696                }
697            } else {
698                // on and off - different days
699                if ((_timeNow >= getFastClockOnCombined()) || (_timeNow < getFastClockOffCombined())) {
700                    // Light should be ON
701                    if (state == Light.OFF) {
702                        logTimeChanges("ON");
703                        _parentLight.setState(Light.ON);
704                    }
705                } else {
706                    // Light should be OFF
707                    if (state == Light.ON) {
708                        logTimeChanges("OFF");
709                        _parentLight.setState(Light.OFF);
710                    }
711                }
712            }
713        }
714    }
715
716    /**
717     * Outputs Time and Light Change info to log file.
718     * eg Output "DEBUG - 11:05 Setting Light My Light 2751 OFF"
719     */
720    private void logTimeChanges(String onOrOff){
721        log.debug("{}:{} Setting Light {} {}",
722            (_timeNow/60),String.format("%02d", (_timeNow % 60)),
723            _parentLight.getDisplayName(),onOrOff);
724    }
725
726    /** {@inheritDoc} */
727    @Override
728    public void deactivateLightControl() {
729        // skip if Light Control is not active
730        if (_active) {
731            _parentLight.removePropertyChangeListener(_parentLightListener);
732            if (_sensorListener != null) {
733                _namedControlSensor.getBean().removePropertyChangeListener(_sensorListener);
734                _sensorListener = null;
735            }
736            if ((_clock != null) && (_timebaseListener != null)) {
737                _clock.removeMinuteChangeListener(_timebaseListener);
738                _timebaseListener = null;
739            }
740            if (_turnoutListener != null) {
741                _controlTurnout.removePropertyChangeListener(_turnoutListener);
742                _turnoutListener = null;
743            }
744            if (_timedSensorListener != null) {
745                _namedTimedControlSensor.getBean().removePropertyChangeListener(_timedSensorListener);
746                _timedSensorListener = null;
747            }
748            if (_timedControlListener != null && _timedControlTimer != null) {
749                _timedControlTimer.removeActionListener(_timedControlListener);
750                _timedControlListener = null;
751            }
752            if (_timedControlTimer != null) {
753                _timedControlTimer.stop();
754                _timedControlTimer = null;
755            }
756            if (_sensor2Listener != null) {
757                _namedControlSensor2.getBean().removePropertyChangeListener(_sensor2Listener);
758                _sensor2Listener = null;
759            }
760            _active = false;
761        }
762    }
763
764    /**
765     * Class for defining ActionListener for TIMED_ON_CONTROL
766     */
767    private class TimeLight implements java.awt.event.ActionListener {
768
769        @Override
770        public void actionPerformed(java.awt.event.ActionEvent event) {
771            // Turn Light OFF
772            _parentLight.setState(Light.OFF);
773            // Turn Timer OFF
774            if (_timedControlTimer != null ) {
775                _timedControlTimer.stop();
776            }
777            _timedControlTimer = null;
778        }
779    }
780
781    private final static Logger log = LoggerFactory.getLogger(DefaultLightControl.class);
782}