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:
Short videos below:
-
Nice, thanks for sharing @blong!
Looking forward to see pictures of your multi sensor/actuator.
-
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
-
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...
-
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
-
@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).
-
-
This one is very cool, especially the slow shutters movement...
-
Very Cool -- Thanks