001package jmri.jmrix.openlcb;
002
003import org.openlcb.NodeID;
004import org.openlcb.OlcbInterface;
005import org.openlcb.protocols.TimeBroadcastConsumer;
006import org.openlcb.protocols.TimeBroadcastGenerator;
007import org.openlcb.protocols.TimeProtocol;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import java.beans.PropertyChangeListener;
012import java.util.Date;
013
014import jmri.Timebase;
015import jmri.TimebaseRateException;
016import jmri.implementation.DefaultClockControl;
017import jmri.util.ThreadingUtil;
018
019/**
020 * Implementation of the ClockControl interface for JMRI using the OpenLCB clock listener or generator.
021 *
022 * @author Balazs Racz, 2018
023 */
024
025public class OlcbClockControl extends DefaultClockControl {
026    public OlcbClockControl(OlcbInterface iface, NodeID clockID, boolean isMaster) {
027        this.clockId = clockID;
028        if (isMaster) {
029            generator = new TimeBroadcastGenerator(iface, clockID);
030            hardwareClock = generator;
031        } else {
032            consumer = new TimeBroadcastConsumer(iface, clockID);
033            hardwareClock = consumer;
034        }
035        jmriClock = jmri.InstanceManager.getDefault(jmri.Timebase.class);
036        listener = propertyChangeEvent -> clockUpdate(propertyChangeEvent.getPropertyName(), propertyChangeEvent.getNewValue());
037        hardwareClock.addPropertyChangeListener(listener);
038    }
039
040    public void dispose() {
041        hardwareClock.removePropertyChangeListener(listener);
042        if (consumer != null) {
043            consumer.dispose();
044            consumer = null;
045            hardwareClock = null;
046        }
047    }
048
049    /// Called when the layout sends an update to state, for example when someone else operates a
050    /// clock controlling node.
051    private void clockUpdate(String property, Object newValue) {
052        switch (property) {
053            case TimeProtocol.PROP_RUN_UPDATE:
054                jmriClock.setRun(hardwareClock.isRunning());
055                break;
056            case TimeProtocol.PROP_RATE_UPDATE:
057                try {
058                    jmriClock.userSetRate(hardwareClock.getRate());
059                } catch (TimebaseRateException e) {
060                    log.warn("Failed to set OpenLCB rate to internal clock.");
061                }
062                break;
063            case TimeProtocol.PROP_TIME_UPDATE:
064                jmriClock.setTime(new Date(hardwareClock.getTimeInMsec()));
065                break;
066            default:
067                // no default action.
068        }
069    }
070
071    @Override
072    public String getHardwareClockName() {
073        String clockName;
074        if (clockId.equals(TimeProtocol.DEFAULT_CLOCK)) {
075            clockName = Bundle.getMessage("OlcbClockDefault");
076        } else if (clockId.equals(TimeProtocol.DEFAULT_RT_CLOCK)) {
077            clockName = Bundle.getMessage("OlcbClockDefaultRT");
078        } else if (clockId.equals(TimeProtocol.ALT_CLOCK_1)) {
079            clockName = Bundle.getMessage("OlcbClockAlt1");
080        } else if (clockId.equals(TimeProtocol.ALT_CLOCK_2)) {
081            clockName = Bundle.getMessage("OlcbClockAlt2");
082        } else {
083            clockName = Bundle.getMessage("OlcbClockCustom", clockId.toString());
084        }
085        if (consumer != null) {
086            return Bundle.getMessage("OlcbClockListenerFor", clockName);
087        } else {
088            return Bundle.getMessage("OlcbClockGeneratorFor", clockName);
089        }
090    }
091
092    @Override
093    public boolean canCorrectHardwareClock() {
094        return false;
095    }
096
097    @Override
098    public boolean canSet12Or24HourClock() {
099        return false;
100    }
101
102    @Override
103    public boolean requiresIntegerRate() {
104        return false;
105    }
106
107    @Override
108    public double getRate() {
109        return hardwareClock.getRate();
110    }
111
112    @Override
113    public Date getTime() {
114        return new Date(hardwareClock.getTimeInMsec());
115    }
116
117    @Override
118    public void stopHardwareClock() {
119        hardwareClock.requestStop();
120    }
121
122    @Override
123    public void startHardwareClock(Date now) {
124        hardwareClock.requestSetTime(now.getTime());
125        hardwareClock.requestStart();
126    }
127
128    @Override
129    public void setRate(double newRate) {
130        // OpenLCB rates are 0.25 resolution, so we use half of that as minimum threshold.
131        if (Math.abs(hardwareClock.getRate() - newRate) > 0.12) {
132            hardwareClock.requestSetRate(newRate);
133        } else if (Math.abs(hardwareClock.getRate() - newRate) > 0.0001) {
134            // Trigger update notification that we rejected the change, but not inline.
135            ThreadingUtil.runOnLayoutDelayed(() -> clockUpdate(TimeProtocol.PROP_RATE_UPDATE, null), 50);
136        }
137
138    }
139
140    @Override
141    public void setTime(Date now) {
142        hardwareClock.requestSetTime(now.getTime());
143    }
144
145    @Override
146    public void initializeHardwareClock(double rate, Date now, boolean getTime) {
147        if (!getTime) {
148            hardwareClock.requestSetTime(now.getTime());
149            if (rate == 0) {
150                hardwareClock.requestStop();
151            } else {
152                hardwareClock.requestSetRate(rate);
153                hardwareClock.requestStart();
154            }
155        } else {
156            hardwareClock.requestQuery();
157        }
158    }
159
160    /// Stores instance to the JMRI clock master.
161    private final Timebase jmriClock;
162    /// This is the interface to the clock generator or consumer.
163    private TimeProtocol hardwareClock;
164    /// The clock identifier on the OpenLCB bus.
165    private final NodeID clockId;
166    /// If we instantiated a clock consumer, this is the object.
167    private TimeBroadcastConsumer consumer;
168    /// If we instantiated a generator, this is the object
169    private TimeBroadcastGenerator generator;
170    /// The listener registered for the hardwareClock.
171    private final PropertyChangeListener listener;
172
173    private final static Logger log = LoggerFactory.getLogger(OlcbClockControl.class);
174}