Monday, April 9, 2012

Tutorial: how to control an IR helicopter programmatically with Arduino, Android, Kinect, brain, and more!

For those of you who are not familiar with an infrared helicopter, please search "SYMA S107" on eBay or on YouTube. It's a little indoor coaxial helicopter around 20 USD, recharged through USB cable.




The original remote control that comes with the helicopter is pretty good, at least a lot easier to use than a touchscreen. 



However, our passion doesn't end here with mere manual control. Inspired by all those robodance projects, we need more joy of robotic automation. 

Let's hack the SYMA S107 helicopter to give it some artificial intelligence! 

You might wonder why this particular model (Syma S107)? Well, simply because it is perhaps the most popular one in the IR helicopter market. And many brilliant hackers have already done the work for us. We don't have to reinvent the wheel to hack it again.

As usual, let's start with an Arduino, the most simple way to prototype electronic projects. Our goal here is to make an arduino based IR transmitter to programmatically control the helicopter. 

Step 1. Prepare the circuit 


The circuit is extremely simple, we just need:

1 Arduino, 1 IR LED and 1 resister of 200 to 1000 ohm.

Optional: an IR receiver (for testing).



That's all to transmit IR signal from Arduino to the helicopter. If you already have an Arduino board, it costs you almost nothing to get the rest parts.

If you are interested in IR tests, here is a detailed tutorial: http://www.ladyada.net/learn/sensors/ir.html

Otherwise, we can directly load the Arduino code and control the helicopter!

Step 2. Program the Arduino

Fortunately, there are several existing Arduino projects we can directly use and customize. 

The first working code (tested):
//Arduino code to control a helicotper.
int IRledPin =  12;    
int incomingByte = 0;
String incomingString;
int pulseValues[33];
int pulseLength = 0;

void setup()   {                
  // initialize the IR digital pin as an output:
  pinMode(IRledPin, OUTPUT);      
  pinMode(13, OUTPUT);   
  Serial.begin(9600);

  for (int i=0; i < 13; i++) 
    pulseValues[i] = 0;


}

void loop()                     
{
  SendCode();
}


void pulseIR(long microsecs) {
  cli();  // this turns off any background interrupts

  while (microsecs > 0) {
    // 38 kHz is about 13 microseconds high and 13 microseconds low
    digitalWrite(IRledPin, HIGH);  // this takes about 3 microseconds to happen
    delayMicroseconds(10);         // hang out for 10 microseconds
    digitalWrite(IRledPin, LOW);   // this also takes about 3 microseconds
    delayMicroseconds(10);         // hang out for 10 microseconds

    // so 26 microseconds altogether
    microsecs -= 26;

  }

  sei();  // this turns them back on
}

void Zero()
{  
  pulseIR(300);
  delayMicroseconds(300);
  pulseLength += 600;
}

void One()
{
  pulseIR(300);
  delayMicroseconds(600); 
  pulseLength += 900;
}

void sendPulseValue(int pulseValue)
{
  if (pulseValue == 1)
    One();
  else
    Zero(); 
}

void checkPulseChanges()
{
  if (Serial.available() > 0)
  {
    incomingByte = Serial.read();

    //Pulse 1
    if (incomingByte == 'a')
      pulseValues[0] = 0;
    if (incomingByte == 'A')
      pulseValues[0] = 1;

    //Pulse 2
    if (incomingByte == 'b')
      pulseValues[1] = 0;
    if (incomingByte =='B')
      pulseValues[1] = 1;

    //Pulse 3
    if (incomingByte == 'c')
      pulseValues[2] = 0;
    if (incomingByte == 'C')
      pulseValues[2] = 1;

    //Pulse 4
    if (incomingByte == 'd')
      pulseValues[3] = 0;
    if (incomingByte == 'D')
      pulseValues[3] = 1;

    //Pulse 5
    if (incomingByte == 'e')
      pulseValues[4] = 0;
    if (incomingByte == 'E')
      pulseValues[4] = 1;

    //Pulse 6
    if (incomingByte == 'f')
      pulseValues[5] = 0;
    if (incomingByte == 'F')
      pulseValues[5] = 1;

    //Pulse 7
    if (incomingByte == 'g')
      pulseValues[6] = 0;
    if (incomingByte == 'G')
      pulseValues[6] = 1;    

    //Pulse 8
    if (incomingByte == 'h')
      pulseValues[7] = 0;
    if (incomingByte == 'H')
      pulseValues[7] = 1;

    //Pulse 9
    if (incomingByte == 'i')
      pulseValues[8] = 0;
    if (incomingByte == 'I')
      pulseValues[8] = 1;

    //Pulse 10
    if (incomingByte == 'j')
      pulseValues[9] = 0;
    if (incomingByte == 'J')
      pulseValues[9] = 1;

    //Pulse 11
    if (incomingByte == 'k')
      pulseValues[10] = 0;
    if (incomingByte == 'K')
      pulseValues[10] = 1;

    //Pulse 12
    if (incomingByte == 'l')
      pulseValues[11] = 0;
    if (incomingByte == 'L')
      pulseValues[11] = 1;

    //Pulse 13
    if (incomingByte == 'm')
      pulseValues[12] = 0;
    if (incomingByte == 'M')
      pulseValues[12] = 1;

    //Pulse 14
    if (incomingByte == 'o')
      pulseValues[13] = 0;
    if (incomingByte == 'O')
      pulseValues[13] = 1;

    //Pulse 15
    if (incomingByte == 'p')
      pulseValues[14] = 0;
    if (incomingByte == 'P')
      pulseValues[14] = 1;

    //Pulse 16
    if (incomingByte == 'q')
      pulseValues[15] = 0;
    if (incomingByte == 'Q')
      pulseValues[15] = 1;

    //Pulse 17
    if (incomingByte == 'r')
      pulseValues[16] = 0;
    if (incomingByte == 'R')
      pulseValues[16] = 1;

    //Pulse 18
    if (incomingByte == 's')
      pulseValues[17] = 0;
    if (incomingByte == 'S')
      pulseValues[17] = 1;

    //Pulse 19
    if (incomingByte == 't')
      pulseValues[18] = 0;
    if (incomingByte == 'T')
      pulseValues[18] = 1;

    //Pulse 20
    if (incomingByte == 'u')
      pulseValues[19] = 0;
    if (incomingByte == 'U')
      pulseValues[19] = 1;

    //Pulse 21
    if (incomingByte == 'v')
      pulseValues[20] = 0;
    if (incomingByte == 'V')
      pulseValues[20] = 1;

    //Pulse 22
    if (incomingByte == 'w')
      pulseValues[21] = 0;
    if (incomingByte == 'W')
      pulseValues[21] = 1;

    //Pulse 23
    if (incomingByte == 'x')
      pulseValues[22] = 0;
    if (incomingByte == 'X')
      pulseValues[22] = 1;

    //Pulse 24
    if (incomingByte == 'y')
      pulseValues[23] = 0;
    if (incomingByte == 'Y')
      pulseValues[23] = 1;

    //Pulse 25
    if (incomingByte == 'z')
      pulseValues[24] = 0;
    if (incomingByte == 'Z')
      pulseValues[24] = 1;

    //Pulse 26
    if (incomingByte == '1')
      pulseValues[25] = 0;
    if (incomingByte == '2')
      pulseValues[25] = 1;

    //Pulse 27
    if (incomingByte == '3')
      pulseValues[26] = 0;
    if (incomingByte == '4')
      pulseValues[26] = 1;

    //Pulse 28
    if (incomingByte == '5')
      pulseValues[27] = 0;
    if (incomingByte == '6')
      pulseValues[27] = 1;

    //Pulse 29
    if (incomingByte == '7')
      pulseValues[28] = 0;
    if (incomingByte == '8')
      pulseValues[28] = 1;

    //Pulse 30
    if (incomingByte == '9')
      pulseValues[29] = 0;
    if (incomingByte == '!')
      pulseValues[29] = 1;

    //Pulse 31
    if (incomingByte == '@')
      pulseValues[30] = 0;
    if (incomingByte == '#')
      pulseValues[30] = 1;

    //Pulse 32
    if (incomingByte == '$')
      pulseValues[31] = 0;
    if (incomingByte == '%')
      pulseValues[31] = 1;

    //Pulse 33
    if (incomingByte == '^')
      pulseValues[32] = 0;
    if (incomingByte == '&')
      pulseValues[32] = 1;      
  }

}

void SendCode() {

  while (true)
  {
    checkPulseChanges();

    pulseIR(4000);
    delayMicroseconds(2000);
    pulseLength=6000;

    sendPulseValue(pulseValues[0]);
    sendPulseValue(pulseValues[1]);
    sendPulseValue(pulseValues[2]);
    sendPulseValue(pulseValues[3]);
    sendPulseValue(pulseValues[4]);
    sendPulseValue(pulseValues[5]);
    sendPulseValue(pulseValues[6]);
    sendPulseValue(pulseValues[7]);
    sendPulseValue(pulseValues[8]);
    sendPulseValue(pulseValues[9]);
    sendPulseValue(pulseValues[10]);
    sendPulseValue(pulseValues[11]);
    sendPulseValue(pulseValues[12]);
    sendPulseValue(pulseValues[13]);
    sendPulseValue(pulseValues[14]);
    sendPulseValue(pulseValues[15]);
    sendPulseValue(pulseValues[16]);
    sendPulseValue(pulseValues[17]);
    sendPulseValue(pulseValues[18]);
    sendPulseValue(pulseValues[19]);
    sendPulseValue(pulseValues[20]);
    sendPulseValue(pulseValues[21]);
    sendPulseValue(pulseValues[22]);
    sendPulseValue(pulseValues[23]);
    sendPulseValue(pulseValues[24]);
    sendPulseValue(pulseValues[25]);
    sendPulseValue(pulseValues[26]);
    sendPulseValue(pulseValues[27]);
    sendPulseValue(pulseValues[28]);
    sendPulseValue(pulseValues[29]);
    sendPulseValue(pulseValues[30]);
    sendPulseValue(pulseValues[31]);

    //Footer
    pulseIR(360); 
    delayMicroseconds( (28600 - pulseLength) ); 
   } 
}

This is the processing code to control the helicopter using the PC's camera and mouse wheel:
 
import processing.serial.*; 
import controlP5.*;
import JMyron.*;

JMyron m;

ControlP5 controlP5;
CheckBox checkbox;
Button b;

float boxX;
float boxY;
int boxSize = 20;
boolean mouseOverBox = false;
byte[] previousFlags = new byte[32];
byte[] flagsToSend = new byte[32];
Serial port; 
String outString;

int helicopterUpSpeed = 0;
int helicopterPitch = 63;
int helicopterYaw = 68;

void setup() {
  m = new JMyron();
  m.start(640,480);
  size(640, 480);

  controlP5 = new ControlP5(this);
  checkbox = controlP5.addCheckBox("checkBox", 20, 20);  
  // make adjustments to the layout of a checkbox.
  checkbox.setColorForeground(color(120));
  checkbox.setColorActive(color(255));
  checkbox.setColorLabel(color(128));
  checkbox.setItemsPerRow(8);
  checkbox.setSpacingColumn(30);
  checkbox.setSpacingRow(10);
  // add items to a checkbox.
  checkbox.addItem("1", 0);
  checkbox.addItem("2", 0);
  checkbox.addItem("3", 0);
  checkbox.addItem("4", 0);
  checkbox.addItem("5", 0);
  checkbox.addItem("6", 0);
  checkbox.addItem("7", 0);
  checkbox.addItem("8", 0);
  checkbox.addItem("9", 0);
  checkbox.addItem("10", 0);
  checkbox.addItem("11", 0);
  checkbox.addItem("12", 0);
  checkbox.addItem("13", 0);
  checkbox.addItem("14", 0);
  checkbox.addItem("15", 0);
  checkbox.addItem("16", 0);
  checkbox.addItem("17", 0);
  checkbox.addItem("18", 0);
  checkbox.addItem("19", 0);
  checkbox.addItem("20", 0);
  checkbox.addItem("21", 0);
  checkbox.addItem("22", 0);
  checkbox.addItem("23", 0);
  checkbox.addItem("24", 0);
  checkbox.addItem("25", 0);
  checkbox.addItem("26", 0);
  checkbox.addItem("27", 0);
  checkbox.addItem("28", 0);
  checkbox.addItem("29", 0);
  checkbox.addItem("30", 0);
  checkbox.addItem("31", 0);
  checkbox.addItem("32", 0);

  checkbox.deactivateAll();


  controlP5.addButton("Up", 0, 120, 120, 35, 20);
  controlP5.addButton("Down", 0, 120, 160, 35, 20);
  
  controlP5.addButton("Forward", 0, 180, 120, 45, 20);
  controlP5.addButton("Backward", 0, 180, 160, 45, 20);  
  
  controlP5.addButton("TurnLeft", 0, 60, 120, 40, 20);
  controlP5.addButton("TurnRight", 0, 60, 160, 40, 20);  
  

  port = new Serial(this, Serial.list()[0], 9600);

  for (int i=0;i<32;i++) 
  {
    flagsToSend[i] = 0;
    previousFlags[i] = 0;
  }

  addMouseWheelListener(new java.awt.event.MouseWheelListener() { 
    public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { 
      mouseWheel(evt.getWheelRotation());
    }
  }
  ); 

  startSetUp();
}

String addForwardZeroesTT(String inputString, int totalLength)
{

  String outString = "";
  for (int i = 0; i < (totalLength - inputString.length()); i++)
    outString += "0";

  outString = outString + inputString;

  return outString;
}


//Incremental like bits
//0000, 0001, 0010, 0011, 0100, etc
void Up()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

  if(helicopterUpSpeed <= 125)
    helicopterUpSpeed += 1;

  String newSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

  setNewSpeed(currentSpeed, newSpeed);
}

void Down()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);
  if (helicopterUpSpeed > 0)
    helicopterUpSpeed -= 1;
  String newSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

  setNewSpeed(currentSpeed, newSpeed);
} 


void Backward()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

  helicopterPitch += 1;

  String newSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

   setNewPitch(currentSpeed, newSpeed);
}

void Forward()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

  helicopterPitch -= 1;

  String newSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

   setNewPitch(currentSpeed, newSpeed);
}

void TurnLeft()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

  helicopterYaw -= 1;

  String newSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

  setNewYaw(currentSpeed, newSpeed);
}

void TurnRight()
{
  String currentSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

  helicopterYaw += 1;

  String newSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

  setNewYaw(currentSpeed, newSpeed);
}


void setNewSpeed(String currentSpeed, String newSpeed)
{

  //Compare each bit and see if it needs changing.
  if  (newSpeed.charAt(6) != currentSpeed.charAt(6) )
    checkbox.toggle(23);

  if  (newSpeed.charAt(5) != currentSpeed.charAt(5) )
    checkbox.toggle(22);

  if  (newSpeed.charAt(4) != currentSpeed.charAt(4) )
    checkbox.toggle(21);    

  if  (newSpeed.charAt(3) != currentSpeed.charAt(3) )
    checkbox.toggle(20);    

  if  (newSpeed.charAt(2) != currentSpeed.charAt(2) )
    checkbox.toggle(19);    

  if  (newSpeed.charAt(1) != currentSpeed.charAt(1) )
    checkbox.toggle(18);    

  if  (newSpeed.charAt(0) != currentSpeed.charAt(0) )
    checkbox.toggle(17);
}

void setNewPitch(String currentSpeed, String newSpeed)
{
  if  (newSpeed.charAt(6) != currentSpeed.charAt(6) )
    checkbox.toggle(15);

  if  (newSpeed.charAt(5) != currentSpeed.charAt(5) )
    checkbox.toggle(14);

  if  (newSpeed.charAt(4) != currentSpeed.charAt(4) )
    checkbox.toggle(13);    

  if  (newSpeed.charAt(3) != currentSpeed.charAt(3) )
    checkbox.toggle(12);    

  if  (newSpeed.charAt(2) != currentSpeed.charAt(2) )
    checkbox.toggle(11);    

  if  (newSpeed.charAt(1) != currentSpeed.charAt(1) )
    checkbox.toggle(10);    

  if  (newSpeed.charAt(0) != currentSpeed.charAt(0) )
    checkbox.toggle(9);
}

void setNewYaw(String currentSpeed, String newSpeed)
{
  if  (newSpeed.charAt(6) != currentSpeed.charAt(6) )
    checkbox.toggle(7);

  if  (newSpeed.charAt(5) != currentSpeed.charAt(5) )
    checkbox.toggle(6);

  if  (newSpeed.charAt(4) != currentSpeed.charAt(4) )
    checkbox.toggle(5);    

  if  (newSpeed.charAt(3) != currentSpeed.charAt(3) )
    checkbox.toggle(4);    

  if  (newSpeed.charAt(2) != currentSpeed.charAt(2) )
    checkbox.toggle(3);    

  if  (newSpeed.charAt(1) != currentSpeed.charAt(1) )
    checkbox.toggle(2);    

  if  (newSpeed.charAt(0) != currentSpeed.charAt(0) )
    checkbox.toggle(1);
}

void startSetUp()
{
  //First clear the arduino.
  port.write('a'); 
  port.write('b'); 
  port.write('c'); 
  port.write('d'); 
  port.write('e'); 
  port.write('f'); 
  port.write('g'); 
  port.write('h'); 
  port.write('i'); 
  port.write('j'); 
  port.write('k'); 
  port.write('l');
  port.write('m'); 
  port.write('o'); 
  port.write('p'); 
  port.write('q');
  port.write('r'); 
  port.write('s'); 
  port.write('t'); 
  port.write('u');
  port.write('v'); 
  port.write('w'); 
  port.write('x'); 
  port.write('y'); 
  port.write('z'); 
  port.write('1'); 
  port.write('3'); 
  port.write('5');
  port.write('7'); 
  port.write('9'); 
  port.write('@'); 
  port.write('$'); 
  port.write('^');


  //Set the pulse to the basic configuration.
  checkbox.toggle(1);
  checkbox.toggle(6);


  checkbox.toggle(10);
  checkbox.toggle(11);

  checkbox.toggle(12);
  checkbox.toggle(13);
  checkbox.toggle(14);
  checkbox.toggle(15);
  checkbox.toggle(16);

  checkbox.toggle(25);

  checkbox.toggle(28);
  checkbox.toggle(29);
  checkbox.toggle(30);
}


void draw() 
{ 
  background(200);
  
  m.update();
  int[] img = m.image();
  
  //first draw the camera view onto the screen
  loadPixels();
  
  for(int i=0;i<640*480;i++){
      pixels[i] = img[i];
  }
  updatePixels();
   noFill();
  int[][] a;
   
  CheckHelicopterPosition();  
  
  
  text(" Current Speed: " + helicopterUpSpeed, 230, 135);
  text(" Pitch: " + helicopterPitch, 230, 165);
  text(" Yaw: " + helicopterYaw, 230, 195);  
}


void CheckHelicopterPosition()
{
  
  noFill();
  int[][] a;
  
  m.trackColor(255,255,0,255);
  //draw bounding boxes of globs
  a = m.globBoxes();
  stroke(255,0,0);
  
  int averageY = 0;
  
  for(int i=0;i<a.length;i++){
    int[] b = a[i];
    rect(b[0], b[1], b[2], b[3]);
    
    averageY += b[1];
    
  }
  
  if (a.length > 0)
  {
  averageY = averageY / a.length;
  line(0,averageY,640,averageY);
  
    text(" Average Y: " + averageY, 230, 215);  
    
    if (averageY > 240)
     {
      text(" Action: up ", 350, 20);
      delay(150);
      //Up();
     } 
     else
     {
      text(" Action down ", 350,20); 
      //delay(250);
      //Down();
     }
  }  

  
}  
  
  
  
void controlEvent(ControlEvent theEvent) {
  if (theEvent.isGroup()) {

    for (int i=0;i<theEvent.group().arrayValue().length;i++) 
    {
      byte n = (byte)theEvent.group().arrayValue()[i];
      flagsToSend[i] = n;
      //there was a change in the flags, send the update.
      if (previousFlags[i] != flagsToSend[i])
      {
        println(i);

        if (i==0) {   
          if (n == 0) { 
            port.write('a');
          } 
          else { 
            port.write('A');
          }
        }
        if (i==1) {   
          if (n == 0) { 
            port.write('b');
          } 
          else { 
            port.write('B');
          }
        } 
        if (i==2) {   
          if (n == 0) { 
            port.write('c');
          } 
          else { 
            port.write('C');
          }
        }
        if (i==3) {   
          if (n == 0) { 
            port.write('d');
          } 
          else { 
            port.write('D');
          }
        }                 
        if (i==4) {   
          if (n == 0) { 
            port.write('e');
          } 
          else { 
            port.write('E');
          }
        }    
        if (i==5) {   
          if (n == 0) { 
            port.write('f');
          } 
          else { 
            port.write('F');
          }
        }  
        if (i==6) {   
          if (n == 0) { 
            port.write('g');
          } 
          else { 
            port.write('G');
          }
        }
        if (i==7) {   
          if (n == 0) { 
            port.write('h');
          } 
          else { 
            port.write('H');
          }
        }
        if (i==8) {   
          if (n == 0) { 
            port.write('i');
          } 
          else { 
            port.write('I');
          }
        }
        if (i==9) {   
          if (n == 0) { 
            port.write('j');
          } 
          else { 
            port.write('J');
          }
        }
        if (i==10) {   
          if (n == 0) { 
            port.write('k');
          } 
          else { 
            port.write('K');
          }
        }
        if (i==11) {   
          if (n == 0) { 
            port.write('l');
          } 
          else { 
            port.write('L');
          }
        }
        if (i==12) {   
          if (n == 0) { 
            port.write('m');
          } 
          else { 
            port.write('M');
          }
        }
        if (i==13) {   
          if (n == 0) { 
            port.write('o');
          } 
          else { 
            port.write('O');
          }
        }  
        if (i==14) {   
          if (n == 0) { 
            port.write('p');
          } 
          else { 
            port.write('P');
          }
        }
        if (i==15) {   
          if (n == 0) { 
            port.write('q');
          } 
          else { 
            port.write('Q');
          }
        }
        if (i==16) {   
          if (n == 0) { 
            port.write('r');
          } 
          else { 
            port.write('R');
          }
        }
        if (i==17) {   
          if (n == 0) { 
            port.write('s');
          } 
          else { 
            port.write('S');
          }
        }
        if (i==18) {   
          if (n == 0) { 
            port.write('t');
          } 
          else { 
            port.write('T');
          }
        }
        if (i==19) {   
          if (n == 0) { 
            port.write('u');
          } 
          else { 
            port.write('U');
          }
        }
        if (i==20) {   
          if (n == 0) { 
            port.write('v');
          } 
          else { 
            port.write('V');
          }
        }
        if (i==21) {   
          if (n == 0) { 
            port.write('w');
          } 
          else { 
            port.write('W');
          }
        }
        if (i==22) {   
          if (n == 0) { 
            port.write('x');
          } 
          else { 
            port.write('X');
          }
        }
        if (i==23) {   
          if (n == 0) { 
            port.write('y');
          } 
          else { 
            port.write('Y');
          }
        }
        if (i==24) {   
          if (n == 0) { 
            port.write('z');
          } 
          else { 
            port.write('Z');
          }
        }
        if (i==25) {   
          if (n == 0) { 
            port.write('1');
          } 
          else { 
            port.write('2');
          }
        }
        if (i==26) {   
          if (n == 0) { 
            port.write('3');
          } 
          else { 
            port.write('4');
          }
        }
        if (i==27) {   
          if (n == 0) { 
            port.write('5');
          } 
          else { 
            port.write('6');
          }
        }
        if (i==28) {   
          if (n == 0) { 
            port.write('7');
          } 
          else { 
            port.write('8');
          }
        }
        if (i==29) {   
          if (n == 0) { 
            port.write('9');
          } 
          else { 
            port.write('!');
          }
        }
        if (i==30) {   
          if (n == 0) { 
            port.write('@');
          } 
          else { 
            port.write('#');
          }
        }
        if (i==31) {   
          if (n == 0) { 
            port.write('$');
          } 
          else { 
            port.write('%');
          }
        }
        if (i==32) {   
          if (n == 0) { 
            port.write('^');
          } 
          else { 
            port.write('&');
          }
        }
      }

      previousFlags[i]=n;
    }
  }
}


void mouseWheel(int delta) {
  if (delta == 1)
    Down();
  else
    Up();
}
We have tested it. It worked like a charm. Exactly as the project creator says:

"Utilizing the input from the webcam, it adjusts the speed until the helicopter is in the middle of the screen.
If it goes too high, it lowers the speed. If it gets too low or is stopped, it slowly increases the upwards speed."



For more details on this project, please have a glance here: http://www.avergottini.com/2011/05/arduino-helicopter-infrared-controller.html


The second working code (not yet tested by DIY Phone Gadgets):


Of course, your SYMA S107 helicopter might not always be exactly the same as others'. The first code might not work for your helicopter. Don't worry, it is possible that you have a 3-channel (30-bit) version, which uses a different protocol. Just load the following Arduino code:
/* S107 3-channel with checksum helicopter control code
 * Copyright (C) 2012, Andrew Barry, Dan Barry
 *
 * Uses an Arduino to control a S107 helicopter
 *
 *
 * Instructions:
 *  Connect an IR LED array to pin 8 (using a FET to amplify the signal)
 *  and use the serial monitor to send commands to the system
 *
 */

#define LED 8

#define STATUS 13

//#define TAKEOFF_THROTTLE 240
//#define HOLDING_THROTTLE 130

byte yawCmd, pitchCmd, throttleCmd, trimCmd;

// Set this value for the default channel
// A = 0
// B = 1
// C = 2
byte channel = 0;

/*
 * Setup function that initializes the serial port and
 * sets some default values for the control variables.
 * Also sets up the pins we'll be using.
 */
void setup()
{
 Serial.begin(9600);
 pinMode(STATUS,OUTPUT);
 digitalWrite(STATUS,LOW);

 pinMode(LED,OUTPUT);
 digitalWrite(LED,LOW);

 yawCmd = 8;
 pitchCmd = 8;
 trimCmd = 0;
 throttleCmd = 0;

 Serial.println("throttle = 0, standing by for commands.");
}

/*
 * Function that does the actual work of converting commands into
 * IR LED pulses and changes the pins in the appropriate manner.
 */
byte sendPacket(byte yaw, byte pitch, byte throttle, byte trim)
{

 int packetData[100];
 int pulseNum;

 digitalWrite(STATUS,HIGH);

 float channelDelayValue = 136500;

 // channel A B or C
 // A is 10 with 136500us packet delay
 // B is 01 with 105200us packet delay
 // C is 11 with 168700us packet delay
 if (channel == 0)
 {
   packetData[0] = 1;
   packetData[1] = 0;
   channelDelayValue = 136500;

 } else if (channel == 1)
 {
   packetData[0] = 0;
   packetData[1] = 1;
   channelDelayValue = 105200;

 } else {
   packetData[0] = 1;
   packetData[1] = 1;
   channelDelayValue = 168700;

 }
 packetData[2] = 0;
 packetData[3] = 0;

 // pitch

 packetData[7] = (pitch & 0b1000) >> 3; // direction bit

 if (pitch < 8)  {    pitch = 8 - pitch;  }  packetData[6] = (pitch & 0b0100) >> 2; // others are speed bits, note that they are reversed
 packetData[5] = (pitch & 0b0010) >> 1;
 packetData[4] = (pitch & 0b0001);

 // throttle
 // bits are reversed in the throttle command
 packetData[15] =  (throttle & 0b10000000) >> 7;
 packetData[14] =  (throttle & 0b01000000) >> 6;
 packetData[13] = (throttle & 0b00100000) >> 5;
 packetData[12] = (throttle & 0b00010000) >> 4;

 packetData[11] = (throttle & 0b00001000) >> 3;
 packetData[10] = (throttle & 0b00000100) >> 2;
 packetData[9] = (throttle & 0b00000010) >> 1;
 packetData[8] = (throttle & 0b00000001);

 // yaw
 packetData[19] = (yaw & 0b1000) >> 3; // direction bit
 if (yaw < 8)  {    yaw = 8 - yaw;  }  packetData[18] = (yaw & 0b0100) >> 2;
 packetData[17] = (yaw & 0b0010) >> 1;
 packetData[16] = (yaw & 0b0001);

 // these 4 bits are the checksum, so make sure they
 // are 0s so they don't change the XOR later on
 packetData[20] = 0;
 packetData[21] = 0;
 packetData[22] = 0;
 packetData[23] = 0;

 // yaw trim / yaw adjust (the little dial on the controller)
 // 6 bits
 packetData[24] = 0;
 packetData[25] = 0;
 packetData[26] = 0;
 packetData[27] = 0;

 packetData[28] = 0;
 packetData[29] = 0;

 // these bits are never sent but we do the checksum
 // computation in 4-bit chunks, with the trailing two
 // bits set to zero, so we set them to zero here to make
 // the checksum a bit easier to compute
 packetData[30] = 0;
 packetData[31] = 0;

 int i;

 int checksum[10];
 checksum[0] = 0;
 checksum[1] = 0;
 checksum[2] = 0;
 checksum[3] = 0;

 // compute checksum -- bitwise XOR of 4-bit chunks
 // with two zeros padding the *end* of the last two bits
 for (i=0; i 0)
  {
    if (Serial.available() == true)
    {
      Serial.println("HOLD ABORTED");
      break;
    }

    packetDelay = sendPacket(yawIn, pitchIn, throttleIn, trimCmd);
    delayTime = delayTime - packetDelay;

    delay(packetDelay);

    delay(delayAmount);
    delayTime = delayTime - delayAmount;
  }
  Serial.println("Done holding.");
}

void Land()
{
 static int i;
 Serial.println("Landing");
 for(i=throttleCmd;i>0;i--){
   HoldCommand(8,8,throttleCmd,50);
 }
 throttleCmd = 0;
}

/*
 * Function that manages receiving data from the serial port.
 * Mostly changes the global variables that are passed to the
 * control functions.
 */
void serialEvent()
{
 char cmd = Serial.read();
 Serial.println();
 Serial.print("command received is ");
 Serial.println(cmd);

 switch (cmd)
 {
   // Take off with 't'
   case 't':
     Serial.println("Taking Off");

     // Yaw: 1-15
     //    8 = no turn
     //    1 = max right turn
     //    15 = max left turn
     //
     // Pitch: 1-15
     //    8 = no pitch
     //    15 = max forward
     //    1 = max backwards
     //
     // Throttle: 0-255
     //    0 = off
     //    ~130 = steady flight
     //    ~240 = fast climb

     // First, go up with lots of throttle for 650ms
     // yaw: 8 --> no yaw
     // pitch: 8 --> no pitch
     // throttle: 240 --> fast climb
     // delay: 650ms --> enough time to climb, not too long so won't hit ceiling

     // HoldCommand: a function that sends the same data for a given amount of time
     // HoldCommand(yaw, pitch, throttle, time-to-hold-in-ms);
     HoldCommand(8, 8, 240, 650);

     // set the *global* throttle to steady flight throttle
     throttleCmd = 130;
     break;

   // land with 'x' or 'q'
   case 'x':
   case 'q':
     Land();
     break;

   // throttle commands
   case '0':
   case '1':
   case '2':
   case '3':
   case '4':
   case '5':
   case '6':
   case '7':
   case '8':
   case '9':
     throttleCmd = atoi(&cmd) * 25;  //single character, so we can go from 0 to 255 by inputting 0 to 9 in the serial monitor
     break;

   // turn left
   case 'a':
     if (yawCmd < 15)      {        yawCmd ++;      }      Serial.print("Yaw is ");      Serial.println(yawCmd);      break;    // turn right    case 'd':      if (yawCmd > 1)
     {
       yawCmd --;
     }
     Serial.print("Yaw is ");
     Serial.println(yawCmd);
     break;

   // move forwards
   case 'w':
     if (pitchCmd < 15){        pitchCmd ++;  // moves forward      }      Serial.print("Pitch is ");      Serial.println(pitchCmd);      break;      // move backwards    case 's':      if (pitchCmd > 1)
     {
       pitchCmd --;  // moves backward
     }
     Serial.print("Pitch is ");
     Serial.println(pitchCmd);
     break;

   // increase throttle
   case 'u':
     if (throttleCmd < 255 - 6)      {        throttleCmd += 6;      }      Serial.print("Throttle is ");      Serial.println(throttleCmd);      break;        // decrease throttle    case 'j':      if (throttleCmd > 6)
     {
       throttleCmd -= 6;
     }
     Serial.print("Trottle is ");
     Serial.println(throttleCmd);
     break;

   // change channel
   case 'c':
     Serial.println("Changing channel");
     if (channel >= 2)
     {
       channel = 0;
     } else
     {
       channel ++;
     }
     Serial.print("Channel is: ");
     Serial.println(channel);
     break;

   // reset yaw and pitch
   case 'r':
     Serial.println("resetting yaw and pitch");
     yawCmd = 8;
     pitchCmd = 8;
     break;

   default:
     Serial.println("Unknown command");
 }
 Serial.print("Throttle is at ");
 Serial.println(throttleCmd);
}

/*
 * Loops continuously sending and delaying for the transmission
 */
void loop()

{
   // Note that serialEvent() gets called on each path of the loop
   // and runs if there is data at the serial port

   // we call delay here on the return value of sendPacket because that will
   // cause us to put the right amount of time between packets.  The delay is
   // not constant, but is instead based on how long the packet was
   // that we sent
   delay(sendPacket(yawCmd, pitchCmd, throttleCmd, trimCmd));
}


To use the code, open the Serial Monitor (Tools > Serial Monitor) and use the following commands:

0-9: throttle
w: forward
a: left
s: backwards
d: right
t: take off
u: increase throttle
j: decrease throttle
r: reset pitch and yaw

For more details on this project, please go to:
http://www.abarry.org/likelytobeforgotten/?p=55

What's next?


Now that we can successfully send programmatical commands from Arduino, we can take advantage of the solutions we have learned here  in DIY Phone Gadgets to control the IR helicopter using PCs, game consoles, Kinect, tablets, smartphones or whatever electronic gadgets.

Here is a project using Kinect:


Here is a project using brain (your mind) to control a helicopter:


Or a Nunchuk-Wiimote-controlled helicopter, if you need more accuracy:



How to decode IR signal (very useful if you don't have a SYMA S107):

Of course, you can always decode the IR signal from scratch, if your helicopter is not SYMA S107. Here is a great video tutorial:


Here is the project owner's original blog: http://technologyonmymind.blogspot.fr/2012/03/helicopter-auto-pilot-introduction.html

DIY Phone-controlled helicopters with Arduino:


Needless to say, using Arduino as a bridge, we can easily control helicopters. Here is "Yan's helicopter Controller" from DIY Phone Gadgets.

Here is how it works:

1. The Android phone is controlling the Arduino using bluetooth.
2. Arduino is controlling the original helicopter transmitter.
3. The transmitter is programmatically controlling the helicopter.











DIY Phone-controlled helicopters with audio dongle:


There are already some fantastic existing tools in the toy market that are really helpful. Like these audio jack dongles:




These dongles capture the audio signal in the 3.5 audio jack and translate the audio signal into wireless signal.

For normal users, these dongles are just wireless transmitters, compatible with Android or iPhone apps. They can use it to control the helicopter with their smartphones.

For DIYers, the dongles should be capable of transmitting IR or other wireless signals from any smartphone or tablet (Android, iPhone, Blackberry or Windows Phone). You simply produce programmatically some audio sound from the phone. To make a touchscreen controller is so boring because the physical joysticks are way better and more precise. A phone should be a mobile command station to interact with the helicopter with a lot more intelligence. The helicopter should be able to dance with your own written program!

The easiest way to get these dongles is searching "iPhone Android Helicopter" on eBay. Then write your iOS or Android code to produce some audio signal, control your TV, fly your helicopter! Don't forget to share your exciting discoveries with the DIY Phone Gadgets community.

Imagine that you are playing your favorite music while watching a bunch of helicopters dancing in the sky, following the melody and rhythm. Yes, that's so geeky. But you are so happy. What's more beautiful than a creative mind? 

110 comments:

  1. This is great! How could I do this with a Raspberry PI and in Python?

    ReplyDelete
    Replies
    1. Using the same Arduino code, and use Raspberry Pi and Python to control Arduino:)

      Delete
  2. how to use the 1st code that you said is tested. "The first working code (tested)". after you upload that program to your arduino it will automatically run or do you have to press something or whats so ever?? can i ask for the exact schematic diagram without the receiver thing? thank you.

    ReplyDelete
    Replies
    1. Anthony, you don't have too press anything. It will run automatically. The only thing you need to do is to fix the camera and the helicopter's initial positions so that the helicopter will always stay in the sight of the camera.

      Delete
  3. Searching the internet has led me to different versions of Arduino. Is there a certain one that works better than others for this coding?

    ReplyDelete
    Replies
    1. Normally we use Arduino Uno, others will definitely work too.

      Delete
  4. first I am surprised to know that IR helicopter can be controlled by the programmatically with android or another mobile application.I is very nice to read this post.

    ReplyDelete
  5. Sounds easy to me, thanks for the steps though, now I can control my helecopter with my android phone..

    ReplyDelete
  6. Hi! I have the basic (one mode only) S107 helicopter, but the Arduino code didn't work properly for me. No idea why. Possibly because I tested it in Arduino Mega, although that shouldn't be the reason.

    In any case, did some modifications and now it works. Hope it helps someone:

    https://gist.github.com/alfongj/5425213

    ReplyDelete
  7. Hey... i want to control syma107 helicopter using kinect sensor n arduino mega. i am working on it for a month but still facing problems. Can you provide me the processing code for controlling it through kinect? Will be extremely thankful to you.

    ReplyDelete
  8. I want to know can I use arduino uno R3 instead of the one you used?? Can I change the helicopter?? And can do all this without changing coding ?? And if PC camera coding would work for kinect or not ?? Plz answer all of my questions

    ReplyDelete
  9. Hi, thanks for sharing your work.
    I have a problem whith the code.
    Processing send a error in line 397.
    "the function arrayValue() does not exists"
    help me please.

    ReplyDelete
  10. The big rc helicopters reviews will react better to your control contrasted with ones with bring down quality edges.

    ReplyDelete
  11. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a Java developer learn from Java Training in Chennai. or learn thru Java Online Training in India . Nowadays Java has tons of job opportunities on various vertical industry.

    ReplyDelete
  12. Thanks for your marvelous posting! It is very useful and good. Come on. I want to introduce an get app installs, I try it and I feel it is so good to rank app to top in app store search results, have you ever heard it?

    ReplyDelete
  13. Now, I don't mean to belittle a time honored classic carnival game like the fish pond, but as I stood there watching child after child saunter up, toss their line over and wait for the telling tug that signaled the arrival of a really great sticker, an ugly plastic bug or an enigmatic Chinese finger trap I thought, "Surely there's a better way".Mark Briggs

    ReplyDelete
  14. Very nice post here and thanks for it .I always like and such a super contents of these post.Excellent and very cool idea and great content of different kinds of the valuable information's.
    rpa training in bangalore
    best rpa training in bangalore
    RPA training in bangalore
    rpa course in bangalore
    rpa training in chennai
    rpa online training

    ReplyDelete
  15. That was a great message in my carrier, and It's wonderful commands like mind relaxes with understand words of knowledge by information's.
    Python Online certification training
    python Training institute in Chennai
    Python training institute in Bangalore

    ReplyDelete
  16. Whoa! I’m enjoying the template/theme of this website. It’s simple, yet effective. A lot of times it’s very hard to get that “perfect balance” between superb usability and visual appeal. I must say you’ve done a very good job with this.

    AWS Training in Bangalore | Best AWS Amazon Web Services…
    Amazon Web Services (AWS) Training in Pune India
    AWS Training | AWS Training and Certification | AWS online training
    AWS Training in Bangalore cost| Aws training in Bangalore with placements

    ReplyDelete
  17. All are saying the same thing repeatedly, but in your blog I had a chance to get some useful and unique information, I love your writing style very much, I would like to suggest your blog in my dude circle, so keep on updates.
    microsoft azure training in bangalore
    rpa training in bangalore
    best rpa training in bangalore
    rpa online training

    ReplyDelete
  18. Your good knowledge and kindness in playing with all the pieces were very useful. I don’t know what I would have done if I had not encountered such a step like this.
    AWS Training in pune
    AWS Online Training

    ReplyDelete
  19. Usually I never comment on blogs but your article is so convincing that I never stop myself to say something about it. You’re doing a great job Man, I like it..Heli Banners Australia..Keep it Up!

    ReplyDelete
  20. Learn the Python Training in Bangalore - Learn python course from ExcelR with real-time training from
    expert trainers and placement assistance.

    Understand the Python course with live project and assignments, which help you to be successfull in your Python domain.

    ExcelR is one of the best insutitute in Bangalore for top noted courses like, Data Science Course, Machine Learning Training, Digital Marketing
    class room training and live projects, and they do 100% job assistance.

    For more information about Pythone Training in Bangalore, please visit our website:


    For More Information about Top courses in Bangalore, click below
    https://www.excelr.com/

    Python course in Bangalore
    https://www.excelr.com/python-training-in-bangalore


    For more videos like Python course, Data Science course, Digital Marketing course & top selected courses.
    https://www.youtube.com/channel/UCF2_gALht1C1NsAm3fmFLsg

    ReplyDelete
  21. Really very nice blog information for this one and more technical skills are improve,i like that kind of post.
    apache spark online training

    ReplyDelete
  22. Thanks for your article on social media marketing course in Delhi! You targeted a topic we have already considered a few times. Our result in the past was a "light" version from what you have suggested. But your proposed way is far more structured and therefore we will follow it the next time we touch that topic :) Thanks again!

    ReplyDelete
  23. Really inspirational to hear someone pursuing their dreams and becoming successful instead of following the traditional path. I have read your article about This topic. I think it's good and impressed to know your service. Thanks for share this Information.
    airplane leasing

    ReplyDelete
  24. This is really nice to read content of this blog. A is very extensive and vast knowledgeable platform has been given by this blog. I really appreciate this blog to has such kind of educational knowledge.
    Helicopter lessons

    ReplyDelete
  25. I am speechless as to how great this article is. The content is simple to understand and very engaging.
    SAP training in Mumbai
    Best SAP training in Mumbai
    SAP training institute Mumbai

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. MP Board 12th Class Blueprint 2021 English Medium & Hindi Medium PDF download, MPBSE 12th Blueprint 2021 Pdf Download, mpbse.nic.in 12th Blue

    Print, Marking Scheme and Arts, Commerce and Science Streams Chapter wise Weightage pdf download. MP Board 12th Blue Print || MPBSE 12th Model Papers || MPBSE 10th Model Papers

    Manabadi AP Intermediate 2nd Year Model Question Paper 2021 MPC, BIPC, CEC, MEC group TM, EM Subject wise Blue Print, Download BIEAP

    Intermediate Second Year Model Question Papers, AP Senior Inter Test Papers, Chapter wise important Questions download. || AP Inter MPC, Bi.PC, CEC Blue Print || AP Inter 1st / 2nd Year Model Papers || AP 2nd year inter Test Papers

    Kar 1st / 2nd PUC Blue Print || UP Board 12th Blueprint 2021

    ReplyDelete
  28. I do agree your blog for quiz programming concepts, which is very helpful to grow up your knowledge. keep sharing more
    AWS training in chennai | AWS training in annanagar | AWS training in omr | AWS training in porur | AWS training in tambaram | AWS training in velachery

    ReplyDelete
  29. Good Post! , it was so good to read and useful to improve my knowledge as an updated one, keep blogging.After seeing your article I want to say that also a well-written article with some very good information which is very useful for the readers....thanks for sharing it and do share more posts likethis. https://www.3ritechnologies.com/course/angular-js-training-institute-in-pune/

    ReplyDelete
  30. Great article with fantastic information found useful and unique content enjoyed reading it thank you, looking forward for next blog.
    typeerror nonetype object is not subscriptable

    ReplyDelete
  31. I’m really happy with this informative blog, thank you so much for sharing this. Ogen Infosystem infosystem is a leading Web Designing and SEO Service provider in Delhi, India.
    Website Designing Company in Delhi

    ReplyDelete
  32. Thanks for Sharing a Very Informative Post & I read Your Article & I must say that is very helpful post for us.

    ReplyDelete
  33. I really happy found this website eventually. Really informative and inoperative, Thanks for the post and effort! Please keep sharing more such blog.
    Data Science
    Selenium
    ETL Testing
    AWS
    Python Online Classes

    ReplyDelete
  34. Get huge discount on Home and Kitchen Appliances,Split and Window Air Conditioner, Mobiles & Laptops online , Television, Speakers & more electronics at best price.
    HITACHI Inverter Split AC

    ReplyDelete
  35. Thank you for sharing this useful material. The information you have mentioned here will be useful. I would like to share with you all one useful source of Travel LaTomatina festival in Spain. Explore the world with a Tour to Review where you can find your destination at one place. Here you can get all the information about travel. La Tomatina is an annual festival celebrated in the small town of Bunol, the largest tomato fight in the world. Which may be interesting for you as well.

    ReplyDelete
  36. Thank you for sharing this useful content. The information you describe here would be useful. I want to share with you all a useful source Telstra Customer Service Number +61-1 800 431 401, which may be interesting for you as well. If you are troubled by problems in your internet or Telecom connectivity and your net surfing then do not worry about it. Telstra Support Number Australia corrects your internet issue.

    ReplyDelete
  37. nice Post thanks for the information, good information & very helpful for others.
    Mulesoft Training
    Looker Training

    ReplyDelete
  38. Each user when purchase printing machine, their primary proverb is to get the quality printouts of any archives. Brother printer error code Ts-02 Along these lines, when we talk about quality-based printing administration, Brother Printer is been ideal as it has novel and progressed highlights that make the print work simple. While few errors may also occur during printing from your Brother Printer. brother Error TS-02 is the one most basic blunder numerous users have gone up against during their print work and raised a voice of help to fix it. This particular error issue seems when the WLAN passage or switch can't be distinguished by the framework while printing. You don't have to sway off; we are here to manage you to manage a particularly risky mistake code in an easy way.

    ReplyDelete
  39. Very nice post. I just stumbled upon your weblog and wanted to say that I’ve truly enjoyed surfing around your blog posts. In any case I will be subscribing to your feed and I hope you write again soon!CBD oil

    ReplyDelete
  40. It’s easy to activate Roku using Roku link Code. If you are ready to begin the activation, connect your device to the TV using HDMI port. Then switch the device ON. Create Roku account, sign in and proceed with the activation settings .Collect the code for activation. The process of Roku activation will complete, if you provide the code by visiting the page, Roku.com/link. Please reach out to our network support for assistance.

    ReplyDelete
  41. As I do not know how to activate Roku, read the recent blog post on your portal. I could understand how to activate Roku and fix activation errors after reading. You have done a great job. I’m happy to mark the best review rating and share the post with new Roku users
    Please provide some information regarding the Roku.com/support services offered.

    ReplyDelete
  42. Thank you for sharing useful and don't forget, keep sharing useful info: Aluminium pipe manufacturer

    ReplyDelete
  43. Really impressive post. I read it whole and going to share it with my social circules. I enjoyed your article and planning to rewrite it on my own blog SSC Free Online Mock Test

    ReplyDelete
  44. Keep up the excellent work, I read few posts on this web site and I think that your weblog is really interesting and has lots of fantastic information . Free SSC CPO Mock Test Online

    ReplyDelete
  45. Great blog article. Really looking forward to read more. Will read on… 야한동영상

    ReplyDelete
  46. With thanks! Valuable information! I think this is among the most vital info for me. 일본야동

    ReplyDelete
  47. This article is an appealing wealth of useful informative that is interesting and well-written. I commend your hard work on this and thank you for this information. I know it very well that if anyone visits your blog, then he/she will surely revisit it again. 한국야동닷컴

    ReplyDelete
  48. Thank you. I authentically greeting your way for writing an article. I safe as a majority loved it to my bookmark website sheet list and will checking rear quite than later. 국산야동

    ReplyDelete
  49. Tremendous blog quite easy to grasp the subject since the content is very simple to understand. Obviously, this helps the participants to engage themselves in to the subject without much difficulty. Hope you further educate the readers in the same manner and keep sharing the content as always you do.
    인터넷경마

    magosucowep

    ReplyDelete
  50. Excellent post with the title, How to activate Roku using the portal Roku.com/link I’m Impressed after reading and I have no other words to comment
    I could learn Roku.com/link activation procedure quickly after reading your post. Kindly post similar blogs explaining the guidelines to add and activate the entertaining channels on Roku
    Let me mark the 100-star rating for your blog post
    Keep up the good work
    Awaiting more informative blogs from now on

    ReplyDelete
  51. Hello, I'm happy to see some great articles on your site. Would you like to come to my site later? My site also has posts, comments and communities similar to yours. Please visit and take a look 토토사이트

    ReplyDelete
  52. The assignment submission period was over and I was nervous, 우리카지노 and I am very happy to see your post just in time and it was a great help. Thank you ! Leave your blog address below. Please visit me anytime.

    ReplyDelete
  53. I finally found what I was looking for! I'm so happy. 안전한놀이터 Your article is what I've been looking for for a long time. I'm happy to find you like this. Could you visit my website if you have time? I'm sure you'll find a post of interest that you'll find interesting.

    ReplyDelete
  54. 토토사이트모음January 10, 2022 at 10:39 PM

    While looking for articles on these topics, I came across this article on the site here. As I read your article, I felt like an expert in this field. I have several articles on these topics posted on my site. Could you please visit my homepage? 토토사이트모음

    ReplyDelete
  55. What a post I've been looking for! I'm very happy to finally read this post. 안전놀이터 Thank you very much. Can I refer to your post on my website? Your post touched me a lot and helped me a lot. If you have any questions, please visit my site and read what kind of posts I am posting. I am sure it will be interesting.

    ReplyDelete
  56. 안전놀이터추천January 19, 2022 at 11:15 PM

    Hello, I am one of the most impressed people in your article. 안전놀이터추천 I'm very curious about how you write such a good article. Are you an expert on this subject? I think so. Thank you again for allowing me to read these posts, and have a nice day today. Thank you.

    ReplyDelete
  57. I have a read all blogs. Your blog is so interactive and information. Keep update more information
    https://shrishuddhinashamuktikendrabhopal.business.site/?m=true

    ReplyDelete
  58. Your ideas inspired me very much. 메이저토토사이트모음 It's amazing. I want to learn your writing skills. In fact, I also have a website. If you are okay, please visit once and leave your opinion. Thank you.

    ReplyDelete
  59. Then call a windshield chip and crack repair technician for repair as soon as you can don't be the one testing your luck with that chipped or cracked windshield. how to get cracked mac apps What's more, the catalogue is being constantly updated with free casual games.

    ReplyDelete
  60. 토토 I would love to visit this website every day. As this promotes eternal knowledge.

    ReplyDelete
  61. 토토사이트 Satisfying blog I am highly satisfied I have been here

    ReplyDelete
  62. 스포츠토토
    Remarkable posting! A lot of useful details and idea

    ReplyDelete
  63. I was eager to find this page. I needed to thank you for ones time for this especially awesome read!! I certainly truly preferred all aspects of it and I likewise have you book-set apart to look at new data in your blog.

    Medicare Insurance

    ReplyDelete
  64. Enjoyed reading the article above , really explains everything in detail,the article is very interesting and effective. Thank you and good luck for the upcoming articles

    ReplyDelete
  65. Xpertscm construction Company is leading construction Company and ranking at the top most position in the list of construction companies in Bihar. They are involve in many commercial and residential projects.

    ReplyDelete
  66. Do you want the best air hostess/cabin crew training institutes then There are many training institutes which train youngsters to be cabin crew in flights.But all depends on you what can you do for your better future. So I will recommend you to join Groundtosky Airhostess Training institutes.They are providing quality of training with placements.Here the training is based on the some special features. which require and help you to clear the all rounds of interview step by step.

    ReplyDelete
  67. Having read this I thought it was extremely enlightening. I like you investing some time and energy to assemble this short article. I once atain wind up investing a ton of energy both perusing and posting comments . But so what, it wass still advantageous.

    ReplyDelete
  68. Now online shopping is the way of life, we need the best shopping apps to make our lives easier.

    ReplyDelete
  69. I think this is one of the most crucial data for me.And i'm happy perusing your article. In any case, should comment on scarcely any broad things, The site style is awesome, the articles is truly fantastic.

    ReplyDelete
  70. Get the best deals on the latest software and bundles, Get thousands of latest software at a significant discount. We’ve everything you need for your business.

    ReplyDelete
  71. UPVC Pipes are our finest offering, made with high-quality raw materials and cutting-edge technology, and are suitable for both hot and cold water applications. When compared to traditional plastic piping systems, these pipes can sustain higher temperatures.

    ReplyDelete
  72. Best interior designing company in patna Bihar. We are the best interior design service provider in patna, the best interior designing company in patna.

    ReplyDelete
  73. Very interesting article. Many articles I come across these days really not provide anything that attracts others, but believe me the way you interact is literally awesome.
    Salesforce Offshore Support Services

    ReplyDelete
  74. This article is very attractive. Those who need this information, it's very informative and understandable for those all. Thanks for this information.
    Microsoft Dynamics CRM Support

    ReplyDelete
  75. Awesome blog.I find it interesting and is pretty fascinating, By reading your blog i got very useful information.Thanks for sharing this blog.
    SharePoint Support

    ReplyDelete
  76. Thanks for sharing this Informative content. Well explained. Got to learn new things from your Blog.
    FinancialForce offshore Support

    ReplyDelete
  77. i'm unequipped for perusing articles online especially every now and again, but Im cheerful I did these days. it is chosen dexterously composed, and your focuses are skillfully communicated. I request you agreeably, engage, dont at any point lower composing.! Pain Tool Sai Crack

    ReplyDelete
  78. Epubor Ultimate Crack Download is a solitary of the top of the line use of it's sort. This application will help you to change over your records! Thums Up Epubor Torrent

    ReplyDelete
  79. सट्टा मटका (Satta Matka) एक तरह का जुआ होता है आप इसे जुओं का राजा भी कह सकते हैं.

    ReplyDelete

Disqus for DIY Phone Gadgets