Project tutorial
Talking Portal 2 Turret Gun

Talking Portal 2 Turret Gun © CC BY-NC

Fully functional, automatic talking turret gun from the video game Portal 2!

  • 2,686 views
  • 4 comments
  • 22 respects

Components and supplies

Necessary tools and machines

3drag
3D Printer (generic)

About this project

The story

Defective Turret example
Cara Mia Opera

This Christmas, I decided to design and build a working Portal turret gun from the game Portal 2. For me, this was an exercise of properly modelling the entire assembly in Fusion 360 first, before building anything. This design uses an Arduino Nano, an MP3 player chip, distance sensor, servos, LEDs and 3D printed parts.

The goal here was to make it move in 3 "axes", with spoken sound from the game and LED's to simulate firing.

  • Open the "wings" if it senses someone in front. Use a crank mechanism with sliders, just because.
  • If the person is still there after opening, fire until they drop. LED's and machine gun sound.
  • If the person is no longer there, run a little scanning search routine.
  • Close up and go to sleep until someone else comes along.
  • Use Portal turret sounds and voices from the game.

I took some liberties in the design, trying to make it appreciably the same as the one seen in the game, but functional and printable. With some basic sketches found online, I started modelling and planning...

The sounds are stored on a microSD card, which is accessible from the back side so that sounds may be updated or changed later. It is inline with the black infill strip, making it essentially invisible once installed. 18 individual expressions and sounds used in this go-round.

The lidar sensor (time-of-flight) is on a chip with a rectangular profile. This sensor is nearly invisible from the front once assembled.

Step 1: Fusion 360 Modelling

Fusion model - range of motion

The design started from sketches found online. Using these images as canvases, I started sketching the outlines of the 3 views. Then it was a matter of extruding them into 3D, then shelling the general shape and making cuts. All electronics were built in Fusion as components that were inserted and placed where I thought it would make sense. Driving parameters were:

  • Arduino Nano had to have connector accessible for updating once fully assembled
  • MicroSD card had to be accessible in the same manner, and ideally invisible once installed
  • Time-of-flight sensor should be invisible as well
  • A 2.1mm electrical connector for power at the back
  • Printed parts to be as large as possible (not a lot of little pieces)
  • Print with no supports

After components (Nano, other chips, servos) were added to the basic shape, they were moved around and positioned as required, and the supporting structures were built to support them inside the shell.

The wing opening mechanism was a crank and slide mechanism. Why? Because I wanted to use a crank mechanism, that's why! This added some complications, but it also had a benefit; once the geometry was determined, operational repeatability would be ensured and the min and max limits were pretty much guaranteed.

Once the entire model was built and I was confident that it would work, and could be built (printed) and assembled, I went ahead and printed out the parts and built a prototype. Once that worked out, I went back to the model and made some tweaks to improve appearance and assemblability (is that a word?). This model is what came of those changes.

This was pretty taxing, as there really aren't many boxy shapes in this thing, and it closes up pretty tightly, with no real access for tweaking once put together. I learned quite a bit on this project, such as using embedded components within other components. This made manipulating and keeping sub-assemblies linked for quick access. In the end, it was worth the effort!

Step 2: 3D Printed Parts

Portal turret v4.zip is updated and should contain all printed parts needed to build the latest turret.

--- new "Rear test Leg". Not true to original, but it's at the back, and allows one to use a straight mini-USB connector straight up. ---

These were printed on a Prusa Mk2, using PLA for all parts. The print orientation should be pretty evident. The body was printed in the vertical position, with no supports. There is the large gap in the sides that has to be bridged, but I really had no big issues with pretty standard settings, other than the bump. The bump on the front and rear can be virtually eliminated with good filament and some good print settings. I found that 0.2mm worked well in the slicer and produced a decent result. Any larger and openings started appearing in the body near the infill strip.

The design of channels and protrusions were done with 45 degree chamfers, so "hanging in space" elements will be minimal.

I had little cleanup to do to put the assembly together. The infill strips that slide into the channels are now pretty straightforward, with reduced width and consistent thickness. I think that one could use a thin, black material cut into strips instead of using these printed pieces (printed on edge).

The one area that does require finesse is the slider pins in the pitch frame. Straight pins (nails) in the bore holes that are chased with a 1/8" drill bit and some lube will go a long way.

Step 3: Components

V4 (Red turret) has smaller bridging, requiring v4 wings and Pitch frame.

The v6 wings are an option that allows different colour"guns". I haven't made one like this personally, but it should work fine.

Once the model was complete, and I was happy with my prototype, I printed rev 2 with the plastic components as shown. Everything here is PLA, with Black and design colour (Blue in this case), and a tiny bit of translucent PLA for the central "Lens" or laser eye.

This photo pretty well captures the components, with the exception of the wiring.

Step 4: Electronics

The build uses the following components:

  • Arduino Nano (1)
  • DFPlayer Mini MP3 player (or MP3-TF-16P) (1)
  • VL53L0X Time-of-Flight Ranging Sensor (1)
  • Generic SG90 micro servos (3)
  • 5mm red LEDs (5)
  • 220 Ohm resistors for LEDs (5)
  • 1kOhm resistor (1)
  • 4cm speaker, 4Ohm, 3 Watt (1)
  • 2.1mm power connector (1)
  • 3" long framing nails (4)
  • M2 x 6 screws (8)
  • M2.5 x 8 screws (2)
  • M3 x 8 screws (4)
  • M3 x 12 screws (4)
  • M3 x 16 screws (2)
  • shrink wrap
  • small tie wraps

All components are readily available from Arduino, Amazon or Banggood and other sources.

The screws were sourced from an assortment kit. It's a pain in the butt to get them otherwise...

Step 5: Mechanical Assembly

Most of the mechanically assembly is reasonable. The printed parts are printed with the bigger holes tapped where necessary, so a quick chase with a screw before final assembly will be helpful and made assembly of the screwed components pretty easy.

The Arduino and the MP3 chip snap into the cradle without hardware. The VL53LOX will slip into the front shell without fasteners. Trial fit first, then remove and install once wired up.

The slider assembly uses 4 framing nails as slider rails. They are about 1/8" in diameter, with the heads clipped. These were taken from a strip of DeWalt framing nails used with their electric framing nailer. Needless to say, smooth nails are a must.

The servos mount as shown. Orientation is important. The pitch and the pivot servos are "centred" when installing into their pieces. The crank is installed such that, when in the open position, it will close by rotating counter-clockwise direction, when viewing from the front. Open position is rods and crank straight in line, with another 10 degrees rotation till lock.

The leg assembly is the easiest part. 2-2.5mm screws, with the leg caps snapping over the elbows of each leg. Use fasteners than don't protrude above the top of the leg plate. That way, the pivoting body won't bind if you tweak the rotation range.

All servo connections to printed parts are made using the short white crank that comes with the servos. These cranks just press into the printed parts. I tried printing the splined bore in the parts that connected to each servo, but had limited, repeatable success. Much easier using the cranks that come with the servos.

The crank assembly uses the longer 2.5mm screws. The crank rods should not be squeezed between the crank halves. In fact, you could try using shorter screws without the Crank2 part. That should work as well (hopefully no appreciable torque here if the wings slide freely).

The speaker is captured by a servo mount (2 pieces) which capture the speaker. Speaker between these "legs", and held in position by securing them to the pitch servo. This servo then connected to the pitch (slider) assembly, followed by the crank assembly with rods. All of this is assembled before being installed in the LHS body with 4 small screws.

Once the main guts are installed, with the Arduino and MP3 player temporarily located, then the fun begins - wiring!

Step 6: Wiring

V5 - Radio option (Red turret photos). This includes an nRF24L01 radio chip. Completely changes the Arduino pin wiring to accommodate all the connections. Details to come...

The final packaging is tight, so spending some time here figuring out wire lengths is well worth it. Most interconnecting wires I ended up with were between 3" - 4".

The LEDs are wired with the 220 Ohm resistors directly, followed by some shrink wrap, and some wiring twisting, then put aside, after they have been tested. I used light gauge wiring here as I had some lying around (CAT5 type communication wiring) and didn't want the visible wiring to be obtrusive.

The mechanical bits are mock fit in the shell, then wire routing is figured out, then cutting and prepping the wires is next.

I built the servo connectors so that I could plug in and replace servos if I ever messed something up and stripped the gears. This was definitely helpful after messing up during my first prototype.

Once happy with the bulk of the wiring, the LEDs were soldered up at the end. Then it's time to carefully stuff the wired assembly into one half of the shell. The last step is to solder the power connector to the power wires once it is all inside.

-- Important note: Make sure the wiring stuffed behind the Nano are not pressing on the reset button!! This will obviously cause problems and prevent the unit from operating properly. --

At this point, all wiring is done, but before final assembly, it is important to upload the code to the Nano and power it up to make sure the LEDs, servos and MP3 player all worked as designed. After that, it's time to put the rest of the mechanical bits together.

Step 7: Arduino Code

Updated code! Cleaned it up and made some tweaks.

The attached file is what I came up with to drive the unit as shown in the videos. I will keep tweaking to change the Turret's character and the way it behaves. Lots of options here.

I structured the code using subroutines that I call as needed. It keeps the main body clean and was pretty helpful when I was playing around with different characteristics. It helped me manipulate the code for different behaviours.

I also used lots of variables up front which helped me tweak and adjust parking positions and min and max ranges, for example.

I used the DFMiniMP3 library in my code. I tried other libraries, like the DFRobot one, but had issues, so went back to this one. It meant that I had to keep the 'static void' pieces to keep it functional. These aren't necessary for the operation, but hey, I'm no master coder. I would love to hear of another library that is similarly as simple and neat as the VL53LOX library. Let me know if you find a better way to do this!

As for sounds, the implementation is done in a simple way, by having a folder named "mp3" on the SD card, with the files names 0001.mp3, 0002.mp3, etc. The first four digits must be in this format, but you can add any suffix after that to help identify the particular sounds. See https://www.dfrobot.com/blog-277.html for an example. I've included a pic of my file names as used on the folder. The numbers correspond to the call-outs in the code.

The sound files I pulled from the Wikipedia page on Portal Turret sounds. The code pics a random sound file (1 of 2, or 3 sounds) to keep things from getting stale.

Step 8: Final Body Assembly

This part is a bit tricky because of the black infill strips. The scale of the final assembly is small enough such that the strips and receiving grooves are tiny. This necessitated chasing the channel with a pointer or other small scraping implement to make sure the strips would fit with little resistance before trying to put the other side on.

Tying the wires together neatly and tie-wrapping as required will make this far easier.

I have put a couple of these together now and find it easier to put the two halves together first, then insert the infill strips. Insert one side into the half with the "shelf" that prevents the infill strip from falling in, then lightly pry open and gently press in. Not too bad now.

This was one of the trickier parts. Maybe one day, I'll rethink this assembly, but I do like the way it looks when done, and it is pretty robust.

Step 9: Wing Assemblies

Now that the body is together, with the wing LEDs sticking out, it is time to prep the wings and assemble.

It is imperative that the slider holes are chased with a 1/8" drill bit, then cleaned out. Clip the heads off the nails using bolt cutters, vise-grips, hacksaw or favorite nail-cutting tool. The slider pins (clipped nails) are installed into the wings by press fitting them into each wing piece. Straight, de-burred and smoothed nails are the key to making this work. The wing sliders and holes should be lubricated and tested before connecting the crank rods and running. Dry graphite, or other PLA suitable lubricant is recommended. I find that a small tube of personal lubricant works really well, and is cheap. It's really slick. It also requires for some 'splainin' when your partner or parent comes in and asks what exactly you need that for at the workbench!!

Start by figuring out which wing part goes where, and trial sliding that part first. Then fit the top and bottom halves together once the pins are installed, apply some lubricant (a q-tip works well for this) and make sure the wings slide well. This can be tricky, but without making sure the wings slide effortlessly, without binding, you'll be in for a frustrating time. Trust me...

Once the wings are good to go, it's just a matter of sliding them into place, locating the connecting rod over the hole in the wing and assembling with a single screw. Then, the LEDs are inserted into the gun holes, wires are tucked against the wing and you're ready to go! You can also use some hot glue to lock them into place once everything is tested.

Step 10: Scare and Shock Your Friends!!

The last little caveat on this design is that an angled plug is a great idea, since it doesn't interfere with the back leg when pivoting. The revised rear leg (v3) has been stretched to give a little more room.

Once built and plugged in (5V or suitable for Nano), it will sit quietly until someone is detected within the programmed distance, then spring to life and slay anyone who enters its domain!!

Let me know if you build one (or more) and if you come up with new features or concepts!

Code

Portal turret w Radio (RED)C/C++
Code used for turret. This includes code that will enable control by the Master Turret Control, featured on another page.
Different flavours require tweaking.
//
/* Portal Turret - Option With Radio!!
 *  Chris Nowak - Feb 2019
 *  https://www.instructables.com/member/ChrisN219/
 *  https://create.arduino.cc/projecthub/Novachris/talking-portal-2-turret-gun-637bf3
 *  
 *  This code includes all the features needed to perform the Cara Mia Opera, 
 *  Chat Time and Manual mode, as controlled by the Maseter Turret Control (MTC).
 *  The MTC is a separate controller featured on another build.  The turret
 *  will operate autonomously using this code without the MTC.
 *  
 *  The code is built to work with three turrets, but each will operate independently.
 *  Lots of debugging code left in.  Use or clean up as desired.  It's mostly debugged,
 *  but isn't perfect.  Given the price you paid, I figure that's ok! ;)
 * ================= RED ====================
 */

#include <Wire.h>
#include <VL53L0X.h>
#include "Arduino.h"
#include <SoftwareSerial.h>
#include <Servo.h>
#include <DFMiniMp3.h>
#include <RF24.h>
#include <RF24Network.h>
#include <SPI.h>
class Mp3Notify
{
public:
  static void OnError(uint8_t errorCode)
  {
    // see DfMp3_Error for code meaning
    Serial.println();
    Serial.print("Com Error ");
    Serial.println(errorCode);
  }
  static void OnPlayFinished(uint8_t globalTrack)
  {
    Serial.println();
    Serial.print("Play finished for #");
    Serial.println(globalTrack);   
  }
  static void OnCardOnline(uint8_t code)
  {
    Serial.println();
    Serial.print("Card online ");
    Serial.println(code);     
  }
  static void OnCardInserted(uint8_t code)
  {
    Serial.println();
    Serial.print("Card inserted ");
    Serial.println(code); 
  }
  static void OnCardRemoved(uint8_t code)
  {
    Serial.println();
    Serial.print("Card removed ");
    Serial.println(code);  
  }
}; 

Servo servo_pivot;
Servo servo_wings;
Servo servo_pitch;
VL53L0X sensor;

// Setup for soundcard
SoftwareSerial secondarySerial(4, 2); // RX, TX
DFMiniMp3<SoftwareSerial, Mp3Notify> mp3(secondarySerial);

// Setup for radio
RF24 radio(10, 9);               // nRF24L01 (CE,CSN)
RF24Network network(radio);      // Include the radio in the network
const uint64_t this_node = 01;   // This turret  - Red - in Octal format ( 04,031, etc)
const uint64_t WHT = 02;      // Turret 02 - White
const uint64_t BLU = 03;      // Turret 03 - Blue
const uint64_t MTC = 00;      // Master Turret Control
unsigned long previousMillis1 = 0;
unsigned long previousMillis2 = 0;
unsigned long previousMillis3 = 0;
byte LED_LH_up = A1;
byte LED_LH_down = A2;
byte LED_CENTRE = A3;
byte LED_RH_up = 7;
byte LED_RH_down = 8;
byte PIVOT = 6; //white
byte WINGS = 3; //yellow
byte PITCH = 5; //blue
byte parkPIVOT = 96;  //Smaller number rotates CW as viewed from the top
byte posPIVOT = 96;
byte maxPIVOT = 116;
byte minPIVOT = 76;
byte WINGclose = 165;
byte WINGopen = 10;
byte WINGpos = 160;
byte parkPITCH = 86;
byte posPITCH = 86;
byte maxPITCH = 96;
byte minPITCH = 75;
byte pos = 90;
byte pulse = 20;
byte pitchCW = 1;
byte pivotCW = 1;
byte randomWake;
byte randomOpen;
byte randomFalse;
byte randomStart;
byte randomDisengage;
int triggerDistance = 300; 
byte buttonStatus;
byte busyRed = 1;
byte restModeWhite;
byte goState;
int x;
int y;
byte pb1State; // Fire
byte pb2State; // say random comment
byte pb3State;
byte randomPic;
// Payload from Master Turret Control (MTC)
// int payload [] = {0, 1,    2    ,    3    ,     4   ,    5   };
// int payload [] = {x, y, pb1State, pb2State, pb3State, goState};
int payload [6];

/* This is the "conversation" map for "Chat Time".  
 * 0-99 = Red Turret sayings 
 * 100-199 = White Turret sayings 
 * 200-299 = Blue Turret sayings
 * Files on all SD cards are saved as 0-100.
 * Add 100 to the file number here for White, 200 for Blue.
 * File 204 would be BLUE turret, file number 0004.
 */ 
int chatSayings [] = {
  0,              // Start pause on i = 0, followed by "rounds"...
  204, 164, 25,   // 1
  205, 127, 76,   // 2
  208, 162, 65,   // 3
  143, 230, 23,   // 4
  130, 41, 225,   // 5
  153, 31, 133,   // 6
  234, 49, 155,   // 7
  229, 175, 74,   // 8
  231, 58, 226,   // 9
  161, 223, 59,   // 10
  227, 68, 236,   // 11
  136, 50, 224,   // 12
  34, 160, 78,    // 13
  222, 42         // End
};

/* This is the RED and WHITE turret timing map.  They share sayings.
 *  These timings correspond to the time needed
 *  to play the individual sayings, in milliseconds.
 *  Change the sayings above as desired, but do not change these timings.
 *  For example, i = 2 will take 0.8 seconds (NormalTimings[2])
 *  to play the chatSayings[2] file.
 */
int NormalTimings [] = {
1000,  // Start pause on i = 0, followed by "rounds"...
2600, 800, 2800, 900, 1700, 1600, 1300, 2500, 1400, 1900,     // 1 - 10
1600, 2300, 800, 3000, 300, 100, 200, 0, 0, 300,              // 11 - 20
298000, 1300, 2600, 1300, 1400, 2100, 1900, 1600, 800, 1700,  // 21 - 30
1100, 1000, 1000, 2100, 1500, 1300, 1100, 800, 1200, 1000,    // 31 - 40
2200, 1700, 1300, 1400, 1500, 1000, 2000, 500, 2700, 9000,    // 41 - 50
1100, 1200, 900, 2400, 1200, 1100, 2100, 2000, 2500, 1700,    // 51 - 60
1100, 1000, 1100, 500, 1900, 0, 1300, 2100, 1700, 900,        // 61 - 70
1100, 800, 1100, 1700, 1100, 1100, 1500, 1500, 500, 900,      // 71 - 80
2100                                                          // 81
};
/* This is the BLUE turret timing map.
 *  These timings correspond to the time needed
 *  to play the individual sayings, in seconds.
 *  For example, i = 2 will take 0.9 seconds (DefectiveTimings[2])
 *  to play the chatSayings [2] file.
 */
int DefectiveTimings [] = {
1000,   // Start pause on i = 0, followed by "rounds"...
1700, 900, 2000, 600, 1100, 1800, 1900, 3000, 1500, 800,    // 1 - 10
2100, 800, 1900, 900, 3200, 2700, 0, 0, 0, 2000,            // 11 - 20
4400, 800, 3200, 900, 1400, 2000, 2100, 1200, 1300, 1000,   // 21 - 30
1100, 1400, 2100, 1000, 1600, 1000, 1200                    // 31 - 40
};
/////////////////////////////////////////////////////////////////// 
//======================= SETUP =================================//
///////////////////////////////////////////////////////////////////
void setup(){
   secondarySerial.begin(9600);//
   Serial.begin(9600);//
   mp3.begin();
   mp3.setVolume(22);
   Wire.begin();
   SPI.begin();  // Radio setup
   radio.begin();
   radio.setPALevel(RF24_PA_LOW); // set radio to low power.  All near each other
   radio.setDataRate(RF24_2MBPS); // I find this works best with multiple radios
   network.begin(70, this_node);  //(channel, node address)
   sensor_read();
   pinMode(LED_LH_up, OUTPUT);
   pinMode(LED_LH_down, OUTPUT);
   pinMode(LED_CENTRE, OUTPUT);
   pinMode(LED_RH_up, OUTPUT);
   pinMode(LED_RH_down, OUTPUT);
   digitalWrite(LED_CENTRE, HIGH);
   activate_servos();
   servo_wings.write(WINGopen); // open wings
   servo_pivot.write(parkPIVOT); // park pivot
   servo_pitch.write(parkPITCH); // park pitch
   randomWake = random(1, 3);
   mp3.playMp3FolderTrack(1); // play waking up comment
   delay(2000);
   servo_wings.write(WINGclose); // close wings
   delay(1500);
   digitalWrite(LED_CENTRE, LOW);
   turn_off_servos();
   busyRed = 0;
}
///////////////////////////////////////////////////////////////////
//======================= MAIN LOOP =============================//
///////////////////////////////////////////////////////////////////
void loop(){
  while (sensor.readRangeSingleMillimeters()>triggerDistance) { //nothing in front of sensor, do nothing
    ReadNet();
    WriteNet();
    output_sensor();
    if (payload[5] == 1) { // Conditions used with MTC. Turret will work automatically without MTC
      delay(500);
      WriteNet();
      Cara_Mia(); // Opera time!!  
    }
    else if (payload[5] == 2) { 
      delay(500);
      WriteNet();
      Chatty_time(); // Chatty time!!}
      ReadNet();
    }
    else if (payload[5] == 3) {
      delay(500);
      WriteNet();
      ManualControl (); // Manual Control
    }     
  }
  
  if (sensor.readRangeSingleMillimeters()<triggerDistance){ //something in front of sensor
    delay (200); //wait before checking again to see if someone still there...
    if (sensor.readRangeSingleMillimeters()<triggerDistance){ //someone there (not false trigger)
      busyRed = 1;
      ReadNet();
      WriteNet();
      output_sensor();
      activate_servos(); // wake up servos
      activate(); // open wings and say "I see you" comment
      if (sensor.readRangeSingleMillimeters()>triggerDistance){ // opened up and person gone
        falseActivate();  //say "where'd you go?" comment and close up
        delay (2000);
        scanArea(); //perform scan of area
      }
      else { // someone definitely there - open fire!! 
        engage();
        delay (2400); 
        for (int j=0; j <= 2; j++){
          if (sensor.readRangeSingleMillimeters()<triggerDistance){ // as long as someone there...
            for (int i=0; i <= 25; i++){ // fire for a short period
              fire();} // shoot them...
            output_sensor();
          }
        }
      }      
    output_sensor(); 
    disengage(); // no more target, stop firing and prepare to close
  }
  else {
    output_sensor();
    delay(350);
    }
  }
}

///////////////////////////////////////////////////////////////////
//======================= SUBROUTINES ===========================//
///////////////////////////////////////////////////////////////////

//=====  read the VLX53 sensor  =====//
void sensor_read(){  
  sensor.init();
  sensor.setTimeout(100);
  // lower the return signal rate limit (default is 0.25 MCPS)
  sensor.setSignalRateLimit(0.25); // started with 0.1
  // increase laser pulse periods (defaults are 14 and 10 PCLKs)
  sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 14); //started with 18
  sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 10); //started with 14
}
//=====  monitor output for debugging  =====//
void output_sensor(){ // Option out or delete if not interested.
  Serial.println();
  Serial.print ("-RED-  ");
  Serial.print("Distance "); Serial.print(sensor.readRangeSingleMillimeters()); Serial.print ("\t");
  if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }
  Serial.print(F("Pivot< ")); Serial.print (posPIVOT); Serial.print ("   ");
  Serial.print(F("Pitch< ")); Serial.print (posPITCH); Serial.print ("   ");
  Serial.print(F("Wings< ")); Serial.print (WINGpos); Serial.print ("   ");
  Serial.print(F("busyRed: "));Serial.print (busyRed); Serial.print ("   ");
  Serial.print(F("x :"));Serial.print (payload[0]); Serial.print ("\t");
  Serial.print(F("y :"));Serial.print (payload[1]); Serial.print ("\t");
  Serial.print(F("pb1 :"));Serial.print (payload[2]); Serial.print ("   ");
  Serial.print(F("pb2 :"));Serial.print (payload[3]); Serial.print ("   ");
  Serial.print(F("pb3 :"));Serial.print (payload[4]); Serial.print ("   ");
  Serial.print(F("goState: ")); Serial.print(payload[5]);Serial.print ("   ");
}
//=====  update servo positions =====//
void increment_servos(){ 
  if (pitchCW == 0) posPITCH = posPITCH - 3; // the higher the number, the jerkier the motion
  if (pitchCW == 1) posPITCH = posPITCH + 3;
  if (pivotCW == 0) posPIVOT = posPIVOT - 3;
  if (pivotCW == 1) posPIVOT = posPIVOT + 3;
  if (posPIVOT >= maxPIVOT) pivotCW = 0;
  if (posPIVOT <= minPIVOT) pivotCW = 1;
  if (posPITCH >= maxPITCH) pitchCW = 0;
  if (posPITCH <= minPITCH) pitchCW = 1;
}
///////////////////////////////////////////////////////////////////
//======================= ACTIVATE ==============================//
///////////////////////////////////////////////////////////////////
void activate(){ // open up wings and say something
  busyRed = 1;
  ReadNet();
  WriteNet();
  output_sensor();
  digitalWrite(LED_CENTRE, HIGH); //LED eye on
  randomOpen = random(3, 6);// pick random opening comment
  mp3.playMp3FolderTrack(randomOpen); // play random "I see you" comment 
  servo_wings.write(WINGopen); // open wings
  output_sensor();
  delay (3400);
}
///////////////////////////////////////////////////////////////////
//====================FALSE ACTIVATE ============================//
///////////////////////////////////////////////////////////////////
void falseActivate(){ // 
  busyRed = 1;
  ReadNet();
  WriteNet();
  output_sensor();
  randomFalse = random(6, 9);// pick random opening comment
  mp3.playMp3FolderTrack(randomFalse); // play random "where'd you go?" comment
  delay (1800);
}
///////////////////////////////////////////////////////////////////
//======================= SCAN AREA =============================//
///////////////////////////////////////////////////////////////////
void scanArea(){ // continue scanning for a bit after falseActivate
  busyRed = 1;
  ReadNet();
  WriteNet();
  output_sensor();
  mp3.playMp3FolderTrack(2); // "searching..."
  servo_pitch.write(parkPITCH);
  delay(1600);
  servo_pitch.detach();
  servo_wings.detach();
  mp3.playMp3FolderTrack(21);
  for (int i=0; i <= 220; i++){ //scan for a little bit...
    output_sensor();
    if (pivotCW == 0) posPIVOT = posPIVOT - 1; // increment one step CW if CW = 0
    if (pivotCW == 1) posPIVOT = posPIVOT + 1; // otherwise go one step other direction
    if (posPIVOT >= maxPIVOT) pivotCW = 0; // if max rotation clockwise, switch to counterclockwise
    if (posPIVOT <= minPIVOT) pivotCW = 1; // if min rotation counterclockwise,switch to clockwise
    servo_pivot.write(posPIVOT);
    if (sensor.readRangeSingleMillimeters()<triggerDistance/2){ //Somebody stepped in front
      i = 350;
      engage();  
      delay (2500);
      activate_servos();
      for (int i=0; i <= 25; i++){ // fire for a short period
        fire(); // shoot them...
      } 
    }
  }
  activate_servos(); // fully scanned with no one passing...
  servo_pivot.write(parkPIVOT); //close up.
  output_sensor();
  delay(1200);
}
///////////////////////////////////////////////////////////////////
//======================= ENGAGE ================================//
///////////////////////////////////////////////////////////////////
void engage(){ //smartass comment before firing
  busyRed = 1;
  ReadNet();
  WriteNet();
  output_sensor();
  randomStart = random(9, 12);// pick random smartass comment before firing
  mp3.playMp3FolderTrack(randomStart);
  output_sensor();
}
///////////////////////////////////////////////////////////////////
//======================= FIRE ==================================//
///////////////////////////////////////////////////////////////////
void fire(){ // fire sound and turret movement
  
  ReadNet();
  WriteNet();
  output_sensor();
  mp3.playMp3FolderTrack(15);
  servo_pivot.write(posPIVOT);
  servo_pitch.write(posPITCH);
  digitalWrite(LED_CENTRE, HIGH);
  digitalWrite(LED_LH_up, LOW);    
  digitalWrite(LED_LH_down, HIGH);        
  digitalWrite(LED_RH_up, LOW);                      
  digitalWrite(LED_RH_down, HIGH);     
  delay(pulse*2); 
  servo_pivot.write(posPIVOT);
  servo_pitch.write(posPITCH);
  digitalWrite(LED_LH_up, HIGH);    
  digitalWrite(LED_LH_down, LOW);        
  digitalWrite(LED_RH_up, HIGH);                      
  digitalWrite(LED_RH_down, LOW);  
  delay(pulse); 
  digitalWrite(LED_LH_up, LOW); 
  digitalWrite(LED_RH_up, LOW);            
  increment_servos(); // update servo position
}
///////////////////////////////////////////////////////////////////
//======================= DISENGAGE =============================//
///////////////////////////////////////////////////////////////////
void disengage(){ //re-align wings (pitch) and pivot home before closing wings
  busyRed = 1;
  ReadNet();
  WriteNet();
  output_sensor();
  delay(2000);
  randomDisengage = random(12, 15);
  mp3.playMp3FolderTrack(randomDisengage); // "Goodbye" comment
  servo_pivot.write(parkPIVOT);
  servo_pitch.write(parkPITCH);
  digitalWrite(LED_LH_up, LOW);    
  digitalWrite(LED_LH_down, LOW);        
  digitalWrite(LED_RH_up, LOW);                      
  digitalWrite(LED_RH_down, LOW); // turn off firing LEDs
  delay (2000);
  servo_wings.write(WINGclose);
  delay (1500);
  digitalWrite(LED_CENTRE, LOW); // turn off central LED
  delay (100);
  output_sensor();
  turn_off_servos();
  busyRed = 0;
}
void turn_off_servos(){ // detach servos so they're not engaged
  servo_pivot.detach();
  servo_pitch.detach();
  servo_wings.detach();  
}
void activate_servos(){ // activate servos for movement
  servo_pivot.attach(PIVOT);
  servo_pitch.attach(PITCH);
  servo_wings.attach(WINGS);  
}

/////////////////////////////////////////////////////////////////// 
//======================= CARA MIA OPERA ========================//
///////////////////////////////////////////////////////////////////
void Cara_Mia(){
  busyRed = 1;
  WriteNet();
  digitalWrite(LED_CENTRE, HIGH);
  int randomWingPos;
  activate_servos();
  unsigned long endTimer[] = {8200, 40000, 100000}; // 
  unsigned long startTimer;
  for (int i=0; i <= 2; i++) {
    startTimer = millis();
    mp3.playMp3FolderTrack(100 + i);
    while (millis() < (startTimer + endTimer[i])){
      ReadNet();
      WriteNet();
      if (payload[4] == 0) {
        disengage();
        busyRed = 0;
        WriteNet();
        return;}
        //  debugging print below.  Delete or comment out as desired.
      Serial.print(F("millis() ")); Serial.print (millis()); Serial.print ("   ");
      Serial.print(F("startTimer ")); Serial.print (startTimer); Serial.print ("   ");
      Serial.print(F("endTimer ")); Serial.print (endTimer[i]); Serial.print ("   ");
      Serial.print(F("End Time ")); Serial.print (endTimer[i] + startTimer); Serial.print ("   ");
      Serial.print(F("previousMillis1 ")); Serial.print (previousMillis1); Serial.print ("   ");
      Serial.println();
      if ((millis() - previousMillis1) >= (850 + (i * 100))) { // 1050
        randomWingPos = random(10 + (i*20), (60 + (i*20)));
        servo_wings.write(randomWingPos);      
        previousMillis1 = millis();    
      }
    } 
  }
  disengage();
  busyRed = 0;
  goState = 0;
  mp3.stop();
  ReadNet();
  delay(1000);
}
/////////////////////////////////////////////////////////////////// 
//======================= CHATTY TIME ===========================//
///////////////////////////////////////////////////////////////////
void Chatty_time(){
  busyRed = 1;
  WriteNet();
  int i = 0;
  int talk;
  int saying;
  int timeadder = 750;
  int talkTime = NormalTimings[i];
  int randomPivotPos;
  activate_servos();
  servo_wings.write(WINGopen);
  digitalWrite(LED_CENTRE, HIGH);
  do {
    ReadNet();
    WriteNet();
    //output_sensor(); //  used for debugging...
    if (i >= 43) { // end of sequence
      busyRed = 0;
      WriteNet();
      disengage();
      return;
    }
    unsigned long currentMillis = millis(); // grab current time
    if ((unsigned long)(currentMillis - previousMillis3) >= talkTime) {
      if (chatSayings[i] < 100) { // RED Turret talking
        talk = chatSayings[i];
        saying = chatSayings[i];
        talkTime = (NormalTimings[saying] + timeadder); 
      }
      else if ((chatSayings[i] > 99) && (chatSayings[i] < 200)) { // WHITE turret talking
        talk = 0;
        saying = chatSayings[i] - 100;
        talkTime = (NormalTimings[saying] + timeadder);
      }
  
      else {  // BLUE turret talking
        talk = 0;
        saying = chatSayings[i] - 200; // sound file # of BLUE
        talkTime = (DefectiveTimings[saying] + timeadder); // Time for that saying
      }

      if (talk == 0) {
        digitalWrite(LED_CENTRE, LOW);
      }
      else {
        digitalWrite(LED_CENTRE, HIGH);
        mp3.playMp3FolderTrack(talk);
      }
      randomPivotPos = random(minPIVOT, maxPIVOT);
      servo_pivot.write(randomPivotPos);
      Serial.println();
      Serial.print(F("i: ")); Serial.print (i); Serial.print ("\t");
      Serial.print(F("chatSayings[i] ")); Serial.print (chatSayings[i]); Serial.print ("\t");
      Serial.print(F("Saying ")); Serial.print (saying); Serial.print ("\t");
      Serial.print(F("talk ")); Serial.print (talk); Serial.print ("\t");
      Serial.print(F("chat time ")); Serial.print (talkTime); Serial.print ("\t");
      Serial.print(F("busyRed: "));Serial.print (busyRed); Serial.print ("   ");
      previousMillis3 = millis();
      i++;
    }
  }
  while (payload[4] == 1);
  busyRed = 0;
  WriteNet();
  digitalWrite(LED_CENTRE, LOW); 
  disengage();
}
/////////////////////////////////////////////////////////////////// 
//======================= MANUAL CONTROL =======================//
///////////////////////////////////////////////////////////////////
void ManualControl(){
  int servoWings;
  int servoPitch;
  int servoPivot;
  activate_servos();
  servo_wings.write(WINGopen);
  digitalWrite(LED_CENTRE, HIGH);
  ReadNet();  
  do {
    output_sensor();
    ReadNet();
    servoPivot = map(payload[0], 1023, 0, minPIVOT, maxPIVOT);  
    servoPitch = map(payload[1], 1023, 0, minPITCH, maxPITCH);  
    servo_pivot.write(servoPivot);
    servo_pitch.write(servoPitch);
    unsigned long currentMillis = millis(); // grab current time
    if (payload[3] == 0) {
      if ((unsigned long)(currentMillis - previousMillis1) >= 2500) {  
      randomPic = random(1, 20);
      mp3.playMp3FolderTrack(randomPic);
      previousMillis1 = millis();
      }
    }
    if (payload[2] == 0){
      fire();
    }  
  }
  while (payload[5] == 3);
  disengage();
  busyRed = 0;
  WriteNet();
  digitalWrite(LED_CENTRE, LOW); 
}
///////////////////////////////////////////////////////////////////
//========================= RECEIVING ===========================//
///////////////////////////////////////////////////////////////////
void ReadNet(){
  network.update();
  if ( network.available() )  {
    RF24NetworkHeader header;
    network.peek(header);
    network.read(header, &payload, sizeof(payload));} // Read the package        
}
///////////////////////////////////////////////////////////////////
//========================= SENDING =============================//
///////////////////////////////////////////////////////////////////
void WriteNet(){
  network.update();
  RF24NetworkHeader header4(MTC);
  bool ok4 = network.write(header4, &busyRed, sizeof(busyRed));
/*  if (ok4) { // used for debugging...
    Serial.print("MTC ok4  ");}
  else {
    Serial.print("-------  ");} 
    */
}

Custom parts and enclosures

Lens+v3b.stl
Lens%2Bv3b.stl
Body-RHS+v4.stl
Body-RHS%2Bv4.stl
Body-LHS+w+components+v4.stl
Body-LHS%2Bw%2Bcomponents%2Bv4.stl
Wing+RH+bottom+v4.stl
Wing%2BRH%2Bbottom%2Bv4.stl
Wing+RH+top+v4.stl
Wing%2BRH%2Btop%2Bv4.stl
Wing+LH+bottom+v4.stl
Wing%2BLH%2Bbottom%2Bv4.stl
Wing+LH+top+v4.stl
Wing%2BLH%2Btop%2Bv4.stl
Pitch+Frame+v4.stl
Pitch%2BFrame%2Bv4.stl
Leg+-+REAR+v4.stl
Leg%2B-%2BREAR%2Bv4.stl
Infill+strips+v3.stl
Infill%2Bstrips%2Bv3.stl
Leg+-+REAR+test.stl
Leg%2B-%2BREAR%2Btest.stl
Pitch+Frame+-+modified+1.stl
Pitch%2BFrame%2B-%2Bmodified%2B1.stl
Arduino+Base+w+Radio.stl
Arduino%2BBase%2Bw%2BRadio.stl

Schematics

Wiring Schematic - radio option
Schematic that I made for building the turret.
Portal turret   radio option dgbcs3zkls

Comments

Similar projects you might like

Portal 2 Turret - Master Turret Control of Portal 2 Turrets

Project tutorial by Novachris

  • 2,230 views
  • 0 comments
  • 17 respects

Autonomous Nerf Sentry Turret

Project tutorial by Arduino “having11” Guy

  • 7,074 views
  • 2 comments
  • 31 respects

Motion Sensor Water Gun

Project tutorial by Engineering Is Easy

  • 15,039 views
  • 4 comments
  • 29 respects

Sigfox Talking Plant

Project tutorial by Nicolas Lesconnec and Louis Moreau

  • 9,907 views
  • 1 comment
  • 17 respects

Humaniod A.I Talking Robot With Arduino

Project showcase by Ashwini kumar sinha

  • 6,595 views
  • 9 comments
  • 13 respects

Automated NERF Gun Shooting Gallery

Project showcase by Keegan Neave

  • 3,773 views
  • 1 comment
  • 17 respects
Add projectSign up / Login