Friday, January 25, 2013

Garbage Collector System

Garbage Collector System

I have been working with robotic system this semester at school. The idea is to create automated system, which will be able to transport something for us. It could be a garbage bean (originally) or it can use another modules, for bringing some coffee from the kitchen, or transfer a documents in a big office from room A into the room B.
The main goal for me was creating an architecture, which will make adding new robots, or modules for them easily, as well as adding another interfaces for asking the system what we want at the moment. (For example voice commands, or buttons, or clicking in computer application). Let's organize what will we talk about:


1) Architecture
2) User interface (Java socket connection to the server)
  • Client side of the socket connection
  • GUI
3) Server (Connecting users to the Droid Base) 
  • Bluetooth communicator (Through XBee shield)
  • Wired communicator (Through Serial with another Arduino)
  • Logic Engine
  • Wired communicator
  • Motor control (Through Adafruit motor shield)
  • Sensor analyzes (8 photo resistors)
So from this post you can learn about all of this, as well as about connection protocols between the project's parts. Use Ctrl+F search tool of your browser if you want to find a certain problem.


I. Architecture
  So you have learned already about all of this, but it's easier to see on diagram usually (;


Another diagram, you have heard already about this too. (Oh, and there is a mistake - must be "Network Interface" on place of "Ethernet Interface".)
Network interface implements an idealistic system-user communication interface, which, as I said, could be buttons on office tables as well.
For making more then one robot moving between more then two points, the idea of binary-tree map has been written. The root of the tree is our base, the leaves are end-points with their id's.
Here are communication protocols:

Grid Server - Droid base communication protocol

Data package view: [‘-’ | data | ‘-’], where data is char massive with length less than 34 characters and ‘-’ is a flag.
Any side after package sending do not waiting for confirmation. If a side hasn’t receive an answer from the other side, it will resend a package.
| data | package contains code characters at first and second place, which tells how to read data, and data characters from the third one. For some cases code characters is all data in a package.

This is a list of code characters. ‘-’ and ‘ ’ are already in use (the flag and empty symbol).
  1. ‘sq’ - server query to base for status.
  2. ‘ss’ - server request magistrale scan.
  3. ‘sc’ - server calls for droid.
  4. ‘da’ - ‘droid available’ status.
  5. ‘du’ - ‘droid unavailable’ status.
  6. ‘dm’ - magistrale map package. (possibly one of many)
  7. ‘ds’ - droid has been sent.
  8. ‘de’ - droid error. (could not find destination point).
  9. ‘dv’ - droid successful.

Droid - Base communication protocol
Low level procedures worked out by EasyTransfer library.
Package is a structure with char command variable, and data int massive with length 4.
Any side will resend package while confirmation answer won’t be received.
command variable contains information about type of assignment from the one side, and completing assignment from another.
data contains information about magistrale map or route.
  1. ‘s’ - scan map.
  2. ‘g’ - go to address.
  3. ‘w’ - wait for another command.
  4. ‘a’ - droid is available.
  5. ‘e’ - droid error, while completing last assignment.
  6. ‘v’ - assignment has been completed.
  7. ‘r’ - droid is ready to connect.

This is a quick diagram for Java's application part. Of course the difficulty of the Grid Server Engine with it's tree is incomparably larger then of the client side's application.

II. User Interface


Start.
After launching the application, you need to set your id-number, which your system’s administrator gave you.
Next you need to connect the server by clicking “Connect the Server” button.

Use.
If you need to call for droid - just click the ‘Call for Droid” button.
All information will be showed in the “Status” field, as well as necessary instructions. If you have incorrect id-number you need to check it with your’s administrator.
That’s it! Just that simple.


Here is the source code. The NetworkClient class is firing events and GUI catches them.

package gcs.communication.network.client;
import gcs.communication.network.events.NetworkClientEvent;
import gcs.communication.network.interfaces.NetworkClientListener;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class NetworkClient {
private int id;
private ClientQueryListener queryListener;
private NetworkClientListener clientListener;
private NetworkClientEvent e;
private Socket channel;
private volatile QueryPackage outPackage;
private boolean isConnected;
public NetworkClient(NetworkClientListener listener) {
this.clientListener = listener;
id = -1;
isConnected = false;
}
public void connect() {
try {
channel = new Socket("localhost", 8000);
queryListener = new ClientQueryListener(this, channel);
queryListener.start();
isConnected = true;
} catch (IOException ex) {
clientListener.newNetworkClientEvent(new NetworkClientEvent(this, new QueryPackage(id, QueryStatus.CLIENT_UNABLE_TO_CONNECT)));
}
}
public void incomingPackage(QueryPackage pack) {
e = new NetworkClientEvent(this, pack);
clientListener.newNetworkClientEvent(e);
}
public void sendQuery(QueryPackage pack) {
if (isConnected) {
this.outPackage = pack;
try {
if (outPackage != null && channel != null) {
ObjectOutputStream outObj = new ObjectOutputStream(channel.getOutputStream());
outObj.writeObject(outPackage);
outObj.flush();
outPackage = null;
clientListener.newNetworkClientEvent(new NetworkClientEvent(this, new QueryPackage(id, QueryStatus.CLIENT_QUERY_CALL)));
}
} catch (Exception ex) {
clientListener.newNetworkClientEvent(new NetworkClientEvent(this, new QueryPackage(id, QueryStatus.CLIENT_UNABLE_TO_CONNECT)));
}
} else {
clientListener.newNetworkClientEvent(new NetworkClientEvent(this, new QueryPackage(id, QueryStatus.CLIENT_UNABLE_TO_CONNECT)));
}
}
public int getId() {
return id;
}
public void setId(String s) {
try {
id = Integer.parseInt(s);
} catch (Exception ex) {
clientListener.newNetworkClientEvent(new NetworkClientEvent(this, new QueryPackage(id, QueryStatus.CLIENT_INCORRECT_INPUT)));
}
}
}
class ClientQueryListener extends Thread {
private Socket channel;
private NetworkClient c;
public ClientQueryListener(NetworkClient c, Socket channel) {
this.channel = channel;
this.c = c;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
sleep(100);
ObjectInputStream inObj = new ObjectInputStream(channel.getInputStream());
c.incomingPackage((QueryPackage) inObj.readObject());
} catch (InterruptedException | IOException | ClassNotFoundException e) {
}
}
}
}
//Events
package gcs.communication.network.events;
import gcs.communication.network.networkPackage.QueryPackage;
public class NetworkClientEvent extends java.util.EventObject {
private static final long serialVersionUID = 1L;
private QueryPackage pack;
public NetworkClientEvent(Object source, QueryPackage pack) {
super(source);
this.pack = pack;
}
public QueryPackage getPackage() {
return this.pack;
}
}
//Interfaces
package gcs.communication.network.interfaces;
import gcs.communication.network.events.NetworkClientEvent;
public interface NetworkClientListener {
public void newNetworkClientEvent(NetworkClientEvent e);
}
//Package
package gcs.communication.network.networkPackage;
import java.io.Serializable;
public class QueryPackage implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int id;
private QueryStatus queryStatus;
public QueryPackage(int id, QueryStatus queryStatus) {
this.id = id;
this.queryStatus = queryStatus;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public QueryStatus getQueryStatus() {
return queryStatus;
}
public void setQueryStatus(QueryStatus queryStatus) {
this.queryStatus = queryStatus;
}
}
//It must be another file
package gcs.communication.network.networkPackage;
public enum QueryStatus {
CLIENT_QUERY_CALL("System send me a robot!"),
CLIENT_UNABLE_TO_CONNECT("Unable to connect to server"),
CLIENT_INCORRECT_INPUT("Incorrect input!"),
SERWER_CONNECTION_CREATED("Port is open."),
SERWER_UNABLE_TO_CREATE_CONNECTION("Port is closed. Abort."),
SERWER_QUERY_SEND("Query send."),
SERWER_UNABLE_TO_SEND_QUERY("Unable to send query."),
SERWER_CONNECTION_TERMINATED("Connection terminated."),
SERWER_CLIENT_DEFINED("Client defined."),
SERWER_SCAN_MAGISTRALES("Request for magistrale scan."),
SERWER_ANSWER_CONNECTED("Connected."),
SERVER_ANSWER_CALL_READ("System got your call. Working..."),
SERVER_ANSWER_DROID_SENDED("Droid is on his road!"),
SERVER_ANSWER_DROID_RETURNED("Droid have returned to base."),
SERVER_ANSWER_DROID_ERROR("Droid have returned with error."),
SERVER_ANSWER_WAIT("Droid is unavailable at the moment. You are in queue."),
SERVER_ANSWER_SYSTEM_OFFLINE("System is offline. Try to call later."),
SERWER_ANSWER_INCORRECT_ID("Incorrect id. Please contact your administrator");
private final String label;
private QueryStatus(final String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
view raw NetworkClient hosted with ❤ by GitHub

III. The Grid Server
Start.
First of all you need to create network connection by clicking “Create Network Connection” button. It will make able the users to connect the server.
Next you need to connect to droid’s base by clicking “Connect To Droid Base” button. It will make able the server to send queries and take responses from the base.
Then you need to load the map of bot’s roads by clicking “Load” button, or you can request the map scanning from droid’s base by clicking the “Scan Magistrales” button. The map is necessary for organising robot’s work. You can see the map by clicking the “Show” button. Leave’s id’s are users id’s, which supposed to be used at user’s application.
After this click the “Start” button. It will start system’s logic engine, which is needed for queries’ transactions.

Use.
You may check map accuracy by sending the droid manually to chosen user, by choosing proper id-number at “Send droid to” box.
By the log field you will be in touch with all information and queries of the system.
You can save current road’s map by clicking the “Save” button.
Also you can stop system work without breaking of the connections to the base or users by clicking the “Stop” button.

Yes, the system is that simple in use!


The server is much more complicated, but not too much. So first of all, the network server. It enabled connection to him, and listens to the ports, on which clients had connected. Another problem is, that we need to organize offline road's ids with channels. You can find how this problem have been solved, with some other problems here:
package gcs.communication.network.server;
import gcs.communication.events.NetworkServerEvent;
import gcs.communication.interfaces.INetworkServerListener;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import gcs.communication.network.threads.ServerAcceptConnection;
import gcs.communication.network.threads.ServerQueryListener;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class NetworkServer {
private Socket[] channelList;
private ServerQueryListener[] listenerList;
private int[] clientIdList;
private volatile List<QueryPackage> queryQueueList;
private volatile List<Socket> undefinedChannels;
private volatile List<ServerQueryListener> undefinedListeners;
private ServerAcceptConnection connector;
private INetworkServerListener serverListener;
public NetworkServer(INetworkServerListener networkServerListener,
int[] clientId) {
channelList = new Socket[clientId.length];
listenerList = new ServerQueryListener[clientId.length];
clientIdList = clientId;
queryQueueList = new ArrayList<>();
undefinedChannels = new ArrayList<>();
undefinedListeners = new ArrayList<>();
this.serverListener = networkServerListener;
}
public void create() {
try {
ServerSocket server = new ServerSocket(8000);
connector = new ServerAcceptConnection(this, server);
this.connector.start();
serverListener
.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0,
QueryStatus.SERWER_CONNECTION_CREATED)));
} catch (IOException ex) {
serverListener.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0,
QueryStatus.SERWER_UNABLE_TO_CREATE_CONNECTION)));
}
}
public void sendQuery(QueryPackage queryPackage, Socket channel) {
ObjectOutputStream outObj;
try {
outObj = new ObjectOutputStream(channel.getOutputStream());
outObj.writeObject(queryPackage);
serverListener.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0, QueryStatus.SERWER_QUERY_SEND)));
} catch (Exception e) {
serverListener
.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0,
QueryStatus.SERWER_UNABLE_TO_SEND_QUERY)));
}
}
public void sendQuery(QueryPackage queryPackage) {
ObjectOutputStream outObj;
Socket channel = getChannelById(queryPackage.getId());
try {
outObj = new ObjectOutputStream(channel.getOutputStream());
outObj.writeObject(queryPackage);
serverListener.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0, QueryStatus.SERWER_QUERY_SEND)));
} catch (Exception e) {
serverListener
.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(0,
QueryStatus.SERWER_UNABLE_TO_SEND_QUERY)));
}
}
public synchronized void defineClientId(int id, Socket channel) {
int idIndex = -1;
int undefinedChannelIndex = undefinedChannels.indexOf(channel);
for (int j = 0; j < clientIdList.length; j++) {
if (id == clientIdList[j]) {
idIndex = j;
break;
}
}
if (idIndex >= 0) {
channelList[idIndex] = undefinedChannels.get(undefinedChannelIndex);
listenerList[idIndex] = undefinedListeners
.get(undefinedChannelIndex);
undefinedChannels.remove(undefinedChannelIndex);
undefinedListeners.remove(undefinedChannelIndex);
serverListener.newNetworkServerEvent(new NetworkServerEvent(this,
new QueryPackage(id, QueryStatus.SERWER_CLIENT_DEFINED)));
}
}
public boolean isIdDefined(int id, Socket channel,
ServerQueryListener listener) {
for (int i = 0; i < channelList.length; i++) {
if (id == clientIdList[i]) {
if (channel.equals(channelList[i])
&& listener.equals(listenerList[i])) {
return true;
}
break;
}
}
return false;
}
public synchronized void addUndefinedChannelListener(ServerQueryListener l,
Socket s) {
undefinedListeners.add(l);
undefinedChannels.add(s);
}
public int getQueryQueueListSize() {
return queryQueueList.size();
}
public QueryPackage getQueryQueuePackage() {
if (!queryQueueList.isEmpty()) {
QueryPackage q = queryQueueList.get(0);
queryQueueList.remove(0);
return q;
} else {
return null;
}
}
public QueryPackage readQueryQueuePackage(int i) {
if (!queryQueueList.isEmpty() && i < queryQueueList.size()) {
return queryQueueList.get(i);
} else {
return null;
}
}
public void addQueryPackageToQueue(QueryPackage pack) {
queryQueueList.add(pack);
}
public INetworkServerListener getServerListener() {
return serverListener;
}
private Socket getChannelById(int id) {
int i;
for (i = 0; i < clientIdList.length; i++) {
if (clientIdList[i] == id) {
break;
}
}
if (i != clientIdList.length) {
return channelList[i];
} else {
return null;
}
}
}
package gcs.communication.network.threads;
import gcs.communication.events.NetworkServerEvent;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import gcs.communication.network.server.NetworkServer;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerAcceptConnection extends Thread {
private NetworkServer networkServer;
private Socket channel;
private ServerSocket server;
public ServerAcceptConnection(NetworkServer s, ServerSocket server) {
this.networkServer = s;
this.server = server;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
sleep(50);
channel = this.server.accept();
ServerQueryListener l = new ServerQueryListener(networkServer,
channel);
networkServer.addUndefinedChannelListener(l, channel);
l.start();
networkServer.sendQuery(new QueryPackage(MIN_PRIORITY,
QueryStatus.SERWER_ANSWER_CONNECTED), channel);
networkServer.getServerListener().newNetworkServerEvent(
new NetworkServerEvent(networkServer, new QueryPackage(
0, QueryStatus.SERWER_ANSWER_CONNECTED)));
} catch (InterruptedException | IOException e) {
}
}
}
}
package gcs.communication.network.threads;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import gcs.communication.network.server.NetworkServer;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
public class ServerQueryListener extends Thread {
private ObjectInputStream inObj;
private NetworkServer s;
private Socket channel;
public ServerQueryListener(NetworkServer s, Socket channel) {
this.s = s;
this.channel = channel;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
sleep(50);
this.inObj = new ObjectInputStream(channel.getInputStream());
QueryPackage p = (QueryPackage) inObj.readObject();
if (p != null) {
if (!s.isIdDefined(p.getId(), channel, this)) {
s.defineClientId(p.getId(), channel);
}
if (s.isIdDefined(p.getId(), channel, this)) {
s.addQueryPackageToQueue(p);
s.sendQuery(new QueryPackage(p.getId(), QueryStatus.SERVER_ANSWER_CALL_READ), channel);
} else {
s.sendQuery(new QueryPackage(0, QueryStatus.SERWER_ANSWER_INCORRECT_ID), channel);
}
}
} catch (InterruptedException | IOException | ClassNotFoundException e) {
}
}
}
}
package gcs.communication.events;
import gcs.communication.network.networkPackage.QueryPackage;
public class NetworkServerEvent extends java.util.EventObject {
private static final long serialVersionUID = 1L;
private QueryPackage pack;
public NetworkServerEvent(Object source, QueryPackage pack) {
super(source);
this.pack = pack;
}
public QueryPackage getPackage() {
return this.pack;
}
}
package gcs.communication.interfaces;
import gcs.communication.events.NetworkServerEvent;
public interface INetworkServerListener {
public void newNetworkServerEvent(NetworkServerEvent e);
}
view raw Network Server hosted with ❤ by GitHub

Another part is Bluetooth connector. The first difficulty is about different views on what server is, and what client is by BlueCove library, and by Bluetooth Bee module for the Arduino.
The second is truly arduino-sided problem, about bits loosing. Third one - arduino has 16-bit processor, and my computer is 64-bit - we have a problem of Intger size definitions and so on.
Protocol-use simplifies our situation, but nevertheless - think twice before you decide to use bluetooth in your Arduino project.

package gcs.communication.droidBaseCommunicator.bluetooth;
import gcs.communication.droidBaseCommunicator.threads.BluetoothConnectorThread;
import gcs.communication.droidBaseCommunicator.threads.BluetoothReceiverThread;
import gcs.communication.events.BluetoothCommunicatorEvent;
import gcs.communication.interfaces.IBluetoothCommunicatorListener;
import gcs.communication.interfaces.IDroidBaseCommunicator;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
public class BluetoothCommunicator implements IDroidBaseCommunicator,
DiscoveryListener {
private static int BT_PACKAGE_LENGTH = 34;
private static String MAGIC_UUID = "27012f0c68af4fbf8dbe6bbaf7aa432a";
private static String MAGIC_BASE_ID = "0018E40C6802";
private final int FLAG = '-';
private List<RemoteDevice> devices;
private List<ServiceRecord> services;
private IBluetoothCommunicatorListener listener;
private List<int[]> baseData;
private BluetoothConnectorThread bluetoothConnectorThread;
private BluetoothReceiverThread bluetoothReceiverThread;
private OutputStream dout;
private boolean isAbleToConnect;
private String baseId = MAGIC_BASE_ID;
public BluetoothCommunicator(IBluetoothCommunicatorListener listener) {
this.listener = listener;
devices = new ArrayList<>();
services = new ArrayList<>();
baseData = new ArrayList<>();
isAbleToConnect = true;
}
@Override
public void connect() {
bluetoothConnectorThread = new BluetoothConnectorThread(baseId, this);
bluetoothConnectorThread.start();
}
@Override
public void sendData(int[] data) {
try {
if (data.length <= BT_PACKAGE_LENGTH) {
dout.write(FLAG);
for (int i = 0; i < data.length; i++) {
dout.write(data[i]);
}
dout.write(FLAG);
} else {
throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"attempt to send too long package");
}
} catch (Exception e) {
disconnect();
}
}
@Override
public int[] receiveData() {
if (!baseData.isEmpty()) {
int[] data = baseData.get(0);
baseData.remove(0);
return data;
}
return null;
}
public void disconnect() {
bluetoothConnectorThread.interrupt();
bluetoothReceiverThread.interrupt();
throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"breaking connection");
}
@Override
public void deviceDiscovered(RemoteDevice rd, DeviceClass dc) {
devices.add(rd);
}
@Override
public void servicesDiscovered(int i, ServiceRecord[] srs) {
services.addAll(Arrays.asList(srs));
}
@Override
public void serviceSearchCompleted(int i, int i1) {
}
@Override
public void inquiryCompleted(int param) {
switch (param) {
case DiscoveryListener.INQUIRY_COMPLETED:
if (devices.size() > 0) {
findServices((RemoteDevice) devices.get(0));
isAbleToConnect = true;
} else {
throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"No Droid Base found in range");
isAbleToConnect = false;
}
break;
case DiscoveryListener.INQUIRY_ERROR:
throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"Inqury error");
isAbleToConnect = false;
break;
case DiscoveryListener.INQUIRY_TERMINATED:
throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"Inqury terminated");
isAbleToConnect = false;
break;
}
}
public boolean isDataUnavailable() {
return baseData.isEmpty();
}
public void addNewDroidBaseData(int[] data) {
baseData.add(data);
}
public void throwNewBluetoothCommunicatorEvent(String t, String m) {
listener.newBluetoothCommunicatorEvent(new BluetoothCommunicatorEvent(
this, t, m));
}
public List<RemoteDevice> getDevices() {
return devices;
}
public boolean isAbleToConnect() {
return isAbleToConnect;
}
public void setBluetoothReceiverThread(InputStream dataInputStream) {
bluetoothReceiverThread = new BluetoothReceiverThread(dataInputStream,
this);
bluetoothReceiverThread.start();
}
public void setDout(OutputStream dout) {
this.dout = dout;
}
private void findServices(RemoteDevice device) {
LocalDevice local;
DiscoveryAgent agent;
try {
UUID[] uuids = new UUID[1];
uuids[0] = new UUID(MAGIC_UUID, false);
local = LocalDevice.getLocalDevice();
agent = local.getDiscoveryAgent();
agent.searchServices(null, uuids, device, this);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package gcs.communication.droidBaseCommunicator.threads;
import gcs.communication.droidBaseCommunicator.bluetooth.BluetoothCommunicator;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
public class BluetoothConnectorThread extends Thread {
private BluetoothCommunicator bluetoothCommunicator;
private String baseId;
private LocalDevice local;
private DiscoveryAgent agent;
private boolean isBaseFinded;
public BluetoothConnectorThread(String baseId, BluetoothCommunicator bc) {
this.baseId = baseId;
this.bluetoothCommunicator = bc;
isBaseFinded = false;
}
@Override
public void run() {
FindDevices();
bluetoothCommunicator.throwNewBluetoothCommunicatorEvent("Bluetooth Communicator",
"trying to connect...");
while (!isBaseFinded && !Thread.currentThread().isInterrupted()) {
try {
for (int i = 0; i < bluetoothCommunicator.getDevices().size(); i++) {
if (bluetoothCommunicator.getDevices().get(i)
.getBluetoothAddress().equals(baseId)) {
isBaseFinded = true;
break;
}
}
if (isBaseFinded && bluetoothCommunicator.isAbleToConnect()) {
bluetoothCommunicator.throwNewBluetoothCommunicatorEvent(
"Bluetooth Communicator", "getting adress...");
String url = "btspp://" + baseId + ":1";
StreamConnection conn = (StreamConnection) Connector
.open(url);
bluetoothCommunicator.setDout(conn.openOutputStream());
bluetoothCommunicator.setBluetoothReceiverThread(conn.openInputStream());
bluetoothCommunicator.throwNewBluetoothCommunicatorEvent(
"Bluetooth Communicator", "connected");
Thread.currentThread().interrupt();
}
} catch (Exception e) {
bluetoothCommunicator.throwNewBluetoothCommunicatorEvent(
"Bluetooth Communicator", "unable to connect");
Thread.currentThread().interrupt();
}
try {
sleep(500);
} catch (Exception e) {
}
if (!bluetoothCommunicator.isAbleToConnect()) {
Thread.currentThread().interrupt();
}
}
}
private void FindDevices() {
try {
local = LocalDevice.getLocalDevice();
agent = local.getDiscoveryAgent();
agent.startInquiry(DiscoveryAgent.GIAC, bluetoothCommunicator);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package gcs.communication.droidBaseCommunicator.threads;
import gcs.communication.droidBaseCommunicator.bluetooth.BluetoothCommunicator;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class BluetoothReceiverThread extends Thread {
private final int PACKAGE_LENGTH = 34;
private final int FLAG = '-';
private InputStream dataInputStream;
private BluetoothCommunicator bluetoothCommunicator;
private boolean incomingPackage;
private int packageLengthCounter;
private List<Integer> temporaryPackage;
public BluetoothReceiverThread(InputStream dataInputStream,
BluetoothCommunicator bluetoothCommunicator) {
this.dataInputStream = dataInputStream;
this.bluetoothCommunicator = bluetoothCommunicator;
temporaryPackage = new ArrayList<>();
incomingPackage = false;
packageLengthCounter = 0;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
if (dataInputStream.available() > 0) {
byte buffer[] = new byte[dataInputStream.available()];
dataInputStream.read(buffer);
String input = new String(buffer);
List<Integer> data = new ArrayList<>();
for (int i = 0; i < input.length(); i++) {
data.add((int) input.charAt(i));
}
analizeData(data);
} else {
sleep(100);
}
} catch (Exception e) {
bluetoothCommunicator.throwNewBluetoothCommunicatorEvent(
"Bluetooth Communicator", "connection has been lost.");
Thread.currentThread().interrupt();
}
}
}
public void analizeData(List<Integer> data) {
if (!incomingPackage) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i) == FLAG) {
incomingPackage = true;
data = data.subList(i + 1, data.size());
break;
}
}
}
if (incomingPackage) {
for (int i = 0; i < data.size(); i++) {
// if it isn't char - error in package
if (data.get(i) < 0 || data.get(i) > 127) {
incomingPackage = false;
packageLengthCounter = 0;
temporaryPackage.clear();
// if FLAG - end of package, we can send it
// to upper level
} else if (data.get(i) == FLAG) {
incomingPackage = false;
packageLengthCounter = 0;
int[] tempData = new int[temporaryPackage.size()];
for (int j = 0; j < tempData.length; j++) {
tempData[j] = (int) temporaryPackage.get(j);
}
bluetoothCommunicator.addNewDroidBaseData(tempData);
temporaryPackage.clear();
}
// Normal character - add it
else {
if (packageLengthCounter < PACKAGE_LENGTH) {
packageLengthCounter++;
temporaryPackage.add(data.get(i));
} else {
incomingPackage = false;
packageLengthCounter = 0;
temporaryPackage.clear();
}
}
}
}
}
}
package gcs.communication.events;
import java.util.EventObject;
public class BluetoothCommunicatorEvent extends EventObject {
/**
*
*/
private static final long serialVersionUID = 1L;
private String type, mess;
public BluetoothCommunicatorEvent(Object source, String type, String mess) {
super(source);
this.type = type;
this.mess = mess;
}
public String getType() {
return type;
}
public String getMessage() {
return mess;
}
}
package gcs.communication.interfaces;
import gcs.communication.events.BluetoothCommunicatorEvent;
public interface IBluetoothCommunicatorListener {
public void newBluetoothCommunicatorEvent(BluetoothCommunicatorEvent e);
}
And finally - the Grid Engine. It fires events to the Administrator's console, and sends queries from network's part to bluetooth's and back, of course with thinking.
Also it uses writing into XML (simple XML database) and a binary tree.

package gcs.communication.engine;
import java.util.List;
import gcs.communication.droidBaseCommunicator.bluetooth.BluetoothCommunicator;
import gcs.communication.engine.BinaryTree.Node;
import gcs.communication.events.GridEngineEvent;
import gcs.communication.interfaces.IGridServerEngineListener;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import gcs.communication.network.server.NetworkServer;
import gcs.communication.network.threads.GridConnector;
public class GridServerEngine {
private BluetoothCommunicator bluetoothCommunicator;
private NetworkServer networkServer;
private GridConnector gridConnector;
private boolean baseIsReady;
private IGridServerEngineListener gridEngineListener;
private BinaryTree map;
public GridServerEngine(IGridServerEngineListener l) {
this.gridEngineListener = l;
baseIsReady = false;
map = new BinaryTree(this);
}
public void start() {
gridConnector = new GridConnector(networkServer, bluetoothCommunicator,
this);
gridConnector.start();
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "System started"));
}
public void stop() {
if (gridConnector != null) {
gridConnector.interrupt();
}
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "System stopped"));
}
public void sendDroidTo(String id) {
try {
int userId = Integer.parseInt(id);
networkServer.addQueryPackageToQueue(new QueryPackage(userId,
QueryStatus.CLIENT_QUERY_CALL));
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Droid request for user [" + userId
+ "] has been added to query"));
} catch (Exception e) {
e.printStackTrace();
}
}
public void scanMagistrales() {
networkServer.addQueryPackageToQueue(new QueryPackage(0,
QueryStatus.SERWER_SCAN_MAGISTRALES));
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine",
"Droid request for magistralle scan has been added to query"));
}
public void loadMap() {
map.loadMap();
}
public void saveMap() {
if (map.getMap().length > 0) {
map.saveMap();
} else {
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Error - no map available"));
}
}
public void showMap() {
if (map.getMap().length > 0) {
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map:"));
printTree(map.getRoot(), 0);
} else {
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Error - no map available"));
}
}
public boolean readMapScan(int[] scan) {
map.createTree(scan);
return true;
}
public int[] getCallRequest(int id, int[] code) {
Integer[] road = map.findRoad(id);
int[] output = new int[road.length + code.length+1];
int stopPoint = 0;
for (int i = 0; i < code.length; i++) {
output[i] = code[i];
stopPoint++;
}
output[stopPoint] = road.length;
stopPoint++;
for (int i = stopPoint; i < road.length; i++) {
output[i] = (int) road[i - stopPoint];
}
return output;
}
public boolean isBaseIsReady() {
return baseIsReady;
}
public void setBaseIsReady(boolean baseIsReady) {
this.baseIsReady = baseIsReady;
}
public BluetoothCommunicator getBluetoothCommunicator() {
return bluetoothCommunicator;
}
public void setBluetoothCommunicator(
BluetoothCommunicator bluetoothCommunicator) {
this.bluetoothCommunicator = bluetoothCommunicator;
}
public NetworkServer getNetworkServer() {
return networkServer;
}
public void setNetworkServer(NetworkServer networkServer) {
this.networkServer = networkServer;
}
public IGridServerEngineListener getGridEngineListener() {
return gridEngineListener;
}
public String[] getUserIds() {
List<String> ids = map.getUserIds();
String[] s = new String[ids.size()];
for (int i = 0; i < s.length; i++) {
s[i] = ids.get(i);
}
return s;
}
private void printTree(Node root, int level) {
if (root == null) {
padding("\t", level, "");
} else {
printTree(root.getRight(), level + 1);
padding("\t", level, (new Integer(root.getData())).toString());
printTree(root.getLeft(), level + 1);
}
}
private void padding(String tabs, int n, String data) {
int i;
String s = "";
for (i = 0; i < n; i++) {
s += tabs;
}
s += data;
gridEngineListener.newGridEngineEvent(new GridEngineEvent(this, "", s));
}
}
package gcs.communication.network.threads;
import gcs.communication.droidBaseCommunicator.bluetooth.BluetoothCommunicator;
import gcs.communication.engine.GridServerEngine;
import gcs.communication.events.GridEngineEvent;
import gcs.communication.network.networkPackage.QueryPackage;
import gcs.communication.network.networkPackage.QueryStatus;
import gcs.communication.network.server.NetworkServer;
public class GridConnector extends Thread {
// Protocol codes
// See "GDS Protocols" in documentation
// For more information
private final int[] SQ = { 's', 'q' };
private final int[] SS = { 's', 's' };
private final int[] SC = { 's', 'c' };
private final int DROID_INFO = 'd';
private final int DROID_AVAILABLE = 'a';
private final int DROID_UNAVAILABLE = 'u';
private final int DROID_MAP = 'm';
private final int DROID_SEND = 's';
private final int DROID_ERROR = 'e';
private final int DROID_SUCCESSFUL = 'v';
private final int WAITING_TIME = 1000;
private final int THREAD_SLEEP_TIME = 100;
private NetworkServer networkServer;
private BluetoothCommunicator bluetoothCommunicator;
private GridServerEngine engine;
private int waitCounter;
private boolean requestToBaseSended;
private QueryPackage currentPackage;
private int lastQueueLength;
private int lastBaseStatus;
private int droidStatus;
private int[] tempMap;
public GridConnector(NetworkServer server, BluetoothCommunicator connector,
GridServerEngine engine) {
super();
this.networkServer = server;
this.bluetoothCommunicator = connector;
this.engine = engine;
requestToBaseSended = false;
waitCounter = 0;
lastQueueLength = 0;
droidStatus = DROID_UNAVAILABLE;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
sleep(THREAD_SLEEP_TIME);
// Check queue
if (networkServer.getQueryQueueListSize() > lastQueueLength) {
for (int i = lastQueueLength; i < networkServer
.getQueryQueueListSize(); i++) {
networkServer.sendQuery(new QueryPackage(networkServer
.readQueryQueuePackage(i).getId(),
QueryStatus.SERVER_ANSWER_WAIT));
}
lastQueueLength = networkServer.getQueryQueueListSize();
}
// if there is no packages in process
if (currentPackage == null) {
if (networkServer.getQueryQueueListSize() > 0) {
if (engine.isBaseIsReady()) {
currentPackage = networkServer
.getQueryQueuePackage();
lastQueueLength--;
} else {
sendRequestToBase(SQ);
}
}
// else if we have a package in process
// (send it or waiting for an answer)
} else {
proceedCurrentPackage();
}
// if there is a data from base
if (!bluetoothCommunicator.isDataUnavailable()) {
readDroidBaseData(bluetoothCommunicator.receiveData());
}
} catch (Exception e) {
e.printStackTrace();
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"System goes offline."));
break;
}
}
}
public void proceedCurrentPackage() {
// If base hasn't send droid yet
// request it to be send
if ((lastBaseStatus == DROID_AVAILABLE && droidStatus == DROID_UNAVAILABLE)
// if droid is on road - keep asking about his return
|| (lastBaseStatus == DROID_SEND && droidStatus == DROID_SEND)){
switch (currentPackage.getQueryStatus()) {
case CLIENT_QUERY_CALL:
sendRequestToBase(engine.getCallRequest(currentPackage.getId(), SC));
break;
case SERWER_SCAN_MAGISTRALES:
sendRequestToBase(SS);
break;
default:
break;
}
}
// if droid has been send - change it's status on grid
if (lastBaseStatus == DROID_SEND && droidStatus == DROID_UNAVAILABLE) {
droidStatus = DROID_SEND;
networkServer.sendQuery(new QueryPackage(currentPackage.getId(),
QueryStatus.SERVER_ANSWER_DROID_SENDED));
}
// in case of calling droid for garbage collecting
// it'll be available after return without any additional data
if (lastBaseStatus == DROID_SUCCESSFUL) {
droidStatus = DROID_UNAVAILABLE;
networkServer.sendQuery(new QueryPackage(currentPackage.getId(),
QueryStatus.SERVER_ANSWER_DROID_RETURNED));
currentPackage = null;
// base will recend SUCCESSFUL code until reading status request
// it isn't available until then
engine.setBaseIsReady(false);
}
// in case of map request base will recend map package
// instead of successful code
if (lastBaseStatus == DROID_MAP) {
if(engine.readMapScan(tempMap)) {
currentPackage = null;
// base will recend SUCCESSFUL code until reading status request
// it isn't available until then
engine.setBaseIsReady(false);
}
sendRequestToBase(SS);
}
if (lastBaseStatus == DROID_ERROR) {
droidStatus = DROID_UNAVAILABLE;
switch (currentPackage.getQueryStatus()) {
case CLIENT_QUERY_CALL:
networkServer.sendQuery(new QueryPackage(currentPackage.getId(),
QueryStatus.SERVER_ANSWER_DROID_ERROR));
break;
case SERWER_SCAN_MAGISTRALES:
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"Map scan error."));
break;
default:
break;
}
currentPackage = null;
// base will recend SUCCESSFUL code until reading status request
// it isn't available until then
engine.setBaseIsReady(false);
}
}
public void readDroidBaseData(int[] data) {
if (data.length >= 2) {
if (data[0] == DROID_INFO) {
switch (data[1]) {
case DROID_AVAILABLE:
if (lastBaseStatus != DROID_AVAILABLE) {
engine.setBaseIsReady(true);
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid base is ready."));
lastBaseStatus = DROID_AVAILABLE;
}
break;
case DROID_UNAVAILABLE:
if (lastBaseStatus != DROID_UNAVAILABLE) {
engine.setBaseIsReady(false);
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid base isn't ready."));
lastBaseStatus = DROID_UNAVAILABLE;
}
break;
case DROID_SEND:
if (lastBaseStatus != DROID_SEND) {
engine.setBaseIsReady(false);
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid send."));
lastBaseStatus = DROID_SEND;
}
break;
case DROID_MAP:
if (lastBaseStatus != DROID_MAP) {
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid has returned with map."));
lastBaseStatus = DROID_MAP;
tempMap = data;
}
break;
case DROID_ERROR:
if (lastBaseStatus != DROID_ERROR) {
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid has returned with error."));
lastBaseStatus = DROID_ERROR;
}
break;
case DROID_SUCCESSFUL:
if (lastBaseStatus != DROID_SUCCESSFUL) {
engine.getGridEngineListener().newGridEngineEvent(
new GridEngineEvent(this, "Grid Engine",
"droid successful."));
lastBaseStatus = DROID_SUCCESSFUL;
}
break;
default:
break;
}
}
}
}
private void sendRequestToBase(int[] data) {
if (!requestToBaseSended) {
bluetoothCommunicator.sendData(data);
requestToBaseSended = true;
} else {
waitCounter++;
if (waitCounter * THREAD_SLEEP_TIME > WAITING_TIME) {
requestToBaseSended = false;
waitCounter = 0;
}
}
}
}
package gcs.communication.interfaces;
public interface IDroidBaseCommunicator {
public void connect();
public void sendData(int[] data);
public int[] receiveData();
}
package gcs.communication.events;
import java.util.EventObject;
public class GridEngineEvent extends EventObject {
/**
*
*/
private static final long serialVersionUID = 1L;
private String type, mess;
public GridEngineEvent(Object source, String type, String mess) {
super(source);
this.type = type;
this.mess = mess;
}
public String getType() {
return type;
}
public String getMessage() {
return mess;
}
}
package gcs.communication.interfaces;
import gcs.communication.events.GridEngineEvent;
public interface IGridServerEngineListener {
public void newGridEngineEvent(GridEngineEvent e);
}
package gcs.communication.engine;
import gcs.communication.events.GridEngineEvent;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
public class BinaryTree {
private Node root;
private Integer[] map;
private List<String> userIds;
private GridServerEngine engine;
public BinaryTree(GridServerEngine engine) {
this.engine = engine;
userIds = new ArrayList<String>();
}
public boolean createTree(int[] treeWalkData) {
// if there is no any data or it is incorrect
if (treeWalkData.length == 0 || treeWalkData.length % 2 != 0) {
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map creation failure"));
return false;
}
map = new Integer[treeWalkData.length];
for (int i = 0; i < map.length; i++) {
map[i] = treeWalkData[i];
}
int idGen = 0;
Node currentNode;
root = new Node(idGen, null);
currentNode = root;
for (int i = 0; i < treeWalkData.length; i++) {
if (treeWalkData[i] == 1) {
if (currentNode.getLeft() == null) {
Node left = new Node(0, currentNode);
currentNode.setLeft(left);
currentNode = left;
} else {
Node right = new Node(0, currentNode);
currentNode.setRight(right);
currentNode = right;
}
} else if (treeWalkData[i] == 0) {
if (treeWalkData[i - 1] == 1) {
idGen++;
currentNode.setData(idGen);
userIds.add((new Integer(idGen)).toString());
}
currentNode = currentNode.getParent();
}
}
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map creation successful"));
return true;
}
public Integer[] findRoad(int id) {
Integer[] road;
int tempId = 0;
int length = 0;
for (int i = 1; i < map.length; i++) {
if(map[i] == 0 && map[i-1] == 1) {
tempId++;
}
if(tempId == id) {
length = i-1;
break;
}
}
road = new Integer[length];
for(int i=0; i<length; i++) {
road[i] = map[i];
}
return road;
}
public void saveMap() {
Document document = DocumentHelper.createDocument();
Element root = document.addElement("MagistraleMap");
for (int i = 0; i < map.length; i++) {
root.addElement("elem").addText(map[i].toString());
}
try {
XMLWriter out = new XMLWriter(new FileWriter("dataBase.xml"),
OutputFormat.createPrettyPrint());
out.write(document);
out.close();
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map save successful"));
} catch (Exception e) {
e.printStackTrace();
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map save failure"));
}
}
public void loadMap() {
try {
SAXReader reader = new SAXReader();
Document document = reader.read("dataBase.xml");
Element root = document.getRootElement();
Element temp = root;
List<Integer> mapList = new ArrayList<>();
for (Iterator<Element> i = root.elementIterator(); i.hasNext();) {
temp = i.next();
mapList.add(new Integer(temp.getText()));
}
int data[] = new int[mapList.size()];
for (int i = 0; i < data.length; i++) {
data[i] = mapList.get(i);
}
createTree(data);
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map load successful"));
} catch (Exception e) {
e.printStackTrace();
engine.getGridEngineListener().newGridEngineEvent(new GridEngineEvent(this,
"Grid Engine", "Map save failure"));
}
}
public Node getRoot() {
return root;
}
public Integer[] getMap() {
return map;
}
public List<String> getUserIds() {
return userIds;
}
public class Node {
int userId;
Node parent;
Node left;
Node right;
public Node(int data, Node parent) {
this.userId = data;
this.parent = parent;
left = null;
right = null;
}
public int getData() {
return userId;
}
public void setData(int data) {
this.userId = data;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
}
}
IV. Droid Base
The droid base is at right on this photo

As I said bluetooth connection enabled by Bluetooth Bee module and Serial connection library. Wired connection (as on photo, just three wires), enabled by EasyTransfer library. The main goal of the base is too connect the robot and the Grid Server (because on larger scale droid can go out from bluetooth or wifi range, without a proper layer which will enable non-stop connection). There are variations of commands, which base can receive, and the task is to organize them in good way. Strategically the base will need to charge robots, and change modules for them if it's needed. But those are plans only (:


V. Garbage Collector Droid
First of all I need to say you, that I failed to find a proper algorithm, which will work wherever you want, with different lines under different lights and so on. I have written a program for moving by the black line on white paper, but because of hardware problems (Adafruit shield failure I think, when motors run, it's not shielding enough the arduino from the voltage change and it restarts.), so because of those problems I couldn't run any program, because the robot unpredictably restarts. It can follow the line, but it can easily loose it on lights change.
The first way I thought about, was to use 4 sensors. Robot starts on the line, and if left or right sensor data is lowing, that means that robot is going out from the line to the right or to the left accordingly. First time I implemented road vision "digitally" - if the sensor reading is low enough (I tryed both - constants, which works fine on paper, but on paper only, and not for too long; and also I tried dynamically change black-line definition, by remembering what was black line and what the current lowest sensor reading is). It hasn't worked well.
I used under-robot light, and program changes it's brightness on lg n time, where n is voltage for LED coded on numbers from 0 to 1023.

  I have used 8 photo resistors on the next construction. The idea was the same, and changing photo resistor's number hasn't worked out. But a new idea had come to my head - to use photo resistors as switches - if the sensor is closing to line, the reading is lowing, if it crossed the line - it's growing back. So we have a minimum, which can be used as switch for our sensor grid. I tried it on this base, and it should work on 2 wheel chassis, but 4 wheel-based are going to far from the rotation point while turning.

 I used it on my third version, which is most ugly and quick maded.
By creating 5 forward sectors, and knowing which sensor had detected the line last time the robot can correct it's speed. (Ex. if line is on the middle sector - max speed. If the line is on the left-to-middle sector - slightly turn right, if the line on the side's sector - stop and rotate in one place. In reality I'm using a different combination, but this is the main idea).
For crossroad detection side sensor are used, and if they do, it comes to logic, which way to go.
He's the code:

#include "EasyTransfer.h"
#include "AFMotor.h"
//----------------------------------
//Variables
//----------------------------------
struct WirePackage {
char command;
int data1;
int data2;
int data3;
int data4;
};
//Protocol codes
const char DROID_SCAN = 's';
const char DROID_GO = 'g';
const char DROID_WAIT = 'w';
const char DROID_AVAILABLE = 'a';
const char DROID_ERROR = 'e';
const char DROID_SUCCESSFUL = 'v';
const char DROID_READY = 'r';
//Wired connector variables
EasyTransfer inputTransfer;
EasyTransfer outputTransfer;
WirePackage inputPackage;
WirePackage outputPackage;
//Sensor control
const int SENSOR_LIGHT = 9;
const int SENSOR_LIGHT_POWER_DOWN = 0;
const int SYGNAL_NOISE = 5;
const int HIGH_SPEED_WIDTH = 10;
const int LOW_SPEED_WIDTH = 7;
const int MIN_SPEED_WIDTH = 4;
const int SIDE_SENSORS_ADJUSTMENT = 5;
int lineReadingWidth;
int ind, blinkDelay;
const int MIDDLE_SENSOR_GRID[4] = { A6, A2, A0, A1 };
const int SIDE_SENSOR_GRID[4] = { A5, A3, A7, A4 };
const int SENSOR_DELAY = 4;
int middleSensorReading[4];
int middleReadingBehavior[4];
int sideSensorReading[4];
int sideReadingBehavior[4];
int linePosition[5];
int roadPosition[5];
//MotorControl
const int MOTOR_FULL_SPEED = 255;
const int MOTOR_HALF_SPEED = 180;
const int MOTOR_QUARD_SPEED = 120;
const int MOTOR_MIN_SPEED = 70;
const int SCALE_SHIFT = 255;
const int SCALE_HALF_SPEED = 384;
const int SCALE_LENGTH = 510;
const int SPEED_STEP = 20;
const int INDICATOR_GRID[8] = { 22, 40, 39, 25, 38, 41, 24, 23 };
const int SHORT_BLINK = 6;
const int LONG_BLINK = 12;
AF_DCMotor frontLeftMotor(4);
AF_DCMotor frontRightMotor(3);
AF_DCMotor backLeftMotor(2);
AF_DCMotor backRightMotor(1);
AF_DCMotor motors[4] = { frontRightMotor, backRightMotor, backLeftMotor,
frontLeftMotor };
//Procedures engine variables
const int MAX_AWAIT_TIME = 60000;
boolean isDataAvailable;
boolean isConnected;
int currentSpeed = 150;
char currentCourse;
int timeout = 0;
//----------------------------------
//Methods declarations
//----------------------------------
//Wired connector
void setupWiredConnection();
void wiredCommunication();
void writeToWiredPort();
void listenToWiredPort();
//Sensors
void adjustSensorLightBrightness();
int getContrast();
void setupSensors();
void analizeSensorData();
int getMiddleSensorData(int sensorId);
int getSideSensorData(int sensorId);
void turnOffLight();
//Motor control
void setupIndicators();
void goForwardToKeyPoint();
void crossroadGoLeft();
void crossroadGoRight();
void goAround();
//Motor control private
void followTheLine();
void catchTheLineOnLeft();
void catchTheLineOnRight();
//Indicators
void turnOnHabarite();
void turnOnFront();
void turnOnBack();
void turnOnLeftSide();
void turnOnFullLeftSide();
void turnOnRightSide();
void turnOnFullRightSide();
void blinkToTheLeft();
void blinkToTheRight();
void turnOffIndicators();
void motorsSetSpeed(int leftSide, int rightSide);
//Procedures engine
void proceedWiredInputData();
void changeRemoteCommand(char command);
void remoteMovement();
void goTo(int way[], int wayLength);
void wait();
void dock();
void undock();
void transformData(int way[]);
//****************************************
//Implementations
//****************************************
void setup() {
setupWiredConnection();
setupIndicators();
// adjustSensorLightBrightness();
// setupSensors();
}
void loop() {
wiredCommunication();
// delay(25);
remoteMovement();
}
//----------------------------------------
//----------------------------------------
//Wired connector
//++++++++++++++++++++++++++++++++++++++++
void setupWiredConnection() {
isConnected = true;
Serial.begin(9600);
inputTransfer.begin(details(inputPackage), &Serial);
outputTransfer.begin(details(outputPackage), &Serial);
}
void wiredCommunication() {
if (isConnected) {
writeToWiredPort();
listenToWiredPort();
}
}
void writeToWiredPort() {
if (isDataAvailable) {
outputTransfer.sendData();
}
}
void listenToWiredPort() {
if (inputTransfer.receiveData()) {
proceedWiredInputData();
}
}
//-------------------------------------
//-------------------------------------
//--------Movement control-------------
//-------------------------------------
void goForwardToKeyPoint() {
while (roadPosition[2] == 1) {
analizeSensorData();
followTheLine();
}
}
void crossroadGoLeft() {
while (true) {
analizeSensorData();
if (roadPosition[1] == 1 || roadPosition[0] == 1) {
catchTheLineOnLeft();
} else {
if (roadPosition[2] == 1) {
ind = 0;
blinkDelay = 0;
break;
}
blinkDelay++;
lineReadingWidth = LOW_SPEED_WIDTH;
if (blinkDelay > LONG_BLINK) {
blinkToTheLeft();
blinkDelay = 0;
}
turnOnHabarite();
motorsSetSpeed(MOTOR_HALF_SPEED, MOTOR_HALF_SPEED);
}
}
}
void crossroadGoRight() {
while (true) {
analizeSensorData();
if (roadPosition[3] == 1 || roadPosition[4] == 1) {
catchTheLineOnRight();
} else {
if (roadPosition[2] == 1) {
ind = 0;
blinkDelay = 0;
break;
}
blinkDelay++;
lineReadingWidth = LOW_SPEED_WIDTH;
if (blinkDelay > LONG_BLINK) {
blinkToTheRight();
blinkDelay = 0;
}
turnOnHabarite();
motorsSetSpeed(MOTOR_HALF_SPEED, MOTOR_HALF_SPEED);
}
}
}
void goAround() {
catchTheLineOnLeft();
catchTheLineOnLeft();
}
void followTheLine() {
turnOffIndicators();
if (linePosition[2] == 1) {
turnOnHabarite();
if (middleReadingBehavior[1] == middleReadingBehavior[2]) {
lineReadingWidth = HIGH_SPEED_WIDTH;
turnOnLeftSide();
turnOnRightSide();
motorsSetSpeed(MOTOR_FULL_SPEED, MOTOR_FULL_SPEED);
} else if (middleReadingBehavior[1] > middleReadingBehavior[2]) {
lineReadingWidth = LOW_SPEED_WIDTH;
turnOnRightSide();
motorsSetSpeed(MOTOR_HALF_SPEED, MOTOR_QUARD_SPEED);
} else {
lineReadingWidth = LOW_SPEED_WIDTH;
turnOnLeftSide();
motorsSetSpeed(MOTOR_QUARD_SPEED, MOTOR_HALF_SPEED);
}
} else if (linePosition[4] == 1) {
lineReadingWidth = MIN_SPEED_WIDTH;
turnOnBack();
turnOnFullRightSide();
motorsSetSpeed(MOTOR_FULL_SPEED, -MOTOR_FULL_SPEED);
} else if (linePosition[3] == 1) {
lineReadingWidth = MIN_SPEED_WIDTH;
turnOnFront();
turnOnFullRightSide();
motorsSetSpeed(MOTOR_FULL_SPEED, 0);
} else if (linePosition[0] == 1) {
lineReadingWidth = MIN_SPEED_WIDTH;
turnOnBack();
turnOnFullLeftSide();
motorsSetSpeed(-MOTOR_FULL_SPEED, MOTOR_FULL_SPEED);
} else if (linePosition[1] == 1) {
lineReadingWidth = MIN_SPEED_WIDTH;
turnOnFront();
turnOnFullLeftSide();
motorsSetSpeed(0, MOTOR_FULL_SPEED);
}
}
void catchTheLineOnLeft() {
blinkDelay++;
turnOffIndicators();
if (roadPosition[0] == 1) {
if (blinkDelay > SHORT_BLINK) {
blinkToTheLeft();
blinkDelay = 0;
}
lineReadingWidth = LOW_SPEED_WIDTH;
motorsSetSpeed(-MOTOR_FULL_SPEED, -MOTOR_HALF_SPEED);
} else {
if (blinkDelay > LONG_BLINK) {
blinkToTheLeft();
blinkDelay = 0;
}
lineReadingWidth = MIN_SPEED_WIDTH;
motorsSetSpeed(-MOTOR_FULL_SPEED, MOTOR_FULL_SPEED);
}
}
void catchTheLineOnRight() {
blinkDelay++;
turnOffIndicators();
if (roadPosition[4] == 1) {
if (blinkDelay > SHORT_BLINK) {
blinkToTheRight();
}
lineReadingWidth = LOW_SPEED_WIDTH;
motorsSetSpeed(-MOTOR_HALF_SPEED, -MOTOR_FULL_SPEED);
} else {
if (blinkDelay > LONG_BLINK) {
blinkToTheRight();
blinkDelay = 0;
}
lineReadingWidth = MIN_SPEED_WIDTH;
motorsSetSpeed(MOTOR_FULL_SPEED, -MOTOR_FULL_SPEED);
}
}
void analizeSensorData() {
int i, j;
int newMiddleReading[4];
int newSideReading[4];
int adjustedWidth = lineReadingWidth + SIDE_SENSORS_ADJUSTMENT;
boolean isPositionChanged = false;
for (i = 0; i < 4; i++) {
newSideReading[i] = getSideSensorData(i);
//data decr
//2 - white, 1 - incr
//-2 - black, -1 - decr
//if maximum/minimum - change state
if (newSideReading[i] + adjustedWidth < sideSensorReading[i]) {
if (sideReadingBehavior[i] == 2) {
sideReadingBehavior[i] = -1;
} else if (sideReadingBehavior[i] == 1) {
sideReadingBehavior[i] = 2;
}
//data inc
} else if (newSideReading[i] - adjustedWidth > sideSensorReading[i]) {
if (sideReadingBehavior[i] == -2) {
sideReadingBehavior[i] = 1;
} else if (sideReadingBehavior[i] == -1) {
sideReadingBehavior[i] = -2;
}
}
}
for (i = 0; i < 4; i++) {
newMiddleReading[i] = getMiddleSensorData(i);
//data decr
//2 - white, 1 - incr
//-2 - black, -1 - decr
//if maximum/minimum - change state
if (newMiddleReading[i] + lineReadingWidth < middleSensorReading[i]) {
if (middleReadingBehavior[i] == 2) {
middleReadingBehavior[i] = -1;
} else if (middleReadingBehavior[i] == 1) {
middleReadingBehavior[i] = 2;
}
//data inc
} else if (newMiddleReading[i] - lineReadingWidth
> middleSensorReading[i]) {
if (middleReadingBehavior[i] == -2) {
middleReadingBehavior[i] = 1;
} else if (middleReadingBehavior[i] == -1) {
middleReadingBehavior[i] = -2;
}
}
}
//road on the left
if (roadPosition[0] == 1) {
if (sideReadingBehavior[0] == -2 || sideReadingBehavior[1] == -2) {
roadPosition[0] = 0;
roadPosition[1] = 1;
} else if (middleReadingBehavior[0] == -2) {
roadPosition[0] = 0;
roadPosition[2] = 1;
linePosition[1] = 1;
}
} else if (roadPosition[1] == 1) {
if (sideReadingBehavior[0] == -2) {
roadPosition[1] = 0;
roadPosition[0] = 1;
} else if (middleReadingBehavior[0] == -2) {
roadPosition[1] = 0;
roadPosition[2] = 1;
linePosition[1] = 1;
}
//road on the right
} else if (roadPosition[4] == 1) {
if (sideReadingBehavior[2] == -2 || sideReadingBehavior[3] == -2) {
roadPosition[4] = 0;
roadPosition[3] = 1;
} else if (middleReadingBehavior[3] == -2) {
roadPosition[4] = 0;
roadPosition[2] = 1;
linePosition[3] = 1;
}
} else if (roadPosition[3] == 1) {
if (sideReadingBehavior[3] == -2) {
roadPosition[3] = 0;
roadPosition[4] = 1;
} else if (middleReadingBehavior[3] == -2) {
roadPosition[3] = 0;
roadPosition[2] = 1;
linePosition[3] = 1;
}
} else {
//under line
//side sensors caught another road
if (sideReadingBehavior[0] == -2) {
roadPosition[0] = 1;
roadPosition[2] = 0;
} else if (sideReadingBehavior[3] == -2) {
roadPosition[4] = 1;
roadPosition[2] = 0;
} else {
//just following the line
for (i = 0; i < 5 && !isPositionChanged; i++) {
if (linePosition[i] == 1) {
for (j = 0; j < 4 && !isPositionChanged; j++) {
if (middleReadingBehavior[j] == -2) {
linePosition[i] = 0;
if (j < i) {
linePosition[j] = 1;
} else {
linePosition[j + 1] = 1;
}
isPositionChanged = true;
}
}
}
}
}
}
for (i = 0; i < 4; i++) {
middleSensorReading[i] = newMiddleReading[i];
sideSensorReading[i] = newSideReading[i];
}
isDataAvailable = true;
outputPackage.command = DROID_ERROR;
outputPackage.data1 = middleReadingBehavior[0];
outputPackage.data2 = middleReadingBehavior[1];
outputPackage.data3 = middleReadingBehavior[2];
outputPackage.data4 = middleReadingBehavior[3];
writeToWiredPort();
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++
//----------------Procedures engine-------------------
//----------------------------------------------------
void proceedWiredInputData() {
switch (inputPackage.command) {
case DROID_GO:
int way[inputPackage.data1];
transformData(way);
goTo(way, inputPackage.data1);
break;
case DROID_SCAN:
break;
case DROID_WAIT:
isDataAvailable = true;
outputPackage.command = DROID_AVAILABLE;
break;
default:
changeRemoteCommand(inputPackage.command);
break;
}
}
void changeRemoteCommand(char c) {
switch (c) {
case '>':
if (currentSpeed < 255) {
currentSpeed++;
}
blinkToTheRight();
break;
case '<':
if (currentSpeed > 120) {
currentSpeed--;
} else {
currentSpeed = 0;
}
blinkToTheLeft();
break;
default:
currentCourse = c;
timeout = 25;
break;
}
}
void remoteMovement() {
turnOffIndicators();
isConnected = true;
if (timeout > 0) {
timeout--;
switch (currentCourse) {
case 'u':
motorsSetSpeed(currentSpeed, currentSpeed);
turnOnHabarite();
turnOnFront();
break;
case 'r':
motorsSetSpeed(MOTOR_FULL_SPEED, -MOTOR_FULL_SPEED);
turnOnFullRightSide();
break;
case 'd':
motorsSetSpeed(-currentSpeed, -currentSpeed);
turnOnHabarite();
turnOnBack();
break;
case 'l':
motorsSetSpeed(-MOTOR_FULL_SPEED, MOTOR_FULL_SPEED);
turnOnFullLeftSide();
break;
}
} else {
motorsSetSpeed(0, 0);
}
}
void goTo(int way[], int wayLength) {
undock();
goForwardToKeyPoint();
for (int i = 0; i < wayLength; i++) {
if (way[i] == 1) {
crossroadGoRight();
} else {
crossroadGoLeft();
}
goForwardToKeyPoint();
}
motorsSetSpeed(0, 0);
turnOffIndicators();
turnOffLight();
wait();
adjustSensorLightBrightness();
setupIndicators();
goAround();
goForwardToKeyPoint();
for (int i = 0; i < wayLength; i++) {
if (way[i] == 0) {
crossroadGoRight();
} else {
crossroadGoLeft();
}
goForwardToKeyPoint();
}
goAround();
dock();
motorsSetSpeed(0, 0);
turnOffIndicators();
turnOffLight();
isDataAvailable = true;
outputPackage.command = DROID_SUCCESSFUL;
}
void wait() {
delay(MAX_AWAIT_TIME);
}
void dock() {
isConnected = true;
}
void undock() {
isConnected = false;
}
void transformData(int way[]) {
if (inputPackage.command == DROID_GO) {
int i = inputPackage.data1 - 1;
while (inputPackage.data4 > 9) {
if (inputPackage.data4 % 2 == 0) {
way[i] = 0;
} else {
way[i] = 1;
}
inputPackage.data4 /= 10;
i--;
}
while (inputPackage.data3 > 9) {
if (inputPackage.data3 % 2 == 0) {
way[i] = 0;
} else {
way[i] = 1;
}
inputPackage.data3 /= 10;
i--;
}
while (inputPackage.data2 > 9) {
if (inputPackage.data2 % 2 == 0) {
way[i] = 0;
} else {
way[i] = 1;
}
inputPackage.data2 /= 10;
i--;
}
}
}
//-------------------------------------------
//-------------------------------------------
//----------------SETUP----------------------
//===========================================
void setupSensors() {
for (int i = 0; i < 4; i++) {
middleSensorReading[i] = getMiddleSensorData(i);
sideSensorReading[i] = getSideSensorData(i);
middleReadingBehavior[i] = 2;
sideReadingBehavior[i] = 2;
}
linePosition[2] = 1;
roadPosition[2] = 1;
lineReadingWidth = HIGH_SPEED_WIDTH;
}
void setupIndicators() {
for (int i = 0; i < 8; i++) {
pinMode(INDICATOR_GRID[i], OUTPUT);
/* digitalWrite(INDICATOR_GRID[i], HIGH);
delay(125);
digitalWrite(INDICATOR_GRID[i], LOW);*/
}
}
void adjustSensorLightBrightness() {
int leftIterator = 322;
int rightIterator = 1023;
int middleIterator = 1;
int diff1, diff2, diff3;
//left
analogWrite(SENSOR_LIGHT, leftIterator);
delay(SENSOR_DELAY);
diff1 = getContrast();
//right
analogWrite(SENSOR_LIGHT, rightIterator);
delay(SENSOR_DELAY);
diff3 = getContrast();
while (!(middleIterator % 2 == 0)) {
middleIterator = leftIterator + (rightIterator - leftIterator) / 2 + 1;
//middle
analogWrite(SENSOR_LIGHT, middleIterator);
delay(SENSOR_DELAY);
diff2 = getContrast();
//check
if (diff1 + diff2 >= diff2 + diff3) {
rightIterator = middleIterator;
diff3 = diff2;
} else {
leftIterator = middleIterator;
diff1 = diff2;
}
}
analogWrite(SENSOR_LIGHT, middleIterator);
}
int getContrast() {
int diff = 0;
for (int i = 0; i < 4; i++) {
for (int j = i; j < 4; j++) {
if (MIDDLE_SENSOR_GRID[i] > MIDDLE_SENSOR_GRID[j]) {
diff += MIDDLE_SENSOR_GRID[i] - MIDDLE_SENSOR_GRID[j];
} else {
diff -= MIDDLE_SENSOR_GRID[i] - MIDDLE_SENSOR_GRID[j];
}
}
}
for (int i = 0; i < 4; i++) {
for (int j = i; j < 4; j++) {
if (SIDE_SENSOR_GRID[i] > SIDE_SENSOR_GRID[j]) {
diff += SIDE_SENSOR_GRID[i] - SIDE_SENSOR_GRID[j];
} else {
diff -= SIDE_SENSOR_GRID[i] - SIDE_SENSOR_GRID[j];
}
}
}
return diff;
}
//-----------------------------------------------------
//----------------LOW LEVEL CONTROL--------------------
void motorsSetSpeed(int leftSideSpeed, int rightSideSpeed) {
isDataAvailable = true;
if (leftSideSpeed > 0) {
motors[0].setSpeed(leftSideSpeed);
motors[2].setSpeed(leftSideSpeed);
motors[2].run(BACKWARD);
motors[0].run(FORWARD);
} else if (leftSideSpeed < 0) {
motors[0].setSpeed(-leftSideSpeed);
motors[2].setSpeed(-leftSideSpeed);
motors[2].run(FORWARD);
motors[0].run(BACKWARD);
} else {
motors[0].run(RELEASE);
motors[2].run(RELEASE);
}
if (rightSideSpeed > 0) {
motors[1].setSpeed(rightSideSpeed);
motors[3].setSpeed(rightSideSpeed);
motors[3].run(FORWARD);
motors[1].run(BACKWARD);
} else if (rightSideSpeed < 0) {
motors[1].setSpeed(-rightSideSpeed);
motors[3].setSpeed(-rightSideSpeed);
motors[3].run(BACKWARD);
motors[1].run(FORWARD);
} else {
motors[1].run(RELEASE);
motors[3].run(RELEASE);
}
}
int getMiddleSensorData(int sensorId) {
delay(SENSOR_DELAY);
return analogRead(MIDDLE_SENSOR_GRID[sensorId]);
}
int getSideSensorData(int sensorId) {
delay(SENSOR_DELAY);
return analogRead(SIDE_SENSOR_GRID[sensorId]);
}
void turnOffLight() {
analogWrite(SENSOR_LIGHT, SENSOR_LIGHT_POWER_DOWN);
}
void turnOnHabarite() {
digitalWrite(INDICATOR_GRID[1], HIGH);
digitalWrite(INDICATOR_GRID[3], HIGH);
digitalWrite(INDICATOR_GRID[5], HIGH);
digitalWrite(INDICATOR_GRID[7], HIGH);
}
void turnOnFront() {
digitalWrite(INDICATOR_GRID[0], HIGH);
}
void turnOnBack() {
digitalWrite(INDICATOR_GRID[4], HIGH);
}
void turnOnLeftSide() {
digitalWrite(INDICATOR_GRID[6], HIGH);
}
void turnOnFullLeftSide() {
digitalWrite(INDICATOR_GRID[5], HIGH);
digitalWrite(INDICATOR_GRID[6], HIGH);
digitalWrite(INDICATOR_GRID[7], HIGH);
}
void turnOnRightSide() {
digitalWrite(INDICATOR_GRID[2], HIGH);
}
void turnOnFullRightSide() {
digitalWrite(INDICATOR_GRID[1], HIGH);
digitalWrite(INDICATOR_GRID[2], HIGH);
digitalWrite(INDICATOR_GRID[3], HIGH);
}
void blinkToTheLeft() {
turnOnLeftSide();
}
void blinkToTheRight() {
turnOnRightSide();
}
void turnOffIndicators() {
for (int i = 0; i < 8; i++) {
digitalWrite(INDICATOR_GRID[i], LOW);
}
}