Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Rafael Diaz
Published

Gesture controlled soft robot tentacle

A soft robot controlled with a wrist mounted accelerometer.

IntermediateFull instructions provided12 hours2,089
Gesture controlled soft robot tentacle

Things used in this project

Hardware components

Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×2
Adafruit air pumps
×3
Adafruit air valves
×3
Grove - 2-Channel SPDT Relay
Seeed Studio Grove - 2-Channel SPDT Relay
I used 6 single channel relays.
×3
Adafruit Silicone tube
You need 6 tubes, but their long enough that you can cut each one in half.
×3
ecoflex 00-30
×1
dragonskin 10
×1
Coin Cell Battery CR2032
Coin Cell Battery CR2032
×2
coin cell holder
×1

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

inner design_mold_tentacle

This forms the inner ridges in the silicone tentacle. Fit the sticks one by one in the triangle & solder them together.

Tentacle base

The base of the tentacle, solder the two pieces together. Once your silicone tentacle has been made, put some silicone on the 3 circles and press the tentacle together with the pla. 3 small square holes in the bigger piece are for you to feed the air tubes through. The bottom of the smaller piece contains a small tube for you to connect the air tube to.

Tentacle mold

Tentacle mold (print 3 times) screw them together, once the silicone is mixed insert the inner design parts & slowly pour the silicone.

Air pump/air valve cases

Case for the adafruit air pumps & air valves.

V2 tentacle mold

A second version of the tentacle mold (cut in half, easier to print, print at different times). Need to print both pieces (top & bottom pieces 3 times each). After, solder top and bottom piece together.

Watch case

A watch case, you need a nano 33 ble without pins & a power source (I used 2 coin cell battery holder from adafruit) use small screws to hold everything to the 3d printed case. I used nylon straps for the wrist part.

Schematics

Sketch

Sketch

Code

Transmitter

C/C++
Simply a arduino nano 33 ble sense.
#include <ArduinoBLE.h>
#include <Arduino_LSM9DS1.h>

const char* deviceServiceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214";
const char* deviceServiceCharacteristicUuid = "19b10001-e8f2-537e-4f6c-d104768a1214";

const int R_LED_PIN = 22;
const int G_LED_PIN = 23;
const int B_LED_PIN = 24;

long tiempo_prev = 0;
float pitch_prev = 0;
float roll_prev = 0;

// Sensibilidad del giroscopio   2000 dps -> 70
// Sensibilidad del acelermetro 4g -> 0.122
const float A_S = 0.122;
const float G_S = 70.0;

void setup() {
  
  Serial.begin(9600);


  setColor("RED");

 if (!IMU.begin()) {
    //Serial.println("Failed to initialize IMU!");
    while (1);
  }

  
  // begin ble initialization
  if (!BLE.begin()) {
    //Serial.println("starting BLE failed!");
    while (1);
  }

  //Serial.println("BLE Central - gesture control");

}

void loop() {
  
     connectToPeripheral();
}


void connectToPeripheral(){

  BLEDevice peripheral;

  do
  {
     // start scanning for peripherals
    BLE.scanForUuid(deviceServiceUuid);
    peripheral = BLE.available();
    
  } while (!peripheral);

  
  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    //Serial.print("Found  ");
    //Serial.print(peripheral.address());
    //Serial.print(" '");
    //Serial.print(peripheral.localName());
    //Serial.print("' ");
    //Serial.print(peripheral.advertisedServiceUuid());
    //Serial.println();
  
    // stop scanning
    BLE.stopScan();
  
    controlPeripheral(peripheral);
   
  }
  
}

void controlPeripheral(BLEDevice peripheral) {

  
  // connect to the peripheral
  //Serial.println("Connecting ...");

  if (peripheral.connect()) {
    //Serial.println("Connected");
    setColor("BLUE");
  } else {
    //Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  //Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    //Serial.println("Attributes discovered");
  } else {
    //Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  BLECharacteristic gestureCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
    
  if (!gestureCharacteristic) {
    //Serial.println("Peripheral does not have gesture characteristic!");
    peripheral.disconnect();
    return;
  } else if (!gestureCharacteristic.canWrite()) {
    //Serial.println("Peripheral does not have a writable gesture characteristic!");
    peripheral.disconnect();
    return;
  }

  
  while (peripheral.connected()) {

    String command = calculoAngulos();

    //Serial.print("comando: ");
    //Serial.println(command);
      
    if (command != "")
    {
      setColor("PURPLE");
      int n = command.length(); 
      // declaring character array 
      char char_array[n + 1]; 
      // copying the contents of the string to char array 
      strcpy(char_array, command.c_str()); 
      gestureCharacteristic.writeValue((const char*)char_array);
 
      delay(1);
    }
  
  }

  //Serial.println("Peripheral disconnected!");

}
  
String calculoAngulosConFiltroComplementario()
{
    float ax, ay, az;
    float gx, gy, gz;
    float pitch,_pitch;
    float roll,_roll;
    float dt;
    String _command = "";
    
    if (IMU.accelerationAvailable()) {
      IMU.readAcceleration(ax, ay, az);
    }
    
    if (IMU.gyroscopeAvailable()) {
      IMU.readGyroscope(gx, gy, gz);
    }
    
    // Ratios
    ax = ax/A_S;
    ay = ay/A_S;
    az = az/A_S;

    gx = gx/G_S;
    gy = gy/G_S;
    gz = gz/G_S;

    dt = (millis() - tiempo_prev) / 1000.0;
    tiempo_prev = millis();
  
    // Calcular los ngulos con el acelerometro
    _pitch = atan2(ax, sqrt(ay*ay + az*az));
    _roll =  atan2(ay, sqrt(ax*ax + az*az));
    
    // Convertimos de radianes a grados
    _pitch *= (180.0 / PI);
    _roll  *= (180.0 / PI);  
     
    // Aplicar el filtro complementario
    pitch = 0.98*(pitch_prev + gx*dt) + (0.02*_pitch);
    roll =  0.98*(roll_prev + gy*dt) + (0.02*_roll);

   //Serial.print(F("Rotacion en X:  "));
   //Serial.print(pitch);
   //Serial.print(F("\t Rotacion en Y: "));
   //Serial.println(roll);
   
    pitch_prev = pitch;
    roll_prev = roll;

    _command =  String(pitch, DEC) + "," + String(roll, DEC);
    
    return _command;
    
}

 
String calculoAngulos()
{
    float ax, ay, az;
    float gx, gy, gz;
    float pitch,roll;
    String _command = "";
    
    if (IMU.accelerationAvailable()) {
      IMU.readAcceleration(ax, ay, az);
    }
    
    // Calcular los ngulos con el acelerometro
    pitch = atan2(ax, sqrt(ay*ay + az*az));
    roll =  atan2(ay, sqrt(ax*ax + az*az));
    
    // Convertimos de radianes a grados
    pitch *= (180.0 / PI);
    roll  *= (180.0 / PI);  
     
   Serial.print(F("Rotacion en X:  "));
   Serial.print(pitch);
   Serial.print(F("\t Rotacion en Y: "));
   Serial.println(roll);
   
    _command =  String(pitch, DEC) + "," + String(roll, DEC);
    
    return _command;
    
}


void setColor(String color)
{

  if (color == "RED")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "GREEN")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, LOW);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "BLUE")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, LOW);
    
  }else if (color == "PURPLE")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, LOW);
  }        

}

Receiver

C/C++
An arduino nano 33 ble sense, with 3 air pumps & 3 air valves
#include <ArduinoBLE.h>

const int R_LED_PIN = 22;
const int G_LED_PIN = 23;
const int B_LED_PIN = 24;

int relay7SOLELEFT = 7;
int relay10PUMPLEFT = 10;


int relay8SOLEMIDDLE = 8;
int relay11PUMPMIDDLE = 11;



int relay9SOLERIGHT = 9;
int relay12PUMPRIGHT = 12;


const char* deviceServiceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214";
const char* deviceServiceCharacteristicUuid = "19b10001-e8f2-537e-4f6c-d104768a1214";

String command = "";


static int dutyX = 0;
static int dutyY = 0;
static int duty1 = 0;
static int duty2 = 0;

// BLE gesture Service
BLEService gestureService(deviceServiceUuid); 

// BLE gesture Switch Characteristic 
BLEStringCharacteristic gestureCharacteristic(deviceServiceCharacteristicUuid, BLERead | BLEWrite,512);


void setup() {
 

 Serial.begin(9600);
 
 pinMode(relay7SOLELEFT, OUTPUT);
  pinMode(relay10PUMPLEFT, OUTPUT);
   
  
 pinMode(relay8SOLEMIDDLE, OUTPUT); 
 pinMode(relay11PUMPMIDDLE, OUTPUT); 
 

 pinMode(relay9SOLERIGHT, OUTPUT); 
 pinMode(relay12PUMPRIGHT, OUTPUT); 
 

  // begin ble initialization
  if (!BLE.begin()) {
   Serial.println("starting BLE ");
    while (1);
  }

  // set advertised local name and service UUID:
  BLE.setLocalName("Mini Robot 4x4 peripheral");
  BLE.setAdvertisedService(gestureService);

  // add the characteristic to the service
  gestureService.addCharacteristic(gestureCharacteristic);

  // add service
  BLE.addService(gestureService);

  // set the initial value for the characeristic:
  gestureCharacteristic.writeValue("");

  // start advertising
  BLE.advertise();

  //Serial.println("Mini Robot 4x4 peripheral");
  setColor("GREEN");
}

void loop() {
  
  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to peripheral:
  if (central) {
    
    //Serial.print("Connected to central: ");
    // print the central's MAC address:
    //Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
      
      // if the remote device wrote to the characteristic,
      if (gestureCharacteristic.written()) {
         command = gestureCharacteristic.value();
     Serial.print(F("commmand value:  "));
        Serial.println(command);
         sendInstruction(command);
       }
      
    }

    // when the central disconnects, print it out:
    //Serial.print(F("Disconnected from central: "));
    //Serial.println(central.address());
  }
}

void sendInstruction(String str) {


  if (str.length() == 0)
    return;
    
  // split 
  int pos = str.indexOf(",");
  int strLength = str.length();

  dutyX = str.substring(0,pos).toInt(); 
  dutyY = str.substring(pos+1,strLength).toInt(); 
 
 Serial.print(F("valor en X:  "));
 Serial.print(dutyX);
  Serial.print(F("\t valor en Y: "));
  Serial.println(dutyY);


  

    if ((dutyY >= 30) && (dutyX > -20) && (dutyX < 20)) // left
    {
    digitalWrite(relay9SOLERIGHT, HIGH);
digitalWrite(relay12PUMPRIGHT, HIGH);
digitalWrite(relay7SOLELEFT, LOW);
digitalWrite(relay10PUMPLEFT, LOW);
digitalWrite(relay8SOLEMIDDLE, LOW);
 digitalWrite(relay11PUMPMIDDLE, LOW);
setColor("RED");
    }
    else if ((dutyY <= -30) && (dutyX > -20) && (dutyX < 20)) //right
    {
      
 digitalWrite(relay7SOLELEFT, HIGH);
 digitalWrite(relay10PUMPLEFT, HIGH);
 digitalWrite(relay9SOLERIGHT, LOW);
 digitalWrite(relay12PUMPRIGHT, LOW);
 digitalWrite(relay8SOLEMIDDLE, LOW);
 digitalWrite(relay11PUMPMIDDLE, LOW);
 setColor("BLUE");

    }
    else if ((dutyX >= 20) && (dutyY > -30) && (dutyY < 30)) //back
    {digitalWrite(relay8SOLEMIDDLE, LOW);
 digitalWrite(relay11PUMPMIDDLE, LOW);
 digitalWrite(relay7SOLELEFT, LOW);
digitalWrite(relay10PUMPLEFT, LOW);
 digitalWrite(relay9SOLERIGHT, LOW);
 digitalWrite(relay12PUMPRIGHT, LOW);
 
setColor("YELLOW");
 
 
    }
    else if ((dutyX <= -20) && (dutyY > -30) && (dutyY < 30)) //foward
    {
 digitalWrite(relay8SOLEMIDDLE, HIGH);
 digitalWrite(relay11PUMPMIDDLE, HIGH);
 digitalWrite(relay7SOLELEFT, LOW);
digitalWrite(relay10PUMPLEFT, LOW);
 digitalWrite(relay9SOLERIGHT, LOW);
 digitalWrite(relay12PUMPRIGHT, LOW);
 setColor("PURPLE");
    }
    else if ((dutyY >= 30) && (dutyX >= 20)) // adelante + izquierda 
    {
    
    }
    else if ((dutyY >= 30) && (dutyX <= -20)) // adelante + derecha 
    {
  
    }
    else if ((dutyY <= -30) && (dutyX >= 20)) // atras + izquierda 
    { }
      
    else if ((dutyY <= -30) && (dutyX <= -20)) // atras + derecha 
    {
          
    }
    else
    {
     
    }

}
void setColor(String color)
{

  if (color == "RED")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "GREEN")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, LOW);
    digitalWrite(B_LED_PIN, HIGH);
    
  }else if (color == "BLUE")
  {
    digitalWrite(R_LED_PIN, HIGH);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, LOW);
    
  }else if (color == "PURPLE")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, HIGH);
    digitalWrite(B_LED_PIN, LOW);
  }else if (color == "YELLOW")
  {
    digitalWrite(R_LED_PIN, LOW);  // High values -> lower brightness
    digitalWrite(G_LED_PIN, LOW);
    digitalWrite(B_LED_PIN, HIGH);
    
  }        

}

  

Credits

Rafael Diaz

Rafael Diaz

2 projects • 2 followers
Computer scientist, love robotics & coding ^_^.

Comments