/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.devices.raspberrypi;
import android.util.Log;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.content.ActionFactory;
import org.catrobat.catroid.content.SingleSprite;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.content.actions.BroadcastAction;
import org.catrobat.catroid.ui.fragment.SpriteFactory;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
public class RPiSocketConnection {
private static final String TAG = AsyncRPiTaskRunner.class.getSimpleName();
private Socket clientSocket;
private String rpiVersion;
private String host;
private boolean isConnected;
private OutputStream outToServer;
private DataOutputStream outStream;
private BufferedReader reader;
private ArrayList<Integer> availableGPIOs;
private int interruptReceiverPort;
private Thread receiverThread;
public RPiSocketConnection() {
}
public void connect(String host, int port) throws Exception {
if (isConnected) {
disconnect();
}
this.host = host;
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(host, port), 2000);
outToServer = clientSocket.getOutputStream();
outStream = new DataOutputStream(outToServer);
reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String hello = reader.readLine();
if (hello.startsWith("quit")) {
throw new NoConnectionException("Server refused to accept our connection!");
} else if (hello.startsWith("hello")) {
isConnected = true;
respondVersion();
readServerPort();
receiverThread = new Thread(new RPiSocketReceiver());
receiverThread.start();
}
}
public void disconnect() throws IOException {
if (!isConnected) {
return;
}
try {
processCommand("quit");
} catch (NoConnectionException e) {
Log.d(TAG, "Error during quit, this should not happen!");
}
isConnected = false;
clientSocket.close();
receiverThread.interrupt();
}
private void respondVersion() throws Exception {
String receivedLine = processCommand("rev");
rpiVersion = receivedLine.split(" ")[1];
availableGPIOs = RaspberryPiService.getInstance().getGpioList(rpiVersion);
}
private void readServerPort() throws Exception {
String receivedLine = processCommand("serverport");
interruptReceiverPort = Integer.parseInt(receivedLine.split(" ")[1]);
}
private String processCommand(String command) throws IOException, NoConnectionException {
if (!isConnected) {
throw new NoConnectionException("No active connection!");
}
Log.d(TAG, "Sending: " + command);
outStream.write(command.getBytes());
String receivedLine = reader.readLine();
Log.d(TAG, "Received: " + receivedLine);
if (receivedLine == null || !receivedLine.startsWith(command.split(" ")[0])) {
throw new IOException("Error with response");
}
return receivedLine;
}
private void callEvent(String broadcastMessage) {
Sprite dummySenderSprite = new SpriteFactory().newInstance(SingleSprite.class.getSimpleName());
dummySenderSprite.setName("raspi_interrupt_dummy");
BroadcastAction action = (BroadcastAction) ActionFactory.createBroadcastAction(dummySenderSprite,
broadcastMessage);
action.act(0);
}
public void setPin(int pin, boolean value) throws NoConnectionException, IOException, NoGpioException {
if (!isConnected) {
throw new NoConnectionException("No active connection!");
}
if (!availableGPIOs.contains(pin)) {
throw new NoGpioException("Pin out of range on this model!");
}
short valueShort = (short) (value ? 1 : 0);
String setRequestMessage = "set " + pin + " " + valueShort;
String receivedLine = processCommand(setRequestMessage);
String[] tokens = receivedLine.split(" ");
if (tokens.length != 3) {
throw new IOException("setRequest: Error with response");
}
}
public boolean getPin(int pin) throws NoConnectionException, IOException, NoGpioException {
if (!availableGPIOs.contains(pin)) {
throw new NoGpioException("Pin out of range on this model!");
}
String readRequestMsg = "read " + pin;
String receivedLine = processCommand(readRequestMsg);
String[] tokens = receivedLine.split(" ");
if (tokens.length != 3) {
throw new IOException("readRequest: Error with response");
}
if (tokens[2].equals("1")) {
return true;
} else if (tokens[2].equals("0")) {
return false;
} else {
throw new IOException("readRequest: Error with response");
}
}
public void activatePinInterrupt(int pin) throws NoConnectionException, IOException, NoGpioException {
if (!availableGPIOs.contains(pin)) {
throw new NoGpioException("Pin out of range on this model!");
}
String readRequestMsg = "readint " + pin;
String receivedLine = processCommand(readRequestMsg);
String[] tokens = receivedLine.split(" ");
if (tokens.length != 3) {
throw new IOException("readRequest: Error with response");
}
}
public void setPWM(int pin, double frequencyInHz, double dutyCycleInPercent) throws NoConnectionException,
IOException, NoGpioException {
if (!availableGPIOs.contains(pin)) {
throw new NoGpioException("Pin out of range on this model!");
}
String pwmRequestMessage = "pwm " + pin + " " + frequencyInHz + " " + dutyCycleInPercent;
String receivedLine = processCommand(pwmRequestMessage);
if (!pwmRequestMessage.equals(receivedLine)) {
throw new IOException("pwmRequest: Error with response");
}
}
private class RPiSocketReceiver implements Runnable {
@Override
public void run() {
Socket receiverSocket = null;
try {
receiverSocket = new Socket(host, interruptReceiverPort);
BufferedReader receiverReader = new BufferedReader(new InputStreamReader(receiverSocket.getInputStream()));
while (!Thread.interrupted()) {
String receivedLine = receiverReader.readLine();
if (receivedLine == null) {
break;
}
Log.d(TAG, "Interrupt: " + receivedLine);
callEvent(Constants.RASPI_BROADCAST_PREFIX + receivedLine);
}
receiverSocket.close();
Log.d(TAG, "RPiSocketReceiver closed");
} catch (IOException e) {
Log.e(TAG, "Exception " + e);
}
}
}
public String getVersion() throws NoConnectionException {
if (!isConnected) {
throw new NoConnectionException("No active connection!");
}
return rpiVersion;
}
public class NoGpioException extends Exception {
private static final long serialVersionUID = 1L;
public NoGpioException(String msg) {
super(msg);
}
}
public class NoConnectionException extends Exception {
private static final long serialVersionUID = 1L;
public NoConnectionException(String msg) {
super(msg);
}
}
public boolean isConnected() {
return isConnected;
}
}