001package jmri.jmrix.bachrus; 002 003import java.io.BufferedWriter; 004import java.io.File; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.OutputStreamWriter; 008import java.nio.charset.StandardCharsets; 009import java.text.SimpleDateFormat; 010import java.util.ArrayList; 011import java.util.Date; 012import java.util.List; 013import java.util.Locale; 014import javax.annotation.Nonnull; 015import javax.swing.JFileChooser; 016import jmri.util.FileUtil; 017import org.apache.commons.csv.CSVFormat; 018import org.apache.commons.csv.CSVParser; 019import org.apache.commons.csv.CSVPrinter; 020import org.apache.commons.csv.CSVRecord; 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Class to represent a dimensionless speed profile of a DCC decoder. 026 * 027 * @author Andrew Crosland Copyright (C) 2010 028 * @author Dennis Miller Copyright (C) 2015 029 */ 030public class DccSpeedProfile { 031 032 protected int _length; 033 protected float[] _dataPoints; 034 protected float _max; 035 // index of last valid data point, -1 means no data 036 protected int _lastPoint; 037 protected List<CSVRecord> dccProfileData; 038 039 public DccSpeedProfile(int len) { 040 _length = len; 041 _dataPoints = new float[_length]; 042 043 for (int i = 0; i < _length; i++) { 044 _dataPoints[i] = 0.0F; 045 } 046 _max = 40; 047 _lastPoint = -1; 048 } 049 050 public boolean setPoint(int idx, float val) { 051 boolean ret = false; 052 if (idx < _length) { 053 _dataPoints[idx] = val; 054 _lastPoint++; 055 log.debug("Index: {} val: {}", idx, val); 056 if (val > _max) { 057 log.debug(" Old max: {}", _max); 058 // Adjust maximum value 059 _max = (float) (Math.floor(val / 20) + 1) * 20; 060 log.debug(" New max: {}", _max); 061 } 062 ret = true; 063 } 064 return ret; 065 } 066 067 public void clear() { 068 for (int i = 0; i < _length; i++) { 069 _dataPoints[i] = 0.0F; 070 } 071 _max = 40; 072 _lastPoint = -1; 073 } 074 075 public float getPoint(int idx) { 076 if ((idx < _length) && (idx <= _lastPoint)) { 077 return _dataPoints[idx]; 078 } else { 079 return -1; 080 } 081 } 082 083 public int getLength() { 084 return _length; 085 } 086 087 public void setMax(float m) { 088 _max = m; 089 } 090 091 public float getMax() { 092 return _max; 093 } 094 095 public int getLast() { 096 return _lastPoint; 097 } 098 099 public static void printHeading(@Nonnull CSVPrinter p, int address) throws IOException { 100 SimpleDateFormat formatter = new SimpleDateFormat("EEE d MMM yyyy", Locale.getDefault()); 101 String today = formatter.format(new Date()); 102 // title 103 String annotate = Bundle.getMessage("ProfileFor") + " " 104 + address + " " + Bundle.getMessage("CreatedOn") 105 + " " + today; 106 // should this be printComment instead? 107 p.printRecord(annotate); 108 } 109 110 // Save data as CSV 111 public static void export(DccSpeedProfile sp, int address, String dirString, Speed.Unit unit) { 112 File file = openExportFile(); 113 try (CSVPrinter p = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) { 114 String unitsString; 115 if (unit == Speed.Unit.MPH) { 116 unitsString = "MPH"; 117 } else { 118 unitsString = "KPH"; 119 } 120 // Save rows 121 printHeading(p, address); 122 p.printRecord("Step", "Speed(" + dirString + " " + unitsString + ")"); 123 // for each data point 124 for (int i = 0; i < sp.getLength(); i++) { 125 p.printRecord(i, unit == Speed.Unit.MPH ? Speed.kphToMph(sp.getPoint(i)) : sp.getPoint(i)); 126 } 127 p.flush(); 128 p.close(); 129 } catch (IOException ex) { 130 log.error("Error exporting speed profile", ex); 131 } 132 } 133 134 public static void export(DccSpeedProfile[] sp, int address, Speed.Unit unit) { 135 File file = openExportFile(); 136 try (CSVPrinter p = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) { 137 138 String unitsString; 139 if (unit == Speed.Unit.MPH) { 140 unitsString = "MPH"; 141 } else { 142 unitsString = "KPH"; 143 } 144 // Save rows 145 printHeading(p, address); 146 p.printRecord("Step", "Speed(fwd " + unitsString + ")", "Speed(rev " + unitsString + ")"); 147 // for each data point 148 for (int i = 0; i < sp[0].getLength(); i++) { 149 ArrayList<Object> list = new ArrayList<>(); 150 list.add(i); 151 // for each profile 152 for (DccSpeedProfile item : sp) { 153 list.add(unit == Speed.Unit.MPH ? Speed.kphToMph(item.getPoint(i)) : item.getPoint(i)); 154 } 155 p.printRecord(list); 156 } 157 p.flush(); 158 p.close(); 159 } catch (IOException ex) { 160 log.error("Error exporting speed profile", ex); 161 } 162 } 163 164 private static File openExportFile() { 165 JFileChooser fileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 166 if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { 167 return fileChooser.getSelectedFile(); 168 } 169 return null; 170 } 171 172 public int importDccProfile(Speed.Unit unit) { 173 openImportFile(); 174 if (dccProfileData.size() < 31) { 175 log.error("Not enough lines in reference speed profile file"); 176 clear(); 177 return -1; 178 } 179 180 String secondLine = dccProfileData.get(1).toString(); 181 if (!(secondLine.contains("MPH") || secondLine.contains("KPH"))) { 182 log.error("Bad 'units' format on line 2 of reference speed profile file"); 183 clear(); 184 return -1; 185 } 186 for (int i = 2; i < dccProfileData.size(); i++) { 187 try { 188 String value = dccProfileData.get(i).get(1); 189 float speed = Float.parseFloat(value); 190 // speed values from the speedometer are calc'd and stored in 191 // the DccSpeedProfile object as KPH so need to convert 192 // if the file was in MPH 193 if (secondLine.contains("MPH")) { 194 speed = Speed.mphToKph(speed); 195 } 196 197 setPoint(i - 2, speed); 198 } catch (NullPointerException | NumberFormatException | ArrayIndexOutOfBoundsException ex) { 199 log.error("Bad data or format in reference speed profile file", ex); 200 clear(); 201 return -1; 202 } 203 } 204 return 0; 205 } 206 207 private void openImportFile() { 208 JFileChooser fileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 209 210 if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { 211 File file = fileChooser.getSelectedFile(); 212 try { 213 dccProfileData = CSVParser.parse(file, StandardCharsets.UTF_8, CSVFormat.DEFAULT).getRecords(); 214 } catch (IOException ex) { 215 log.error("Failed to read reference profile file", ex); 216 } 217 } 218 } 219 220 private final static Logger log = LoggerFactory.getLogger(DccSpeedProfile.class); 221 222}