package cnadeau.driver;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import javax.usb.UsbEndpoint;
import javax.usb.UsbException;
import javax.usb.UsbInterface;
import javax.usb.UsbPipe;
import org.apache.log4j.Logger;
import cnadeau.driver.exceptions.CM19AException;
import cnadeau.driver.protocol.CM19AProtocol;
import cnadeau.driver.threads.ReceiverThread;
import cnadeau.driver.threads.TransmitterThread;
import cnadeau.enums.AvailableLoggers;
/**
* User space driver for the CM19a X10 RF transceiver
*
* @author Jan Roberts
*
*/
public class CM19ADevice extends Thread
{
private final Logger logger = Logger.getLogger(AvailableLoggers.Driver.toString());
private final UsbInterface usbInterface = null;
private final CM19AProtocol protocol;
private LinkedList<byte[]> outQueue = null;
private final UsbInterfaceInfos usbInterfaceInfos;
private final CM19AInputReader cm19aInputReader;
private boolean running;
public CM19ADevice(CM19AUsbDeviceLookup cm19aUsbDevideLookup, CM19AInputReader cm19aInputReader, CM19AProtocol protocol) throws CM19AException,
UnsupportedEncodingException, UsbException
{
this.cm19aInputReader = cm19aInputReader;
this.protocol = protocol;
this.outQueue = new LinkedList<byte[]>();
this.usbInterfaceInfos = cm19aUsbDevideLookup.findCM19ADevice();
this.setName(getClass().getSimpleName() + " input reader thread");
StringBuilder info = new StringBuilder("Using usb device lookup ");
info.append(cm19aUsbDevideLookup.getClass().getSimpleName());
info.append(", input reader");
info.append(cm19aInputReader.getClass().getSimpleName());
info.append(", protocol ");
info.append(protocol.getClass().getSimpleName());
logger.info(info.toString());
}
protected CM19AProtocol getProtocol()
{
return protocol;
}
public UsbInterfaceInfos getUsbDeviceInfos()
{
return usbInterfaceInfos;
}
/**
* Formats the incoming command to be consistent with what is expected, which is a single uppercased word, e.g. A1OFF. Commands can then be entered as
* reader friendly way, such as a1 off, A1 Off, etc.
*
* @param command
* the incoming x10 command
* @return the correctly formatted string
* @throws CM19AException
* If the incoming string is blank or empty
* @author Jan Roberts
*
*/
public String formatCommand(String command) throws CM19AException
{
/* remove any spaces in the command */
String[] words = command.split("\\s+");
if (words.length == 0)
throw new CM19AException("Command is blank or empty");
String spacelessCommand = words[0].trim();
/* scrunch the string back together without the spaces */
if (words.length > 1)
{
for (int i = 1; i < words.length; i++)
{
spacelessCommand = spacelessCommand.concat(words[i].trim());
}
}
return spacelessCommand.toUpperCase();
}
/**
* Sends the incoming command to the CM19a.
*
* @param command
* A properly formatted x10 command
* @return is the driver should be closed or not
* @author Jan Roberts
*
*/
public boolean send(String command)
{
byte[] rfTransmittal = getRFTransmittal(command);
if (rfTransmittal != null)
{
send(rfTransmittal);
}
return rfTransmittal == CM19AProtocol.CLOSE_DRIVER_BYTES;
}
/**
* Retrieves the actual bytes to transmit to the CM19a.
*
* Retrieves the bytes to transmit to the CM19a. They're originally stored in a single string which has to be broken up and converted to a byte array.
*
* @param command
* A properly formatted x10 command
* @return The byte array to be transmitted or null if not found
* @author Jan Roberts
*
*/
public byte[] getRFTransmittal(String command)
{
return getProtocol().convertCommandToRfTransmitterString(command);
}
/**
* Notify the transmitter that there is a message to be sent
*
* @param rfTransmittal
* The bytes to be sent to the CM19a
* @author Jan Roberts
*
*/
protected void send(byte[] rfTransmittal)
{
synchronized (getOutQueue())
{
getOutQueue().add(rfTransmittal);
getOutQueue().notify();
}
}
/**
* Stops running threads and releases the CM19a
*
* @throws CM19AException
* If there was a problem releasing the CM19a
* @author Jan Roberts
*/
public void releaseDevice() throws CM19AException
{
getOutQueue().notify();
/* Release the interface */
try
{
usbInterface.release();
}
catch (UsbException e)
{
throw new CM19AException("Failed to release device: " + e.getMessage());
}
}
public ReceiverThread createReceiverThread(UsbInterfaceInfos cm19aDeviceInfos) throws CM19AException
{
logger.info("Starting receiver thread... ");
UsbPipe inPipe = openPipe(cm19aDeviceInfos.getInEndpoint());
ReceiverThread receiver = new ReceiverThread(inPipe);
return receiver;
}
public TransmitterThread createTransmitterThread(UsbInterfaceInfos cm19aDeviceInfos) throws CM19AException
{
logger.info("Starting transmitter thread... ");
UsbPipe outPipe = openPipe(cm19aDeviceInfos.getOutEndpoint());
TransmitterThread transmitter = new TransmitterThread(this, outPipe);
return transmitter;
}
private UsbPipe openPipe(UsbEndpoint endPoint) throws CM19AException
{
UsbPipe outPipe = null;
try
{
outPipe = endPoint.getUsbPipe();
outPipe.open();
}
catch (NullPointerException e)
{
try
{
endPoint.getUsbPipe().close();
endPoint.getUsbInterface().release();
throw new CM19AException("pipe is null; interface released");
}
catch (UsbException ue)
{
throw new CM19AException("pipe is null; interface not released " + ue.getMessage());
}
}
catch (UsbException ue2)
{
try
{
endPoint.getUsbPipe().close();
endPoint.getUsbInterface().release();
throw new CM19AException("Usb exception on pipe " + ue2.getMessage() + "; interface released");
}
catch (UsbException ue)
{
throw new CM19AException("Usb exception on pipe " + ue2.getMessage() + "; interface not released " + ue.getMessage());
}
}
return outPipe;
}
public LinkedList<byte[]> getOutQueue()
{
return outQueue;
}
@Override
public void run()
{
running = true;
// Restart driver automatically when any error occurs until explicitly stopped
while (running)
{
logger.info("Starting driver... ");
UsbInterfaceInfos cm19aDeviceInfos = getUsbDeviceInfos();
ReceiverThread receiverThread = createReceiverThread(cm19aDeviceInfos);
TransmitterThread transmitterThread = createTransmitterThread(cm19aDeviceInfos);
logger.info("Driver started!");
try
{
receiverThread.start();
transmitterThread.start();
while (running)
{
byte[] buffer = new byte[16];
int readCount = cm19aInputReader.read(buffer);
if (readCount > 0)
{
String string = new String(buffer);
if (send(string.trim()))
{
// explicitly closing the driver
running = false;
}
}
else
{
Thread.sleep(250);
}
}
}
catch (Exception e)
{
logger.error(e);
}
finally
{
if (receiverThread != null)
{
receiverThread.quit();
}
if (transmitterThread != null)
{
transmitterThread.quit();
}
logger.info("Driver stopped");
}
}
}
public void quit()
{
logger.info("Closing driver");
cm19aInputReader.close();
try
{
usbInterface.release();
}
catch (Exception e)
{}
logger.info("Driver closed");
}
}