001package jmri.jmris.srcp;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.io.IOException;
006import java.io.OutputStream;
007
008import jmri.JmriException;
009import jmri.TimebaseRateException;
010import jmri.jmris.AbstractTimeServer;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * interface between the JMRI (fast) clock and an SRCP network connection
016 *
017 * @author Paul Bender Copyright (C) 2013
018 */
019public class JmriSRCPTimeServer extends AbstractTimeServer {
020
021    private final OutputStream output;
022
023    private int modelrate = 1;
024    private int realrate = 1;
025
026    public JmriSRCPTimeServer(OutputStream outStream) {
027        super();
028        output = outStream;
029    }
030
031    /*
032     * Protocol Specific Abstract Functions
033     */
034    @Override
035    public void sendTime() throws IOException {
036        // prepare to format the date as <JulDay> <Hour> <Minute> <Seconds>
037        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH mm ss");
038        java.util.GregorianCalendar cal = new java.util.GregorianCalendar();
039        cal.setTime(timebase.getTime());
040        long day = jmri.util.DateUtil.julianDayFromCalendar(cal);
041        output.write(("100 INFO 0 TIME " + day + " " + sdf.format(timebase.getTime()) + "\n\r").getBytes());
042    }
043
044    @Override
045    public void sendRate() throws IOException {
046        output.write(("101 INFO 0 TIME " + modelrate + " " + realrate + "\n\r").getBytes());
047    }
048
049    @Override
050    public void sendStatus() throws IOException {
051        // send "102 INFO 0 TIME" if fastclock stops?
052        // sendRate() if fastclock starts?
053    }
054
055    @Override
056    public void sendErrorStatus() throws IOException {
057    }
058
059    @Override
060    public void parseTime(String statusString) throws JmriException, IOException {
061        // parsing is handled by the SRCP parser; this routine should never
062        // be called for SRCP
063    }
064
065    public void parseTime(long JulDay, int Hour, int Minute, int Second) {
066        java.util.GregorianCalendar cal = jmri.util.DateUtil.calFromJulianDate(JulDay);
067        cal.set(java.util.Calendar.HOUR, Hour);
068        cal.set(java.util.Calendar.MINUTE, Minute);
069        cal.set(java.util.Calendar.SECOND, Second);
070        timebase.userSetTime(cal.getTime());
071    }
072
073    @Override
074    public void parseRate(String statusString) throws JmriException, IOException {
075        // parsing is handled by the SRCP parser; this routine should never
076        // be called for SRCP
077    }
078
079    public void parseRate(int modelRate, int realRate) {
080        modelrate = modelRate;
081        realrate = realRate;
082        try {
083            timebase.userSetRate((double) modelRate / (double) realRate);
084        } catch (TimebaseRateException ex) {
085            // Although this Exception is declared to be thrown for userSetRate,
086            // it is never created in code, so just log it should we begin to see it
087            log.error("Something went wrong", ex);
088        }
089    }
090
091    @Override
092    public void stopTime() {
093        super.stopTime();
094        // when the clock stops, we need to notify any
095        // waiting timers the clock has stoped.
096    }
097
098    public void setAlarm(long JulDay, int Hour, int Minute, int Second) {
099        if (log.isDebugEnabled()) {
100            log.debug("setting alarm for {} {}:{}:{}", JulDay, Hour, Minute, Second);
101        }
102
103        java.util.GregorianCalendar cal = jmri.util.DateUtil.calFromJulianDate(JulDay);
104        cal.set(java.util.Calendar.HOUR, Hour);
105        cal.set(java.util.Calendar.MINUTE, Minute);
106        cal.set(java.util.Calendar.SECOND, Second);
107
108        java.util.GregorianCalendar now = new java.util.GregorianCalendar();
109        now.setTime(timebase.getTime());
110        if (now.after(cal)) {
111            try {
112                sendTime();
113            } catch (IOException ex) {
114                log.warn("Unable to send message to client: {}", ex.getMessage());
115            }
116        } else {
117            // add this alarm to the list of alarms.
118            if (alarmList == null) {
119                alarmList = new java.util.ArrayList<>();
120            }
121            alarmList.add(cal);
122            // and start the timeListener.
123            listenToTimebase(true);
124            try {
125                output.write("200 Ok\n\r".getBytes());
126            } catch (IOException ie) {
127                log.warn("Unable to send message to client: {}", ie.getMessage());
128            }
129        }
130    }
131
132    private java.util.ArrayList<java.util.GregorianCalendar> alarmList = null;
133
134    private void checkAlarmList() throws IOException {
135        if (alarmList == null) {
136            return;
137        }
138
139        java.util.GregorianCalendar cal = new java.util.GregorianCalendar();
140        cal.setTime(timebase.getTime());
141        if (log.isDebugEnabled()) {
142            log.debug("checking alarms at {} {}:{}:{}", jmri.util.DateUtil.julianDayFromCalendar(cal), cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), cal.get(java.util.Calendar.SECOND));
143        }
144        java.util.Iterator<java.util.GregorianCalendar> alarm = alarmList.iterator();
145        while (alarm.hasNext()) {
146            if (cal.after(alarm.next())) {
147                sendTime();
148                alarm.remove();
149            }
150        }
151    }
152
153    @Override
154    public void listenToTimebase(boolean listen) {
155        if (!listen && timeListener == null) {
156            return; // nothing to do.
157        }
158        if (timeListener == null) {
159            timeListener = evt -> {
160                try {
161                    if (evt.getPropertyName().equals("minutes")) {
162                        checkAlarmList();
163                    }
164                } catch (IOException ex) {
165                    log.warn("Unable to send message to client: {}", ex.getMessage());
166                    timebase.removeMinuteChangeListener(timeListener);
167                }
168            };
169        }
170        if (listen) {
171            timebase.addMinuteChangeListener(timeListener);
172        } else {
173            timebase.removeMinuteChangeListener(timeListener);
174        }
175    }
176
177    private static final Logger log = LoggerFactory.getLogger(JmriSRCPTimeServer.class);
178
179}