diff --git a/ci-test.sh b/ci-test.sh
index 4504f9a6d9d422964da0fc469bcba52befdb65e2..61d28b9155431dcc434b683b09ab33f32ebf82d1 100644
--- a/ci-test.sh
+++ b/ci-test.sh
@@ -2,5 +2,10 @@
 
 set -e
 
-# Quad
-cd quad && make && make test
+# Quad Library Unit tests
+(cd quad && make && make test)
+
+# Quad functional safety tests
+export PATH=/usr/local/bin:$PATH
+echo $(ruby --version)
+ruby quad/scripts/tests/test_safety_checks.rb
diff --git a/quad/scripts/tests/test_safety_checks.rb b/quad/scripts/tests/test_safety_checks.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2ed03ed7f9f78f3b243aac5937779b1d1123027e
--- /dev/null
+++ b/quad/scripts/tests/test_safety_checks.rb
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+
+# Safety Checks
+#
+# Startup the virtual quad and make sure it doesn't allow combinations of things
+# that could hurt people.
+THROTTLE_MIN = 110200
+THROTTLE_MAX = 191900
+THROTTLE_MID = (THROTTLE_MAX + THROTTLE_MIN)/2
+THROTTLE_3_4 = (THROTTLE_MAX + THROTTLE_MID)/2
+THROTTLE_QUAR = (THROTTLE_MID + THROTTLE_MIN)/2
+THROTTLE_EIGHTH = (THROTTLE_QUAR + THROTTLE_MIN)/2
+THROTTLE_16 = (THROTTLE_EIGHTH + THROTTLE_MIN)/2
+MOTOR_MIN = 100000
+MOTOR_MAX = 200000
+GEAR_ON = 170800
+GEAR_OFF = 118300
+
+require 'test/unit/assertions'
+require 'thread'
+include Test::Unit::Assertions
+
+# Utility functions
+def check_motors_are_off
+  motor1 = File.read("virt-quad-fifos/pwm-output-motor1")
+  motor2 = File.read("virt-quad-fifos/pwm-output-motor2")
+  motor3 = File.read("virt-quad-fifos/pwm-output-motor3")
+  motor4 = File.read("virt-quad-fifos/pwm-output-motor4")
+  assert_operator motor1.to_i, :<=, THROTTLE_MIN
+  assert_operator motor2.to_i, :<=, THROTTLE_MIN
+  assert_operator motor3.to_i, :<=, THROTTLE_MIN
+  assert_operator motor4.to_i, :<=, THROTTLE_MIN
+end
+
+def check_led(on)
+  led = File.read("virt-quad-fifos/mio7-led")
+  assert_equal(led.to_i, on)
+end
+
+script_dir = File.expand_path(File.dirname(__FILE__))
+bin_dir = script_dir + "/../../bin/"
+Dir.chdir(bin_dir)
+
+# Start virtual quad
+quad = Process.spawn("./virt-quad")
+
+sleep 1
+
+#################
+#  Begin Tests
+#################
+
+begin
+
+  # Check that motors are off at startup
+  check_motors_are_off
+
+  # Check that LED is off at startup
+  check_led(0)
+
+  # Check that increasing the throttle does nothing to motors
+  # (because gear is still off)
+  for i in (THROTTLE_MIN..THROTTLE_MAX).step(1000)
+    File.write("virt-quad-fifos/pwm-input-throttle", i)
+    check_motors_are_off
+    sleep 0.005
+  end
+
+  # Check that flipping gear to 1 while throttle is high does nothing
+  # (motors should still be off, LED should still be off)
+  File.write("virt-quad-fifos/pwm-input-gear", GEAR_ON)
+  sleep 0.015
+  check_motors_are_off
+  i = THROTTLE_MAX
+  while i > THROTTLE_MID
+    i -= 1000
+    File.write("virt-quad-fifos/pwm-input-throttle", i)
+    check_motors_are_off
+    check_led 0
+    sleep 0.005
+  end
+
+  # (swtich GEAR back to off and bring throttle off)
+  File.write("virt-quad-fifos/pwm-input-gear", GEAR_OFF)
+  File.write("virt-quad-fifos/pwm-input-throttle", THROTTLE_MIN)
+
+  # Check that the LED turns on when gear is flipped on
+  # (motors should still be off because our throttle is low)
+  File.write("virt-quad-fifos/pwm-input-gear", GEAR_ON)
+  sleep 0.010
+  check_led 1
+  check_motors_are_off
+
+  # Check that motors turn on
+  File.write("virt-quad-fifos/pwm-input-throttle", THROTTLE_MID)
+  motor1 = []
+  motor2 = []
+  motor3 = []
+  motor4 = []
+  for i in 0..100
+    motor1.push(File.read("virt-quad-fifos/pwm-output-motor1").to_i)
+    motor2.push(File.read("virt-quad-fifos/pwm-output-motor2").to_i)
+    motor3.push(File.read("virt-quad-fifos/pwm-output-motor3").to_i)
+    motor4.push(File.read("virt-quad-fifos/pwm-output-motor4").to_i)
+    sleep 0.005
+  end
+  average1 = motor1.inject(:+).to_f / motor1.size
+  average2 = motor2.inject(:+).to_f / motor2.size
+  average3 = motor3.inject(:+).to_f / motor3.size
+  average4 = motor4.inject(:+).to_f / motor4.size
+  average = (average1 + average2 + average3 + average4)/4
+  puts average1, average2, average3, average4, "(#{average})"
+  assert average.between?(THROTTLE_EIGHTH, MOTOR_MAX)
+
+  # Check that gear switch kills the motors
+  # (and that light goes off)
+  File.write("virt-quad-fifos/pwm-input-gear", GEAR_OFF)
+  sleep 0.015
+  check_motors_are_off
+  check_led 0
+
+  # (Bring the RC throttle back down)
+  File.write("virt-quad-fifos/pwm-input-throttle", THROTTLE_MIN)
+
+  # Check that we can resume flight
+  File.write("virt-quad-fifos/pwm-input-gear", GEAR_ON)
+  sleep 0.015
+  check_led 1
+
+  sleep 1
+  puts "All safety checks passed."
+
+ensure
+
+  Process.kill(9, quad)
+
+end
+
diff --git a/quad/src/quad_app/controllers.h b/quad/src/quad_app/controllers.h
index 33b6237957b49d4253c3fec7c26b917cdfe3f74e..1e12c7590e5fe661b81a1bca31b7398da3e457d3 100644
--- a/quad/src/quad_app/controllers.h
+++ b/quad/src/quad_app/controllers.h
@@ -102,6 +102,9 @@
 #define min 100000
 #define max 200000
 
+#define MOTOR_MIN 100000
+#define MOTOR_MAX 200000
+
 void filter_PWMs(int* mixer);
 void PWMS_to_Aero(int* PWMs, int* aero); // <= javey: unused
 void Aero_to_PWMS(int* PWMs, int* aero);
diff --git a/quad/src/quad_app/initialize_components.c b/quad/src/quad_app/initialize_components.c
index 858c0e9ffb80525c72c04726698a98eac45b518f..57d3e2b96eaef8951425652c4fb61ac5ec4b1529 100644
--- a/quad/src/quad_app/initialize_components.c
+++ b/quad/src/quad_app/initialize_components.c
@@ -7,6 +7,7 @@
  
 #include "initialize_components.h"
 #include "communication.h"
+#include "controllers.h"
 #include "sensor.h"
 #include "iic_utils.h"
 
@@ -83,6 +84,12 @@ int init_structs(modular_structs_t *structs) {
   struct PWMOutputDriver *pwm_outputs = &structs->hardware_struct.pwm_outputs;
   if (pwm_outputs->reset(pwm_outputs)) return -1;
 
+  // Set motor outputs to off
+  int i;
+  for (i = 0; i < 4; i += 1) {
+    pwm_outputs->write(pwm_outputs, i, MOTOR_MIN);
+  }
+
   // Initialize sensors
 
   //manual flight mode
diff --git a/quad/src/quad_app/util.c b/quad/src/quad_app/util.c
index c021fef37cc1daeb6adafe20a75b25e1ec99e558..2f1512a0e407385648f442083b2e5152b3fa8dac 100644
--- a/quad/src/quad_app/util.c
+++ b/quad/src/quad_app/util.c
@@ -68,10 +68,10 @@ int read_flap(int flap)
  * Turns off the motors
  */
 void kill_motors(struct PWMOutputDriver *pwm_outputs) {
-  pwm_outputs->write(pwm_outputs, 0, 0);
-  pwm_outputs->write(pwm_outputs, 1, 0);
-  pwm_outputs->write(pwm_outputs, 2, 0);
-  pwm_outputs->write(pwm_outputs, 3, 0);
+  pwm_outputs->write(pwm_outputs, 0, MOTOR_MIN);
+  pwm_outputs->write(pwm_outputs, 1, MOTOR_MIN);
+  pwm_outputs->write(pwm_outputs, 2, MOTOR_MIN);
+  pwm_outputs->write(pwm_outputs, 3, MOTOR_MIN);
 }
 
 int build_int(u8 *buff) {
diff --git a/quad/src/virt_quad/Makefile b/quad/src/virt_quad/Makefile
index 008468333bac4f1e9bc5387671111648b48f5a24..acf6d27cbbea07d639fadfda691a01d6edd89fb7 100644
--- a/quad/src/virt_quad/Makefile
+++ b/quad/src/virt_quad/Makefile
@@ -1,7 +1,7 @@
 TOP=../..
 
 NAME = virt-quad
-REQLIBS = -Wl,--whole-archive -lquad_app -Wl,--no-whole-archive -lcommands -lgraph_blocks -lcomputation_graph -lm
+REQLIBS = -Wl,--whole-archive -lquad_app -Wl,--no-whole-archive -lcommands -lgraph_blocks -lcomputation_graph -lm -lpthread
 
 include $(TOP)/executable.mk
 
diff --git a/quad/src/virt_quad/hw_impl_unix_mio7_led.c b/quad/src/virt_quad/hw_impl_unix_mio7_led.c
index f96fda029e0c0a9664be8bd81e76a93af606e7ba..5215423781ca1ebdd0366e95ac4b824c8ec09ce4 100644
--- a/quad/src/virt_quad/hw_impl_unix_mio7_led.c
+++ b/quad/src/virt_quad/hw_impl_unix_mio7_led.c
@@ -1,8 +1,30 @@
 #include "hw_impl_unix.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <pthread.h>
+
+void * output_cached_led();
 
 int on;
+static char *led_fifo_name;
+pthread_t worker;
 
 int unix_mio7_led_reset(struct LEDDriver *self) {
+  led_fifo_name = VIRT_QUAD_FIFOS_DIR "/mio7-led";
+
+  mkdir(VIRT_QUAD_FIFOS_DIR, 0777);
+  int i;
+  for (i = 0; i < 4; i += 1) {
+    unlink(led_fifo_name);
+    mkfifo(led_fifo_name, 0666);
+  }
+
+  // Start up worker thread whose job is to update the caches
+  pthread_create(&worker, 0, output_cached_led, NULL);
   return 0;
 }
 
@@ -21,3 +43,16 @@ int unix_mio7_led_turn_off(struct LEDDriver *self) {
   }
   return 0;
 }
+
+void * output_cached_led() {
+  char buff[16];
+  while (1) {
+    usleep(500); // don't spam the reader
+    int fifo = open(led_fifo_name, O_WRONLY | O_NONBLOCK);
+    if (fifo >= 0) {
+      sprintf(buff, "%d ", on);
+      write(fifo, buff, strlen(buff));
+    }
+    close(fifo);
+  }
+}
diff --git a/quad/src/virt_quad/hw_impl_unix_pwm_input.c b/quad/src/virt_quad/hw_impl_unix_pwm_input.c
index c49e466ce0470f60fda3bff49874e298e606380b..75805653cc400e8c0707e7d4955fb22a119ef471 100644
--- a/quad/src/virt_quad/hw_impl_unix_pwm_input.c
+++ b/quad/src/virt_quad/hw_impl_unix_pwm_input.c
@@ -3,29 +3,36 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <pthread.h>
 
-static char *pwms[6];
+void * update_input_caches();
+
+static char *input_names[6];
 static int fifos[6];
 static unsigned long cache[6];
+pthread_t worker;
 
 int unix_pwm_input_reset(struct PWMInputDriver *self) {
-  pwms[0] = "pwm-input-throttle";
-  pwms[1] = "pwm-input-roll";
-  pwms[2] = "pwm-input-pitch";
-  pwms[3] = "pwm-input-yaw";
-  pwms[4] = "pwm-input-gear";
-  pwms[5] = "pwm-input-flap";
+  input_names[0] = "pwm-input-throttle";
+  input_names[1] = "pwm-input-roll";
+  input_names[2] = "pwm-input-pitch";
+  input_names[3] = "pwm-input-yaw";
+  input_names[4] = "pwm-input-gear";
+  input_names[5] = "pwm-input-flap";
 
   mkdir(VIRT_QUAD_FIFOS_DIR, 0777);
   int i;
   for (i = 0; i < 6; i += 1) {
-    unlink(pwms[i]);
+    unlink(input_names[i]);
     char fifoname[64];
-    sprintf(fifoname, "%s/%s", VIRT_QUAD_FIFOS_DIR, pwms[i]);
+    sprintf(fifoname, "%s/%s", VIRT_QUAD_FIFOS_DIR, input_names[i]);
     mkfifo(fifoname, 0666);
     fifos[i] = open(fifoname, O_RDONLY | O_NONBLOCK);
   }
 
+  // Start up worker thread whose job is to update the caches
+  pthread_create(&worker, 0, update_input_caches, NULL);
+
   cache[0] = THROTTLE_MIN;
   cache[1] = ROLL_CENTER;
   cache[2] = PITCH_CENTER;
@@ -34,7 +41,7 @@ int unix_pwm_input_reset(struct PWMInputDriver *self) {
   cache[5] = FLAP_1;
 
   for (i = 0; i < 6; i += 1) {
-    printf("%s: %d\n", pwms[i], cache[i]);
+    printf("%s: %d\n", input_names[i], cache[i]);
   }
 
   return 0;
@@ -44,17 +51,25 @@ int unix_pwm_input_read(struct PWMInputDriver *self,
                         unsigned int channel,
                         unsigned long *pulse_width_us) {
 
+  *pulse_width_us = cache[channel];
+  return 0;
+}
+
+void * update_input_caches() {
   char buff[16];
-  int bytes_read = read(fifos[channel], buff, 15);
-  if (bytes_read >= 6) {
-    buff[bytes_read] = '\0';
-    unsigned long val = strtoll(buff, NULL, 10);
-    if (val < max && val > min) {
-      cache[channel] = val;
-      printf("%s: %d\n", pwms[channel], val);
+  int i;
+  while (1) {
+    for (i = 0; i < 6; i += 1) {
+      int bytes_read = read(fifos[i], buff, 15);
+      if (bytes_read >= 6) {
+        buff[bytes_read] = '\0';
+        unsigned long val = strtoll(buff, NULL, 10);
+        if (val < max && val > min) {
+          cache[i] = val;
+          printf("%s: %d\n", input_names[i], val);
+        }
+      }
     }
   }
-
-  *pulse_width_us = cache[channel];
-  return 0;
+  return NULL;
 }
diff --git a/quad/src/virt_quad/hw_impl_unix_pwm_output.c b/quad/src/virt_quad/hw_impl_unix_pwm_output.c
index 94ad65f0fb860260c4206bd4210e5913af9f09f1..03f9815cfb3e9dfc1673e7fcda021c9e2b79e44d 100644
--- a/quad/src/virt_quad/hw_impl_unix_pwm_output.c
+++ b/quad/src/virt_quad/hw_impl_unix_pwm_output.c
@@ -3,8 +3,14 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+
+void * output_caches();
 
 static char *output_pwms[4];
+static unsigned long cache[4];
+pthread_t worker;
 
 int unix_pwm_output_reset(struct PWMOutputDriver *self) {
   output_pwms[0] = VIRT_QUAD_FIFOS_DIR "/pwm-output-motor1";
@@ -19,18 +25,35 @@ int unix_pwm_output_reset(struct PWMOutputDriver *self) {
     mkfifo(output_pwms[i], 0666);
   }
 
+  // Start up worker thread whose job is to update the caches
+  pthread_create(&worker, 0, output_caches, NULL);
+
   return 0;
 }
 
 int unix_pwm_output_write(struct PWMOutputDriver *self,
                           unsigned int channel,
                           unsigned long pulse_width_us) {
-  char buff[16];
-  int fifo = open(output_pwms[channel], O_WRONLY | O_NONBLOCK);
-  if (fifo >= 0) {
-    sprintf(buff, "%d\0", pulse_width_us);
-    write(fifo, buff, strlen(buff));
+  if (cache[channel] != pulse_width_us) {
+    printf("%s: %ld\n", output_pwms[channel], pulse_width_us);
   }
-  close(fifo);
+  cache[channel] = pulse_width_us;
   return 0;
 }
+
+void * output_caches() {
+  char buff[16];
+  int i;
+  while (1) {
+    usleep(500); // don't spam the reader
+    for (i = 0; i < 4; i += 1) {
+      int fifo = open(output_pwms[i], O_WRONLY | O_NONBLOCK);
+      if (fifo >= 0) {
+        sprintf(buff, " %ld ", cache[i]);
+        write(fifo, buff, strlen(buff));
+      }
+      close(fifo);
+    }
+  }
+  return NULL;
+}