org.harctoolbox.harchardware.ir.IrToy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of HarcHardware Show documentation
Show all versions of HarcHardware Show documentation
Helper functions for accessing hardware etc.
/*
Copyright (C) 2013, 2014, 2015 Bengt Martensson.
This program 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.
This program 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
this program. If not, see http://www.gnu.org/licenses/.
*/
package org.harctoolbox.harchardware.ir;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.UnsupportedCommOperationException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import org.harctoolbox.harchardware.HarcHardwareException;
import org.harctoolbox.harchardware.comm.LocalSerialPort;
import org.harctoolbox.harchardware.comm.LocalSerialPortRaw;
import org.harctoolbox.ircore.InvalidArgumentException;
import org.harctoolbox.ircore.IrCoreUtils;
import org.harctoolbox.ircore.IrSequence;
import org.harctoolbox.ircore.IrSignal;
import org.harctoolbox.ircore.ModulatedIrSequence;
import org.harctoolbox.ircore.OddSequenceLengthException;
import org.harctoolbox.ircore.Pronto;
/**
* This class contains a driver for Dangerous Prototype's IrToy.
* @see http://www.dangerousprototypes.com/docs/USB_IR_Toy:_Sampling_mode
*
*/
public final class IrToy extends IrSerial implements IRawIrSender, ICapture, IReceive {
public static final String defaultPortName = "/dev/ttyACM0";
public static final int defaultBaudRate = 9600;
public static final LocalSerialPort.FlowControl defaultFlowControl = LocalSerialPort.FlowControl.RTSCTS;
private static final int dataSize = 8;
private static final int stopBits = 1;
private static final LocalSerialPort.Parity parity = LocalSerialPort.Parity.NONE;
private static final double oscillatorFrequency = 48000000;
private static final double period = 21.3333; // microseconds
private static final double PICClockFrequency = 12000000;
private static final byte dutyCycle = 0; // semantically: don't care
private static final boolean transmitNotifyEnabled = true;
private static final boolean transmitByteCountReportEnabled = true;
private static final boolean transmitHandshakeEnabled = true;
private final static byte cmdReset = 0x00; // Reset (returns to remote decoder mode)
// 0x01 RESERVED for SUMP RUN
// 0x02 RESERVED for SUMP ID
private final static byte cmdTransmit = 0x03; // Transmit (FW v07+)
private final static byte cmdFrequencyReport = 0x04; // Frequency report (reserved for future hardware)
// 0x05 Setup sample timer (FW v07+)
private final static byte cmdSetFrequency = 0x06; // Setup frequency modulation timer (FW v07+)
private final static byte cmdLedMuteOn = 0x10; // LED mute on (FW v07+)
private final static byte cmdLedMuteOff = 0x11; // LED mute off (FW v07+)
private final static byte cmdLedOn = 0x12; // LED on (FW v07+)
private final static byte cmdLedOff = 0x13; // LED off (FW v07+)
// 0x23 Settings descriptor report (FW v20+)
private final static byte cmdTransmitByteCountReport = 0x24; // Enable transmit byte count report (FW v20+)
private final static byte cmdTransmitNotify = 0x25; // Enable transmit notify on complete (FW v20+)
private final static byte cmdTransmitHandshake = 0x26; // Enable transmit handshake (FW v20+)
private final static byte cmdIOwrite = 0x30; // Sets the IO pins to ground (0) or +5volt (1).
private final static byte cmdIOdirection = 0x31; // Sets the IO pins to input (1) or output (0).
private final static byte cmdIOread = 0x32; // Read the IO pins, returns 1 byte.
private final static byte cmdUARTsetup = 0x40; // Setup the UART to send serial data. Uses the current virtual serial port settings.
private final static byte cmdUARTclose = 0x41; // Close the UART.
private final static byte cmdUARTwrite = 0x42; // Send a byte to the serial UART.
// Source: http://dangerousprototypes.com/docs/USB_IR_Toy:_IRman_decoder_mode
private final static byte cmdSamplingMode = (byte) 's';
private final static byte cmdSelfTest = (byte) 't';
private final static byte cmdVersion = (byte) 'v';
private final static byte cmdBootloaderMode = (byte) '$';
private final static byte endOfData = (byte) 0xff;
private final static int transmitByteCountToken = 't';
private final static int transmitCompleteSuccess = 'C';
private final static int transmitCompleteFailure = 'F';
// Versions strings are exactly 4 chars in length, see http://dangerousprototypes.com/docs/USB_IR_Toy:_IRman_decoder_mode
private final static int lengthVersionString = 4;
private final static int lengthSelftestVersionString = 4;
private final static int lengthProtocolVersionString = 3;
private final static String expectedProtocolVersion = "S01";
private final static int emptyBufferSize = 62;
private final static int powerPin = 5;
private final static int receivePin = 3;
private final static int sendingPin = 4;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
String portName = "/dev/ttyACM0";
IrToy toy = null;
try {
toy = new IrToy(portName);
toy.open();
if (args.length >= 1 && args[0].equals("-b"))
toy.bootloaderMode();
else {
// //int[] data = new int[]{889, 889, 1778, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 889, 90886};
// System.out.println(toy.getVersion());
// //String result = toy.selftest();
// //System.out.println(result);
// toy.setLed(true);
// toy.setLedMute(false);
// IrSignal signal = new IrSignal("../IrpMaster/data/IrpProtocols.ini", "nec1", "D=122 F=26");
// boolean success = toy.sendIr(signal, 10, null);
// //String success = toy.selftest();
// toy.setPin(powerPin, true);
// toy.setPin(receivePin, true);
// toy.setPin(sendingPin, true);
//
// System.out.println(success);
}
} catch (NoSuchPortException ex) {
System.err.println("Port for IRToy " + portName + " was not found");
} catch (PortInUseException ex) {
System.err.println("Port for IRToy in use");
} catch (HarcHardwareException | UnsupportedCommOperationException | IOException ex) {
System.err.println(ex.getMessage());
} finally {
if (toy != null) {
try {
toy.close();
} catch (IOException ex) {
}
}
}
}
private boolean stopCaptureRequest = true;
private String protocolVersion;
private String version;
private int captureMaxSize = defaultCaptureMaxSize;
private int IOdirections = -1;
private int IOdata = 0;
private boolean useSignalingLed;
public IrToy() throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(defaultPortName, defaultBaudRate, defaultFlowControl, defaultBeginTimeout, defaultCaptureMaxSize, false);
}
public IrToy(String portName) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(portName, defaultBaudRate, defaultFlowControl, defaultBeginTimeout, defaultCaptureMaxSize, false);
}
public IrToy(String portName, int timeout, boolean verbose) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(portName, defaultBaudRate, defaultFlowControl, timeout, defaultCaptureMaxSize, verbose);
}
public IrToy(String portName, int baudRate, int timeout, boolean verbose) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(portName, baudRate, defaultFlowControl, timeout, defaultCaptureMaxSize, verbose);
}
public IrToy(String portName, int beginTimeout, int captureMaxSize, int endingTimeout, boolean verbose) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(portName, defaultBaudRate, defaultFlowControl, beginTimeout, captureMaxSize, verbose);
}
public IrToy(String portName, int baud, int beginTimeout, int captureSize, int endingTimeout, boolean verbose) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
this(portName, baud, defaultFlowControl, beginTimeout, captureSize, verbose);
}
public IrToy(String portName, int baudRate, LocalSerialPort.FlowControl flowControl, int timeout, int maxLearnLength, boolean verbose) throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException, IOException {
super(LocalSerialPortRaw.class, portName, baudRate, dataSize, stopBits, parity, flowControl, timeout, verbose);
this.captureMaxSize = maxLearnLength;
}
private void goSamplingMode() throws IOException, HarcHardwareException {
send(cmdSamplingMode);
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
}
protocolVersion = readString(lengthProtocolVersionString);
if (!protocolVersion.equals(expectedProtocolVersion))
throw new HarcHardwareException("Unsupported IrToy protocol version: " + protocolVersion);
}
private void setupSendingModes() throws IOException {
if (transmitNotifyEnabled)
send(cmdTransmitNotify);
if (transmitHandshakeEnabled)
send(cmdTransmitHandshake);
if (transmitByteCountReportEnabled)
send(cmdTransmitByteCountReport);
}
private byte[] prepare3(byte cmd, int data) {
byte[] array = new byte[3];
array[0] = cmd;
array[1] = (byte) ((data >> 8) & 0xff);
array[2] = (byte) (data & 0xff);
return array;
}
private void setIOData() throws IOException {
send(prepare3(cmdIOdirection, IOdirections));
send(prepare3(cmdIOwrite, IOdata));
}
/**
* pin 2: RA2
* pin 3: RA3
* pin 4: RA4
* pin 5: RA5
* pin 11: RB3
* pin 13: RB5
*
* @param pin
* @param state
* @throws IOException
*/
public void setPin(int pin, boolean state) throws IOException {
if (useSignalingLed) {
int mask = 1 << pin;
IOdirections &= ~mask;
if (state)
IOdata |= mask;
else
IOdata &= ~mask;
setIOData();
}
}
@Override
public void open() throws IOException, HarcHardwareException {
super.open();
reset(5);
send(cmdVersion);
version = readString(lengthVersionString);
checkVersion();
goSamplingMode();
setupSendingModes();
setPin(powerPin, true);
}
public void checkVersion() throws HarcHardwareException, IOException {
int numerical;
try {
numerical = Integer.parseInt(version.substring(1));
} catch (NumberFormatException ex) {
throw new HarcHardwareException("Unsupported firmware: " + version);
}
int hwVersion = numerical / 100;
int swMainVersion = (numerical / 10) % 10;
int swMinorVersion = numerical % 10;
if (!(hwVersion == 2 && swMainVersion == 2 && swMinorVersion != 3))
// Just does not work, see http://dangerousprototypes.com/forum/viewtopic.php?f=29&t=4024&start=23
throw new HarcHardwareException("Unsupported firmware: " + version);
useSignalingLed = swMinorVersion >= 2;
}
@Override
public void close() throws IOException {
if (isValid()) {
IOdirections = -1;
setIOData();
reset(1);
}
super.close();
}
public void reset(int times) throws IOException {
for (int i = 0; i < times; i++)
send(cmdReset);
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
}
serialPort.flushInput();
}
private void send(byte[] buf) throws IOException {
serialPort.sendBytes(buf);
//serialPort.flush();
}
private void send(byte[] buf, int offset, int length) throws IOException {
serialPort.sendBytes(buf, offset, length);
//serialPort.flush();
}
private void send(byte b) throws IOException {
serialPort.sendByte(b);
//serialPort.flush();
}
private byte[] toByteArray(int[] data) {
byte[] buf = new byte[2*data.length];
for (int i = 0; i < data.length; i++) {
int periods = (int)Math.round(data[i]/period);
buf[2*i] = (byte)(periods / 256);
buf[2*i+1] = (byte) (periods % 256);
}
// REPLACE last gap by 0xFFFF
buf[2*data.length-2] = endOfData;
buf[2*data.length-1] = endOfData;
return buf;
}
private int[] recv() throws IOException {
int[] result = null;
try {
ArrayList array = new ArrayList<>(16);
stopCaptureRequest = false;
long maxLearnLengthMicroSeconds = captureMaxSize * 1000L;
long sum = 0;
setPin(receivePin, true);
while (!stopCaptureRequest && sum <= maxLearnLengthMicroSeconds) { // if leaving here, reset is needed.
int val = read2Bytes(); // throws TimeoutException
int ms = (int) Math.round(val * period);
array.add(ms);
sum += ms;
if (val == 0xffff)
// Only way for timeout, 1.4 seconds. Too long for most use cases ... :-\
break;
}
if (stopCaptureRequest) {
setPin(receivePin, false);
return null;
}
result = new int[array.size()];
for (int i = 0; i < array.size(); i++) {
result[i] = array.get(i);
}
} finally {
setPin(receivePin, false);
}
return result;
}
private double getFrequency(int onTimes) throws IOException {
send(cmdFrequencyReport);
/*int t1 =*/ read2Bytes();
/*int t2 =*/ read2Bytes();
/*int t3 =*/ read2Bytes();
int count = read2Bytes();
//System.err.println(t1);System.err.println(t2);System.err.println(t3);System.err.println(count);System.err.println(onTimes);
//return (2*PICClockFrequency)/((double)(t3 - t1));
return count/IrCoreUtils.microseconds2seconds(onTimes) ;
}
@Override
public ModulatedIrSequence capture() throws HarcHardwareException, IOException {
// reset, while I do not want any already recorder signals.
reset(5);
goSamplingMode();
int[] data = recv(); // throws TimeoutException
if (stopCaptureRequest || data == null)
return null;
int sum = 0;
for (int i = 0; i < data.length / 2; i++) {
sum += data[2 * i];
}
double frequency = getFrequency(sum);
ModulatedIrSequence seq = null;
try {
seq = new ModulatedIrSequence(data, frequency);
} catch (OddSequenceLengthException ex) {
for (int i = 0; i < data.length; i++)
System.err.print(data[i] + " ");
System.err.println();
throw new HarcHardwareException("IrToy: Erroneous data received.");
}
return seq;
}
@Override
public boolean stopCapture() {
stopCaptureRequest = true;
return true;
}
@Override
public IrSequence receive() throws HarcHardwareException, IOException {
return capture();
}
@Override
public boolean stopReceive() {
return stopCapture();
}
private boolean transmit(int[] data, double frequency) throws IOException, HarcHardwareException {
if (frequency > 0)
setFrequency(frequency);
return transmit(data);
}
private boolean transmit(int[] data) throws IOException, HarcHardwareException {
reset(1);
goSamplingMode();
setupSendingModes();
setPin(sendingPin, true);
byte[] buf = toByteArray(data);
send(cmdTransmit);
boolean succcess = true;
try {
if (transmitHandshakeEnabled) {
int bytesSent = 0;
while (bytesSent < buf.length) {
int noBytes = readByte(); // number of bytes free in buffer, the number we should send
if (noBytes != emptyBufferSize)
continue;
int toSend = Math.min(noBytes, buf.length - bytesSent);
send(buf, bytesSent, toSend);
bytesSent += toSend;
}
}
int noBytes = readByte();
if (noBytes != emptyBufferSize) {
System.err.println("got " + noBytes + " should: " + emptyBufferSize);
succcess = false;
}
if (succcess && transmitByteCountReportEnabled) {
int token = readByte();
if (token == transmitByteCountToken) { // 't'
int bytesSent = read2Bytes();
if (bytesSent != data.length * 2) {
System.err.println("sent " + bytesSent + " should: " + (data.length * 2));
succcess = false;
}
} else {
System.err.println("did not get t but " + token);
succcess = false;
}
}
if (succcess && transmitNotifyEnabled) {
int token = readByte();
if (token != transmitCompleteSuccess) {
System.err.println("Status: " + token);
succcess = false;
}
}
} finally {
setPin(sendingPin, false);
}
return succcess;
}
private int byte2unsignedInt(byte b) {
return b >= 0 ? b : b + 256;
}
private String readString(int length) throws IOException {
byte[] buf = serialPort.readBytes(length);
return new String(buf, 0, length, Charset.forName("US-ASCII"));
}
private int readByte() throws IOException {
byte[] a = serialPort.readBytes(1);
return a.length > 0 ? byte2unsignedInt(a[0]) : -1;
}
private int read2Bytes() throws IOException {
byte[] a = serialPort.readBytes(2);
return a.length < 2 ? -1 : 256*byte2unsignedInt(a[0]) + byte2unsignedInt(a[1]);
}
public String selftest() throws IOException {
reset(5);
send(cmdSelfTest);
String ver = readString(lengthSelftestVersionString);
return ver;
}
public void bootloaderMode() throws IOException {
reset(5);
send(cmdBootloaderMode);
}
public String getProtocolVersion() {
return protocolVersion;
}
public void setLedMute(boolean status) throws IOException {
if (useSignalingLed)
send(status ? cmdLedMuteOn : cmdLedMuteOff);
}
public void setLed(boolean status) throws IOException {
if (useSignalingLed)
send(status ? cmdLedOn : cmdLedOff);
}
private void setFrequency(double frequency) throws IOException {
byte pr2 = (byte) Math.round(oscillatorFrequency/(16*frequency) - 1);
byte[] buf = new byte[3];
buf[0] = cmdSetFrequency;
buf[1] = pr2;
buf[2] = dutyCycle;
send(buf);
}
@Override
public String getVersion() {
return version;
}
@Override
public void setBeginTimeout(int beginTimeout) throws IOException {
super.setTimeout(beginTimeout);
}
@Override
public void setCaptureMaxSize(int captureMaxSize) {
this.captureMaxSize = captureMaxSize;
}
@Override
public void setEndingTimeout(int timeout) {
}
@Override
public boolean sendIr(IrSignal code, int count, Transmitter transmitter) throws IOException, HarcHardwareException {
return transmit(code.toIntArray(count), code.getFrequency());
}
public boolean sendCcf(String ccf, int count, Transmitter transmitter) throws IOException, HarcHardwareException, Pronto.NonProntoFormatException, InvalidArgumentException {
return sendIr(Pronto.parse(ccf), count, transmitter);
}
/**
* Not supported due to hardware restrictions.
*
* @param ccf
* @param transmitter
* @return
*/
public boolean sendCcfRepeat(String ccf, Transmitter transmitter) {
throw new UnsupportedOperationException("Not supported due to hardware restrictions.");
}
@Override
public void setDebug(int debug) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}