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