StuartMVG
Published © GPL3+

The KOTOR Crate

Side table storage unit with light up base and piezo buzzer. Change the light color and play sounds over the web.

IntermediateShowcase (no instructions)3 days707
The KOTOR Crate

Things used in this project

Hardware components

Photon
Particle Photon
×1
NeoPixel Ring: WS2812 5050 RGB LED
Adafruit NeoPixel Ring: WS2812 5050 RGB LED
NeoPixel Jewel
×1
Servos (Tower Pro MG996R)
×1
Latching Pushbutton
The Pushbutton has a LED that is made to light up when no pushed in.
×1
3-in-1 USB
Cut off one of the Lighting heads to using as a power line to the Photon
×1
Piezo Buzzer
×1
Capacitor 100 µF
Capacitor 100 µF
×1

Software apps and online services

Dropbox
Used to run the web app from my phone

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Craft Knife

Story

Read more

Schematics

KOTOR Crate board

Code

home.html

HTML
Web Application that controls the light, servo and buzzer.
<!DOCTYPE HTML>
<html>
<!--<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>-->

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css">
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>

<script> //This is here to allow the app to run in a browser
$(document).bind('mobileinit',function(){
  $.mobile.changePage.defaults.changeHash = false;
  $.mobile.hashListeningEnabled = false;
  $.mobile.pushStateEnabled = false;
});
</script>

<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<head>
  <title>KOTOR Crate Control</title>
  <style type="text/css">
  body {
    padding-left: 20px;
  }
  .center-wrapper{
    text-align: center;
  }
  .center-wrapper * {
    margin: 0 auto;
  }

  /* Custom indentations are needed because the length of custom labels differs from
  the length of the standard labels */
  .custom-size-flipswitch.ui-flipswitch .ui-btn.ui-flipswitch-on {
    text-indent: -3.5em;
  }
  .custom-size-flipswitch.ui-flipswitch .ui-flipswitch-off {
    text-indent: 0.5em;
  }
  /* Custom widths are needed because the length of custom labels differs from
  the length of the standard labels */
  .custom-size-flipswitch.ui-flipswitch {
    width: 6em;
  }
  .custom-size-flipswitch.ui-flipswitch.ui-flipswitch-active {
    padding-left: 4em;
    width: 1.875em;
  }
  @media (min-width: 28em) {
    /*Repeated from rule .ui-flipswitch above*/
    .ui-field-contain > label + .custom-size-flipswitch.ui-flipswitch {
      width: 1.875em;
    }
  }

  </style>
</head>
<body onload = "startLoad()">

  <div data-role="page" data-theme="a" id="pageone">
    <div data-role="header" data-theme="b">
      <h1>KOTOR Crate</h1>
      <div data-role="navbar">
        <ul>
          <li><a href="#pagethree" data-icon="arrow-l">Light Side</a></li>
          <li><a href="#" class="ui-btn-active ui-state-persist" data-icon="power">Control</a></li>
          <li><a href="#pagetwo" data-icon="arrow-r">Dark Side</a></li>
        </ul>
      </div>
    </div>

    <div data-role="main" class="ui-content">
      <div data-role="fieldcontain" class="center-wrapper">
        <h2>Servo Control</h2>
        <br>
        <select id="select-based-flipswitch" data-role="flipswitch" data-wrapper-class="custom-size-flipswitch" onchange="servoMode()">
          <option value="close">Close</option>
          <option value="open">Open</option>
        </select>
        <br><br>
        <p>Current State: <span id="curServo"></span></p>
      </div>


      <div data-role="fieldcontain" class="center-wrapper">
        <h2>Light Control</h2>
        <br>
        <button type="button" style="height:50px;width:100px" onclick="onOffBut()"><span id="curLight"></span></button>
      </div>

      <div data-role="fieldcontain" >
        <div class="center-wrapper">
          <h2>Color Control</h2>
        </div>

        <input type="range" name="degBox" id="colorBoxId" min="0" max="100" step="0.5" value="hslCode" onchange="hslToHex(this)">

        <button id="colorBut" onclick="test()"></button>
      </div>
    </div>
    <div data-role="footer" data-theme="b">
      <h1>Force 2.0</h1>
    </div>
  </div>

  <div data-role="page" data-theme="b" id="pagetwo">
    <div data-role="header">
      <h1>Dark Side</h1>
      <div data-role="navbar">
        <ul>
          <li><a href="#pagethree" data-icon="arrow-l">Light Side</a></li>
          <li><a href="#pageone" data-icon="power">Control</a></li>
          <li><a href="#" class="ui-btn-active ui-state-persist" data-icon="arrow-r">Dark Side</a></li>
        </ul>
      </div>
    </div>

    <div data-role="main" class="ui-content">
      <div class="center-wrapper">
        <p><q>Nw&ucirc;l tash.<br>Dzwol sh&acirc;sotkun.<br>Sh&acirc;sotjont&ucirc; ch&acirc;tsatul nu ty&ucirc;k.<br>Ty&ucirc;kjont&ucirc; ch&acirc;tsatul nu midwan.<br>Midwanjont&ucirc; ch&acirc;tsatul nu asha.<br>Ashajont&ucirc; kotswinot itsu nuyak.<br>Wonoksh Qy&acirc;sik nun.</q><br><strong><a href="#myPopup" data-rel="popup" style="text-decoration: none; color: red;" data-transition="pop" data-overlay-theme="b">Qotsisajak</a></strong></p>
        <br>
        <button id="darkButton" onclick="force('dark')">Sith</button>
        <br>
      </div>
      <div data-role="popup" id="myPopup" class="ui-content">
        <p><q>Peace is a lie, there is only passion.<br>Through passion, I gain strength.<br>Through strength, I gain power.<br>Through power, I gain victory.<br>Through victory, my chains are broken.<br>The Force shall free me.</q><br>- Sith Code</p>
      </div>
    </div>

    <div data-role="footer">
      <h1>Feel the Power</h1>
    </div>
  </div>

  <div data-role="page" data-theme="a" id="pagethree">
    <div data-role="header">
      <h1>Light Side</h1>
      <div data-role="navbar">
        <ul>
          <li><a href="#" class="ui-btn-active ui-state-persist" data-icon="arrow-l">Light Side</a></li>
          <li><a href="#pageone" data-icon="power">Coltrol</a></li>
          <li><a href="#pagetwo" data-icon="arrow-r">Dark Side</a></li>
        </ul>
      </div>
    </div>

    <div data-role="main" class="ui-content">
      <div class="center-wrapper">
        <p><q>There is no emotion, there is peace.<br>There is no ignorance, there is knowledge.<br>There is no passion, there is serenity.<br>There is no chaos, there is harmony.<br>There is no death, there is the Force.</q> <br><strong><a href="#" style="text-decoration: none;">Jedi Code</a></strong></p>
        <br>
        <button id="lightButton" onclick="force('light')">Jedi</button>
        <br>
      </div>
    </div>

    <div data-role="footer">
      <h1>May the Force Be With You</h1>
    </div>
  </div>


  <script type="text/javascript">
// Remember to include your Particle Device ID & Token

  var deviceID = "Device ID";
  var accessToken = "Particle Token";

  var servoState = "close";
  var setServo = "setServo";

  var curLightVal = document.getElementById("curLight").value;

  var setSwitch = "setSwitch";
  var getSwitch = "getSwitch";

  var setColor = "setColor";
  var getColor = "getColor";
  var hexCode = document.getElementById("colorBut").value;
  var hslCode = 0;

  var setForce = "setForce";

window.setInterval(function() {
  startSwitch();
  startLight();
},1000)

function onOffBut() {
  curLightVal = document.getElementById("curLight").value;
  console.log(curLightVal);

  if (curLightVal == "on") {
    curLightVal = document.getElementById("curLight").value = "off";
    curLightVal = "off"
  } else{
    curLightVal = document.getElementById("curLight").value = "on";
    curLightVal = "on";
  }
  console.log("end " + curLightVal);

  var requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + setSwitch + "/";
  $.post( requestURL, { params: curLightVal, access_token: accessToken });
}

function servoMode() {
  if (servoState == "close") {
    servoState = "open";
  } else {
    servoState = "close";
  }
  var requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + setServo + "/";
  $.post( requestURL, { params: servoState, access_token: accessToken });
  console.log(servoState);
}


function componentToHex(c) {
  var hex = c.toString(16);
  return hex.length == 1 ? "0" + hex : hex;
}


function hslToHex (h) {
  var s = 1;
  var l = 0.5;
  var r, g, b;
  var red, green, blue;
  var hue = document.getElementById('colorBoxId').value;
  hue = hue/100;
  console.log(hue);

  if(s == 0){
    r = g = b = l; // achromatic
  }else{
    var hue2rgb = function hue2rgb(p, q, t){
      if(t < 0) t += 1;
      if(t > 1) t -= 1;
      if(t < 1/6) return p + (q - p) * 6 * t;
      if(t < 1/2) return q;
      if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
      return p;
    }

    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    var p = 2 * l - q;
    r = hue2rgb(p, q, hue + 1/3);
    g = hue2rgb(p, q, hue);
    b = hue2rgb(p, q, hue - 1/3);
  }

  red = Math.round(r * 255);
  green = Math.round(g * 255);
  blue = Math.round(b * 255);

  console.log(r + " RED " + red);
  console.log(g + " GREEN " + green);
  console.log(b + " BLUE " + blue);

  hexCode = "#" + componentToHex(red) + componentToHex(green) + componentToHex(blue);

  console.log("Hex " + hexCode);

  var requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + setColor + "/";
  $.post( requestURL, { params: hexCode, access_token: accessToken });


  //return hexCode;
}

function hexToHsl (hex){
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);

  var r, g, b;

  r = parseInt(result[1], 16);
  g = parseInt(result[2], 16);
  b = parseInt(result[3], 16);

  r /= 255;
  g /= 255;
  b /= 255;

  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, l = (max + min) / 2;

  if(max == min){
    h = s = 0; // achromatic
  }else{
    var d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch(max){
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }
  h = Math.round(h * 100);
  return h;
}

function startLoad() {
  startSwitch();
  startLight();
}

function startSwitch () {
  requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + getSwitch + "/?access_token=" + accessToken;
  $.getJSON(requestURL, function(json) {
    curLightVal = json.result;
    document.getElementById("curServo").innerHTML = servoState;
    document.getElementById("curServo").style.fontSize = "20px";

    document.getElementById("curLight").innerHTML = curLightVal;
    document.getElementById("curLight").style.fontSize = "20px";

    document.getElementById("colorBut").style = "background-color:"+hexCode;
  });
}

function startLight () {
  requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + getColor + "/?access_token=" + accessToken;
  $.getJSON(requestURL, function(json) {
    hexCode = json.result;
    hslCode = hexToHsl(hexCode);
    document.getElementById("colorBoxId").value = hslCode;
    //console.log(hexCode);
    //console.log(hslCode);
  });
}

function force (side) {
  var requestURL = "https://api.spark.io/v1/devices/" + deviceID + "/" + setForce + "/";
  $.post( requestURL, { params: side, access_token: accessToken });
  //console.log(side);
  //console.log(typeof(side));
}

function test() {
  alert("This is a text");
}
</script>
</body>
</html>

KOTOR_Crate.ino

C/C++
This is the code for the Photon. I have tried to comment as much as I can to help others.
//KOTOT Crate - Particle Photon Code
// This #include statement was automatically added by the Particle IDE.
#include "neopixel/neopixel.h"

//NeoPixel SetUp
#define NEO_PIN D1
#define NEO_COUNT 7
#define NEO_TYPE WS2812B

//Servo SetUp
Servo myServo;

int const serWrite = D2;
int potVal;
int angle;

//Light State On/Off
String lightState;

//NeoPixel Color
String hexCode;
int red;
int green;
int blue;

//Physical Button SetUp
int const ledPin = A3;
int const buttonPin = D4;
int ledState = LOW;
int buttonState;
int breath = 10;

int i = 0; 
int d = 0;

//Used for the flashing LED when web app is ingadged
unsigned long previousMillis = 0;       
const long interval = 1000;           

//Speaker
int const speakerPin = D3;

Adafruit_NeoPixel neoRing = Adafruit_NeoPixel(NEO_COUNT, NEO_PIN, NEO_TYPE);

void setup() {
    
    neoRing.begin();
    neoRing.setBrightness(128); //NeoPixel Brightness set to half
    neoRing.show(); // Initialize all pixels to 'off'
    
     //setup REST service for servo control
    Particle.function("setServo", serControl);
    
    //setup REST service for NeoPixel control
    Particle.function("setSwitch", neoLight);
    Particle.variable("getSwitch", &lightState, STRING);
    
    //setup REST service for NeoPixel colour
    Particle.function("setColor", pixelColor);
    Particle.variable("getColor", &hexCode, STRING);
    Particle.variable("getRed", &red, INT);
    Particle.variable("getGreen", &green, INT);
    Particle.variable("getBlue", &blue, INT);
    
    Particle.variable("getButton", &buttonState, INT);
    
    Particle.function("setForce", force);
    
    myServo.attach(serWrite);
    
    //Button Connect
    pinMode(ledPin, OUTPUT);
    pinMode(buttonPin, INPUT_PULLDOWN);
    
    //Sound Output Pin
    pinMode(speakerPin, OUTPUT);
    
  
    Serial.begin(9600);
    delay(1000);
    
    serControl ("close");
    
    delay(1000);
    
    //Show that set up is done by turning on the NeoPixel's for 2 seconds
    pixelColor ("#00ffff");
    
    delay(2000);
    
    //Turn off NeoPixel
    lightOff ();

}

void loop() {
    
    buttonState = digitalRead(buttonPin);
    
    // check to see if it's time to blink the LED; that is, if the
    // difference between the current time and last time you blinked
    // the LED is bigger than the interval at which you want to
    // blink the LED.
    unsigned long currentMillis = millis();

    
    if (buttonState == 1 && lightState == "On") {
        lightState = "Off";

    }
    if (buttonState == 1 && lightState == "Off") {
        pixelColor(hexCode);
        digitalWrite(ledPin, LOW);
    }
    if (buttonState == 0 && lightState == "On") {
        if (buttonState == 0 && lightState == "On") {
            pixelColor(hexCode);
            if (buttonState == 1) {
                lightState = "Off";
            }
        }

//This is were the LED will flash if the web app button is pushed
  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
        //digitalWrite(ledPin, LOW);
    }
    if (buttonState == 0 && lightState == "Off") {
        lightOff();
        digitalWrite(ledPin, HIGH);
    }
    

}



int serControl (String state) {
    
    if (state == "open") {
        potVal = 0;
        angle = map(potVal, 0, 4095, 0, 179);
        myServo.write(angle);
        return 1;
    }
    
    if (state == "close") {
        potVal = 4000;
        angle = map(potVal, 0, 4095, 0, 179);
        myServo.write(angle);
        return 1;
    }
    
    //return 0;
}

int neoLight (String state) {
    
    if (state == "on") {
        lightState = "On";
        return 1;
    } else if (state == "off") {
        lightState = "Off";
        return 1;
    }
}


//Change the color of the NeoPixel by using a hex code and converting it to RGB
int pixelColor (String color) {
    hexCode = color;
    char tempInt;
    
    //Red hex numbers to INT - Red color
    tempInt = color[1];
    red = hexToDec(tempInt,1);
    tempInt = color[2];
    red = (hexToDec(tempInt,0)) + red;
    Serial.println(red);
    
    //Green Hex numbers to INT - Green color
    tempInt = color[3];
    green = hexToDec(tempInt,1);
    tempInt = color[4];
    green = (hexToDec(tempInt,0)) + green;
    Serial.println(green);
    
    //Blue hex numbers to INT - Blue color
    tempInt = color[5];
    blue = hexToDec(tempInt,1);
    tempInt = color[6];
    blue = (hexToDec(tempInt,0)) + blue;
    Serial.println(blue);
        
        for (int i = 0; i < 7; i++){
            neoRing.setPixelColor(i, red, green, blue); //Red Colour
            if (lightState == "On" || buttonState == 1) {
                neoRing.show(); //refresh the NeoPixel color
            }
            
        }
}


//Color Hex to Dec converter

int hexToDec(char hexString,int x) {
  
  int decValue = 0;
  int tempvalue = 0;
  
  if ((hexString != 'a') && (hexString != 'b') && (hexString != 'c') && (hexString != 'd') && (hexString != 'e') && (hexString != 'f'))
  {
      int tempvalue = hexString - '0';
      if (x==1){
        decValue=tempvalue * 16;
      } else {
          decValue = tempvalue + decValue;
      }
    }
    
    else {
        if (hexString == 'a'){
            tempvalue=10; } 
        if (hexString == 'b'){
            tempvalue=11; }
        if (hexString == 'c'){
            tempvalue=12; } 
        if (hexString == 'd'){
            tempvalue=13; } 
        if (hexString == 'e'){
            tempvalue=14; }
        if (hexString == 'f'){
          tempvalue=15; } 
        
        if (x==1){
        decValue = tempvalue * 16;
        } else {
          decValue = tempvalue + decValue;
        }
    }

return decValue;
}

void cyanLight () {
    for (int i = 0; i < 7; i++){
        neoRing.setPixelColor(i, 0, 255, 255); //Cyan Colour
    }   
    neoRing.show(); //refresh the NeoPixel color
}

void lightOff () {
    for (int i = 0; i < 7; i++){
        neoRing.setPixelColor(i, 0, 0, 0);
    }
    neoRing.show(); //refresh the NeoPixel color
}

int force (String side) {
    String tempHex = hexCode;
    if (side == "light") {
    //Theme Song
  int length = 17; // the number of notes
  String themeNotes[] = {"g","D/D5","C","b","a/A4","G/G5","D/D5","C","b","a/A4","G/G5","D/D5","C","b","C","a/A4","rest"};
  int themeBeats[] = { 4, 4, 1, 1, 1, 4, 4, 1, 1, 1, 4, 4, 1, 1, 1, 5, 2 };
  int tempo = 150;
        lightState = "On";
        pixelColor("#0000FF");
        
        for (int i = 0; i < length; i++) {
      if (themeNotes[i] == "rest") {
        delay(themeBeats[i] * tempo);
      } else {
        playNote(themeNotes[i], themeBeats[i] * tempo);
      }
      delay(tempo / 2);
    }

        lightOff();
        lightState = "Off";
        hexCode = tempHex; //Return the Light to the color before playing
        return 1;
    }
    
    if (side == "dark") {
        //Imperial March
  int length = 10; //47
  String marchNotes[] = {"G4","G4", "G4", "D#4/Eb4", "A#4/Bb4", "G4", "D#4/Eb4","A#4/Bb4", "G4", "rest"};
  int marchBeats[] = { 8,8,8,6,2,8,6,2,16,2 };
  int tempo = 100;
        lightState = "On";
        pixelColor("#FF0000");
        for (int i = 0; i < length; i++) {
      if (marchNotes[i] == "rest") {
        delay(marchBeats[i] * tempo);
      } else {
        playNote(marchNotes[i], marchBeats[i] * tempo);
      }
      delay(tempo / 2);
    }
        lightOff();
        lightState = "Off";
        hexCode = tempHex;
        return 1;
    }
}

//Sound Engine
// The two functions here are used to read in and the play the sounds. 
  void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
      digitalWrite(speakerPin, HIGH);
      delayMicroseconds(tone);
      digitalWrite(speakerPin, LOW);
      delayMicroseconds(tone);
    }
  }

  void playNote(String note, int duration) {
    String noteNames[] = { "G6","F#6/Gb6","F6","E6","D#6/Eb6","D6","C#6/Db6","C6","B5","A#5/Bb5","A5","G#5/Ab5","G/G5","F#5/Gb5","F5","E5","D#5/Eb5","D/D5","C#5/Db5","C5","C","B4","b","A#4/Bb4","a/A4","G#4/Ab4","g","G4","F#4/Gb4","F4","E4","D#4/Eb4"};
    int tones[] = { 318, 337, 357, 379, 401, 425, 450, 477, 506, 536, 568, 601, 637, 675, 715, 758, 803, 851, 901, 955, 956, 1012, 1014, 1072, 1136, 1203, 1274, 1275, 1351, 1431, 1516, 1607 };
    for (int i = 0; i < 32; i++) {
      if (noteNames[i] == note) {
        playTone(tones[i], duration);
      }
    }
  }

Credits

StuartMVG

StuartMVG

1 project • 1 follower
Thanks to Brian Ogilvie.

Comments