001package jmri.jmrix;
002
003import java.io.DataInputStream;
004import java.io.DataOutputStream;
005import java.util.ArrayList;
006import java.util.List;
007import javax.annotation.Nonnull;
008import javax.usb.UsbControlIrp;
009import javax.usb.UsbDevice;
010import javax.usb.UsbDisconnectedException;
011import javax.usb.UsbException;
012
013import jmri.SystemConnectionMemo;
014import jmri.util.usb.UsbUtil;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018/**
019 * Enables basic setup of a USB interface for a jmrix implementation.
020 *
021 * @author George Warner Copyright (c) 2017-2018
022 */
023public class UsbPortAdapter extends AbstractPortController {
024
025    private Short vendorID = 0;
026    private Short productID = 0;
027    private String serialNumber = null;
028    protected UsbDevice usbDevice = null;
029
030    public UsbPortAdapter(SystemConnectionMemo memo) {
031        super(memo);
032    }
033
034    public Short getVendorID() {
035        return vendorID;
036    }
037
038    public void setVendorID(Short value) {
039        vendorID = value;
040    }
041
042    public Short getProductID() {
043        return productID;
044    }
045
046    public void setProductID(Short value) {
047        productID = value;
048    }
049
050    /**
051     * Get the device serial number.
052     *
053     * @return the serial number or null if there is no serial number
054     */
055    public String getSerialNumber() {
056        if (serialNumber != null && serialNumber.trim().isEmpty()) {
057            serialNumber = null;
058        }
059        return serialNumber;
060    }
061
062    /**
063     * Set the device serial number.
064     *
065     * @param serialNumber the serial number; if null, empty, or only containing
066     *                     whitespace, sets property to null
067     */
068    public void setSerialNumber(String serialNumber) {
069        if (serialNumber == null || serialNumber.trim().isEmpty()) {
070            this.serialNumber = null;
071        } else {
072            this.serialNumber = serialNumber;
073        }
074    }
075
076    public UsbDevice getUsbDevice() {
077        if (usbDevice == null) {
078            log.debug("Getting device at {}", port);
079            String error = openPort(port, serialNumber);
080            if (error != null) {
081                log.error("Could not open {}",error);
082            }
083        }
084        return usbDevice;
085    }
086
087    public String openPort(String portName, String serialNumber) {
088        usbDevice = UsbUtil.getMatchingDevice(vendorID, productID, serialNumber, portName);
089        if (usbDevice == null) {
090            List< UsbDevice> usbDevices = UsbUtil.getMatchingDevices(vendorID, productID, serialNumber);
091            if (usbDevices.size() == 1) {
092                usbDevice = usbDevices.get(0);
093            } else {
094                // search for device with same vendor/product ID, but possibly different serial number
095                usbDevices = UsbUtil.getMatchingDevices(vendorID, productID, null);
096                if (usbDevices.size() == 1) {
097                    usbDevice = usbDevices.get(0);
098                } else {
099                    return String.format("Single USB device with vendor id %s and product id %s not found.", vendorID, productID);
100                }
101            }
102        }
103        return null;
104    }
105
106    /**
107     * {@inheritDoc}
108     */
109    @Override
110    public void connect() throws java.io.IOException {
111        log.debug("connect()");
112    }
113
114    /**
115     * {@inheritDoc}
116     */
117    @Override
118    public DataInputStream getInputStream() {
119        log.debug("getInputStream()");
120        return null;
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public DataOutputStream getOutputStream() {
128        log.debug("getOutputStream()");
129        return null;
130    }
131
132    /**
133     * {@inheritDoc}
134     */
135    @Override
136    public void recover() {
137        log.debug("recover()");
138    }
139
140    /**
141     * {@inheritDoc}
142     */
143    @Override
144    public void configure() {
145        log.debug("configure()");
146    }
147
148    /**
149     * Get the list of USB locations with devices matching a single
150     * vendor/product ID combination. These are "portNames" to match the calling
151     * API.
152     *
153     * @return the list of locations with matching devices; this is an empty
154     *         list if there are no matches
155     */
156    @Nonnull
157    public List<String> getPortNames() {
158        log.debug("getPortNames()");
159
160        List<String> results = new ArrayList<>();
161        List<UsbDevice> usbDevices = UsbUtil.getMatchingDevices(vendorID, productID, null);
162        usbDevices.forEach((device) -> {
163            results.add(UsbUtil.getLocation(device));
164        });
165
166        return results;
167    }
168
169    private String port = null;
170
171    public void setPort(String s) {
172        log.debug("setPort('{}')", s);
173        port = s;
174    }
175
176    /**
177     * {@inheritDoc}
178     */
179    @Override
180    public String getCurrentPortName() {
181        log.debug("getCurrentPortName()");
182        return port;
183    }
184
185    /**
186     * send USB control transfer
187     *
188     * @param requestType the request type
189     * @param request     the request
190     * @param value       the value
191     * @param index       the index
192     * @param data        the data
193     * @return true if successful sent
194     */
195    public boolean sendControlTransfer(int requestType, int request, int value, int index, byte[] data) {
196        boolean result = false;    // assume failure (pessimist!)
197        if (usbDevice != null) {
198            try {
199                UsbControlIrp usbControlIrp = usbDevice.createUsbControlIrp(
200                        (byte) requestType, (byte) request,
201                        (short) value, (short) index);
202                if (data == null) {
203                    data = new byte[0];
204                }
205                usbControlIrp.setData(data);
206                usbControlIrp.setLength(data.length);
207
208                //log.trace("sendControlTransfer,  requestType: {}, request: {}, value: {}, index: {}, data: {}", requestType, request, value, index, getByteString(data));
209                usbDevice.syncSubmit(usbControlIrp);
210                result = true; // it's good!
211            } catch (IllegalArgumentException | UsbException | UsbDisconnectedException e) {
212                log.error("Exception transferring control", e);
213            }
214        }
215        return result;
216    }
217
218    private final static Logger log = LoggerFactory.getLogger(UsbPortAdapter.class
219    );
220}