From 1dcb88002affbe8ded0d42d5677d8b14496b5d46 Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 24 Jul 2011 12:41:45 +0000 Subject: [PATCH] Afstandbediening voor iPod toegevoegd. Omdat deze met raw codes werkt, is eerst geprobeerd een command line programma (irtoy) te interfacen. Het blijkt makkelijker om de irtoy plugin voor winlirc aan te passen. De command line code wordt alsnog gecommit, om niet verloren te gaan. --- java/src/mimis/Main.java | 4 +- java/src/mimis/application/irtoy/Command.java | 19 +++ java/src/mimis/application/irtoy/Config.java | 84 +++++++++++ java/src/mimis/application/irtoy/Remote.java | 30 ++++ .../irtoy/ipod/iPodApplication.java | 139 ++++++++++++++++++ .../application/itunes/iTunesApplication.java | 4 +- java/src/mimis/device/lirc/LircDevice.java | 15 +- java/src/mimis/device/lirc/LircService.java | 41 +++--- .../device/lirc/remote/WC02IPOButton.java | 28 ++++ java/src/mimis/util/Reader.java | 19 +++ java/src/mimis/util/Sound.java | 27 ++-- java/wiiuse.dll | Bin 4110222 -> 4110222 bytes 12 files changed, 365 insertions(+), 45 deletions(-) create mode 100644 java/src/mimis/application/irtoy/Command.java create mode 100644 java/src/mimis/application/irtoy/Config.java create mode 100644 java/src/mimis/application/irtoy/Remote.java create mode 100644 java/src/mimis/application/irtoy/ipod/iPodApplication.java create mode 100644 java/src/mimis/device/lirc/remote/WC02IPOButton.java create mode 100644 java/src/mimis/util/Reader.java diff --git a/java/src/mimis/Main.java b/java/src/mimis/Main.java index 9ccd0d3..b3744a8 100644 --- a/java/src/mimis/Main.java +++ b/java/src/mimis/Main.java @@ -3,6 +3,7 @@ package mimis; import mimis.application.cmd.windows.gomplayer.GomPlayerApplication; import mimis.application.cmd.windows.winamp.WinampApplication; import mimis.application.cmd.windows.wmp.WMPApplication; +import mimis.application.irtoy.ipod.iPodApplication; import mimis.application.itunes.iTunesApplication; import mimis.application.mpc.MPCApplication; import mimis.application.vlc.VLCApplication; @@ -35,7 +36,8 @@ public class Main { new WMPApplication(), new MPCApplication(), new VLCApplication(), - new WinampApplication()}; + new WinampApplication(), + new iPodApplication()}; deviceArray = new Device[] { new LircDevice(), new WiimoteDevice(), diff --git a/java/src/mimis/application/irtoy/Command.java b/java/src/mimis/application/irtoy/Command.java new file mode 100644 index 0000000..f568d1c --- /dev/null +++ b/java/src/mimis/application/irtoy/Command.java @@ -0,0 +1,19 @@ +package mimis.application.irtoy; + +public class Command { + protected String name; + protected String data; + + public Command(String name, String data) { + this.name = name; + this.data = data; + } + + public String getName() { + return name; + } + + public String getData() { + return data; + } +} diff --git a/java/src/mimis/application/irtoy/Config.java b/java/src/mimis/application/irtoy/Config.java new file mode 100644 index 0000000..4d394c6 --- /dev/null +++ b/java/src/mimis/application/irtoy/Config.java @@ -0,0 +1,84 @@ +package mimis.application.irtoy; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import mimis.util.Reader; + +public class Config { + public static final String FILE = "C:\\Users\\Rik\\Dropbox\\Gedeeld\\Mimis\\IRToy\\WinLIRC\\remote.txt"; + + protected Log log = LogFactory.getLog(getClass()); + protected static Config config; + protected File file; + protected ArrayList remoteList; + + private Config() { + file = new File(FILE); + } + + public static Config getInstance() { + if (config == null) { + config = new Config(); + } + return config; + } + + public Remote getRemote(String name) { + if (remoteList == null) { + remoteList = new ArrayList(); + try { + parse(); + } catch (IOException e) { + log.error(e); + return null; + } + } + for (Remote remote : remoteList) { + if (remote.getName().equals(name)) { + return remote; + } + } + return null; + } + + protected void parse() throws IOException { + String contents = Reader.getContents(file); + Pattern pattern = Pattern.compile("begin remote(.+?)end remote", Pattern.DOTALL); + Matcher remoteMatcher = pattern.matcher(contents); + while (remoteMatcher.find()) { + pattern = Pattern.compile("begin raw_codes(.+)end raw_codes", Pattern.DOTALL); + Matcher rawMatcher = pattern.matcher(remoteMatcher.group(1)); + if (rawMatcher.find()) { + String raw = rawMatcher.group(1); + pattern = Pattern.compile("name[ ]+(.+)"); + Matcher nameMatcher = pattern.matcher(remoteMatcher.group(1)); + if (nameMatcher.find()) { + String name = nameMatcher.group(1); + pattern = Pattern.compile("name[ ]+([^\n]+)(.*?)(?=name)", Pattern.DOTALL); + Remote remote = new Remote(name); + Matcher commandMatcher = pattern.matcher(raw); + while (commandMatcher.find()) { + String data = commandMatcher.group(2); + pattern = Pattern.compile("([0-9]+)"); + Matcher dataMatcher = pattern.matcher(data); + StringBuffer stringBuffer = new StringBuffer(); + while (dataMatcher.find()) { + stringBuffer.append((char) Math.round(Double.valueOf(dataMatcher.group(1)) / 21.3333)); + } + stringBuffer.append(0xffff); + Command command = new Command(commandMatcher.group(1), stringBuffer.toString()); + remote.add(command); + } + remoteList.add(remote); + } + } + } + } +} diff --git a/java/src/mimis/application/irtoy/Remote.java b/java/src/mimis/application/irtoy/Remote.java new file mode 100644 index 0000000..1131940 --- /dev/null +++ b/java/src/mimis/application/irtoy/Remote.java @@ -0,0 +1,30 @@ +package mimis.application.irtoy; + +import java.util.ArrayList; + +public class Remote { + protected String name; + protected ArrayList commandList; + + public Remote(String name) { + this.name = name; + commandList = new ArrayList(); + } + + public void add(Command command) { + commandList.add(command); + } + + public String getName() { + return name; + } + + public Command getCommand(String name) { + for (Command command : commandList) { + if (command.getName().equals(name)) { + return command; + } + } + return null; + } +} diff --git a/java/src/mimis/application/irtoy/ipod/iPodApplication.java b/java/src/mimis/application/irtoy/ipod/iPodApplication.java new file mode 100644 index 0000000..248a39a --- /dev/null +++ b/java/src/mimis/application/irtoy/ipod/iPodApplication.java @@ -0,0 +1,139 @@ +package mimis.application.irtoy.ipod; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; + +import mimis.Application; +import mimis.Worker; +import mimis.application.irtoy.Command; +import mimis.application.irtoy.Config; +import mimis.application.irtoy.Remote; +import mimis.device.lirc.LircService; +import mimis.device.lirc.remote.WC02IPOButton; +import mimis.exception.worker.ActivateException; +import mimis.exception.worker.DeactivateException; +import mimis.value.Action; + +public class iPodApplication extends Application { + public static void main(String[] args) { + Config config = Config.getInstance(); + Remote remote = config.getRemote("WC02-IPO"); + if (remote != null) { + Command command = remote.getCommand("PLAY"); + if (command != null) { + try { + File file = File.createTempFile("irtoy", ".bin"); + file.deleteOnExit(); + BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); + bufferedWriter.write(command.getData()); + bufferedWriter.close(); + String cmd = String.format("irtoy -d COM3 -f %s -p -a 0", file.getAbsoluteFile()); + System.out.println(cmd); + Process process = Runtime.getRuntime().exec(cmd); + process.waitFor(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + process.getOutputStream().write(byteArrayOutputStream.toByteArray()); + System.out.println(byteArrayOutputStream.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + protected static final String TITLE = "iPod"; + protected static final boolean QUIT = true; + + protected static final int VOLUME_SLEEP = 100; + + protected LircService lircService; + protected VolumeWorker volumeWorker; + + public iPodApplication() { + super(TITLE); + lircService = new LircService(); + lircService.put(WC02IPOButton.NAME, WC02IPOButton.values()); + volumeWorker = new VolumeWorker(); + } + + public void activate() throws ActivateException { + lircService.activate(); + super.activate(); + } + + public boolean active() { + return active = lircService.active(); + } + + public void deactivate() throws DeactivateException { + super.deactivate(); + lircService.deactivate(); + } + + public void stop() { + super.stop(); + volumeWorker.stop(); + } + + protected void begin(Action action) { + log.trace("iPodApplication begin: " + action); + if (!active) return; + switch (action) { + case VOLUME_UP: + try { + volumeWorker.activate(1); + } catch (ActivateException e) { + log.error(e); + } + break; + case VOLUME_DOWN: + try { + volumeWorker.activate(-1); + } catch (ActivateException e) { + log.error(e); + } + break; + } + } + + protected void end(Action action) { + log.trace("iPodApplication end: " + action); + if (!active) return; + switch (action) { + case PLAY: + lircService.send(WC02IPOButton.PLAY); + break; + case NEXT: + lircService.send(WC02IPOButton.NEXT); + break; + case PREVIOUS: + lircService.send(WC02IPOButton.PREVIOUS); + break; + case VOLUME_UP: + case VOLUME_DOWN: + try { + volumeWorker.deactivate(); + } catch (DeactivateException e) { + log.error(e); + } + break; + } + } + + protected class VolumeWorker extends Worker { + protected int volumeChangeRate; + + public void activate(int volumeChangeRate) throws ActivateException { + super.activate(); + this.volumeChangeRate = volumeChangeRate; + lircService.send(volumeChangeRate > 0 ? WC02IPOButton.PLUS : WC02IPOButton.MINUS); + } + + public void work() { + lircService.send(WC02IPOButton.HOLD); + sleep(VOLUME_SLEEP); + } + }; +} \ No newline at end of file diff --git a/java/src/mimis/application/itunes/iTunesApplication.java b/java/src/mimis/application/itunes/iTunesApplication.java index 707848d..950732d 100644 --- a/java/src/mimis/application/itunes/iTunesApplication.java +++ b/java/src/mimis/application/itunes/iTunesApplication.java @@ -15,7 +15,7 @@ import com.dt.iTunesController.iTunesEventsInterface; public class iTunesApplication extends Application implements iTunesEventsInterface { protected static final String TITLE = "iTunes"; protected static final String PROGRAM = "iTunes.exe"; - protected static final boolean QUIT = true; + protected static final boolean QUIT = false; protected static final int VOLUME_CHANGE_RATE = 5; protected static final int VOLUME_SLEEP = 100; @@ -28,7 +28,7 @@ public class iTunesApplication extends Application implements iTunesEventsInterf protected boolean quiting; public iTunesApplication() { - super(TITLE); + super(TITLE); iTunes = new iTunes(); volumeWorker = new VolumeWorker(); handle = quiting = false; diff --git a/java/src/mimis/device/lirc/LircDevice.java b/java/src/mimis/device/lirc/LircDevice.java index 6a51699..c270b1b 100644 --- a/java/src/mimis/device/lirc/LircDevice.java +++ b/java/src/mimis/device/lirc/LircDevice.java @@ -1,7 +1,5 @@ package mimis.device.lirc; -import java.util.HashMap; - import mimis.Button; import mimis.Device; import mimis.device.lirc.remote.DenonRC176Button; @@ -25,17 +23,12 @@ public class LircDevice extends Device implements LircButtonListener, SignalList public LircDevice() { super(TITLE); - - /* Initialise remotes */ - HashMap buttonMap = new HashMap(); - buttonMap.put(PhiliphsRCLE011Button.NAME, PhiliphsRCLE011Button.values()); - buttonMap.put(DenonRC176Button.NAME, DenonRC176Button.values()); - buttonMap.put(DenonRC176Button.NAME, DenonRC176Button.values()); - multiplexer = new Multiplexer(this); - lircService = new LircService(buttonMap); + lircService = new LircService(); + lircService.put(PhiliphsRCLE011Button.NAME, PhiliphsRCLE011Button.values()); + lircService.put(DenonRC176Button.NAME, DenonRC176Button.values()); + lircService.put(DenonRC176Button.NAME, DenonRC176Button.values()); lircService.add(this); - eventMapCycle = new LircEventMapCycle(); } diff --git a/java/src/mimis/device/lirc/LircService.java b/java/src/mimis/device/lirc/LircService.java index f5b27d4..f0f0297 100644 --- a/java/src/mimis/device/lirc/LircService.java +++ b/java/src/mimis/device/lirc/LircService.java @@ -36,18 +36,22 @@ public class LircService extends Worker { protected HashMap buttonMap; protected String send; - public LircService(HashMap buttonMap) { - this(buttonMap, IP, PORT); + public LircService() { + this(IP, PORT); + buttonMap = new HashMap(); send = Native.getValue(Registry.CURRENT_USER, "Software\\LIRC", "password"); } - public LircService(HashMap buttonMap, String ip, int port) { - this.buttonMap = buttonMap; + public LircService(String ip, int port) { this.ip = ip; this.port = port; lircButtonListenerList = new ArrayList(); } + public void put(String name, LircButton[] LircButtonArray) { + buttonMap.put(name, LircButtonArray); + } + public void add(LircButtonListener lircButtonListener) { lircButtonListenerList.add(lircButtonListener); } @@ -97,9 +101,13 @@ public class LircService extends Worker { public void work() { try { - String string = bufferedReader.readLine(); + String line = bufferedReader.readLine(); + while (line.equals("BEGIN")) { + while (!bufferedReader.readLine().equals("END")); + line = bufferedReader.readLine(); + } try { - LircButton lircButton = parseButton(new Scanner(string)); + LircButton lircButton = parseButton(new Scanner(line)); for (LircButtonListener lircbuttonListener : lircButtonListenerList) { lircbuttonListener.add(lircButton); } @@ -134,27 +142,16 @@ public class LircService extends Worker { throw new UnknownButtonException(); } - public boolean send(LircButton button) { - return send(button, 0); + public void send(LircButton button) { + send(button, 0); } - public boolean send(LircButton button, int repeat) { + public void send(LircButton button, int repeat) { if (send == null) { - return false; + return; } - String command = String.format("%s %s %s\n", send, button.getName(), button.getCode()); - log.debug(command); + String command = String.format("%s %s %s \n", send, button.getName(), button.getCode()); printWriter.append(command); printWriter.flush(); - try { - bufferedReader.readLine(); - bufferedReader.readLine(); - String result = bufferedReader.readLine(); - bufferedReader.readLine(); - return result.equals("SUCCESS"); - } catch (IOException e) { - log.error(e); - } - return false; } } diff --git a/java/src/mimis/device/lirc/remote/WC02IPOButton.java b/java/src/mimis/device/lirc/remote/WC02IPOButton.java new file mode 100644 index 0000000..4894e88 --- /dev/null +++ b/java/src/mimis/device/lirc/remote/WC02IPOButton.java @@ -0,0 +1,28 @@ +package mimis.device.lirc.remote; + +import mimis.device.lirc.LircButton; + +public enum WC02IPOButton implements LircButton { + MINUS ("MINUS"), + PLUS ("PLUS"), + NEXT ("NEXT"), + PREVIOUS ("PREVIOUS"), + PLAY ("PLAY"), + HOLD ("HOLD"); + + public static final String NAME = "WC02-IPO"; + + protected String code; + + private WC02IPOButton(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public String getName() { + return NAME; + } +} diff --git a/java/src/mimis/util/Reader.java b/java/src/mimis/util/Reader.java new file mode 100644 index 0000000..271860d --- /dev/null +++ b/java/src/mimis/util/Reader.java @@ -0,0 +1,19 @@ +package mimis.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Reader { + public static String getContents(File file) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + StringBuffer stringBuffer = new StringBuffer(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuffer.append(line + "\n"); + } + return stringBuffer.toString(); + } +} diff --git a/java/src/mimis/util/Sound.java b/java/src/mimis/util/Sound.java index 9e22935..7c477ab 100644 --- a/java/src/mimis/util/Sound.java +++ b/java/src/mimis/util/Sound.java @@ -13,11 +13,22 @@ import wiiusej.Wiimote; import mimis.device.wiimote.WiimoteDevice; import mimis.device.wiimote.WiimoteService; +//Sample Rate - equation is y = -280x + 7280, where x is the actual sample rate. ex: sample rate of 4200 = 0x0B +//Freq (y=real, x=wii) y=-1070*ln(x)+4442.6 of 2788.1*e^(-0.041*x) + public class Sound { - public static final byte PCM = 64; - public static final byte ADPCM = 0; + public static final byte PCM = 0x40; // signed 8-bit PCM + public static final byte ADPCM = 0x00; // Yamaha 4-bit ADPCM public static final byte BLOCK_SIZE = 20; + public static final int yamaha_indexscale[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 230, 230, 230, 230, 307, 409, 512, 614}; + + public static final int yamaha_difflookup[] = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1, -3, -5, -7, -9, -11, -13, -15}; + public Object object = new Object(); public static void main(String[] args) { @@ -30,16 +41,15 @@ public class Sound { WiimoteDevice wiimoteDevice = new WiimoteDevice(); Wiimote wiimote = wiimoteService.getDevice(wiimoteDevice); - File file = new File("sound2.wav"); + File file = new File("sound.wav"); AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file); BufferedSound bufferedSound = bufferSound(audioInputStream); wiimote.activateSpeaker(); - wiimote.setSpeakerConfig(ADPCM, bufferedSound.getSampleRate(), 0.1); + wiimote.setSpeakerConfig(PCM, bufferedSound.getSampleRate(), 1); - AudioFormat audioFormat = audioInputStream.getFormat(); double sampleSizeInBytes = audioFormat.getSampleSizeInBits() / 8D; double samplesPerBlock = BLOCK_SIZE / sampleSizeInBytes; @@ -83,15 +93,14 @@ public class Sound { } j += read; } while (j < BLOCK_SIZE); - int length = BLOCK_SIZE; for (j = 0; j < BLOCK_SIZE; ++j) { if ((i * 20 + j) > size) { - length = j; break; } - sound[i][j + 1] = (byte) (Math.random() * 0xff) ;//block[j]; + sound[i][j] = block[j]; + //sound[i][j] = 0x34; + sound[i][j] = (byte) (Math.random() * 0xff); } - sound[i][0] = (byte) (length << 3); // Todo: add later } audioInputStream.close(); return new BufferedSound(sound, (int) audioFormat.getSampleRate(), audioFormat.getSampleSizeInBits()); diff --git a/java/wiiuse.dll b/java/wiiuse.dll index a9c52915ef7e4408f97db19a926b7fd7e3d6800c..0e4621585ea05c5d4e055b974c87d053035c120c 100644 GIT binary patch delta 7842 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 aR-`@cEMp(qD`l_mLqo~t8~dbvz5fpZz={I^ delta 7809 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 a#ZBiF8@UVpjb*RxLPw$2o4e#)-Tw>A#Fvo(