Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
This tank is my entry for the IDT Wireless Power challenge. The tank is a treaded vehicle which is controlled using the Particle Photon platform.
The project was initially set up to create an easy to print and build, remote controlled vehicle. It uses 2 9g Fitec servo's which are easy to come by. Optionally you can add 3 LEDs for a cool 'Knight Rider' effect.
Here is the first testing of the Tank and the 'Knight Rider' effect:
Furthermore, the tank is built to be easily extendable, providing a lot of mounting holes to add your own components.
One of these components is the IDT Wireless Power Receiver unit. This way you can drive around with the tank and when you are done, park it on a charging station, so you are good to go the next day.
To get your own tank, get the plans at Thingiverse.com and get them printed or print them yourself.
Connect the Photon with the LEDs, servo's and IDT Power Receiver and you and good to go.
All code is provided: you will need to flash your Photon with the provided code.
The Photon has a method provided which will receive an IP address and port, and then it will connect to there. Connecting to the Photon appears to have some problems, so that is why this method of working has been chosen.
The Photon will then listen to small commands: SL90 will set the left servo to move full speed forward, SL50 forward slower, SL-50, slow backwards.
Also the LEDs can be controlled in this way:
L01#W100#L11#W100#L21#W100#L00#W100#L10#W100#L20#W100#"
+ "L21#W100#L11#W100#L01#W100#L20#W100#L10#W100#L00#W100
will provided you with a 'Knight Rider' effect, for those who still know this.
The Visual Studio code allows you to run a console program which gets the commands from an XBox controller, it is fun to drive around with this. But you can also command it from your own program.
Here is my son driving the tank:
And most importantly: the IDT Receiver can be bolted under the tank, so you can drive around, park it on the sender and be ready to go in the morning.
Particle Photon code
ArduinoString iCommand = "";
Servo iServoLeft;
Servo iServoRight;
TCPClient iClient;
byte iServerIP[] = { 192, 168, 1, 6 };
int iLED0 = A0;
int iLED1 = A1;
int iLED2 = A2;
void setup()
{
pinMode(iLED0, OUTPUT);
pinMode(iLED1, OUTPUT);
pinMode(iLED2, OUTPUT);
//digitalWrite(iLED0, HIGH);
digitalWrite(iLED1, HIGH);
//digitalWrite(iLED2, HIGH);
iServoLeft.attach(D0);
iServoRight.attach(D1);
Particle.function("connect", ConnectToServer);
Particle.publish("I am up at " + WiFi.localIP().toString());
}
int ConnectToServer(String p)
{
//Particle.publish("Parameter: " + p + "--");
int lP1 = p.indexOf('.');
int lP2 = p.indexOf('.', lP1 + 1);
int lP3 = p.indexOf('.', lP2 + 1);
int lP4 = p.indexOf('.', lP3 + 1);
int lP5 = p.indexOf('.', lP4 + 1);
String l1 = p.substring(0, lP1);
String l2 = p.substring(lP1 + 1, lP2);
String l3 = p.substring(lP2 + 1, lP3);
String l4 = p.substring(lP3 + 1, lP4);
String l5 = p.substring(lP4 + 1, lP5);
Particle.publish("Connecting to '" + p + "' --> '" + l1 + "+" + l2 + "+" + l3 + "+" + l4 + ":" + l5 + "'");
iClient.connect({ l1.toInt(), l2.toInt(), l3.toInt(), l4.toInt() }, l5.toInt());
Particle.publish("Connected!");
return 1;
}
void loop()
{
if (!iClient.connected())
{
iClient.stop();
Particle.publish("Lost connection!");
digitalWrite(iLED0, LOW);
digitalWrite(iLED1, HIGH);
digitalWrite(iLED0, LOW);
iServoLeft.write(90);
iServoRight.write(90);
}
/*if (!iClient.connected())
{
Particle.publish("Waiting for connection...");
delay(1000);
}
*/
while (iClient.available())
{
char lChar = iClient.read();
if(lChar == '#')
{
ProcessCommand();
}
else
{
iCommand += lChar;
}
}
}
void ProcessCommand()
{
Particle.publish("Processing '" + iCommand + "'");
String lType = iCommand.substring(0, 1);
String lParameters = iCommand.substring(1);
iCommand = "";
if(lType == "S")
{
// Example: SL10#
SetServo(lParameters);
// iClient.printf("S");
}
else if(lType == "L")
{
// Example: L11# 1 = HIGH, 0 = LOW
SetLED(lParameters);
//iClient.printf("L");
}
else if(lType == "W")
{
delay(lParameters.toInt());
//iClient.printf("W");
}
}
int SetLED(String pParameters)
{
String lLED = pParameters.substring(0, 1);
String lValue = pParameters.substring(1);
//Particle.publish("##" + pParameters + "-" + lLED + "-" + lValue + "-");
if(lLED == "0")
{
if(lValue == "1")
{
digitalWrite(iLED0, HIGH);
}
else if(lValue == "0")
{
digitalWrite(iLED0, LOW);
}
}
else if(lLED == "1")
{
if(lValue == "1")
{
digitalWrite(iLED1, HIGH);
}
else if(lValue == "0")
{
digitalWrite(iLED1, LOW);
}
}
else if(lLED == "2")
{
if(lValue == "1")
{
digitalWrite(iLED2, HIGH);
}
else if(lValue == "0")
{
digitalWrite(iLED2, LOW);
}
}
return lValue.toInt();
}
int SetServo(String pParameters)
{
String lServo = pParameters.substring(0, 1);
String lValueRaw = pParameters.substring(1);
int lValue = lValueRaw.toInt();
if(lValue < -89) lValue = -89;
if(lValue > 89) lValue = 89;
if(lServo == "L")
{
iServoLeft.write(90 + lValue);
}
if(lServo == "R")
{
iServoRight.write(90 - lValue);
}
if(lServo == "X")
{
iServoLeft.write(90 + lValue);
iServoRight.write(90 - lValue);
}
return lValue;
}
Visual Studio code
C#private static NetworkStream iStream;
private static StringBuilder iCommandBuilder;
private static bool iCommandFlush = true;
private static int iTestSpeedL = 30;
private static int iTestSpeedR = 30;
private static Controller iController;
static void Main(string[] args)
{
Console.WriteLine("Looking for Xbox controller...");
iController = new Controller(UserIndex.One);
if (iController.IsConnected)
{
Console.WriteLine("Xbox controller connected!");
}
iCommandBuilder = new StringBuilder();
string lAvogadro = "provide your photon key";
string lToken = "provide your token";
string lMethodName = "connect";
string lUrl = @"https://api.particle.io/v1/devices/" + lAvogadro + @"/" + lMethodName + "?access_token=" + lToken;
int lPort = 13914;
Console.WriteLine("Start listener...");
TcpListener lListener = new TcpListener(lPort);
lListener.Start();
Console.WriteLine("Sending IP '" + GetOwnIP() + "'...");
WebClient lWebClient = new WebClient();
var lValues = new NameValueCollection();
lValues.Add("args", GetOwnIP() + "." + lPort.ToString());
lWebClient.UploadValues(lUrl, lValues);
Console.Write("Waiting for a connection... ");
TcpClient lClient = lListener.AcceptTcpClient();
Console.WriteLine("Connected!");
iStream = lClient.GetStream();
int lAngle = 90;
short lOldLeft = -10000;
short lOldRight = -10000;
bool lLed0 = false;
bool lLed1 = false;
bool lLed2 = false;
bool lA = false;
if (iController != null)
{
while (true)
{
var lState = iController.GetState();
var lLeft = lState.Gamepad.LeftThumbY;
var lRight = lState.Gamepad.RightThumbY;
Console.WriteLine("Left: " + lLeft + " - Right: " + lRight);
if (Math.Abs(lOldLeft - lLeft) + Math.Abs(lOldRight - lRight) > 5000)
{
BeginCommand();
WriteCommand(ObjectEnum.ServoLeft, Convert.ToInt32(Math.Round(lLeft / 1000.0)));
WriteCommand(ObjectEnum.ServoRight, Convert.ToInt32(Math.Round(lRight / 1000.0)));
EndCommand();
lOldLeft = lLeft;
lOldRight = lRight;
}
if (lState.Gamepad.LeftTrigger == 255 && !lLed0)
{
string lCommand = "L01#";
WriteCommand(lCommand);
lLed0 = true;
}
else if (lState.Gamepad.LeftTrigger == 0 && lLed0)
{
string lCommand = "L00#";
WriteCommand(lCommand);
lLed0 = false;
}
if (lState.Gamepad.RightTrigger == 255 && !lLed2)
{
string lCommand = "L21#";
WriteCommand(lCommand);
lLed2 = true;
}
else if (lState.Gamepad.RightTrigger == 0 && lLed2)
{
string lCommand = "L20#";
WriteCommand(lCommand);
lLed2 = false;
}
if (lState.Gamepad.Buttons == GamepadButtonFlags.A && !lA)
{
WriteCommand("L01#W100#L11#W100#L21#W100#L00#W100#L10#W100#L20#W100#"
+ "L21#W100#L11#W100#L01#W100#L20#W100#L10#W100#L00#W100#");
//lState.Gamepad.Buttons = GamepadButtonFlags.None;
lA = true;
}
if (lState.Gamepad.Buttons != GamepadButtonFlags.A)
{
lA = false;
}
}
}
Console.WriteLine("Give input...");
var lKey = Console.ReadKey();
while (lKey.KeyChar != 'c')
{
if (lKey.KeyChar == 'a')
{
//WriteCommand("SR10#SL170#W300#SR90#SL90#");
BeginCommand();
WriteCommand(ObjectEnum.ServoBoth, 50);
WriteCommand(ObjectEnum.Wait, 300);
WriteCommand(ObjectEnum.ServoBoth, 0);
EndCommand();
}
else if (lKey.KeyChar == 'z')
{
//WriteCommand("SR170#SL10#W300#SR90#SL90#");
BeginCommand();
WriteCommand(ObjectEnum.ServoBoth, -50);
WriteCommand(ObjectEnum.Wait, 300);
WriteCommand(ObjectEnum.ServoBoth, 0);
EndCommand();
}
else if (lKey.KeyChar == 'o')
{
//WriteCommand("SR170#SL170#W100#SR90#SL90#");
BeginCommand();
WriteCommand(ObjectEnum.ServoLeft, -50);
WriteCommand(ObjectEnum.ServoRight, 50);
WriteCommand(ObjectEnum.Wait, 300);
WriteCommand(ObjectEnum.ServoBoth, 0);
EndCommand();
}
else if (lKey.KeyChar == 'p')
{
//WriteCommand("SR10#SL10#W100#SR90#SL90#");
BeginCommand();
WriteCommand(ObjectEnum.ServoLeft, 50);
WriteCommand(ObjectEnum.ServoRight, -50);
WriteCommand(ObjectEnum.Wait, 300);
WriteCommand(ObjectEnum.ServoBoth, 0);
EndCommand();
}
else if (lKey.KeyChar == '3')
{
string lCommand = "L0";
if (lLed0) lCommand += "0#";
else lCommand += "1#";
WriteCommand(lCommand);
lLed0 = !lLed0;
}
else if (lKey.KeyChar == '2')
{
string lCommand = "L1";
if (lLed1) lCommand += "0#";
else lCommand += "1#";
WriteCommand(lCommand);
lLed1 = !lLed1;
}
else if (lKey.KeyChar == '1')
{
string lCommand = "L2";
if (lLed2) lCommand += "0#";
else lCommand += "1#";
WriteCommand(lCommand);
lLed2 = !lLed2;
}
else if (lKey.KeyChar == '4')
{
WriteCommand("L01#W100#L11#W100#L21#W100#L00#W100#L10#W100#L20#W100#"
+ "L21#W100#L11#W100#L01#W100#L20#W100#L10#W100#L00#W100#");
}
else if (lKey.KeyChar == 'h')
{
iTestSpeedR += 1;
Console.WriteLine("TestspeedR " + iTestSpeedR.ToString());
WriteCommand(ObjectEnum.ServoRight, iTestSpeedR);
}
else if (lKey.KeyChar == 'n')
{
iTestSpeedR -= 1;
Console.WriteLine("iTestSpeedR " + iTestSpeedR.ToString());
WriteCommand(ObjectEnum.ServoRight, iTestSpeedR);
}
else if (lKey.KeyChar == 'g')
{
iTestSpeedL += 1;
Console.WriteLine("iTestSpeedL " + iTestSpeedL.ToString());
WriteCommand(ObjectEnum.ServoLeft, iTestSpeedL);
}
else if (lKey.KeyChar == 'b')
{
iTestSpeedL -= 1;
Console.WriteLine("iTestSpeedL " + iTestSpeedL.ToString());
WriteCommand(ObjectEnum.ServoLeft, iTestSpeedL);
}
lKey = Console.ReadKey();
while (iStream.DataAvailable)
{
// Read the first batch of the TcpServer response bytes.
Byte[] lDataBuffer = new Byte[256];
Int32 lBytes = iStream.Read(lDataBuffer, 0, lDataBuffer.Length);
var lData = System.Text.Encoding.ASCII.GetString(lDataBuffer, 0, lBytes);
Console.WriteLine("Received: {0}", lData);
}
}
// Close everything.
iStream.Close();
lClient.Close();
}
private static void EndCommand()
{
iCommandFlush = true;
FlushCommand();
}
private static void FlushCommand()
{
if (iCommandFlush)
{
Byte[] lD = System.Text.Encoding.ASCII.GetBytes(iCommandBuilder.ToString());
iStream.Write(lD, 0, lD.Length);
iCommandBuilder.Clear();
}
}
private static void BeginCommand()
{
iCommandFlush = false;
}
private static void WriteCommand(ObjectEnum pObject, int pValue)
{
switch (pObject)
{
case ObjectEnum.ServoLeft:
WriteCommand("SL" + pValue.ToString());
break;
case ObjectEnum.ServoRight:
WriteCommand("SR" + pValue.ToString());
break;
case ObjectEnum.ServoBoth:
WriteCommand("SX" + pValue.ToString());
break;
/*case ObjectEnum.Led0:
case ObjectEnum.Led1:
case ObjectEnum.Led2:
*/
case ObjectEnum.Wait:
WriteCommand("W" + pValue.ToString());
break;
default:
throw new Exception("Invalid command!");
}
}
private static float iSpeedZero = -1.0F;
private static float iSpeedRange = 1.2F;
private static float iDirectionZero = 0.02F;
private static float iDirectionFactor = 10F;
private static void WriteCommand(string pCommand)
{
if (iStream == null || !iStream.CanWrite) return;
if (pCommand.EndsWith("#"))
{
iCommandBuilder.Append(pCommand);
}
else
{
iCommandBuilder.Append(pCommand + "#");
}
FlushCommand();
}
private static string GetOwnIP()
{
IPHostEntry host;
host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily.ToString() == "InterNetwork")
{
return ip.ToString();
}
}
return null;
}
Comments