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: