package org.zu.ardulink.connection.pi;
import static org.zu.ardulink.util.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.zu.ardulink.ConnectionContact;
import org.zu.ardulink.connection.Connection;
import org.zu.ardulink.protocol.ALProtocol;
import org.zu.ardulink.protocol.IProtocol;
import org.zu.ardulink.protocol.parser.IProtocolMessageStore;
import org.zu.ardulink.protocol.parser.IProtocolParser;
import org.zu.ardulink.protocol.parser.MessageParsedInfo;
import org.zu.ardulink.protocol.parser.MessageType;
import org.zu.ardulink.protocol.parser.ParseException;
import org.zu.ardulink.protocol.parser.ProtocolParserHandler;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPin;
import com.pi4j.io.gpio.GpioPinAnalogInput;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.GpioPinPwmOutput;
import com.pi4j.io.gpio.PinMode;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.event.GpioPinAnalogValueChangeEvent;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerAnalog;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import com.pi4j.io.gpio.event.PinEventType;
public class RaspberryPIConnection implements Connection {
public static final String CONNECTION_NAME = "Raspberry PI";
private ConnectionContact connectionContact;
/**
* The status of the connection.
*/
private boolean connected;
private GpioController gpioController;
/**
* The protocol used by the link instance for this connection
*/
private IProtocolParser protocolParser;
private IProtocolMessageStore messageStore;
/**
* Listeners
*/
private Map<Integer, GpioPinListenerDigital> digitalListeners = new HashMap<Integer, GpioPinListenerDigital>();
private Map<Integer, GpioPinListenerAnalog> analogListeners = new HashMap<Integer, GpioPinListenerAnalog>();
public RaspberryPIConnection() {
this(ALProtocol.NAME);
}
public RaspberryPIConnection(String protocolName) {
super();
protocolParser = ProtocolParserHandler.getProtocolParserImplementation(protocolName);
checkNotNull(protocolParser, "Protocol not supported. Resiter the right parser for: " + protocolName);
messageStore = protocolParser.getMessageStore();
}
@Override
public List<String> getPortList() {
return Collections.singletonList(CONNECTION_NAME);
}
public boolean connect() {
if(!connected) {
gpioController = GpioFactory.getInstance();
if(gpioController != null) {
connected = true;
if(connectionContact != null) {
connectionContact.connected(CONNECTION_NAME, CONNECTION_NAME);
}
}
}
return connected;
}
@Override
public boolean connect(Object... params) {
checkNotNull(params, "Params must be null");
return connect();
}
@Override
public boolean disconnect() {
if(connected) {
gpioController.shutdown();
gpioController = null;
connected = false;
if(connectionContact != null) {
connectionContact.disconnected(CONNECTION_NAME);
}
}
return !connected;
}
@Override
public boolean isConnected() {
return connected;
}
@Override
public boolean writeSerial(String message) {
boolean success = false;
if (isConnected()) {
try {
success = true;
messageStore.addMessageChunck(message);
if(messageStore.isMessageComplete()) {
MessageParsedInfo messageParsedInfo = protocolParser.parse(messageStore.getNextMessage());
success = processMessage(messageParsedInfo);
if(messageParsedInfo.getId() != IProtocol.UNDEFINED_ID) {
reply(success, messageParsedInfo);
}
}
} catch (ParseException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
disconnect();
}
} else {
connectionContact.writeLog(CONNECTION_NAME, "No port is connected.");
}
return success;
}
private void reply(final boolean success, final MessageParsedInfo messageParsedInfo) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int[] reply = protocolParser.reply(success, messageParsedInfo);
connectionContact.parseInput(CONNECTION_NAME, reply.length, reply);
}
});
thread.start();
}
@Override
public boolean writeSerial(int numBytes, int[] message) {
throw new UnsupportedOperationException("Please use: writeSerial(String message) instead.");
}
@Override
public void setConnectionContact(ConnectionContact contact) {
connectionContact = contact;
}
public void writeLog(String text) {
connectionContact.writeLog(CONNECTION_NAME, text);
}
private boolean processMessage(MessageParsedInfo messageParsedInfo) {
boolean retvalue = false;
if(MessageType.PPSW == messageParsedInfo.getMessageType()) {
retvalue = sendPowerPinSwitch(messageParsedInfo);
} else if(MessageType.PPIN == messageParsedInfo.getMessageType()) {
retvalue = sendPowerPinIntensity(messageParsedInfo);
} else if(MessageType.KPRS == messageParsedInfo.getMessageType()) {
throw new UnsupportedOperationException("This connection doesn't support key press messages");
} else if(MessageType.TONE == messageParsedInfo.getMessageType()) {
throw new UnsupportedOperationException("This connection doesn't support tone messages");
} else if(MessageType.NOTN == messageParsedInfo.getMessageType()) {
throw new UnsupportedOperationException("This connection doesn't support notone messages");
} else if(MessageType.SRLD == messageParsedInfo.getMessageType()) {
retvalue = startListenDigitalPin(messageParsedInfo);
} else if(MessageType.SPLD == messageParsedInfo.getMessageType()) {
retvalue = stopListenDigitalPin(messageParsedInfo);
} else if(MessageType.SRLA == messageParsedInfo.getMessageType()) {
retvalue = startListenAnalogPin(messageParsedInfo);
} else if(MessageType.SPLA == messageParsedInfo.getMessageType()) {
retvalue = stopListenAnalogPin(messageParsedInfo);
} else if(MessageType.CUST == messageParsedInfo.getMessageType()) {
throw new UnsupportedOperationException("This connection doesn't support custom messages");
} else if(MessageType.ARED == messageParsedInfo.getMessageType()) {
throw new IllegalStateException("Analog Read Event Message shoudn't come here");
} else if(MessageType.DRED == messageParsedInfo.getMessageType()) {
throw new IllegalStateException("Digital Read Event Message shoudn't come here");
} else if(MessageType.RPLY == messageParsedInfo.getMessageType()) {
throw new IllegalStateException("Reply Event Message shoudn't come here");
}
return retvalue;
}
private boolean sendPowerPinSwitch(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
int power = (Integer)messageParsedInfo.getParsedValues()[1];
GpioPinDigitalOutput pin = (GpioPinDigitalOutput)getPin(pinNum);
pin.setMode(PinMode.DIGITAL_OUTPUT);
if(power == IProtocol.LOW) {
pin.low();
} else {
pin.high();
}
return true;
}
private boolean sendPowerPinIntensity(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
int intensity = (Integer)messageParsedInfo.getParsedValues()[1];
GpioPinPwmOutput pin = (GpioPinPwmOutput )getPin(pinNum);
pin.setMode(PinMode.PWM_OUTPUT);
pin.setPwm(intensity);
return true;
}
private boolean startListenDigitalPin(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
GpioPinListenerDigital listener = digitalListeners.get(pinNum);
if(listener == null) {
listener = createDigitalListener(pinNum);
GpioPinDigitalInput pin = (GpioPinDigitalInput)getPin(pinNum);
pin.setMode(PinMode.DIGITAL_INPUT);
pin.setPullResistance(PinPullResistance.PULL_DOWN);
digitalListeners.put(pinNum, listener);
pin.addListener(listener);
}
return true;
}
private boolean stopListenDigitalPin(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
GpioPinListenerDigital listener = digitalListeners.get(pinNum);
if(listener != null) {
GpioPinDigitalInput pin = (GpioPinDigitalInput)getPin(pinNum);
digitalListeners.remove(pin);
pin.removeListener(listener);
}
return true;
}
private boolean startListenAnalogPin(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
GpioPinListenerAnalog listener = analogListeners.get(pinNum);
if(listener == null) {
listener = createAnalogListener(pinNum);
GpioPinAnalogInput pin = (GpioPinAnalogInput)getPin(pinNum);
pin.setMode(PinMode.ANALOG_INPUT);
pin.setPullResistance(PinPullResistance.PULL_DOWN);
analogListeners.put(pinNum, listener);
pin.addListener(listener);
}
return true;
}
private boolean stopListenAnalogPin(MessageParsedInfo messageParsedInfo) {
int pinNum = (Integer)messageParsedInfo.getParsedValues()[0];
GpioPinListenerAnalog listener = analogListeners.get(pinNum);
if(listener != null) {
GpioPinAnalogInput pin = (GpioPinAnalogInput)getPin(pinNum);
analogListeners.remove(pin);
pin.removeListener(listener);
}
return true;
}
private GpioPin getPin(int address) {
GpioPin retvalue = null;
Collection<GpioPin> provisioned = gpioController.getProvisionedPins();
for (GpioPin gpioPin : provisioned) {
if(gpioPin.getPin().getAddress() == address) {
retvalue = gpioPin;
break;
}
}
if(retvalue == null) {
retvalue = gpioController.provisionPin(RaspiPin.getPinByName("GPIO " + address), PinMode.DIGITAL_OUTPUT);
}
return retvalue;
}
private GpioPinListenerDigital createDigitalListener(int pinNum) {
return new DigitalListener(pinNum);
}
private GpioPinListenerAnalog createAnalogListener(int pinNum) {
return new AnalogListener(pinNum);
}
class DigitalListener implements GpioPinListenerDigital {
private int pin;
public DigitalListener(int pin) {
super();
this.pin = pin;
}
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
if(event.getEventType() == PinEventType.DIGITAL_STATE_CHANGE) {
int[] message;
if(event.getState().isHigh()) {
message = protocolParser.digitalRead(pin, IProtocol.HIGH);
} else {
message = protocolParser.digitalRead(pin, IProtocol.LOW);
}
connectionContact.parseInput(CONNECTION_NAME, message.length, message);
}
}
}
class AnalogListener implements GpioPinListenerAnalog {
private int pin;
public AnalogListener(int pin) {
super();
this.pin = pin;
}
@Override
public void handleGpioPinAnalogValueChangeEvent(GpioPinAnalogValueChangeEvent event) {
if(event.getEventType() == PinEventType.ANALOG_VALUE_CHANGE) {
int[] message = protocolParser.analogRead(pin, (int)event.getValue());
connectionContact.parseInput(CONNECTION_NAME, message.length, message);
}
}
}
}