/* * Copyright 2011 Ytai Ben-Tsvi. All rights reserved. * * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARSHAN POURSOHI OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied. */ package ioio.lib.impl; import java.io.IOException; import ioio.lib.api.AnalogInput; import ioio.lib.api.CapSense; import ioio.lib.api.DigitalInput; import ioio.lib.api.DigitalInput.Spec; import ioio.lib.api.DigitalInput.Spec.Mode; import ioio.lib.api.DigitalOutput; import ioio.lib.api.IOIO; import ioio.lib.api.IOIOConnection; import ioio.lib.api.IcspMaster; import ioio.lib.api.PulseInput; import ioio.lib.api.PulseInput.ClockRate; import ioio.lib.api.PulseInput.PulseMode; import ioio.lib.api.PwmOutput; import ioio.lib.api.Sequencer; import ioio.lib.api.SpiMaster; import ioio.lib.api.TwiMaster; import ioio.lib.api.TwiMaster.Rate; import ioio.lib.api.Uart; import ioio.lib.api.exception.ConnectionLostException; import ioio.lib.api.exception.IncompatibilityException; import ioio.lib.impl.IOIOProtocol.PwmScale; import ioio.lib.impl.IncomingState.DisconnectListener; import ioio.lib.impl.ResourceManager.Resource; import ioio.lib.impl.ResourceManager.ResourceType; import ioio.lib.spi.Log; public class IOIOImpl implements IOIO, DisconnectListener { private static class SyncListener implements IncomingState.SyncListener, DisconnectListener { enum State {WAITING, SIGNALED, DISCONNECTED} ; private State state_ = State.WAITING; @Override public synchronized void sync() { state_ = State.SIGNALED; notifyAll(); } public synchronized void waitSync() throws InterruptedException, ConnectionLostException { while (state_ == State.WAITING) { wait(); } if (state_ == State.DISCONNECTED) { throw new ConnectionLostException(); } } @Override public synchronized void disconnected() { state_ = State.DISCONNECTED; notifyAll(); } } private static final String TAG = "IOIOImpl"; private boolean disconnect_ = false; private static final byte[] REQUIRED_INTERFACE_ID = new byte[]{'I', 'O', 'I', 'O', '0', '0', '0', '5'}; IOIOProtocol protocol_; ResourceManager resourceManager_; IncomingState incomingState_ = new IncomingState(); Board.Hardware hardware_; private IOIOConnection connection_; private State state_ = State.INIT; public IOIOImpl(IOIOConnection con) { connection_ = con; } @Override public void waitForConnect() throws ConnectionLostException, IncompatibilityException { if (state_ == State.CONNECTED) { return; } if (state_ == State.DEAD) { throw new ConnectionLostException(); } addDisconnectListener(this); Log.d(TAG, "Waiting for IOIO connection"); try { try { Log.v(TAG, "Waiting for underlying connection"); connection_.waitForConnect(); synchronized (this) { if (disconnect_) { throw new ConnectionLostException(); } protocol_ = new IOIOProtocol(connection_.getInputStream(), connection_.getOutputStream(), incomingState_); // Once this block exits, a disconnect will also involve // softClose(). } } catch (ConnectionLostException e) { incomingState_.handleConnectionLost(); throw e; } Log.v(TAG, "Waiting for handshake"); incomingState_.waitConnectionEstablished(); initBoard(); Log.v(TAG, "Querying for required interface ID"); checkInterfaceVersion(); Log.v(TAG, "Required interface ID is supported"); state_ = State.CONNECTED; Log.i(TAG, "IOIO connection established"); } catch (ConnectionLostException e) { Log.d(TAG, "Connection lost / aborted"); state_ = State.DEAD; throw e; } catch (IncompatibilityException e) { throw e; } catch (InterruptedException e) { Log.e(TAG, "Unexpected exception", e); } } @Override public synchronized void disconnect() { Log.d(TAG, "Client requested disconnect."); if (disconnect_) { return; } disconnect_ = true; try { if (protocol_ != null && !connection_.canClose()) { protocol_.softClose(); } } catch (IOException e) { Log.e(TAG, "Soft close failed", e); } connection_.disconnect(); } @Override public synchronized void disconnected() { state_ = State.DEAD; if (disconnect_) { return; } Log.d(TAG, "Physical disconnect."); disconnect_ = true; // The IOIOConnection doesn't necessarily know about the disconnect connection_.disconnect(); } @Override public void waitForDisconnect() throws InterruptedException { incomingState_.waitDisconnect(); } @Override public State getState() { return state_; } private void initBoard() throws IncompatibilityException { if (incomingState_.board_ == null) { throw new IncompatibilityException("Unknown board: " + incomingState_.hardwareId_); } hardware_ = incomingState_.board_.hardware; resourceManager_ = new ResourceManager(hardware_); } private void checkInterfaceVersion() throws IncompatibilityException, ConnectionLostException, InterruptedException { try { protocol_.checkInterface(REQUIRED_INTERFACE_ID); } catch (IOException e) { throw new ConnectionLostException(e); } if (!incomingState_.waitForInterfaceSupport()) { state_ = State.INCOMPATIBLE; Log.e(TAG, "Required interface ID is not supported"); throw new IncompatibilityException( "IOIO firmware does not support required firmware: " + new String(REQUIRED_INTERFACE_ID)); } } synchronized void removeDisconnectListener(DisconnectListener listener) { incomingState_.removeDisconnectListener(listener); } synchronized void addDisconnectListener(DisconnectListener listener) throws ConnectionLostException { incomingState_.addDisconnectListener(listener); } synchronized void closePin(ResourceManager.Resource pin) { try { protocol_.setPinDigitalIn(pin.id, DigitalInput.Spec.Mode.FLOATING); resourceManager_.free(pin); } catch (IOException e) { } } @Override synchronized public void softReset() throws ConnectionLostException { checkState(); try { protocol_.softReset(); } catch (IOException e) { throw new ConnectionLostException(e); } } @Override synchronized public void hardReset() throws ConnectionLostException { checkState(); try { protocol_.hardReset(); } catch (IOException e) { throw new ConnectionLostException(e); } } @Override public String getImplVersion(VersionType v) { if (state_ == State.INIT) { throw new IllegalStateException( "Connection has not yet been established"); } switch (v) { case HARDWARE_VER: return incomingState_.hardwareId_; case BOOTLOADER_VER: return incomingState_.bootloaderId_; case APP_FIRMWARE_VER: return incomingState_.firmwareId_; case IOIOLIB_VER: return "IOIO0504"; } return null; } @Override public DigitalInput openDigitalInput(int pin) throws ConnectionLostException { return openDigitalInput(new DigitalInput.Spec(pin)); } @Override public DigitalInput openDigitalInput(int pin, Mode mode) throws ConnectionLostException { return openDigitalInput(new DigitalInput.Spec(pin, mode)); } @Override synchronized public DigitalInput openDigitalInput(DigitalInput.Spec spec) throws ConnectionLostException { checkState(); Resource pin = new Resource(ResourceType.PIN, spec.pin); resourceManager_.alloc(pin); DigitalInputImpl result = new DigitalInputImpl(this, pin); addDisconnectListener(result); incomingState_.addInputPinListener(spec.pin, result); try { protocol_.setPinDigitalIn(spec.pin, spec.mode); protocol_.setChangeNotify(spec.pin, true); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public DigitalOutput openDigitalOutput(int pin, ioio.lib.api.DigitalOutput.Spec.Mode mode, boolean startValue) throws ConnectionLostException { return openDigitalOutput(new DigitalOutput.Spec(pin, mode), startValue); } @Override synchronized public DigitalOutput openDigitalOutput( DigitalOutput.Spec spec, boolean startValue) throws ConnectionLostException { checkState(); Resource pin = new Resource(ResourceType.PIN, spec.pin); resourceManager_.alloc(pin); DigitalOutputImpl result = new DigitalOutputImpl(this, pin, startValue); addDisconnectListener(result); try { protocol_.setPinDigitalOut(spec.pin, startValue, spec.mode); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public DigitalOutput openDigitalOutput(int pin, boolean startValue) throws ConnectionLostException { return openDigitalOutput(new DigitalOutput.Spec(pin), startValue); } @Override public DigitalOutput openDigitalOutput(int pin) throws ConnectionLostException { return openDigitalOutput(new DigitalOutput.Spec(pin), false); } @Override synchronized public AnalogInput openAnalogInput(int pinNum) throws ConnectionLostException { checkState(); hardware_.checkSupportsAnalogInput(pinNum); Resource pin = new Resource(ResourceType.PIN, pinNum); resourceManager_.alloc(pin); AnalogInputImpl result = new AnalogInputImpl(this, pin); addDisconnectListener(result); incomingState_.addInputPinListener(pinNum, result); try { protocol_.setPinAnalogIn(pinNum); protocol_.setAnalogInSampling(pinNum, true); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public CapSense openCapSense(int pin) throws ConnectionLostException { return openCapSense(pin, CapSense.DEFAULT_COEF); } @Override public synchronized CapSense openCapSense(int pinNum, float filterCoef) throws ConnectionLostException { checkState(); hardware_.checkSupportsCapSense(pinNum); Resource pin = new Resource(ResourceType.PIN, pinNum); resourceManager_.alloc(pin); CapSenseImpl result = new CapSenseImpl(this, pin, filterCoef); addDisconnectListener(result); incomingState_.addInputPinListener(pinNum, result); try { protocol_.setPinCapSense(pinNum); protocol_.setCapSenseSampling(pinNum, true); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public PwmOutput openPwmOutput(int pin, int freqHz) throws ConnectionLostException { return openPwmOutput(new DigitalOutput.Spec(pin), freqHz); } @Override synchronized public PwmOutput openPwmOutput(DigitalOutput.Spec spec, int freqHz) throws ConnectionLostException { checkState(); hardware_.checkSupportsPeripheralOutput(spec.pin); Resource pin = new Resource(ResourceType.PIN, spec.pin); Resource oc = new Resource(ResourceType.OUTCOMPARE); resourceManager_.alloc(pin, oc); int scale = 0; float baseUs; int period; while (true) { final int clk = 16000000 / IOIOProtocol.PwmScale.values()[scale].scale; period = clk / freqHz; if (period <= 65536) { baseUs = 1000000.0f / clk; break; } if (++scale >= PwmScale.values().length) { throw new IllegalArgumentException("Frequency too low: " + freqHz); } } PwmImpl pwm = new PwmImpl(this, pin, oc, period, baseUs); addDisconnectListener(pwm); try { protocol_.setPinDigitalOut(spec.pin, false, spec.mode); protocol_.setPinPwm(spec.pin, oc.id, true); protocol_.setPwmPeriod(oc.id, period - 1, IOIOProtocol.PwmScale.values()[scale]); } catch (IOException e) { pwm.close(); throw new ConnectionLostException(e); } return pwm; } @Override public Uart openUart(int rx, int tx, int baud, Uart.Parity parity, Uart.StopBits stopbits) throws ConnectionLostException { return openUart(rx == INVALID_PIN ? null : new DigitalInput.Spec(rx), tx == INVALID_PIN ? null : new DigitalOutput.Spec(tx), baud, parity, stopbits); } @Override synchronized public Uart openUart(DigitalInput.Spec rx, DigitalOutput.Spec tx, int baud, Uart.Parity parity, Uart.StopBits stopbits) throws ConnectionLostException { checkState(); if (rx != null) { hardware_.checkSupportsPeripheralInput(rx.pin); } if (tx != null) { hardware_.checkSupportsPeripheralOutput(tx.pin); } Resource rxPin = rx != null ? new Resource(ResourceType.PIN, rx.pin) : null; Resource txPin = tx != null ? new Resource(ResourceType.PIN, tx.pin) : null; Resource uart = new Resource(ResourceType.UART); resourceManager_.alloc(rxPin, txPin, uart); UartImpl result = new UartImpl(this, txPin, rxPin, uart); addDisconnectListener(result); incomingState_.addUartListener(uart.id, result); try { if (rx != null) { protocol_.setPinDigitalIn(rx.pin, rx.mode); protocol_.setPinUart(rx.pin, uart.id, false, true); } if (tx != null) { protocol_.setPinDigitalOut(tx.pin, true, tx.mode); protocol_.setPinUart(tx.pin, uart.id, true, true); } boolean speed4x = true; int rate = Math.round(4000000.0f / baud) - 1; if (rate > 65535) { speed4x = false; rate = Math.round(1000000.0f / baud) - 1; } protocol_.uartConfigure(uart.id, rate, speed4x, stopbits, parity); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override synchronized public TwiMaster openTwiMaster(int twiNum, Rate rate, boolean smbus) throws ConnectionLostException { checkState(); final int[][] twiPins = hardware_.twiPins(); Resource twi = new Resource(ResourceType.TWI, twiNum); Resource[] pins = new Resource[]{ new Resource(ResourceType.PIN, twiPins[twiNum][0]), new Resource(ResourceType.PIN, twiPins[twiNum][1])}; resourceManager_.alloc(twi, pins); TwiMasterImpl result = new TwiMasterImpl(this, twi, pins); addDisconnectListener(result); incomingState_.addTwiListener(twiNum, result); try { protocol_.i2cConfigureMaster(twiNum, rate, smbus); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override synchronized public IcspMaster openIcspMaster() throws ConnectionLostException { checkState(); final int[] icspPins = hardware_.icspPins(); Resource icsp = new Resource(ResourceType.ICSP); Resource[] pins = new Resource[]{ new Resource(ResourceType.PIN, icspPins[0]), new Resource(ResourceType.PIN, icspPins[1]), new Resource(ResourceType.PIN, icspPins[2])}; resourceManager_.alloc(icsp, pins); IcspMasterImpl result = new IcspMasterImpl(this, icsp, pins); addDisconnectListener(result); incomingState_.addIcspListener(result); try { protocol_.icspOpen(); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public SpiMaster openSpiMaster(int miso, int mosi, int clk, int slaveSelect, SpiMaster.Rate rate) throws ConnectionLostException { return openSpiMaster(miso, mosi, clk, new int[]{slaveSelect}, rate); } @Override public SpiMaster openSpiMaster(int miso, int mosi, int clk, int[] slaveSelect, SpiMaster.Rate rate) throws ConnectionLostException { DigitalOutput.Spec[] slaveSpecs = new DigitalOutput.Spec[slaveSelect.length]; for (int i = 0; i < slaveSelect.length; ++i) { slaveSpecs[i] = new DigitalOutput.Spec(slaveSelect[i]); } return openSpiMaster(new DigitalInput.Spec(miso, Mode.PULL_UP), new DigitalOutput.Spec(mosi), new DigitalOutput.Spec(clk), slaveSpecs, new SpiMaster.Config(rate)); } @Override synchronized public SpiMaster openSpiMaster(DigitalInput.Spec miso, DigitalOutput.Spec mosi, DigitalOutput.Spec clk, DigitalOutput.Spec[] slaveSelect, SpiMaster.Config config) throws ConnectionLostException { checkState(); hardware_.checkSupportsPeripheralInput(miso.pin); hardware_.checkSupportsPeripheralOutput(mosi.pin); hardware_.checkSupportsPeripheralOutput(clk.pin); Resource ssPins[] = new Resource[slaveSelect.length]; Resource misoPin = new Resource(ResourceType.PIN, miso.pin); Resource mosiPin = new Resource(ResourceType.PIN, mosi.pin); Resource clkPin = new Resource(ResourceType.PIN, clk.pin); for (int i = 0; i < slaveSelect.length; ++i) { ssPins[i] = new Resource(ResourceType.PIN, slaveSelect[i].pin); } Resource spi = new Resource(ResourceType.SPI); resourceManager_.alloc(ssPins, misoPin, mosiPin, clkPin, spi); SpiMasterImpl result = new SpiMasterImpl(this, spi, mosiPin, misoPin, clkPin, ssPins); addDisconnectListener(result); incomingState_.addSpiListener(spi.id, result); try { protocol_.setPinDigitalIn(miso.pin, miso.mode); protocol_.setPinSpi(miso.pin, 1, true, spi.id); protocol_.setPinDigitalOut(mosi.pin, true, mosi.mode); protocol_.setPinSpi(mosi.pin, 0, true, spi.id); protocol_.setPinDigitalOut(clk.pin, config.invertClk, clk.mode); protocol_.setPinSpi(clk.pin, 2, true, spi.id); for (DigitalOutput.Spec spec : slaveSelect) { protocol_.setPinDigitalOut(spec.pin, true, spec.mode); } protocol_.spiConfigureMaster(spi.id, config); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public PulseInput openPulseInput(Spec spec, ClockRate rate, PulseMode mode, boolean doublePrecision) throws ConnectionLostException { checkState(); hardware_.checkSupportsPeripheralInput(spec.pin); Resource pin = new Resource(ResourceType.PIN, spec.pin); Resource incap = new Resource( doublePrecision ? ResourceType.INCAP_DOUBLE : ResourceType.INCAP_SINGLE); resourceManager_.alloc(pin, incap); IncapImpl result = new IncapImpl(this, mode, incap, pin, rate.hertz, mode.scaling, doublePrecision); addDisconnectListener(result); incomingState_.addIncapListener(incap.id, result); try { protocol_.setPinDigitalIn(spec.pin, spec.mode); protocol_.setPinIncap(spec.pin, incap.id, true); protocol_.incapConfigure(incap.id, doublePrecision, mode.ordinal() + 1, rate.ordinal()); } catch (IOException e) { result.close(); throw new ConnectionLostException(e); } return result; } @Override public PulseInput openPulseInput(int pin, PulseMode mode) throws ConnectionLostException { return openPulseInput(new DigitalInput.Spec(pin), ClockRate.RATE_16MHz, mode, true); } @Override public Sequencer openSequencer(Sequencer.ChannelConfig config[]) throws ConnectionLostException { return new SequencerImpl(this, config); } private void checkState() throws ConnectionLostException { if (state_ == State.DEAD) { throw new ConnectionLostException(); } if (state_ == State.INCOMPATIBLE) { throw new IllegalStateException( "Incompatibility has been reported - IOIO cannot be used"); } if (state_ != State.CONNECTED) { throw new IllegalStateException( "Connection has not yet been established"); } } @Override public synchronized void beginBatch() throws ConnectionLostException { checkState(); protocol_.beginBatch(); } @Override public synchronized void endBatch() throws ConnectionLostException { checkState(); try { protocol_.endBatch(); } catch (IOException e) { throw new ConnectionLostException(e); } } @Override public void sync() throws ConnectionLostException, InterruptedException { boolean added = false; SyncListener listener = new SyncListener(); try { synchronized (this) { checkState(); incomingState_.addSyncListener(listener); addDisconnectListener(listener); added = true; try { protocol_.sync(); } catch (IOException e) { throw new ConnectionLostException(e); } } listener.waitSync(); } finally { if (added) { removeDisconnectListener(listener); } } } }