Plantation Shutters - Auto\Lux\ Motorized 1.4b1



  • I typically lurk on these sites, but I conducted so much research on this project that I felt that I should share and contribute back to this excellent platform by @hek. * Please take it easy on me, as I am a former website designer and a current hobby programmer. * This was not an easy project for me and it took plenty of time, research and new tools! This project is certainly not for the faint of heart, but the WAF is outstanding once completed! I will update this post with video/pictures at a later date. Special thanks to @naveen for his excellent examples that got me started (and finished). I would truly appreciate any updates or modifications that you can provide to make this code more efficient!

    See code below:

    /*
    Description: Sensor to control 2 or more servos and light sensor.  Manual and lux sensor feedback changes shutter position.
    Sensor type: Adafruit TSL2561 luminosity sensor 
    Servo type:  Hitec HS-5645MG High Torque, Metal Gear Digital Sport Servo
    Controllers: Arduino Nano v3.1 / Veralite UI5
    
    Websites:    https://learn.adafruit.com/tsl2561
    			 http://homeawesomation.wordpress.com/2013/02/26/automated-window-blinds-with-arduino/
    			 https://github.com/ssshake/arduino/blob/master/autoblinds-0.5.ino
    			 http://hitecrcd.com/products/servos/sport-servos/digital-sport-servos/hs-5645mg-high-torque-metal-gear-servo/product
    		  
    Libraries: Adafruit sensor libraries are required for this sketch at the above URL.
    
    Acknowledgements: 
    
    Sketch is adapted from multiple resources and is shared in order to minimize your learning curve.
    Thanks to everyone who has contibuted directly and indirectly!  Please feel free to use and distribute this sketch at your own risk! 
    BPL 7/31/2014
    */
    
    #include <SPI.h>
    #include <Wire.h>
    #include <Servo.h>
    #include <MySensor.h>
    #include <Adafruit_Sensor.h>
    #include <Adafruit_TSL2561_U.h>
    
    #define servos 2
    #define servo_min 900 // Fine tune your servos min. 900-2100
    #define servo_max 2100  // Fine tune your servos max. 900-2100
    #define detach_delay 1500 // Tune this to let your movement finish before detaching the servo
    #define child_id_light servos
    #define servo_west 0
    #define servo_west_pin 6
    #define servo_east 1
    #define servo_east_pin 5
    
    MySensor gw;
    MyMessage servo_msg;
    MyMessage lux_msg(child_id_light, V_LIGHT_LEVEL);
    
    Adafruit_TSL2561_Unified lightsensor = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345);
    Servo myservo[servos] = Servo();
    
    // Configuration Settings
    int servoPin[servos] = {servo_east_pin, servo_west_pin};  // Input pin for servo
    int lastPos = 0;  // variable to store the servo position
    int detension_val = 12;
    int spd[10] = {5, 10, 15, 20, 25, 30, 35, 40, 45, 50};     // how fast should the servo move? 50 is quiet
    bool attachedServo = false;
    bool auto_lux_shutters = true;
    
    uint16_t lux;
    uint16_t lastlux;
    int state = 0; //Keep track of state so we don't send signal to the servo without reason
    int prevstate = 0; //Keeps history of prior state
    int servo_time_base = 0;
    int lux_read_delay = 25000;
    int lux_time_base = 0;
    
    /**************************************************************************/
    /*   Configures all sensors                                               */
    /**************************************************************************/
    void setup()
    {
      gw.begin(incomingMessage);                  // Attach method for incoming messages
      gw.sendSketchInfo("Shutters", "2.0");       // Send Sketch information
      gw.present(child_id_light, S_LIGHT_LEVEL);  // Create child device (Lux sensor)
    
      /* Initialise the sensor */
      if (lightsensor.begin()) {
    	/*
    	Best resolution time is also slowest (402ms = 16-bit data)
    	lightsensor.setIntegrationTime(TSL2561_INTEGRATIONTIME_13MS);   /* fast but low resolution
    	lightsensor.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS);  /* 16-bit data but slowest conversions
    	*/
    	lightsensor.enableAutoRange(true);                              // Auto-gain; switches automatically between 1x and 16x
    	lightsensor.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS);  // medium resolution and speed
      }
      else {
    	Serial.println("Sensor not working!?");
      }
    
      for (int servo = 0; servo <= servos - 1; servo++) {
    	MyMessage servo_msg(servo, V_DIMMER);
    	gw.present(servo, S_COVER);     // Create child devices
    	gw.request(servo, V_DIMMER);    // Request previous state
    	delay(500);
      }
    }
    
    void loop()
    {
      gw.process();
    
      sensors_event_t event;           // Get a new sensor event
      lightsensor.getEvent(&event);
      uint16_t lux = event.light;      // Get Lux value;
    
      if (lux != lastlux) {
    	int rotate_value;
    	Serial.print("[Lux auto-sense routine] ");
    
    	/*
    	Illumination Examples
    	from https://learn.adafruit.com/photocells/measuring-light
    
    	0.002 lux    Moonless clear night sky
    	0.2 lux	 Design minimum for emergency lighting (AS2293).
    	0.27 - 1 lux Full moon on a clear night
    	3.4 lux	 Dark limit of civil twilight under a clear sky
    	*/
    	if (lux >= 0 && lux < 50) {
    	  //      Serial.print("Night or very low light");
    	  rotate_value = 2100;
    	  state = 1;
    	}
    
    	/*
    	50 lux	 Family living room
    	80 lux	 Hallway/toilet
    	*/
    	else if ( lux >= 50 && lux < 100) {
    	  Serial.print("Dusk or Family living room");
    	  rotate_value = 1785;
    	  state = 2;
    	}
    
    	/*
    	100 lux        Very dark overcast day
    	300 - 500 lux  Sunrise or sunset on a clear day. Well-lit office area.
    	*/
    	else if (lux >= 100 && lux < 500) {
    	  Serial.print("Very dark overcast day");
    	  rotate_value = 1500;
    	  state = 3;
    	}
    
    	/*
    	1,000 lux      Overcast day; typical TV studio lighting
    	*/
    	else if (lux >= 500 && lux < 10000) {
    	  Serial.print("Overcast Day or Indoor Studio");
    	  rotate_value = 1155;
    	  state = 4;
    	}
    
    	/*
    	10,000 - 25,000 lux    Full daylight (not direct sun)
    	*/
    	else if (lux >= 10000 && lux < 25000) {
    	  Serial.print("Indirect Sunlight");
    	  rotate_value = 1365;
    	  state = 5;
    	}
    
    	/*
    	32,000 - 130,000 lux    Direct sunlight
    	*/
    	else if (lux >= 25000 && lux < 40000) {
    	  Serial.print("Full Direct Sunlight");
    	  rotate_value = 1575;
    	  state = 6;
    	}
    
    	Serial.println("");
    
    	// Execute servo controls when state changes
    	if (state != prevstate && auto_lux_shutters) {
    	  luxAutoUpdate(rotate_value);
    	  gw.send(lux_msg.set(lux));
    	  lux_time_base = millis();
    	}
      }
    
      //  Serial.print("Update lux value: "); Serial.println(lux);
    
      prevstate = state; //Save state so we can compare it again
      lastlux = lux;
    
      if (attachedServo && millis() - servo_time_base > detach_delay) {
    	for (int servo = 0; servo <= servos - 1; servo++) {
    	  myservo[servo].detach();
    
    	  if (millis() - lux_time_base > lux_read_delay) {
    		auto_lux_shutters = true;
    	  }
    	  else {
    		auto_lux_shutters = false;
    	  }
    
    	  attachedServo = false;
    	  Serial.println("Servos detached");
    	}
      }
    }
    
    long detension(int delay_time, int det_val, bool isClockwiseRotation) {
      // Reduce Stress on servo
      int destination;
    
      if (isClockwiseRotation) {
    	// Serial.println("    CW rotation >>>");
    	destination = lastPos - det_val;
      } else if (!isClockwiseRotation) {
    	// Serial.println("    CCW rotation <<<");
    	destination = lastPos + det_val;
      }
    
      if (destination != lastPos) {
    	delay(delay_time);
    	myservo[servo_west].writeMicroseconds(destination);
    	myservo[servo_east].writeMicroseconds(destination);
      }
      else {
    	Serial.println("New destination is same as last position.");
      }
    
      // Report completed time
      return millis();
    }
    
    void incomingMessage(const MyMessage & message) {
      // Disable lux sensor
      Serial.println("[incomingMessage]");
      auto_lux_shutters = false;
    
      int servoVal = map(message.getInt(), 0, 100, servo_min, servo_max);
      int pluginVal = servoVal;
      bool isClockwiseRotation;
    
      for (int servo = 0; servo <= servos - 1; servo++) {
    	if (!myservo[servo].attached()) {
    	  myservo[servo].attach(servoPin[servo], servo_min, servo_max);
    	  lastPos = myservo[servo].readMicroseconds();
    	}
    	attachedServo = true;
      }
    
      if (message.type == V_DIMMER) {
    	if (servoVal <= lastPos) {
    	  for (int pos = lastPos; pos >= servoVal && pos >= servo_min; pos -= 1)
    	  {
    		myservo[servo_west].writeMicroseconds(pos);
    		myservo[servo_east].writeMicroseconds(pos);
    		delay(spd[4]); lastPos = pos;
    	  }
    	  pluginVal = map(servoVal, servo_min, servo_max, 0, 100);
    	  isClockwiseRotation = false;
    	}
    	else if (servoVal > lastPos) {
    	  for (int pos = lastPos; pos <= servoVal && pos <= servo_max; pos += 1)
    	  {
    		myservo[servo_west].writeMicroseconds(pos);
    		myservo[servo_east].writeMicroseconds(pos);
    		delay(spd[3]); lastPos = pos;
    	  }
    	  pluginVal = map(servoVal, servo_min, servo_max, 0, 100);
    	  isClockwiseRotation = true;
    	}
    
    	// Reduce Stress on servo
    	servo_time_base = detension(500, detension_val, isClockwiseRotation);
      }
    
      else if (message.type == V_DOWN) {
    	Serial.println("Servo down command");
    	for (int pos = lastPos; pos <= servoVal && pos <= servo_max; pos += 1)
    	{
    	  myservo[servo_west].writeMicroseconds(servo_max);
    	  myservo[servo_east].writeMicroseconds(servo_max);
    	  delay(spd[2]); lastPos = pos;
    	}
    	isClockwiseRotation == true;
    	pluginVal = map(servo_max, servo_min, servo_max, 0, 100);
    
    	// Reduce Stress on servo
    	servo_time_base = detension(200, 5, isClockwiseRotation);
    
      } else if (message.type == V_UP) {
    	Serial.println("Servo up command");
    	for (int pos = lastPos; pos >= servoVal && pos >= servo_min; pos -= 1)
    	{
    	  myservo[servo_west].writeMicroseconds(servo_max);
    	  myservo[servo_east].writeMicroseconds(servo_max);
    	  delay(spd[1]); lastPos = pos;
    	}
    	isClockwiseRotation == false;
    	pluginVal = map(servo_min, servo_min, servo_max, 0, 100);
    
    	// Reduce Stress on servo
    	servo_time_base = detension(250, 5, isClockwiseRotation);
      } else if (message.type == V_STOP) {
    	Serial.println("Servo stop command");
    	for (int servo = 0; servo <= servos - 1; servo++) {
    	  myservo[servo].detach();
    	  attachedServo = false;
    	}
      }
    
      for (int servo = 0; servo <= servos - 1; servo++) {
    	MyMessage servo_msg(servo, V_DIMMER);
    	gw.send(servo_msg.set(pluginVal));
      }
    }
    
    void luxAutoUpdate(int servoVal) {
      Serial.println("[luxAutoUpdate]");
      int pluginVal = map(servoVal, servo_min, servo_max, 0, 100);
      bool isClockwiseRotation;
    
      if (auto_lux_shutters) {
    	for (int servo = 0; servo <= servos - 1; servo++) {
    	  if (!myservo[servo].attached()) {
    		myservo[servo].attach(servoPin[servo], servo_min, servo_max);
    		attachedServo = true;
    	  }
    	  lastPos = myservo[servo].readMicroseconds();
    	}
    
    	if (servoVal <= lastPos) {
    	  for (int pos = lastPos; pos >= servoVal && pos >= servo_min; pos -= 1)
    	  {
    		myservo[servo_west].writeMicroseconds(pos);
    		myservo[servo_east].writeMicroseconds(pos);
    		delay(spd[3]); lastPos = pos;
    	  }
    	  isClockwiseRotation = true;
    	}
    	else if (servoVal > lastPos) {
    	  for (int pos = lastPos; pos <= servoVal && pos <= servo_max; pos += 1)
    	  {
    		myservo[servo_west].writeMicroseconds(pos);
    		myservo[servo_east].writeMicroseconds(pos);
    		delay(spd[3]); lastPos = pos;
    	  }
    	  isClockwiseRotation = false;
    	}
    
    	for (int servo = 0; servo <= servos - 1; servo++) {
    	  MyMessage servo_msg(servo, V_DIMMER);
    	  gw.send(servo_msg.set(pluginVal));
    	}
    
    	// Reduce Stress on servo
    	servo_time_base = detension(350, detension_val, isClockwiseRotation);
      }
      else {
    	Serial.print("Auto-shutter sensor is disabled at this time.");
      }
    }
    

    Pictures of project setup and Vera screenshot:

    servo disguised.jpg
    Nano Controller.jpg
    Lux sensor contolling automated angles.jpg
    inside view.jpg
    vera screenshot.jpg

    Short videos below:

    link text

    link text


  • Admin

    Nice, thanks for sharing @blong!

    Looking forward to see pictures of your multi sensor/actuator.


  • Mod

    Nice work! Especially the part where messages from the gateway are interpreted will be helpful to some people as we get a lot of questions on this :+1:



  • @blong

    Hey! Cool to see you translated your project over to integrate with Vera - you were using a self contained sensor/actuator before right?



  • @naveen No, this was a build from the ground up. From routering the servos into the shutters to compiling the code. The project has been completed for a full day now and works great. Thanks for your encouragement and code guidance...



  • @blong

    Nice! Please do post videos of its operation when you get a chance!



  • @naveen @hek pics posted, but video wouldn't upload in .mov format... Also, I plan on building a 3D case for the controller. I think I am just going to enjoy my work for now though :)


  • Admin

    @blong said:

    @hek pics posted, but video wouldn't upload in .mov format.

    Post movie to youtube and put the link to your movie here (it will be embedded).



  • Videos are now posted! Forgive the shaky hand! @hek @naveen


  • Plugin Developer

    This one is very cool, especially the slow shutters movement...


  • Hero Member

    Very Cool -- Thanks


Log in to reply
 

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.