Curtain Control Node.



  • My First version of curtain control Node is ready, installed and tested. Some work is pending, which I'll complete on my weekend.
    Edit: Code for the Node:Curtain Node
    Curtain Control Node.

    Please share your feedback so that I can improvise.


  • Admin

    What type of motor are you using?



  • Its a Geared DC Motor with Voltage Rating of 6-12V and 100 RPM. I am using a L239 to control the motor.
    alt text


  • Hero Member

    Looks good. I am curious to see the details... jealous for the speed :runner: your curtain is moving.

    I have been working on a similar project last week. The mechanics and ´looks´ are the most difficult part for me.

    For now I have used a ´tuned´ cheap & small stepper which makes it slooow but effective ;-) The rail and mounting is standard IKEA. The pro-mini has the jModule attached. Works like a charm (/snail)!

    0_1457980545567_upload-c50fc1e9-f23e-458d-a812-62ad4bf478e0
    0_1457981153586_upload-c7064209-3900-4e4b-b335-8c88721367c0



  • @AWI Thanks. Why dont you increase the pulley size (larger pulley) that will increase the speed.

    I found driving stepper motor to be bit more complicated than driving a DC motor. I picked the Geared motor basis the calculation using this calculator.

    Since without rails pulling the curtains on a metal pipe needed more torque. Thus I chose 100 RPM to be a suitable fit.

    Will share the Schematics and PCB layout files soon. I have already added the sketch for reference. Since I was testing I did not put the circuit in any housing. But will give some shape soon.

    Also will be adding 3 buttons for Manual Control.



  • @Suresh-Mali

    DC Motor, very interesting!

    Do you have end stops? The sketch doesn't open for some reason...

    Cheers



  • @barduino I tried downloading the code, it works. Anyways I am pasting the code below.

    For end stops I have not yet decided. I had some Ideas of using Hall effect sensor on both ends and a small piece of magnet in the string to detect ends or Maybe use stop switches. Not sure but will have to try both and see which works the best.

    I am yet to clean the code I have some debugging serial prints in code which will remove once finalized.

    #include <SPI.h>
    #include <MySensor.h>
    
    
    int motor_forward = 5;
    int motor_reverse = 6;
    
    int LDRinput = 1;     //analog pin to which LDR is connected, here we set it to 0 so it means A0
    int LDRValue = 0;      //that�s a variable to store LDR values
    int light_sensitivity = 500;    //This is the approx value of light surrounding your LDR
    
    int stopSwLeft = 4;
    int stopSwRight = 3;
    int stopSwNow = 2;
    
    int CHILD_CURTAIN_ID =1;
    int lastState;
    MySensor gw;
    MyMessage msg_S_COVER_U(CHILD_CURTAIN_ID, V_UP);
    MyMessage msg_S_COVER_D(CHILD_CURTAIN_ID, V_DOWN);
    MyMessage msg_S_COVER_S(CHILD_CURTAIN_ID, V_STOP);
    //MyMessage msg(CHILD_CURTAIN_ID,V_UP);
    // the setup routine runs once when you press reset:
    void setup()
    {
    	Serial.begin(115200);
    	// initialize the digital pin as an output for L239D.
    	pinMode(motor_forward, OUTPUT);
    	pinMode(motor_reverse, OUTPUT);
    	
    	// initialize the digital pin as an output for Stop Switches.
    	pinMode(stopSwLeft, INPUT_PULLUP);
    	pinMode(stopSwRight, INPUT_PULLUP);
    	pinMode(stopSwNow, INPUT_PULLUP);
    	
    	gw.begin(incomingMessage, AUTO, true);
    	// Send the sketch version information to the gateway and Controller
    	gw.sendSketchInfo("Window_Curtain", "1.0");
    	gw.present(CHILD_CURTAIN_ID, S_COVER);
    	
    }
    
    void loop(){
    	
    	gw.process();
    	
    	}
    void cover(int coverVal){
    
    	//int coverVal = gw.loadState(CHILD_CURTAIN_ID);
    
    	Serial.print("Cover is : ");
    	lastState = coverVal;
    	switch (coverVal) {
    	case 0:
    		Serial.println("Opening");
    			while (lastState == coverVal)
    			{
    				m_left();
    				gw.process();
    				checkHWInnputs();
    				coverVal = gw.loadState(CHILD_CURTAIN_ID);
    			}
    			gw.send(msg_S_COVER_U.set(V_UP));
    		break;
    	case 1:
    		Serial.println("Closing");
    			while (lastState == coverVal)
    			{
    				m_right();
    				gw.process();
    				checkHWInnputs();
    				coverVal = gw.loadState(CHILD_CURTAIN_ID);
    			}
    			gw.send(msg_S_COVER_D.set(V_DOWN));
    		break;
    	case 2:
    		Serial.println("Idle");
    			while (lastState == coverVal)
    				{
    					m_stop();
    					gw.process();
    					checkHWInnputs();
    					coverVal = gw.loadState(CHILD_CURTAIN_ID);
    				}
    			gw.send(msg_S_COVER_S.set(V_STOP));
    			break;
    	}
    	return;
    
    }
    void incomingMessage(const MyMessage &message) {
    	// We only expect one type of message from controller. But we better check anyway.
    	Serial.println("recieved incomming message");
    	switch (message.type) {
    	case V_UP:
    		gw.saveState(CHILD_CURTAIN_ID, 0);
    		Serial.print("Incoming change for ID_S_COVER:");
    		Serial.print(message.sensor);
    		Serial.print(", New status: ");
    		Serial.println("V_UP");
    		cover(gw.loadState(CHILD_CURTAIN_ID));
    		Serial.print("Done cover procedure");
    		//m_right();
    		break;
    
    	case V_DOWN:
    		gw.saveState(CHILD_CURTAIN_ID, 1);
    		Serial.print("Incoming change for ID_S_COVER:");
    		Serial.print(message.sensor);
    		Serial.print(", New status: ");
    		Serial.println("V_DOWN");
    		cover(gw.loadState(CHILD_CURTAIN_ID));
    		Serial.print("Done cover procedure");
    		//m_left();
    		break;
    
    	case V_STOP:
    		gw.saveState(CHILD_CURTAIN_ID, 2);
    		Serial.print("Incoming change for ID_S_COVER:");
    		Serial.print(message.sensor);
    		Serial.print(", New status: ");
    		Serial.println("V_STOP");
    		cover(gw.loadState(CHILD_CURTAIN_ID));
    		Serial.print("Done cover procedure");
    		//m_stop();
    		break;
    	}
    	Serial.print("exiting incoming message");
    	return;
    	
    }
    // the loop routine runs over and over again forever:
    void m_right() {
    	digitalWrite(motor_forward, HIGH); //terminal D1 will be HIGH
    	digitalWrite(motor_reverse, LOW); //terminal D2 will be LOW
    	Serial.print("Set For:High and Rev: Low");
    	exit;
    }
    
    void m_left() {
    	digitalWrite(motor_forward, LOW); //terminal D1 will be LOW
    	digitalWrite(motor_reverse, HIGH); //terminal D2 will be HIGH
    	Serial.print("Set For:Low and Rev: High");
    	exit;
    }
    
    void m_stop() {
    	digitalWrite(motor_forward, LOW); //terminal D1 will be LOW
    	digitalWrite(motor_reverse, LOW); //terminal D2 will be HIGH
    	Serial.print("Set For:Low and Rev: Low");
    	exit;
    }
    
    void checkHWInnputs()
    	{
    		if (digitalRead(stopSwNow) == LOW)
    		{
    			Serial.println("Detected stop button push. Stopping");
    			cover(2);
    			Serial.println("Saved stop state to EEPROM");
    		}
    		if (digitalRead(stopSwLeft) == LOW)
    		{
    			Serial.println("Detected stop button push. Stopping");
    			cover(1);
    			Serial.println("Saved stop state to EEPROM");
    		}
    		if (digitalRead(stopSwRight) == LOW)
    		{
    			Serial.println("Detected stop button push. Stopping");
    			cover(0);
    			Serial.println("Saved stop state to EEPROM");
    		}
    	}
    
    


  • Sweet!
    My girl has put this on my todolist...
    But still have to get started, this is some nice reference for my new porject!



  • @franzelare Great. Good Luck with the build.

    I'll be posting my HW design and Code as part of Contest on openhardware.io



  • I have published my project with all the files.
    https://www.openhardware.io/view/45/Curtain-Control-Node



  • @Suresh-Mali

    I'm just curious, maybe its the arduino IDE version or something.

    I'm using 1.6.4 and I get this:

    0_1458129604092_upload-1364c257-d6a3-46d0-bdec-ff24cf9d2b41

    However opening with a text editor is fine:

    0_1458129652911_upload-e1883418-162b-4aba-bacf-d3e85436cca7

    Cheers



  • @barduino Seems like the IDE. Open any example MySensors Sketch. Delete the contents from sketch and copy paste from my sketch. I guess that should work.

    Are you able to compile any other sketch in Arduino?



  • @AWI Are you able to share the sketch you used with the UL2003 driver board? I have tried to combine the MySensors servo sketch with other arduino only sketches utilizing the UL2003, but without luck. Thanks.



  • @patchmaster I used the relay sketch and modified it to my needs. Sorry but I dont have any sketch for ULN2003. :disappointed:


  • Hero Member

    @patchmaster This is my current curtain control sketch. I work with the development branch, so the syntax can be a little different.

    The Accelstepper library gives you all the abilities to slow start/stop with numerous kinds of steppers.
    My sketch includes a calibration function (stored in EEPROM) so the you don't need end stop detection (a stepper knows where it is)

    /*
     PROJECT: MY Sensors curtain controller
     PROGRAMMER: AWI
     DATE: march 11, 2016
     FILE: AWI stepper1.ino
     LICENSE: Public domain
    
     Hardware: ATMega328p board w/ NRF24l01
    	and MySensors 2.0 (Development)
    		
    Special:
    	uses AccelStepper library
    	
    Summary:
    	Curtain control with stepper motor. 
    	Manual operation with 1 push button 
    	Calibration with manual button
    
    Remarks:
    	Fixed node-id
    	
    Change log:
    20160312 - Cleanup
    */
    
    // Enable debug prints to serial monitor
    #define MY_DEBUG 
    
    #define MY_NODE_ID 13											// fixed node number
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>
    #include <MySensor.h> 
    
    // stepper library
    #include <AccelStepper.h>										// http://www.airspayce.com/mikem/arduino/AccelStepper/
    #define HALFSTEP 8  											// Stepper uses "Halfstep" mode
    
    // button library
    // used for:
    // - manual open close - single click: close/ stop/ open/ stop
    // - calibration  - after long press: open calibarion- until single press - closed calibration - until single press (stop)
    #include <Button.h>												// https://github.com/JChristensen/Button
    
    #define CHILD_ID 1   											// Id of the sensor child
    
    #define SN "Curtain control 13"
    #define SV "1.0"
    
    #define buttonPin1 7  											// Arduino pin connected to buttonPin1
    #define buttonPin2 A0  											// Arduino pin connected to buttonPin2 (fixed to ground)
    
    // Motor pin definitions
    #define motorPin1  3     										// IN1 on the ULN2003 driver 1
    #define motorPin2  4     										// IN2 on the ULN2003 driver 1
    #define motorPin3  5     										// IN3 on the ULN2003 driver 1
    #define motorPin4  6     										// IN4 on the ULN2003 driver 1
    
    const unsigned long heartbeatInterval = 1 * 3600UL * 1000UL ;	// heartbeatinterval
    unsigned long heartbeatCounter = 0 ;
    
    //
    // helper routines to store and retrieve long in mysensors EEPROM
    union {															// used to convert long to bytes for EEPROM storage
    	long longInt;
    	uint8_t LongByte[4];
    	} convLongInt ;
    
    void saveStateL(int EEposition, long StateL){
    	convLongInt.longInt = StateL ;
    	for (int y = 0; y < 4 ; y++){								// convert to bytes
    		saveState(EEposition + y , convLongInt.LongByte[y]) ;
    		}
    	Serial.print("State saved: "); Serial.println(StateL);
    	}
    		
    long loadStateL(int EEposition){
    	for (int y = 0; y < 4 ; y++){								// convert from bytes
    		convLongInt.LongByte[y] = loadState(EEposition + y) ;
    		}
    	Serial.print("State read: "); Serial.println(convLongInt.longInt);
    	return convLongInt.longInt ;
    	}
    	
    
    // Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
    AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4);
    // Initialize button active low, debounce and internal pull-up
    Button myBtn(buttonPin1, true, true, 40);						// Initiate the button (pin, pull_up, invert, debounce_ms)
    
    MyMessage percentageMsg(CHILD_ID, V_PERCENTAGE);				// used to send updates to controller
    
    const long maxRun = 4000000L ;									// maximum runway
    long setPosition = 0 ; 											// remembers set position, need to be saved in EEPROM
    const int setPositionEE = 4 ;									// eeprom location
    long openPosition = 0 ; 										// Position at open, need to be saved in EEPROM?
    const int openPositionEE = setPositionEE + 4 ;					// eeprom location
    long closedPosition = 120000UL ; 								// Position at full close, need to be saved in EEPROM
    const int closedPositionEE = openPositionEE + 4 ;				// eeprom location
    
    unsigned long idleTimer = millis() ;							// return to idle timer
    unsigned long idleTime = 100000UL ;								// return to idle after 100 secs
    
    unsigned long printTimer = millis() ;							// print timer
    unsigned long printTime = 1000UL ;								// print after 1 secs
    
    enum position_t {Open, Close, Idle, Running} ;
    position_t lastDirection = Open ; 								// lastDirection only for buttonpress
    position_t runStatus = Idle ;									// indicates current status for running motor. used for status reporting to controller
    
    enum State_t {sIdle, sCalibrateOpen, sCalibrateClose} ;
    State_t State = sIdle ;
    
    
    void setup() {
    	// setup buttons
    	pinMode(buttonPin1, OUTPUT);
    	stepper1.setMaxSpeed(2000.0);
    	stepper1.setAcceleration(1000.0);
    	//saveStateL(closedPositionEE, closedPosition) ; 			// INIT: save closed position in EEPROM
    	closedPosition = loadStateL(closedPositionEE) ;				// need to get last values from EEPROM and assume the current position is correct
    	setPosition = loadStateL(setPositionEE) ;
    	stepper1.setCurrentPosition(setPosition );
    }//--(end setup )---
    
    void presentation() {
      present(CHILD_ID, S_COVER, "Curtain");						// Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    
      // Register the LED Dimmable Light with the gateway
      sendSketchInfo(SN, SV);
    }
    
    
    void loop() {
    unsigned int now = millis() ;								// current time for loop
    // simple state machine for button press
    	myBtn.read();  
    	switch (State) {
            // Idle state, waiting for some action
    		// - button press
    		// - idleTimer
            case sIdle:                
                if (myBtn.wasReleased()){						// idle
    				Serial.println("Button release") ;
    				// if running stop
    				if (stepper1.isRunning()){
    					setPosition = stepper1.currentPosition();
    					stepper1.moveTo(setPosition) ;			// move to current position (was already there..)
    				} else if (lastDirection == Open) {
    					stepper1.moveTo(closedPosition) ;
    					lastDirection = Close ;
    				} else {									// lastDirection == Close
    					stepper1.moveTo(openPosition) ;
    					lastDirection = Open ;
    				}	
    			} else if (myBtn.pressedFor(3000)){				// move to calibratete state with long press
    				Serial.println("Button press long") ;
    				idleTimer = now ;							// return to idle after ...
    				State = sCalibrateOpen ;
    				stepper1.move(-maxRun) ;					// let the stepper open with maximum
    				}
    			break ;
    		// if not running and last action was open close ;  else open
    		// if longpress Calibrate open
            case sCalibrateOpen:           						// calibration going on     
    			if (myBtn.wasPressed()){
    				stepper1.setCurrentPosition(0 );		// set new 0 position ??
    				openPosition = setPosition = 0 ;
    				State = sCalibrateClose ;					// next is close calibarion
    				stepper1.move(maxRun) ;						// let the stepper close with maximum
    			} else if (now > idleTimer + idleTime) {		// timer expired -> abort calibration
    				State = sIdle ;
    			}
    			break ;
            case sCalibrateClose:								// calibrate closed position, end with keypress
    			if (myBtn.wasPressed())	{
    				closedPosition = setPosition = stepper1.currentPosition() ;
    				saveStateL(closedPositionEE, closedPosition) ; // save closed position in EEPROM
    				State = sIdle ;
    				stepper1.moveTo(openPosition) ;				// return to open after calibration
    			} else if (now > idleTimer + idleTime) {		// timer expired -> abort calibration
    				State = sIdle ;
    			}
    			break ;
    		default :
    			break ;
    		}
    	// power off stepper if not running (no need to reenable))
    	if (!stepper1.isRunning()){
    		if (runStatus != Idle){								// there was a change in runningstatus, so report to controller
    			setPosition = stepper1.currentPosition() ;		// store in EEPROM and report ready to controller
    			saveStateL(setPositionEE, setPosition) ;
    			send( percentageMsg.set((100 * setPosition)/(closedPosition - openPosition))) ;
    			runStatus = Idle ;
    		}
    		stepper1.disableOutputs();
    	} else {
    		runStatus = Running ;
    	}
    	stepper1.run();
    	/*
    	if (printTimer++ > now + printTime){
    		printTimer = now ;
    		Serial.println(stepper1.currentPosition());
    		}
    	*/
    }
    
    // This is called when a message is received 
    void receive(const MyMessage &message) {
    // We only expect few types of messages from controller, check which
      switch (message.type) {
    	case V_PERCENTAGE:
    	// Curtain should be opened
    	stepper1.moveTo(message.getInt() * (closedPosition - openPosition)/100);
    	Serial.print("Message: "); Serial.print(message.sensor); Serial.print(" , value: % "); Serial.println( message.getInt());
    	Serial.print("Moving to: "); Serial.println(message.getInt() * (closedPosition - openPosition)/100);
    	break ;
    	case V_STATUS:
    	// Curtain should be opened or closed full
    	stepper1.moveTo((message.getInt() == HIGH)?openPosition:closedPosition);
    	Serial.print("Message - valid: ");
    	Serial.print(message.sensor);
    	Serial.print(" , value: % ");
    	break ;
    	default : 
    	// not recognizable message
    	Serial.print("Message - valid: ");
    	Serial.print(message.sensor);
    	Serial.print(", Unrecognized ");
    	break ;
    	}
      }
      
    
    
    
    


  • @AWI Thanks. I have been using the Accelstepper library, but this has already given me several other ideas.
    I appreciate the help.



  • @AWI
    I have been searching for something like this for a while, thank you for the code!

    But i have some problems compiling it :\

    C:\Users\ToreR\Documents\Arduino\stepper_mysensor\stepper_mysensor.ino: In function 'void saveStateL(int, long int)':
    
    stepper_mysensor:74: error: 'saveState' was not declared in this scope
    
             saveState(EEposition + y , convLongInt.LongByte[y]) ;
    
                                                               ^
    
    C:\Users\ToreR\Documents\Arduino\stepper_mysensor\stepper_mysensor.ino: In function 'long int loadStateL(int)':
    
    stepper_mysensor:81: error: 'loadState' was not declared in this scope
    
             convLongInt.LongByte[y] = loadState(EEposition + y) ;
    
                                                               ^
    
    C:\Users\ToreR\Documents\Arduino\stepper_mysensor\stepper_mysensor.ino: In function 'void presentation()':
    
    stepper_mysensor:129: error: 'present' was not declared in this scope
    
       present(CHILD_ID, S_COVER, "Curtain");                        // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    
                                           ^
    
    stepper_mysensor:132: error: 'sendSketchInfo' was not declared in this scope
    
       sendSketchInfo(SN, SV);
    
                            ^
    
    C:\Users\ToreR\Documents\Arduino\stepper_mysensor\stepper_mysensor.ino: In function 'void loop()':
    
    stepper_mysensor:195: error: 'send' was not declared in this scope
    
                 send( percentageMsg.set((100 * setPosition)/(closedPosition - openPosition))) ;
    
                                                                                             ^
    
    exit status 1
    'saveState' was not declared in this scope```

  • Hero Member

    @Tore-André-Rosander are you using the development branch? The errors seem to indicate that is the case.



  • @AWI Thank you, that helped :)
    I now have it up and running.

    But how do i connect and use this with a controller (like Domoticz)?

    I use an ESP gateway (from the dev branch) and the curtain node is connected and showing up in domoticz.
    But how do i make domoticz send commands to the curtain node?

    All i get when adding the curtain node both as on/off button and blinds in domoticz is "unreconized" in the curtain serial monoitor.


  • Hero Member

    @Tore-André-Rosander I have defined it as "Blinds Percentage" in Domoticz. This send the V_STATUS and V_PERCENTAGE messages where the node should respond to...
    0_1458557025507_upload-d16cb932-d511-433f-9488-112b68d308f4


Log in to reply
 

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