import java.awt.Color; import java.awt.Container; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.text.NumberFormat; import java.util.Properties; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.TargetDataLine; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class Mpg extends Thread { static String propFile = "./mpg.properties"; // place to persist confguration and trips. //Will be created on first run if it doesnt exist. static int injThreshold = Integer .parseInt(getProperty("injThreshold", "-30")); // value above the noise static int vssThreshold = Integer .parseInt(getProperty("vssThreshold", "100")); // value above the noise static double distanceFudge = Double.parseDouble(getProperty("distanceFudge", "3200.0")); static double fuelFudge = Double.parseDouble(getProperty("fuelFudge", "8000000.00")); static String dummyFile = getProperty("dummyFile", ""); // low level stats tracked in the "trip" class. class Trip { String name; long sampleCount; // num samples, used to compute elapsed time, good // for about 58 billion hours @ 44100hz long injHi; // stores number of samples that were "HI" (injector was // open) long vssTot; // how many pulses from the vss, indication of distance // travelled public Trip(String _name) { name = _name; } // real lightweight update process, gets called at end of audio chunk public void Update(long _sampleCount, long _injHi, long _vssTot) { sampleCount += _sampleCount; injHi += _injHi; vssTot += _vssTot; } public String toString() { return "name=" + name + ";sampleCount=" + sampleCount + ";injHi=" + injHi + ";vssTot=" + vssTot; } public void reset() { sampleCount = 0; injHi = 0; vssTot = 0; } public Trip load() { sampleCount = Long .parseLong(getProperty(name + ".sampleCount", "0")); injHi = Long.parseLong(getProperty(name + ".injHi", "0")); vssTot = Long.parseLong(getProperty(name + ".vssTot", "0")); return this; } public void save() { properties.put(name + ".sampleCount", "" + sampleCount); properties.put(name + ".injHi", "" + injHi); properties.put(name + ".vssTot", "" + vssTot); } public double miles() { return (double) vssTot / distanceFudge; } public double hours() { return ((double) sampleCount) / (44100.0D * 3600.0D); } public double gallons() { return (double) injHi / fuelFudge; } public double mpg() { return gallons() > 0.0D ? (miles() / gallons()) : Double.POSITIVE_INFINITY; } } class TripPanel extends JPanel { Trip trip = null; JLabel name = new JLabel("name"); JLabel miles = new JLabel("miles"); JLabel gallons = new JLabel("gallons"); JLabel mpg = new JLabel("mpg"); JLabel hours = new JLabel("gallons"); JLabel mph = new JLabel("mph"); JButton reset = new JButton("reset"); /** used for limiting numbers to 4 decimal places*/ NumberFormat fm = NumberFormat.getNumberInstance(); public TripPanel(Trip _trip) { trip = _trip; setLayout(new GridLayout(1, 8)); setBorder(BorderFactory.createLineBorder(Color.BLACK)); add(name); add(miles); add(gallons); add(mpg); add(hours); add(mph); add(reset); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { trip.reset(); } }); fm.setMaximumFractionDigits(4); } public void upDateLabels() { name.setText(" " + trip.name); miles.setText(fm.format(trip.miles())); gallons.setText(fm.format(trip.gallons())); mpg.setText(fm.format(trip.mpg())); hours.setText(fm.format(trip.hours())); mph.setText(fm.format(trip.miles() / trip.hours())); } } private TargetDataLine m_line; protected boolean m_bRecording = true; Trip instant = new Trip("instant"); TripPanel instantPanel = new TripPanel(instant); Trip current = new Trip("current"); TripPanel currentPanel = new TripPanel(current); Trip tank = new Trip("tank").load(); TripPanel tankPanel = new TripPanel(tank); public Mpg(TargetDataLine line, AudioFileFormat.Type targetType) { m_line = line; new Thread(new Runnable() {// thread to update the view every second public void run() { while (m_bRecording) { instantPanel.upDateLabels(); instant.reset();// reset the instant trip after // displaying it currentPanel.upDateLabels(); tankPanel.upDateLabels(); try { Thread.sleep(1000); } catch (Exception e) { } } } }).start(); } public void start() { m_line.start(); super.start(); } public void stopRecording() { m_line.stop(); m_line.close(); m_bRecording = false; System.out.println(" inj sampleCount = " + current.sampleCount + " inj hi = " + current.injHi + " inj vssTot = " + current.vssTot); } boolean ig = true; boolean vg = true; void processChunk(byte[] b, int c) { long ih = 0; long vt = 0; for (int x = 0; x < c; x += 2) { int val = ((int) b[x] & 255) - 127; if (val > vssThreshold && vg) { vt++; System.out.println(" vss going hi " + (current.sampleCount + (x / 2))); vg = false; } if (val < 0) { vg = true; } val = ((int) b[x + 1] & 255) - 127; if (val < injThreshold) { ig = true; } if (val > 0) ig = false; if (ig) ih++; } instant.Update(c / 2, ih, vt); current.Update(c / 2, ih, vt); tank.Update(c / 2, ih, vt); } public void realrun() { byte[] buffer = new byte[m_line.getBufferSize()]; while (m_bRecording) { int c = m_line.read(buffer, 0, m_line.available()); if (c != 0) { processChunk(buffer, c); } } } public void run() { if ("".equals(dummyFile)) realrun(); else dummyrun(); } public void dummyrun() { try { String strFilename = dummyFile; File soundFile = new File(strFilename); AudioInputStream audioInputStream = null; audioInputStream = AudioSystem.getAudioInputStream(soundFile); int nBytesRead = 0; byte[] abData = new byte[44100]; while (nBytesRead != -1) { nBytesRead = audioInputStream.read(abData, 0, abData.length); if (nBytesRead >= 0) { processChunk(abData, nBytesRead); } try { Thread.sleep(1000); } catch (Exception e) { } } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { AudioFormat audioFormat = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, 44100.0F, 8, 2, 2, 44100.0F, false); DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat); TargetDataLine targetDataLine = null; targetDataLine = (TargetDataLine) AudioSystem.getLine(info); targetDataLine.open(audioFormat); AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE; final Mpg recorder = new Mpg(targetDataLine, targetType); JFrame j = new JFrame("MPG Monitor!!!"); j.setSize(640, 125); Container c = j.getContentPane(); c.setLayout(new GridLayout(4, 1)); JPanel hd = new JPanel(); hd.setLayout(new GridLayout(1, 8)); hd.add(new JLabel("")); hd.add(new JLabel("MILES")); hd.add(new JLabel("GAL")); hd.add(new JLabel("MPG")); hd.add(new JLabel("HRS")); hd.add(new JLabel("MPH")); hd.add(new JLabel("")); c.add(hd); c.add(recorder.instantPanel); c.add(recorder.currentPanel); c.add(recorder.tankPanel); j.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { recorder.stopRecording(); recorder.tank.save();//this just adds the trip fields to the properties object try { properties.store(new FileOutputStream(new File(propFile)),""); } catch (Exception f) { } System.exit(0); } }); j.setVisible(true); recorder.start(); } static Properties properties; static String getProperty(String tag, String dflt) { String s = ""; try { if (properties == null) { properties = new Properties(); try { properties.load(new FileInputStream(new File(propFile))); } catch (Exception e) { } ; } s = properties.getProperty(tag); if (s == null) { s = dflt; properties.put(tag, s);// will propogate default values to the // file } } catch (Exception e) { } return s; } }