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}