/*
* 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 android.util.Log;
import ioio.lib.api.AnalogInput;
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.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 java.io.IOException;
public class IOIOImpl implements IOIO, DisconnectListener {
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', '3' };
private final IOIOConnection connection_;
private final IncomingState incomingState_ = new IncomingState();
private final boolean openPins_[] = new boolean[Constants.NUM_PINS];
private final boolean openTwi_[] = new boolean[Constants.NUM_TWI_MODULES];
private boolean openIcsp_ = false;
private final ModuleAllocator pwmAllocator_ = new ModuleAllocator(
Constants.NUM_PWM_MODULES, "PWM");
private final ModuleAllocator uartAllocator_ = new ModuleAllocator(
Constants.NUM_UART_MODULES, "UART");
private final ModuleAllocator spiAllocator_ = new ModuleAllocator(
Constants.NUM_SPI_MODULES, "SPI");
private final ModuleAllocator incapAllocatorDouble_ = new ModuleAllocator(
Constants.INCAP_MODULES_DOUBLE, "INCAP_DOUBLE");
private final ModuleAllocator incapAllocatorSingle_ = new ModuleAllocator(
Constants.INCAP_MODULES_SINGLE, "INCAP_SINGLE");
IOIOProtocol protocol_;
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();
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 (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) {
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 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(int pin) {
try {
checkState();
if (!openPins_[pin]) {
throw new IllegalStateException("Pin not open: " + pin);
}
protocol_.setPinDigitalIn(pin, DigitalInput.Spec.Mode.FLOATING);
openPins_[pin] = false;
} catch (IOException e) {
} catch (ConnectionLostException e) {
}
}
synchronized void closePwm(int pwmNum) {
try {
checkState();
pwmAllocator_.releaseModule(pwmNum);
protocol_.setPwmPeriod(pwmNum, 0, PwmScale.SCALE_1X);
} catch (IOException e) {
} catch (ConnectionLostException e) {
}
}
synchronized void closeUart(int uartNum) {
try {
checkState();
uartAllocator_.releaseModule(uartNum);
protocol_.uartClose(uartNum);
} catch (IOException e) {
} catch (ConnectionLostException e) {
}
}
synchronized void closeTwi(int twiNum) {
try {
checkState();
if (!openTwi_[twiNum]) {
throw new IllegalStateException("TWI not open: " + twiNum);
}
openTwi_[twiNum] = false;
openPins_[Constants.TWI_PINS[twiNum][0]] = false;
openPins_[Constants.TWI_PINS[twiNum][1]] = false;
protocol_.i2cClose(twiNum);
} catch (IOException e) {
} catch (ConnectionLostException e) {
}
}
synchronized void closeIcsp() {
try {
checkState();
if (!openIcsp_) {
throw new IllegalStateException("ICSP not open");
}
openIcsp_ = false;
openPins_[Constants.ICSP_PINS[0]] = false;
openPins_[Constants.ICSP_PINS[1]] = false;
protocol_.icspClose();
} catch (ConnectionLostException e) {
} catch (IOException e) {
}
}
synchronized void closeSpi(int spiNum) {
try {
checkState();
spiAllocator_.releaseModule(spiNum);
protocol_.spiClose(spiNum);
} catch (IOException e) {
} catch (ConnectionLostException e) {
}
}
synchronized void closeIncap(int incapNum, boolean doublePrecision) {
try {
checkState();
if (doublePrecision) {
incapAllocatorDouble_.releaseModule(incapNum);
} else {
incapAllocatorSingle_.releaseModule(incapNum);
}
protocol_.incapClose(incapNum);
} catch (IOException e) {
} catch (ConnectionLostException 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) throws ConnectionLostException {
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 "IOIO0323";
}
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();
PinFunctionMap.checkValidPin(spec.pin);
checkPinFree(spec.pin);
DigitalInputImpl result = new DigitalInputImpl(this, spec.pin);
addDisconnectListener(result);
openPins_[spec.pin] = true;
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();
PinFunctionMap.checkValidPin(spec.pin);
checkPinFree(spec.pin);
DigitalOutputImpl result = new DigitalOutputImpl(this, spec.pin, startValue);
addDisconnectListener(result);
openPins_[spec.pin] = true;
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 pin)
throws ConnectionLostException
{
checkState();
PinFunctionMap.checkSupportsAnalogInput(pin);
checkPinFree(pin);
AnalogInputImpl result = new AnalogInputImpl(this, pin);
addDisconnectListener(result);
openPins_[pin] = true;
incomingState_.addInputPinListener(pin, result);
try {
protocol_.setPinAnalogIn(pin);
protocol_.setAnalogInSampling(pin, 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();
PinFunctionMap.checkSupportsPeripheralOutput(spec.pin);
checkPinFree(spec.pin);
int pwmNum = pwmAllocator_.allocateModule();
int scale = 0;
float baseUs;
int period;
while (true) {
final int clk = 16000000 / 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, spec.pin, pwmNum, period, baseUs);
addDisconnectListener(pwm);
openPins_[spec.pin] = true;
try {
protocol_.setPinDigitalOut(spec.pin, false, spec.mode);
protocol_.setPinPwm(spec.pin, pwmNum, true);
protocol_.setPwmPeriod(pwmNum, period - 1,
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) {
PinFunctionMap.checkSupportsPeripheralInput(rx.pin);
checkPinFree(rx.pin);
}
if (tx != null) {
PinFunctionMap.checkSupportsPeripheralOutput(tx.pin);
checkPinFree(tx.pin);
}
int rxPin = rx != null ? rx.pin : INVALID_PIN;
int txPin = tx != null ? tx.pin : INVALID_PIN;
int uartNum = uartAllocator_.allocateModule();
UartImpl uart = new UartImpl(this, txPin, rxPin, uartNum);
addDisconnectListener(uart);
incomingState_.addUartListener(uartNum, uart);
try {
if (rx != null) {
openPins_[rx.pin] = true;
protocol_.setPinDigitalIn(rx.pin, rx.mode);
protocol_.setPinUart(rx.pin, uartNum, false, true);
}
if (tx != null) {
openPins_[tx.pin] = true;
protocol_.setPinDigitalOut(tx.pin, true, tx.mode);
protocol_.setPinUart(tx.pin, uartNum, 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(uartNum, rate, speed4x, stopbits, parity);
} catch (IOException e) {
uart.close();
throw new ConnectionLostException(e);
}
return uart;
}
@Override
synchronized public TwiMaster openTwiMaster(int twiNum, Rate rate,
boolean smbus) throws ConnectionLostException
{
checkState();
checkTwiFree(twiNum);
checkPinFree(Constants.TWI_PINS[twiNum][0]);
checkPinFree(Constants.TWI_PINS[twiNum][1]);
openPins_[Constants.TWI_PINS[twiNum][0]] = true;
openPins_[Constants.TWI_PINS[twiNum][1]] = true;
openTwi_[twiNum] = true;
TwiMasterImpl twi = new TwiMasterImpl(this, twiNum);
addDisconnectListener(twi);
incomingState_.addTwiListener(twiNum, twi);
try {
protocol_.i2cConfigureMaster(twiNum, rate, smbus);
} catch (IOException e) {
twi.close();
throw new ConnectionLostException(e);
}
return twi;
}
@Override
synchronized public IcspMaster openIcspMaster()
throws ConnectionLostException
{
checkState();
checkIcspFree();
checkPinFree(Constants.ICSP_PINS[0]);
checkPinFree(Constants.ICSP_PINS[1]);
checkPinFree(Constants.ICSP_PINS[2]);
openPins_[Constants.ICSP_PINS[0]] = true;
openPins_[Constants.ICSP_PINS[1]] = true;
openPins_[Constants.ICSP_PINS[2]] = true;
openIcsp_ = true;
IcspMasterImpl icsp = new IcspMasterImpl(this);
addDisconnectListener(icsp);
incomingState_.addIcspListener(icsp);
try {
protocol_.icspOpen();
} catch (IOException e) {
icsp.close();
throw new ConnectionLostException(e);
}
return icsp;
}
@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();
int ssPins[] = new int[slaveSelect.length];
checkPinFree(miso.pin);
PinFunctionMap.checkSupportsPeripheralInput(miso.pin);
checkPinFree(mosi.pin);
PinFunctionMap.checkSupportsPeripheralOutput(mosi.pin);
checkPinFree(clk.pin);
PinFunctionMap.checkSupportsPeripheralOutput(clk.pin);
for (int i = 0; i < slaveSelect.length; ++i) {
checkPinFree(slaveSelect[i].pin);
ssPins[i] = slaveSelect[i].pin;
}
int spiNum = spiAllocator_.allocateModule();
SpiMasterImpl spi = new SpiMasterImpl(this, spiNum, mosi.pin, miso.pin,
clk.pin, ssPins);
addDisconnectListener(spi);
openPins_[miso.pin] = true;
openPins_[mosi.pin] = true;
openPins_[clk.pin] = true;
for (int i = 0; i < slaveSelect.length; ++i) {
openPins_[slaveSelect[i].pin] = true;
}
incomingState_.addSpiListener(spiNum, spi);
try {
protocol_.setPinDigitalIn(miso.pin, miso.mode);
protocol_.setPinSpi(miso.pin, 1, true, spiNum);
protocol_.setPinDigitalOut(mosi.pin, true, mosi.mode);
protocol_.setPinSpi(mosi.pin, 0, true, spiNum);
protocol_.setPinDigitalOut(clk.pin, config.invertClk, clk.mode);
protocol_.setPinSpi(clk.pin, 2, true, spiNum);
for (DigitalOutput.Spec spec : slaveSelect) {
protocol_.setPinDigitalOut(spec.pin, true, spec.mode);
}
protocol_.spiConfigureMaster(spiNum, config);
} catch (IOException e) {
spi.close();
throw new ConnectionLostException(e);
}
return spi;
}
@Override
public PulseInput openPulseInput(Spec spec, ClockRate rate, PulseMode mode,
boolean doublePrecision) throws ConnectionLostException
{
checkState();
checkPinFree(spec.pin);
PinFunctionMap.checkSupportsPeripheralInput(spec.pin);
int incapNum = doublePrecision ? incapAllocatorDouble_.allocateModule()
: incapAllocatorSingle_.allocateModule();
IncapImpl incap = new IncapImpl(this, mode, incapNum, spec.pin,
rate.hertz, mode.scaling, doublePrecision);
addDisconnectListener(incap);
incomingState_.addIncapListener(incapNum, incap);
openPins_[spec.pin] = true;
try {
protocol_.setPinDigitalIn(spec.pin, spec.mode);
protocol_.setPinIncap(spec.pin, incapNum, true);
protocol_.incapConfigure(incapNum, doublePrecision,
mode.ordinal() + 1, rate.ordinal());
} catch (IOException e) {
incap.close();
throw new ConnectionLostException(e);
}
return incap;
}
@Override
public PulseInput openPulseInput(int pin, PulseMode mode)
throws ConnectionLostException
{
return openPulseInput(new DigitalInput.Spec(pin), ClockRate.RATE_16MHz,
mode, true);
}
private void checkPinFree(int pin) {
if (openPins_[pin]) {
throw new IllegalArgumentException("Pin already open: " + pin);
}
}
private void checkTwiFree(int twi) {
if (openTwi_[twi]) {
throw new IllegalArgumentException("TWI already open: " + twi);
}
}
private void checkIcspFree() {
if (openIcsp_) {
throw new IllegalArgumentException("ICSP already open");
}
}
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);
}
}
}