Files
sdrtrunk/src/source/tuner/rtl/RTL2832TunerController.java
2014-04-26 19:48:24 +00:00

1849 lines
48 KiB
Java

/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* Java version based on librtlsdr
* Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de>
* Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net>
*
* 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 source.tuner.rtl;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JPanel;
import javax.usb.UsbClaimException;
import javax.usb.UsbConfiguration;
import javax.usb.UsbControlIrp;
import javax.usb.UsbDevice;
import javax.usb.UsbDisconnectedException;
import javax.usb.UsbEndpoint;
import javax.usb.UsbException;
import javax.usb.UsbInterface;
import javax.usb.UsbInterfacePolicy;
import javax.usb.UsbIrp;
import javax.usb.UsbNotActiveException;
import javax.usb.UsbNotOpenException;
import javax.usb.UsbPipe;
import javax.usb.event.UsbPipeDataEvent;
import javax.usb.event.UsbPipeErrorEvent;
import javax.usb.event.UsbPipeListener;
import javax.usb.util.DefaultUsbControlIrp;
import log.Log;
import org.usb4java.LibUsb;
import sample.Listener;
import source.SourceException;
import source.tuner.TunerController;
import source.tuner.TunerType;
import source.tuner.usb.USBTunerDevice;
import util.TimeStamp;
import controller.ResourceManager;
public abstract class RTL2832TunerController extends TunerController
{
public final static int sINT_NULL_VALUE = -1;
public final static long sLONG_NULL_VALUE = -1l;
public final static double sDOUBLE_NULL_VALUE = -1.0D;
public final static byte sUSB_INTERFACE = (byte)0x0;
public final static byte sUSB_ENDPOINT = (byte)0x81;
public final static boolean sUSB_FORCE_CLAIM_INTERFACE = true;
public final static byte sREQUEST_TYPE_IN =
(byte)( LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR );
public final static byte sREQUEST_TYPE_OUT =
(byte)( LibUsb.ENDPOINT_OUT | LibUsb.REQUEST_TYPE_VENDOR );
public final static byte sREQUEST_ZERO = (byte)0;
public final static int sUSB_IRP_DATA_SIZE = 65536;
public final static int sUSB_IRP_POOL_SIZE = 10;
public final static byte EEPROM_ADDRESS = (byte)0xA0;
public final static byte BIT_0 = (byte)0x01;
public final static byte BIT_1 = (byte)0x02;
public final static byte BIT_2 = (byte)0x04;
public final static byte BIT_3 = (byte)0x08;
public final static byte BIT_4 = (byte)0x10;
public final static byte BIT_5 = (byte)0x20;
public final static byte BIT_6 = (byte)0x40;
public final static byte BIT_7 = (byte)0x80;
public final static byte E4K_I2C_ADDRESS = (byte)0xC8;
public final static byte E4K_CHECK_ADDRESS = (byte)0x02;
public final static byte E4K_CHECK_VALUE = (byte)0x40;
public final static byte FC0012_I2C_ADDRESS = (byte)0xC6;
public final static byte FC0012_CHECK_ADDRESS = (byte)0x00;
public final static byte FC0012_CHECK_VALUE = (byte)0xA1;
public final static byte FC0013_I2C_ADDRESS = (byte)0xC6;
public final static byte FC0013_CHECK_ADDRESS = (byte)0x00;
public final static byte FC0013_CHECK_VALUE = (byte)0xA3;
public final static byte FC2580_I2C_ADDRESS = (byte)0xAC;
public final static byte FC2580_CHECK_ADDRESS = (byte)0x01;
public final static byte FC2580_CHECK_VALUE = (byte)0x56;
public final static byte R820T_I2C_ADDRESS = (byte)0x34;
public final static byte R820T_CHECK_ADDRESS = (byte)0x00;
public final static byte R820T_CHECK_VALUE = (byte)0x69;
public final static byte R828D_I2C_ADDRESS = (byte)0x74;
public final static byte R828D_CHECK_ADDRESS = (byte)0x00;
public final static byte R828D_CHECK_VALUE = (byte)0x69;
public static final int sMIN_OSCILLATOR_FREQUENCY = 28799000;
public static final int sMAX_OSCILLATOR_FREQUENCY = 28801000;
public final static byte[] sFIR_COEFFICIENTS =
{
(byte)0xCA, (byte)0xDC, (byte)0xD7, (byte)0xD8, (byte)0xE0,
(byte)0xF2, (byte)0x0E, (byte)0x35, (byte)0x06, (byte)0x50,
(byte)0x9C, (byte)0x0D, (byte)0x71, (byte)0x11, (byte)0x14,
(byte)0x71, (byte)0x74, (byte)0x19, (byte)0x41, (byte)0xA5
};
private static final DecimalFormat mDecimalFormatter =
new DecimalFormat( "###,###,###.#" );
private USBTunerDevice mUSBTunerDevice;
protected UsbDevice mUSBDevice;
private UsbConfiguration mUSBConfiguration;
protected UsbInterface mUSBInterface;
private UsbPipe mUSBPipe;
private Descriptor mDescriptor;
public static final int sTWO_TO_22_POWER = 4194304;
public static final int sOSCILLATOR_FREQUENCY = 28800000; //28.8 MHz
public static final SampleRate sDEFAULT_SAMPLE_RATE = SampleRate.RATE_1_200MHZ;
private SampleRate mSampleRate = sDEFAULT_SAMPLE_RATE;
private BufferProcessor mBufferProcessor = new BufferProcessor();
private RTL2832SampleAdapter mSampleAdapter = new RTL2832SampleAdapter();
private CopyOnWriteArrayList<Listener<Float[]>> mSampleListeners =
new CopyOnWriteArrayList<Listener<Float[]>>();
private LinkedTransferQueue<byte[]> mRawSampleBuffer =
new LinkedTransferQueue<byte[]>();
private AtomicInteger mSampleCounter = new AtomicInteger();
private double mSampleRateAverageSum;
private int mSampleRateAverageCount;
/**
* Abstract tuner controller device. Use the static getTunerClass() method
* to determine the tuner type, and construct the corresponding child
* tuner controller class for that tuner type.
*/
public RTL2832TunerController( USBTunerDevice tunerDevice,
int minTunableFrequency,
int maxTunableFrequency ) throws SourceException
{
super( minTunableFrequency, maxTunableFrequency );
mUSBTunerDevice = tunerDevice;
mUSBDevice = tunerDevice.getDevice();
mUSBConfiguration = mUSBDevice.getActiveUsbConfiguration();
mUSBInterface = mUSBConfiguration.getUsbInterface( sUSB_INTERFACE );
try
{
if( claim( mUSBInterface ) )
{
/* Get the USB endpoint */
UsbEndpoint endpoint = mUSBInterface.getUsbEndpoint( sUSB_ENDPOINT );
mUSBPipe = endpoint.getUsbPipe();
}
else
{
throw new SourceException( "RTL2832 Tuner Controller - couldn't "
+ "claim USB interface" );
}
}
catch( UsbException e )
{
throw new SourceException( "RTL2832 Tuner Controller - couldn't "
+ "claim USB interface or get endpoint or pipe", e );
}
catch ( UsbDisconnectedException e )
{
throw new SourceException( "RTL2832 Tuner Controller - usb device "
+ "is disconnected", e );
}
}
public void init() throws SourceException
{
try
{
setSampleRate( sDEFAULT_SAMPLE_RATE );
/* Read the contents of the 256-byte EEPROM */
byte[] eeprom = readEEPROM( mUSBDevice, (short)0, 256 );
/* Create the descriptor with the contents from the EEPROM */
mDescriptor = new Descriptor( eeprom );
}
catch( Exception e )
{
throw new SourceException( "RTL2832 Tuner Controller - couldn't "
+ "set default sample rate or read EEPROM descriptor", e );
}
}
/**
* Descriptor contains all identifiers and labels parsed from the EEPROM.
*
* May return null if unable to get 256 byte eeprom descriptor from tuner
* or if the descriptor doesn't begin with byte values of 0x28 and 0x32
* meaning it is a valid (and can be parsed) RTL2832 descriptor
*/
public Descriptor getDescriptor()
{
if( mDescriptor != null && mDescriptor.isValid() )
{
return mDescriptor;
}
return null;
}
// public void start()
// {
// if( !mBufferProcessor.isRunning() )
// {
// try
// {
// resetUSBBuffer();
//
// /* Open the USB pipe */
// openPipe();
//
// mBufferProcessor.start();
// }
// catch ( UsbException e )
// {
// Log.error( "RTL2832TunerController - error starting sample "
// + "buffer processor - " + e.getLocalizedMessage() );
// }
// }
// }
public void setSamplingMode( SampleMode mode ) throws UsbException
{
switch( mode )
{
case QUADRATURE:
// /* Initialize the tuner */
// initTuner();
/* Set intermediate frequency to 0 Hz */
setIFFrequency( 0 );
/* Enable I/Q ADC Input */
writeDemodRegister( mUSBDevice,
Page.ZERO,
(short)0x08,
(short)0xCD,
1 );
/* Enable zero-IF mode */
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0xB1,
(short)0x1B,
1 );
/* Set default i/q path */
writeDemodRegister( mUSBDevice,
Page.ZERO,
(short)0x06,
(short)0x80,
1 );
break;
case DIRECT:
//TODO: add code for direct sampling
break;
default:
break;
}
}
public void setIFFrequency( int frequency ) throws UsbException
{
long ifFrequency = ( (long)sTWO_TO_22_POWER * (long)frequency ) /
(long)sOSCILLATOR_FREQUENCY * -1;
/* Write byte 2 (high) */
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0x19,
(short)( Long.rotateRight( ifFrequency, 16 ) & 0x3F ),
1 );
/* Write byte 1 (middle) */
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0x1A,
(short)( Long.rotateRight( ifFrequency, 8 ) & 0xFF ),
1 );
/* Write byte 0 (low) */
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0x1B,
(short)( ifFrequency & 0xFF ),
1 );
}
public abstract void initTuner( boolean controlI2CRepeater )
throws UsbException;
/**
* Provides a unique identifier to use in distinctly identifying this
* tuner from among other tuners of the same type, so that we can fetch a
* tuner configuration from the settings manager for this specific tuner.
*
* @return serial number of the device
*/
public String getUniqueID()
{
if( mDescriptor != null && mDescriptor.hasSerial() )
{
return mDescriptor.getSerial();
}
else
{
int serial =
( 0xFF & mUSBDevice.getUsbDeviceDescriptor().iSerialNumber() );
return "SER#" + serial;
}
}
public abstract JPanel getEditor( ResourceManager resourceManager );
public abstract void setSampleRateFilters( int sampleRate ) throws UsbException;
public abstract TunerType getTunerType();
public static TunerType identifyTunerType( USBTunerDevice tunerDevice )
{
TunerType tunerClass = TunerType.UNKNOWN;
try
{
UsbDevice device = tunerDevice.getDevice();
UsbConfiguration config = device.getActiveUsbConfiguration();
UsbInterface iface = config.getUsbInterface( sUSB_INTERFACE );
if( claim( iface ) )
{
/* Perform a dummy write to see if the device needs reset */
writeRegister( device, Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1 );
/* Initialize the baseband */
initBaseband( device );
enableI2CRepeater( device, true );
boolean controlI2CRepeater = false;
/* Test for each tuner type until we find the correct one */
if( isE4000Tuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.ELONICS_E4000;
}
else if( isFC0013Tuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.FITIPOWER_FC0013;
}
else if( isR820TTuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.RAFAELMICRO_R820T;
}
else if( isR828DTuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.RAFAELMICRO_R828D;
}
else if( isFC2580Tuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.FCI_FC2580;
}
else if( isFC0012Tuner( device, controlI2CRepeater ) )
{
tunerClass = TunerType.FITIPOWER_FC0012;
}
enableI2CRepeater( device, false );
iface.release();
}
}
catch( Exception e )
{
Log.error( "RTL2832TunerController - error while determining "
+ "tuner type - " + e.getLocalizedMessage() );
}
return tunerClass;
}
public USBTunerDevice getUSBTunerDevice()
{
return mUSBTunerDevice;
}
// protected void openPipe() throws UsbException, UsbClaimException
// {
// /* Get the USB endpoint */
// UsbEndpoint endpoint = mUSBInterface.getUsbEndpoint( sUSB_ENDPOINT );
//
// /* Get the pipe from the endpoint */
// if( endpoint != null )
// {
// mUSBPipe = endpoint.getUsbPipe();
//
// if( mUSBPipe != null )
// {
// if( !mUSBPipe.isOpen() )
// {
// try
// {
// resetUSBBuffer();
//
// mUSBPipe.open();
// }
// catch( Exception e )
// {
// Log.error( "RTL2832 Tuner Controller - exception while " +
// "opening the endpoint pipe." +
// e.getLocalizedMessage() );
// }
// }
// }
// else
// {
// Log.error( "RTL2832 Tuner Controller - returned endpoint "
// + "OUT pipe is null" );
// }
// }
// }
//
/**
* Claims the USB interface. If another application currently has
* the interface claimed, the sFCD_FORCE_CLAIM_HID_INTERFACE setting
* will dictate if the interface is forcibly claimed from the other
* application
*/
public static boolean claim( UsbInterface iface ) throws UsbException,
UsbClaimException
{
if( !iface.isClaimed() )
{
iface.claim( new UsbInterfacePolicy()
{
@Override
public boolean forceClaim( UsbInterface arg0 )
{
return sUSB_FORCE_CLAIM_INTERFACE;
}
} );
return iface.isClaimed();
}
else
{
Log.error( "RTL2832 Tuner Controller - attempt to claim USB "
+ "interface failed - in use by another application" );
}
return false;
}
/**
* Releases the claimed USB interface
*/
private void closePipe() throws UsbException
{
mBufferProcessor.stop();
mUSBPipe.close();
}
/**
* Releases the USB interface
*/
public void release()
{
try
{
if( mBufferProcessor.isRunning() )
{
mBufferProcessor.stop();
}
if( mUSBPipe.isOpen() )
{
mUSBPipe.close();
}
mUSBInterface.release();
}
catch ( Exception e )
{
Log.error( "RTL2832 Tuner Controller - attempt to release USB "
+ "interface failed - " + e.getLocalizedMessage() );
}
}
public void resetUSBBuffer() throws UsbException
{
writeRegister( mUSBDevice, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2 );
writeRegister( mUSBDevice, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x0000, 2 );
}
public static void initBaseband( UsbDevice device )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
/* Initialize USB */
writeRegister( device, Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1 );
writeRegister( device, Block.USB, Address.USB_EPA_MAXPKT.getAddress(), 0x0002, 2 );
writeRegister( device, Block.USB, Address.USB_EPA_CTL.getAddress(), 0x1002, 2 );
/* Power on demod */
writeRegister( device, Block.SYS, Address.DEMOD_CTL_1.getAddress(), 0x22, 1 );
writeRegister( device, Block.SYS, Address.DEMOD_CTL.getAddress(), 0xE8, 1 );
/* Reset demod */
writeDemodRegister( device, Page.ONE, (short)0x01, 0x14, 1 ); //Bit 3 = soft reset
writeDemodRegister( device, Page.ONE, (short)0x01, 0x10, 1 );
/* Disable spectrum inversion and adjacent channel rejection */
writeDemodRegister( device, Page.ONE, (short)0x15, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x16, 0x0000, 2 );
/* Clear DDC shift and IF frequency registers */
writeDemodRegister( device, Page.ONE, (short)0x16, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x17, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x18, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x19, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x1A, 0x00, 1 );
writeDemodRegister( device, Page.ONE, (short)0x1B, 0x00, 1 );
/* Set FIR coefficients */
for( int x = 0; x < sFIR_COEFFICIENTS.length; x++ )
{
writeDemodRegister( device,
Page.ONE,
(short)( 0x1C + x ),
sFIR_COEFFICIENTS[ x ],
1 );
}
/* Enable SDR mode, disable DAGC (bit 5) */
writeDemodRegister( device, Page.ZERO, (short)0x19, 0x05, 1 );
/* Init FSM state-holding register */
writeDemodRegister( device, Page.ONE, (short)0x93, 0xF0, 1 );
writeDemodRegister( device, Page.ONE, (short)0x94, 0x0F, 1 );
/* Disable AGC (en_dagc, bit 0) (seems to have no effect) */
writeDemodRegister( device, Page.ONE, (short)0x11, 0x00, 1 );
/* Disable RF and IF AGC loop */
writeDemodRegister( device, Page.ONE, (short)0x04, 0x00, 1 );
/* Disable PID filter */
writeDemodRegister( device, Page.ZERO, (short)0x61, 0x60, 1 );
/* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */
writeDemodRegister( device, Page.ZERO, (short)0x06, 0x80, 1 );
/* Enable Zero-if mode (en_bbin bit),
* DC cancellation (en_dc_est),
* IQ estimation/compensation (en_iq_comp, en_iq_est) */
writeDemodRegister( device, Page.ONE, (short)0xB1, 0x1B, 1 );
/* Disable 4.096 MHz clock output on pin TP_CK0 */
writeDemodRegister( device, Page.ZERO, (short)0x0D, 0x83, 1 );
}
protected void deinitBaseband( UsbDevice device )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
// setI2CRepeater( device, true );
writeRegister( device, Block.SYS, Address.DEMOD_CTL.getAddress(), 0x20, 1);
// setI2CRepeater( device, false );
}
/**
* Sets the General Purpose Input/Output (GPIO) register bit
*
* @param device - USB tuner device
* @param bitMask - bit mask with one for targeted register bits and zero
* for the non-targeted register bits
* @param enabled - true to set the bit and false to clear the bit
* @throws UsbDisconnectedException - if the tuner device is disconnected
* @throws UsbException - if there is a USB error while communicating with
* the device
*/
protected static void setGPIOBit( UsbDevice device, byte bitMask, boolean enabled )
throws UsbDisconnectedException, UsbException
{
//Get current register value
int value = readRegister( device, Block.SYS, Address.GPO.getAddress(), 1 );
//Update the masked bits
if( enabled )
{
value |= bitMask;
}
else
{
value &= ~bitMask;
}
//Write the change back to the device
writeRegister( device, Block.SYS, Address.GPO.getAddress(), value, 1 );
}
/**
* Enables GPIO Output
* @param device - usb tuner device
* @param bitMask - mask containing one bit value in targeted bit field(s)
* @throws UsbDisconnectedException
* @throws UsbException
*/
protected static void setGPIOOutput( UsbDevice device, byte bitMask )
throws UsbDisconnectedException, UsbException
{
//Get current register value
int value = readRegister( device, Block.SYS, Address.GPD.getAddress(), 1 );
//Mask the value and rewrite it
writeRegister( device, Block.SYS, Address.GPO.getAddress(),
value & ~bitMask, 1 );
//Get current register value
value = readRegister( device, Block.SYS, Address.GPOE.getAddress(), 1 );
//Mask the value and rewrite it
writeRegister( device, Block.SYS, Address.GPOE.getAddress(),
value | bitMask, 1 );
}
protected static void enableI2CRepeater( UsbDevice device, boolean enabled )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
Page page = Page.ONE;
short address = 1;
int value;
if( enabled )
{
value = 0x18; //ON
}
else
{
value = 0x10; //OFF
}
writeDemodRegister( device, page, address, value, 1 );
}
protected boolean isI2CRepeaterEnabled() throws UsbException
{
int register = readDemodRegister( mUSBDevice, Page.ONE, (short)0x1, 1 );
return register == 0x18;
}
protected static int readI2CRegister( UsbDevice device,
byte i2CAddress,
byte i2CRegister,
boolean controlI2CRepeater )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
short address = (short)( i2CAddress & 0xFF );
byte[] writeData = new byte[ 1 ];
writeData[ 0 ] = i2CRegister;
byte[] readData = new byte[ 1 ];
if( controlI2CRepeater )
{
enableI2CRepeater( device, true );
write( device, Block.IIC, address, writeData );
read( device, Block.IIC, address, readData );
enableI2CRepeater( device, false );
}
else
{
write( device, Block.IIC, address, writeData );
read( device, Block.IIC, address, readData );
}
return (int)( readData[ 0 ] & 0xFF );
}
protected void writeI2CRegister( UsbDevice device,
byte i2CAddress,
byte i2CRegister,
byte value,
boolean controlI2CRepeater ) throws UsbException
{
short address = (short)( i2CAddress & 0xFF );
byte[] data = new byte[ 2 ];
data[ 0 ] = i2CRegister;
data[ 1 ] = value;
if( controlI2CRepeater )
{
enableI2CRepeater( device, true );
write( mUSBDevice, Block.IIC, address, data );
enableI2CRepeater( device, false );
}
else
{
write( mUSBDevice, Block.IIC, address, data );
}
}
protected static void writeDemodRegister( UsbDevice device, Page page,
short address, int value, int length )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
byte[] data;
if( length == 1 )
{
data = new byte[ 1 ];
data[0] = (byte)( value & 0xFF );
}
else if( length == 2 )
{
data = new byte[ 2 ];
data[1] = (byte)( value & 0xFF ); //LSB
data[0] = (byte)( ( value >> 8 ) & 0xFF ); //MSB
}
else
{
throw new IllegalArgumentException( "Cannot write value greater "
+ "than 16 bits to the register - length [" + length + "]" );
}
short index = (short)( 0x10 | page.getPage() );
short newAddress = (short)( address << 8 | 0x20 );
write( device, newAddress, index, data );
readDemodRegister( device, Page.TEN, (short)1, length );
}
protected static int readDemodRegister( UsbDevice device,
Page page,
short address,
int length )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
short index = page.getPage();
short newAddress = (short)( ( address << 8 ) | 0x20 );
byte[] empty = new byte[ 2 ];
byte[] data = read( device, newAddress, index, empty );
if( length == 2 )
{
return (int)( ( data[ 1 ] << 8 | data[ 0 ] ) & 0xFFFF );
}
else
{
return (int)( data[ 0 ] & 0xFF );
}
}
protected static void writeRegister( UsbDevice device, Block block, short address,
int value, int length )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
byte[] data;
if( length == 1 )
{
data = new byte[ 1 ];
data[0] = (byte)( value & 0xFF );
}
else if( length == 2 )
{
data = new byte[ 2 ];
data[1] = (byte)( value & 0xFF ); //LSB
data[0] = (byte)( ( value >> 8 ) & 0xFF ); //MSB
}
else
{
throw new IllegalArgumentException( "Cannot write value greater "
+ "than 16 bits to the register - length [" + length + "]" );
}
write( device, block, address, data );
}
protected static int readRegister( UsbDevice device, Block block, short address, int length )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
byte[] empty = new byte[ 2 ];
byte[] data = read( device, block, address, empty );
if( length == 2 )
{
return (int)( ( data[ 1 ] << 8 | data[ 0 ] ) & 0xFFFF );
}
else
{
return (int)( data[ 0 ] & 0xFF );
}
}
/**
* Write array convenience method. Allow you to specify the Block that you
* are writing to.
*
* @param device
* @param block
* @param address
* @param data
* @throws IllegalArgumentException
* @throws UsbDisconnectedException
* @throws UsbException
*/
protected static void write( UsbDevice device, Block block, short address, byte[] data )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
write( device, address, block.getWriteIndex(), data );
}
/**
* Write byte array
* @param device - tuner
* @param value -
* @param index - start
* @param data - to write
* @throws IllegalArgumentException
* @throws UsbDisconnectedException
* @throws UsbException
*/
protected static void write( UsbDevice device, short value, short index, byte[] data )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
UsbControlIrp irp =
device.createUsbControlIrp( sREQUEST_TYPE_OUT,
sREQUEST_ZERO,
value,
index );
irp.setData( data );
device.syncSubmit( irp );
}
/**
* Reads byte array from index at the address.
*
* @return big-endian byte array (needs to be swapped to be usable)
*
*/
protected static byte[] read( UsbDevice device, Block block, short address, byte[] data )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
byte[] retVal;
retVal = read( device, address, block.getReadIndex(), data );
return retVal;
}
/**
* Reads the byte array from the index at the address with the length of the
* read determined by the size of the data array
*
* @return - big-endian byte array (needs to be swapped to be usable)
*/
protected static byte[] read( UsbDevice device, short address, short index, byte[] data )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
DefaultUsbControlIrp irp =
new DefaultUsbControlIrp( sREQUEST_TYPE_IN,
(byte)0,
address,
index );
irp.setData( data );
device.syncSubmit( irp );
return irp.getData();
}
protected static boolean isE4000Tuner( UsbDevice device,
boolean controlI2CRepeater ) throws UsbClaimException
{
try
{
int value = readI2CRegister( device, E4K_I2C_ADDRESS,
E4K_CHECK_ADDRESS, controlI2CRepeater );
return ( value == E4K_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing ... it's not an E4000
}
return false;
}
protected static boolean isFC0012Tuner( UsbDevice device,
boolean controlI2CRepeater ) throws UsbClaimException
{
try
{
/* Initialize the GPIOs */
setGPIOOutput( device, BIT_5 );
/* Reset tuner before probing */
setGPIOBit( device, BIT_5, true );
setGPIOBit( device, BIT_5, false );
int value = readI2CRegister( device,
FC0012_I2C_ADDRESS,
FC0012_CHECK_ADDRESS,
controlI2CRepeater );
return ( value == FC0012_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing, it's not an FC0012
}
return false;
}
protected static boolean isFC0013Tuner( UsbDevice device,
boolean controlI2CRepeater )
throws UsbClaimException
{
try
{
int value = readI2CRegister( device, FC0013_I2C_ADDRESS,
FC0013_CHECK_ADDRESS, controlI2CRepeater );
return ( value == FC0013_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing ... it's probably not an FC0013
}
return false;
}
/**
* Determines if the device's tuner is an FC2580 Tuner.
*
* Note: call initBaseband(device) prior to calling this method.
*
* @param device - usb tuner device
* @return true if the tuner is an FC2580
* @throws UsbException
* @throws UsbClaimException
*/
protected static boolean isFC2580Tuner( UsbDevice device,
boolean controlI2CRepeater ) throws UsbClaimException
{
try
{
/* Initialize the GPIOs */
setGPIOOutput( device, BIT_5 );
/* Reset tuner before probing */
setGPIOBit( device, BIT_5, true );
setGPIOBit( device, BIT_5, false );
/* Read the check value */
int value = readI2CRegister( device, FC2580_I2C_ADDRESS,
FC2580_CHECK_ADDRESS, controlI2CRepeater );
/* Compare masked return value to the known check value */
return ( ( value & 0x7F ) == FC2580_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing, it's not an FC2580
}
return false;
}
protected static boolean isR820TTuner( UsbDevice device,
boolean controlI2CRepeater ) throws UsbClaimException
{
try
{
int value = readI2CRegister( device, R820T_I2C_ADDRESS,
R820T_CHECK_ADDRESS, controlI2CRepeater );
return ( value == R820T_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing, it's not an R820T
}
return false;
}
protected static boolean isR828DTuner( UsbDevice device,
boolean controlI2CRepeater ) throws UsbClaimException
{
try
{
int value = readI2CRegister( device, R828D_I2C_ADDRESS,
R828D_CHECK_ADDRESS, controlI2CRepeater );
return ( value == R828D_CHECK_VALUE );
}
catch( UsbException e )
{
//Do nothing, it's not an R828D
}
return false;
}
public int getCurrentSampleRate() throws SourceException
{
return mSampleRate.getRate();
}
public int getSampleRate() throws SourceException
{
try
{
int high= readDemodRegister( mUSBDevice, Page.ONE, (short)0x9F, 2 );
int low = readDemodRegister( mUSBDevice, Page.ONE, (short)0xA1, 2 );
int ratio = Integer.rotateLeft( high, 16 ) | low;
int rate = (int)( sOSCILLATOR_FREQUENCY * sTWO_TO_22_POWER / ratio );
SampleRate sampleRate = SampleRate.getClosest( rate );
/* If we're not currently set to this rate, set it as the current rate */
if( sampleRate.getRate() != rate )
{
setSampleRate( sampleRate );
return sampleRate.getRate();
}
}
catch ( Exception e )
{
throw new SourceException( "RTL2832 Tuner Controller - cannot get "
+ "current sample rate", e );
}
return sDEFAULT_SAMPLE_RATE.getRate();
}
public void setSampleRate( SampleRate sampleRate )
throws SourceException, UsbException
{
/* Write high-order 16-bits of sample rate ratio to demod register */
writeDemodRegister( mUSBDevice, Page.ONE, (short)0x9F,
sampleRate.getRatioHighBits(), 2 );
/* Write low-order 16-bits of sample rate ratio to demod register */
writeDemodRegister( mUSBDevice, Page.ONE, (short)0xA1,
sampleRate.getRatioLowBits(), 2 );
/* Set sample rate correction to 0 */
setSampleRateFrequencyCorrection( 0 );
/* Reset the demod for the changes to take effect */
writeDemodRegister( mUSBDevice, Page.ONE, (short)0x01, 0x14, 1 );
writeDemodRegister( mUSBDevice, Page.ONE, (short)0x01, 0x10, 1 );
/* Apply any tuner specific sample rate filter settings */
setSampleRateFilters( sampleRate.getRate() );
mSampleRate = sampleRate;
mFrequencyController.setBandwidth( sampleRate.getRate() );
}
public void setSampleRateFrequencyCorrection( int ppm )
throws SourceException, UsbException
{
int offset = -ppm * sTWO_TO_22_POWER / 1000000;
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0x3F,
( offset & 0xFF ),
1 );
writeDemodRegister( mUSBDevice,
Page.ONE,
(short)0x3E,
( Integer.rotateRight( offset, 8 ) & 0xFF ),
1 );
/* Test to retune controller to apply frequency correction */
mFrequencyController.setFrequency( mFrequencyController.getFrequency() );
}
public int getSampleRateFrequencyCorrection() throws UsbException
{
int high = readDemodRegister( mUSBDevice, Page.ONE, (short)0x3E, 1 );
int low = readDemodRegister( mUSBDevice, Page.ONE, (short)0x3F, 1 );
return ( Integer.rotateLeft( high, 8 ) | low );
}
/**
* Returns contents of the 256-byte EEPROM. The contents are as follows:
*
* 256-byte EEPROM (in hex):
* 00/01 - 2832 Signature
* 03/02 - 0BDA Vendor ID
* 05/04 - 2832 Product ID
* 06 - A5 (has serial id?)
* 07 - 16 (bit field - bit 0 = remote wakeup, bit 1 = IR enabled
* 08 - 02 or 12
* 10/09 0310 ETX(0x03) plus label length (includes length and ETX bytes)
* 12/11 First UTF-16 character
* 14/13 Second UTF-16 character ...
*
* Label 1: vendor
* Label 2: product
* Label 3: serial
* Label 4,5 ... (user defined)
*/
public byte[] readEEPROM( UsbDevice device, short offset, int length )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
if( offset + length > 256 )
{
throw new IllegalArgumentException( "RTL2832 Tuner Controller - "
+ "cannot read more than 256 bytes from EEPROM" );
}
byte[] data = new byte[ length ];
byte[] onebyte = new byte[ 1 ];
/* Tell the RTL-2832 to address the EEPROM */
writeRegister( device, Block.IIC, EEPROM_ADDRESS, (byte)offset, 1 );
for( int x = 0; x < length; x++ )
{
byte[] temp = read( device, Block.IIC, EEPROM_ADDRESS, onebyte );
data[ x ] = temp[ 0 ];
}
return data;
}
/**
* Writes a single byte to the 256-byte EEPROM using the specified offset.
*
* Note: introduce a 5 millisecond delay between each successive write to
* the EEPROM or subsequent writes may fail.
*/
public void writeEEPROMByte( UsbDevice device, byte offset, byte value )
throws IllegalArgumentException, UsbDisconnectedException, UsbException
{
if( offset < 0 || offset > 255 )
{
throw new IllegalArgumentException( "RTL2832 Tuner Controller - "
+ "EEPROM offset must be within range of 0 - 255" );
}
int offsetAndValue = Integer.rotateLeft( ( 0xFF & offset ), 8 ) |
( 0xFF & value );
writeRegister( device, Block.IIC, EEPROM_ADDRESS, offsetAndValue, 2 );
}
public enum Address
{
USB_SYSCTL( 0x2000 ),
USB_CTRL( 0x2010 ),
USB_STAT( 0x2014 ),
USB_EPA_CFG( 0x2144 ),
USB_EPA_CTL( 0x2148 ),
USB_EPA_MAXPKT( 0x2158 ),
USB_EPA_MAXPKT_2( 0x215A ),
USB_EPA_FIFO_CFG( 0x2160 ),
DEMOD_CTL( 0x3000 ),
GPO( 0x3001 ),
GPI( 0x3002 ),
GPOE( 0x3003 ),
GPD( 0x3004 ),
SYSINTE( 0x3005 ),
SYSINTS( 0x3006 ),
GP_CFG0( 0x3007 ),
GP_CFG1( 0x3008 ),
SYSINTE_1( 0x3009 ),
SYSINTS_1( 0x300A ),
DEMOD_CTL_1( 0x300B ),
IR_SUSPEND( 0x300C );
private int mAddress;
private Address( int address )
{
mAddress = address;
}
public short getAddress()
{
return (short)mAddress;
}
}
public enum Page
{
ZERO( 0x0 ),
ONE( 0x1 ),
TEN( 0xA );
private int mPage;
private Page( int page )
{
mPage = page;
}
public byte getPage()
{
return (byte)( mPage & 0xFF );
}
}
public enum SampleMode
{
QUADRATURE, DIRECT;
}
public enum Block
{
DEMOD( 0 ),
USB( 1 ),
SYS( 2 ),
TUN( 3 ),
ROM( 4 ),
IR( 5 ),
IIC( 6 ); //I2C controller
private int mValue;
private Block( int value )
{
mValue = value;
}
public int getValue()
{
return mValue;
}
/**
* Returns the value left shifted 8 bits
*/
public short getReadIndex()
{
return (short)Integer.rotateLeft( mValue, 8 );
}
public short getWriteIndex()
{
return (short)( getReadIndex() | 0x10 );
}
}
/**
* Sample rates supported by the RTL-2832.
*
* Formula to calculate the ratio value:
*
* ratio = ( ( crystal_frequency * 2^22 ) / sample_rate ) & ~3
*
* Default crystal_frequency is 28,800,000
*
* This produces a 32-bit value that has to be set in 2 x 16-bit registers.
* Place the high 16-bit value in ratioMSB and the low 16-bit value in
* ratioLSB. Use integer for these values to avoid sign-extension issues.
*
* Mask the value with 0xFFFF when setting the register.
*/
public enum SampleRate
{
/* Note: sample rates below 1.0MHz are subject to aliasing */
RATE_0_240MHZ( 0x0DFC, 0x0000, 240000, "0.240 MHz" ),
RATE_0_288MHZ( 0x08FC, 0x0000, 288000, "0.288 MHz" ),
RATE_0_912MHZ( 0x07E6, 0x0000, 912000, "0.912 MHz" ),
RATE_0_960MHZ( 0x0780, 0x0000, 960000, "0.960 MHz" ),
RATE_1_200MHZ( 0x05F4, 0x0000, 1200000, "1.200 MHz" ),
RATE_1_440MHZ( 0x0500, 0x0000, 1440000, "1.440 MHz" ),
RATE_1_680MHZ( 0x044A, 0x0000, 1680000, "1.680 MHz" ),
RATE_1_824MHZ( 0x03F3, 0x0000, 1824000, "1.824 MHz" ),
RATE_2_016MHZ( 0x038E, 0x0000, 2016000, "2.016 MHz" );
/* Note: usb4java library buffer processing scheme doesn't seem capable
* of supporting these rates */
//RATE_2_400MHZ( 0x0300, 0x0000, 2400000, "2.400 MHz" ),
//RATE_2_880MHZ( 0x0280, 0x0000, 2880000, "2.880 MHz" ),
//RATE_3_072MHZ( 0x0258, 0x0000, 3072000, "3.072 MHz" ),
//RATE_3_216MHZ( 0x023D, 0x0000, 3216000, "3.216 MHz" );
private int mRatioHigh;
private int mRatioLow;
private int mRate;
private String mLabel;
private SampleRate( int ratioHigh, int ratioLow, int rate, String label )
{
mRatioHigh = ratioHigh;
mRatioLow = ratioLow;
mRate = rate;
mLabel = label;
}
public int getRatioHighBits()
{
return mRatioHigh;
}
public int getRatioLowBits()
{
return mRatioLow;
}
public int getRate()
{
return mRate;
}
public String getLabel()
{
return mLabel;
}
public String toString()
{
return mLabel;
}
/**
* Returns the sample rate that is equal to the argument or the next
* higher sample rate
* @param sampleRate
* @return
*/
public static SampleRate getClosest( int sampleRate )
{
for( SampleRate rate: values () )
{
if( rate.getRate() >= sampleRate )
{
return rate;
}
}
return sDEFAULT_SAMPLE_RATE;
}
}
/**
* Adds a sample listener. If the buffer processing thread is
* not currently running, starts it running in a new thread.
*/
public void addListener( Listener<Float[]> listener )
{
mSampleListeners.add( listener );
mBufferProcessor.start();
}
/**
* Removes the sample listener. If this is the last registered listener,
* shuts down the buffer processing thread.
*/
public void removeListener( Listener<Float[]> listener )
{
mSampleListeners.remove( listener );
if( mSampleListeners.isEmpty() )
{
mBufferProcessor.stop();
}
}
/**
* Dispatches float sample buffers to all registered listeners
*/
public void broadcast( Float[] samples )
{
Iterator<Listener<Float[]>> it = mSampleListeners.iterator();
while( it.hasNext() )
{
Listener<Float[]> next = it.next();
/* if this is the last (or only) listener, send him the original
* buffer, otherwise send him a copy of the buffer */
if( it.hasNext() )
{
next.receive( Arrays.copyOf( samples, samples.length ) );
}
else
{
next.receive( samples );
}
}
}
/**
* Buffer processing thread. Fetches samples from the RTL2832 Tuner and
* dispatches them to all registered listeners
*/
public class BufferProcessor implements UsbPipeListener
{
private ScheduledExecutorService mExecutor =
Executors.newScheduledThreadPool( 2 );
private ScheduledFuture<?> mSampleDispatcherTask;
private ScheduledFuture<?> mSampleRateCounterTask;
private AtomicBoolean mRunning = new AtomicBoolean();
private AtomicBoolean mResetting = new AtomicBoolean();
private Boolean mTransitioning = false;
public void start()
{
synchronized( mTransitioning )
{
if( mRunning.compareAndSet( false, true ) )
{
Log.debug( "RTL2832TunerController [" + mDescriptor.getSerial() + "] - starting sample fetch thread" );
ArrayList<UsbIrp> irps = new ArrayList<UsbIrp>();
for( int x = 0; x < sUSB_IRP_POOL_SIZE; x++ )
{
UsbIrp irp = mUSBPipe.createUsbIrp();
irp.setData( new byte[ sUSB_IRP_DATA_SIZE ] );
irps.add( irp );
}
try
{
if( mUSBPipe.isActive() )
{
if( !mUSBPipe.isOpen() )
{
resetUSBBuffer();
mUSBPipe.open();
//TODO: move this to the constructor
mUSBPipe.addUsbPipeListener( this );
}
if( mUSBPipe.isOpen() )
{
mSampleDispatcherTask = mExecutor
.scheduleAtFixedRate( new BufferDispatcher(),
0, 20, TimeUnit.MILLISECONDS );
mSampleRateCounterTask = mExecutor
.scheduleAtFixedRate( new SampleRateMonitor(),
60, 60, TimeUnit.SECONDS );
mUSBPipe.asyncSubmit( irps );
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
}
/**
* Stops the sample fetching thread
*/
public void stop()
{
synchronized( mTransitioning )
{
if( mRunning.compareAndSet( true, false ) )
{
Log.debug( "RTL2832TunerController - stopping sample fetch thread" );
mUSBPipe.abortAllSubmissions();
if( mSampleDispatcherTask != null )
{
mSampleDispatcherTask.cancel( true );
mSampleRateCounterTask.cancel( true );
mRawSampleBuffer.clear();
mSampleCounter.set( 0 );
}
}
}
}
/**
* Indicates if this thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
/**
* This method is invoked by the usb pipe when a response is returned
* from the data request. If there is byte sample data in the response
* then we will buffer it and allow the buffer processor to convert it
* and dispatch it to listeners. We automatically insert another
* irp to get more data.
*
* This method runs on the usb4java library processing thread, so we
* don't want to weigh it down.
*/
@Override
public synchronized void dataEventOccurred( UsbPipeDataEvent event )
{
UsbIrp irp = event.getUsbIrp();
byte[] data = irp.getData();
if( data.length > 0 )
{
mRawSampleBuffer.add( data );
mSampleCounter.addAndGet( data.length );
}
if( mRunning.get() )
{
try
{
mUSBPipe.asyncSubmit( new byte[ sUSB_IRP_DATA_SIZE ] );
}
catch ( UsbNotActiveException | UsbNotOpenException
| IllegalArgumentException | UsbDisconnectedException
| UsbException e )
{
Log.error( "RTL2832TunerController - error while "
+ "processing samples - " + e.getLocalizedMessage() );
}
}
}
@Override
public void errorEventOccurred( UsbPipeErrorEvent e )
{
/* When an error occurs, we'll get errors on all of the
* queued IRPS in succession. We use the mResetting to control
* the reset process and ignore all subsequent errors */
if( mResetting.compareAndSet( false, true ) )
{
Log.error( "RTL2832TunerController - error during data transfer - "
+ "resetting USB pipe to RTL-2832 - " +
e.getUsbException().getLocalizedMessage() );
stop();
if( !mSampleListeners.isEmpty() )
{
start();
}
mResetting.set( false );
}
}
}
/**
* Fetches byte[] chunks from the raw sample buffer. Converts each byte
* array and broadcasts the array to all registered listeners
*/
public class BufferDispatcher implements Runnable
{
@Override
public void run()
{
ArrayList<byte[]> buffers = new ArrayList<byte[]>();
mRawSampleBuffer.drainTo( buffers );
for( byte[] buffer: buffers )
{
Float[] samples = mSampleAdapter.convert( buffer );
broadcast( samples );
}
}
}
/**
* Averages the sample rate over a 10-second period. The count is for the
* number of bytes received from the tuner. There are two bytes for each
* sample. So, we divide by 20 to get the average sample rate.
*/
public class SampleRateMonitor implements Runnable
{
@Override
public void run()
{
int count = mSampleCounter.getAndSet( 0 );
double rate = (double)count / 120.0d;
mSampleRateAverageSum += rate;
mSampleRateAverageCount++;
double average = mSampleRateAverageSum / (double)mSampleRateAverageCount;
StringBuilder sb = new StringBuilder();
sb.append( TimeStamp.getTimeStamp( " " ) );
sb.append( " RTL-2832 [" );
if( mDescriptor != null )
{
sb.append( mDescriptor.getSerial() );
}
else
{
sb.append( "DESCRIPTOR IS NULL" );
}
sb.append( "] Sample Rate - current: " );
sb.append( mDecimalFormatter.format( rate ) );
sb.append( "\taverage: " );
sb.append( mDecimalFormatter.format( average ) );
sb.append( " count:" );
sb.append( mSampleRateAverageCount );
Log.info( sb.toString() );
}
}
/**
* RTL2832 EEPROM byte array descriptor parsing class
*/
public class Descriptor
{
private byte[] mData;
private ArrayList<String> mLabels = new ArrayList<String>();
public Descriptor( byte[] data )
{
mData = data;
getLabels();
}
public boolean isValid()
{
return mData[ 0 ] == (byte)0x28 &&
mData[ 1 ] == (byte)0x32;
}
public String getVendorID()
{
int id = Integer.rotateLeft( ( 0xFF & mData[ 3] ), 8 ) |
( 0xFF & mData[ 2 ] );
return String.format("%04X", id );
}
public String getVendorLabel()
{
return mLabels.get( 0 );
}
public String getProductID()
{
int id = Integer.rotateLeft( ( 0xFF & mData[ 5] ), 8 ) |
( 0xFF & mData[ 4 ] );
return String.format("%04X", id );
}
public String getProductLabel()
{
return mLabels.get( 1 );
}
public boolean hasSerial()
{
return mData[ 6 ] == (byte)0xA5;
}
public String getSerial()
{
return mLabels.get( 2 );
}
public boolean remoteWakeupEnabled()
{
byte mask = (byte)0x01;
return ( mData[ 7 ] & mask ) == mask;
}
public boolean irEnabled()
{
byte mask = (byte)0x02;
return ( mData[ 7 ] & mask ) == mask;
}
private void getLabels()
{
mLabels.clear();
int start = 0x09;
while( start < 256 )
{
start = getLabel( start );
}
}
private int getLabel( int start )
{
/* Validate length and check second byte for ETX (0x03) */
if( start > 254 || mData[ start + 1 ] != (byte)0x03 )
{
return 256;
}
/* Get label length, including the length and ETX bytes */
int length = 0xFF & mData[ start ];
if( start + length > 255 )
{
return 256;
}
/* Get the label bytes */
byte[] data = Arrays.copyOfRange( mData, start + 2, start + length );
/* Translate the bytes as UTF-16 Little Endian and store the label */
String label = new String( data, Charset.forName( "UTF-16LE" ) );
mLabels.add( label );
return start + length;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "RTL-2832 EEPROM Descriptor\n" );
sb.append( "Vendor: " );
sb.append( getVendorID() );
sb.append( " [" );
sb.append( getVendorLabel() );
sb.append( "]\n" );
sb.append( "Product: " );
sb.append( getProductID() );
sb.append( " [" );
sb.append( getProductLabel() );
sb.append( "]\n" );
sb.append( "Serial: " );
if( hasSerial() )
{
sb.append( "yes [" );
sb.append( getSerial() );
sb.append( "]\n" );
}
else
{
sb.append( "no\n" );
}
sb.append( "Remote Wakeup Enabled: " );
sb.append( ( remoteWakeupEnabled() ? "yes" : "no" ) );
sb.append( "\n" );
sb.append( "IR Enabled: " );
sb.append( ( irEnabled() ? "yes" : "no" ) );
sb.append( "\n" );
if( mLabels.size() > 3 )
{
sb.append( "Additional Labels: " );
for( int x = 3; x < mLabels.size(); x++ )
{
sb.append( " [" );
sb.append( mLabels.get( x ) );
sb.append( "\n" );
}
}
return sb.toString();
}
}
}