/* * cubeWorker.java * * Copyright 2011 Thomas Buck * Copyright 2011 Max Nuding * Copyright 2011 Felix Bäder * * This file is part of LED-Cube. * * LED-Cube is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LED-Cube is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LED-Cube. If not, see . */ /* * This class handles one animation file. This file can contain * many animations, but has to be only 1Mbit in size (128*1024 Byte). */ import java.util.ArrayList; import java.util.Arrays; import java.util.Scanner; import java.util.Collections; import java.io.FileWriter; import java.io.File; import java.io.IOException; import javax.xml.bind.annotation.adapters.HexBinaryAdapter; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.BufferedReader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.lang.Process; import java.util.StringTokenizer; public class cubeWorker { // -------------------- // Definitions // -------------------- final int UP = 0; final int DOWN = 1; // -------------------- // Fields // -------------------- private ArrayList animations = new ArrayList(); private int framesRemaining = 2016; // (128 * 1024) / 65 = 2016,... private boolean changedState = false; // -------------------- cubeWorker() { animations.add(new Animation()); animations.get(0).setName("Animation 1"); animations.get(0).add(0); animations.get(0).get(0).setName("Frame 1"); framesRemaining--; } cubeWorker(ArrayList anims) { animations = anims; } // -------------------- // Misc. Methods // -------------------- // Returns how many animations are defined public int numOfAnimations() { return animations.size(); } // Returns how many frames are in the current animation public int numOfFrames(int selectedAnimation) { return animations.get(selectedAnimation).size(); } // Tells how many Frames you can add until you reached 1 Mbit... public int framesRemaining() { return framesRemaining; } // -------------------- // Animation Specific // -------------------- // Adds a new Animation // Returns id if ok, -1 if error or not enough space for // another animation public int addAnimation() { changedState = true; if (framesRemaining <= 0) { return -1; } else { int s = animations.size(); animations.add(s, new Animation()); animations.get(s).setName("Animation " + animations.size()); return s; } } // Removes an animation public void removeAnimation(int selectedAnimation) { changedState = true; animations.remove(selectedAnimation); selectedAnimation = 0; } public String getAnimationName(int selectedAnimation) { return animations.get(selectedAnimation).getName(); } public void setAnimationName(String s, int selectedAnimation) { changedState = true; animations.get(selectedAnimation).setName(s); } public void moveAnimation(int dir, int selectedAnimation) { changedState = true; if (dir == UP){ //animation moved up if (selectedAnimation > 0) { Collections.swap(animations, selectedAnimation, selectedAnimation - 1); } } else if (dir == DOWN){ //animation moved down if (selectedAnimation < (animations.size() - 1)) { Collections.swap(animations, selectedAnimation, selectedAnimation + 1); } } } // -------------------- // Frame Specific // -------------------- public String getFrameName(int anim, int frame) { return animations.get(anim).get(frame).getName(); } public void setFrameName(String s, int anim, int frame) { changedState = true; animations.get(anim).get(frame).setName(s); } // Adds a Frame to the current animation. // Returns id if okay, -1 if error public int addFrame(int anim) { changedState = true; if (framesRemaining <= 0) { return -1; } framesRemaining--; int s = animations.get(anim).size(); animations.get(anim).add(s); animations.get(anim).get(s).setName("Frame " + animations.get(anim).size()); return s; } // Remove the frame public void removeFrame(int anim, int frame) { changedState = true; animations.get(anim).remove(frame); } // Returns array with 64 bytes with led values public short[] getFrame(int anim, int frame) { return animations.get(anim).get(frame).getData(); } public void setFrame(short[] data, int anim, int frame) { changedState = true; animations.get(anim).get(frame).setData(data); } // Frame duration in 1/24th of a second public short getFrameTime(int anim, int frame) { return animations.get(anim).get(frame).getTime(); } public void setFrameTime(short time, int anim, int frame) { changedState = true; animations.get(anim).get(frame).setTime(time); } public void moveFrame(int dir, int anim, int frame){ changedState = true; if (dir == UP){ // frame moved up if (frame > 0) { Collections.swap(animations.get(anim).frames, frame, frame - 1); } } else if (dir == DOWN){ // frame moved down if (frame < (animations.get(anim).size() - 1)) { Collections.swap(animations.get(anim).frames, frame, frame + 1); } } } // -------------------- // File Specific // -------------------- // Loads an animation file into this object public int loadState(String path) { changedState = false; try { animations = AnimationUtility.readFile(path); } catch (Exception e) { System.out.println("Did not load!"); e.printStackTrace(System.out); return -1; } int size = 0; for (int i = 0; i < animations.size(); i++) { size += animations.get(i).size(); } framesRemaining = 2016 - size; if (size > 2016) { return -1; } return 0; } // Saves the state of this object in an animation file public int saveState(String path) { changedState = false; AnimationUtility.writeFile(path, animations); if (AnimationUtility.getLastError() != null) { System.out.println(AnimationUtility.getLastError()); return -1; } return 0; } // Returns true if last saved state != current state public boolean changedStateSinceSave() { return changedState; } // -------------------- // Serial Port Specific // -------------------- // sends state of object to led cube public int uploadState(String port) { return 0; } // loads all state from the cube into this object public int downloadState(String port) { return 0; } public String[] getSerialPorts() { String[] ports = {"Select serial port..."}; String helperName; if ((System.getProperty("os.name").toLowerCase()).indexOf("win") >= 0) { helperName = "serialHelper.exe"; } else { helperName = "serialHelper"; } String[] arg = {"p"}; String portLines = HelperUtility.runHelper(arg); System.out.println("Output: " + portLines); if (portLines == null) { return ports; } StringTokenizer sT = new StringTokenizer(portLines, "\n"); int size = sT.countTokens() + 1; ports = new String[size]; ports[0] = "Select serial port..."; for (int i = 1; i < size; i++) { ports[i] = sT.nextToken(); } return ports; } // -------------------- } class HelperUtility { public static String runHelper(String[] args) { String[] helperName = new String[args.length + 1]; boolean windows = false; if ((System.getProperty("os.name").toLowerCase()).indexOf("win") >= 0) { helperName[0] = "serialHelper.exe"; windows = true; } else { helperName[0] = "serialHelper"; } for (int i = 0; i < args.length; i++) { helperName[i + 1] = args[i]; } String ret = ""; try { File helper = new File(getFile(getJarURI(), helperName[0])); helperName[0] = helper.getAbsolutePath(); if (!windows) { Process execute = Runtime.getRuntime().exec("chmod a+x " + helper.getAbsolutePath()); execute.waitFor(); if (execute.exitValue() != 0) { System.out.println("Could not set helper as executeable (" + execute.exitValue()+ ")"); return null; } } Process p = Runtime.getRuntime().exec(helperName); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; boolean fin = false; do { // Wait for process to finish... Doesn't work...? fin = false; try { p.waitFor(); } catch (Exception e) { fin = true; } // Read output in same loop... Should work...! line = br.readLine(); if (line != null) { ret = ret + line + "\n"; fin = true; } } while (fin); br.close(); if (ret.length() == 0) { ret = "g"; // We have added a last \n... We will remove it, so add garbage to be removed... } ret = ret.substring(0, ret.length() - 1); return ret; } catch(Exception e) { e.printStackTrace(); } return null; } // From http://stackoverflow.com/questions/600146/run-exe-which-is-packaged-inside-jar private static URI getJarURI() throws URISyntaxException { final ProtectionDomain domain; final CodeSource source; final URL url; final URI uri; domain = cubeWorker.class.getProtectionDomain(); source = domain.getCodeSource(); url = source.getLocation(); uri = url.toURI(); return (uri); } private static URI getFile(final URI where, final String fileName) throws ZipException, IOException { final File location; final URI fileURI; location = new File(where); // not in a JAR, just return the path on disk if(location.isDirectory()) { fileURI = URI.create(where.toString() + fileName); } else { final ZipFile zipFile; zipFile = new ZipFile(location); try { fileURI = extract(zipFile, fileName); } finally { zipFile.close(); } } return (fileURI); } private static URI extract(final ZipFile zipFile, final String fileName) throws IOException { final File tempFile; final ZipEntry entry; final InputStream zipStream; OutputStream fileStream; tempFile = File.createTempFile(fileName, Long.toString(System.currentTimeMillis())); tempFile.deleteOnExit(); entry = zipFile.getEntry(fileName); if(entry == null) { throw new FileNotFoundException("cannot find file: " + fileName + " in archive: " + zipFile.getName()); } zipStream = zipFile.getInputStream(entry); fileStream = null; try { final byte[] buf; int i; fileStream = new FileOutputStream(tempFile); buf = new byte[1024]; i = 0; while((i = zipStream.read(buf)) != -1) { fileStream.write(buf, 0, i); } } finally { close(zipStream); close(fileStream); } return (tempFile.toURI()); } private static void close(final Closeable stream) { if(stream != null) { try { stream.close(); } catch(final IOException ex) { ex.printStackTrace(); } } } } class AnimationUtility { private static String lastError = null; public static ArrayList readFile(String path) throws Exception { Scanner sc = new Scanner(new File(path)); ArrayList animations = new ArrayList(); do { Animation tmp = readAnimation(sc); if (tmp == null) { return animations; } if (sc.hasNextLine()) { sc.nextLine(); } animations.add(tmp); } while (sc.hasNextLine()); return animations; } public static void writeFile(String path, ArrayList animations) { File f = new File(path); if (f.exists()) { try { f.delete(); } catch (Exception e) { lastError = e.toString(); return; } } FileWriter output = null; try { output = new FileWriter(f); for (int i = 0; i < animations.size(); i++) { writeAnimation(animations.get(i), output, (i == (animations.size() - 1))); } } catch (Exception e) { lastError = e.toString(); return; } finally { if (output != null) { try { output.close(); } catch (Exception e) { lastError = e.toString(); } } } } public static String getLastError() { String tmp = lastError; lastError = null; return tmp; } private static Animation readAnimation(Scanner sc) { Animation anim = new Animation(); AFrame f = null; int index = 0; String tmpSize = sc.nextLine().replaceAll("\\n", ""); if (tmpSize.equals("")) { return null; } Integer tmpSizeAgain = new Integer(tmpSize); int size = tmpSizeAgain.intValue(); anim.setName(sc.nextLine()); while (size > 0) { f = readFrame(sc, index); anim.add(index, f); index++; size--; } return anim; } private static AFrame readFrame(Scanner sc, int index) { AFrame frame = new AFrame(); frame.setName(sc.nextLine()); short[] d = {}; for (int i = 0; i < 8; i++) { short[] data = hexConvert(sc.nextLine()); d = concat(data, d); } frame.setData(d); d = hexConvert(sc.nextLine()); frame.setTime(d[0]); return frame; } private static short[] concat(short[] a, short[] b) { short[] c = new short[a.length + b.length]; System.arraycopy(a, 0, c, 0, a.length); System.arraycopy(b, 0, c, a.length, b.length); return c; } private static short[] hexConvert(String hex) { hex = hex.replaceAll("\\n", ""); short[] tmp = new short[hex.length()/2]; for (int i = 0; i < hex.length(); i=i+2){ char[] tmpString = new char[2]; tmpString[0] = hex.charAt(i); tmpString[1] = hex.charAt(i+1); String tmpS = String.copyValueOf(tmpString); if(i == 0){ tmp[0] = Short.parseShort(tmpS, 16); } else { tmp[i/2] = Short.parseShort(tmpS, 16); } } return tmp; } private static void writeAnimation(Animation anim, FileWriter f, boolean last) throws IOException { f.write(anim.size() + "\n"); f.write(anim.getName() + "\n"); for (int i = 0; i < anim.size(); i++) { writeFrame(anim.get(i), f); } if (!last) { f.write("\n"); } } private static void writeFrame(AFrame fr, FileWriter f) throws IOException { f.write(fr.getName() + "\n"); for (int i = 0; i < 8; i++) { writeLayer(fr.getLayer(i), f); } f.write(Integer.toString( (fr.getTime() & 0xff) + 0x100, 16).substring(1) + "\n"); } private static void writeLayer(short[] l, FileWriter f) throws IOException { String hex = ""; for (int i = 0; i < l.length; i++) { hex += Integer.toString( (l[i] & 0xff) + 0x100, 16).substring(1); } hex += "\n"; f.write(hex); } } class AFrame { private short[] data = new short[64]; private short duration = 1; private String name = "Frame"; String getName() { return name; } void setName(String s) { name = s; } void setData(short[] d) { data = d; } short[] getData() { return data; } void setTime(short t) { duration = t; } short getTime() { return duration; } short[] getLayer(int i) { return Arrays.copyOfRange(data, (i * 8), (i * 8) + 8); } } class Animation { ArrayList frames = new ArrayList(); private int lastFrameIndex = 0; private String name = "Animation"; String getName() { return name; } void setName(String s) { name = s; } AFrame get(int i) { try { return frames.get(i); } catch (IndexOutOfBoundsException e) { System.out.println(e.toString()); return null; } } void set(AFrame f, int i) { if (lastFrameIndex <= i) { try { frames.set(i, f); } catch (IndexOutOfBoundsException e) { System.out.println(e.toString()); } } } void remove(int i) { try { frames.remove(i); } catch (IndexOutOfBoundsException e) { System.out.println(e.toString()); } } void add(int i) { try { frames.add(i, new AFrame()); lastFrameIndex++; } catch (IndexOutOfBoundsException e) { System.out.println(e.toString()); } } void add(int i, AFrame f) { try { frames.add(i, f); lastFrameIndex++; } catch (IndexOutOfBoundsException e) { System.out.println(e.toString()); } } int size() { return frames.size(); } }