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 🏃 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. 😞


  • 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



  • @AWI THanks again for your fast replys!

    This is what i get in serial monitor when i use the "up" "down" buttons for blinds percentage in domoticz.

    This is my very first MySensor setup so it might be some error with my setup, i will have a deeper look at it later tonight.

    Starting sensor (RNNNA-, 2.0.0-beta)
    Radio init successful.
    State read: -1
    State read: -1
    send: 13-13-0-0 s=255,c=3,t=15,pt=0,l=2,sg=0,st=ok:
    send: 13-13-0-0 s=255,c=0,t=17,pt=0,l=10,sg=0,st=ok:2.0.0-beta
    send: 13-13-0-0 s=255,c=3,t=6,pt=1,l=1,sg=0,st=ok:0
    read: 0-0-13 s=255,c=3,t=15,pt=0,l=2,sg=0:
    read: 0-0-13 s=255,c=3,t=6,pt=0,l=1,sg=0:M
    send: 13-13-0-0 s=1,c=0,t=5,pt=0,l=7,sg=0,st=ok:Curtain
    send: 13-13-0-0 s=255,c=3,t=11,pt=0,l=18,sg=0,st=ok:Curtain control 13
    send: 13-13-0-0 s=255,c=3,t=12,pt=0,l=3,sg=0,st=ok:1.0
    Init complete, id=13, parent=0, distance=1
    read: 0-0-13 s=1,c=1,t=29,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=30,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=29,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=30,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=29,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=30,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=29,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=30,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=29,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized read: 0-0-13 s=1,c=1,t=30,pt=0,l=0,sg=0:
    Message - valid: 1, Unrecognized```

  • Hero Member

    @Tore-André-Rosander Nothing wrong with you interpretation. I have not written this sketch as a complete S_COVER implementation. Only the V_PERCENTAGE part (not V_UP/V_DOWN). If you define it in Domoticz as "Blinds percentage" (see my previous post) it will work. Sorry for the confusion.



  • @AWI Seems like my problem is with Domoticz itselfe.
    I have the same problem as described in this post https://www.domoticz.com/forum/viewtopic.php?f=6&t=7748


  • Hero Member

    @Tore-André-Rosander You can probably solve it by deleting the sensor in Domoticz and let the sketch present itself as S_DIMMER (and use another sensor id)



  • Maybe its because i have not calibrated it? I dont have any pushbuttons laying around, so im gonna make one with some wires. But i see that there is two pins defined as buttons. Should i use the buttonPin1 as VCC?

    So it would look something like buttonPin1 -> buttonPin2 -> button -> gnd?


  • Hero Member

    @Tore-André-Rosander You can ignore buttonpin2. Just connect your "button" to buttonpin1 and ground.


  • Contest Winner

    @AWI I'm curious where you got the rails for the curtain. I wasn't been able to find them, but stumbled on this topic.


  • Hero Member

    @TheoL Ikea 😄


  • Contest Winner

    @AWI Really interested in the mechanics 😉



  • I'm currently working on a similar project, only I got started with 24V DC motors that I got from my work (left overs from a test project) These motors are very strong (way way over rated) and have a big gearbox what unfortunately makes the movement a bit slow.
    I started off with 1 motor to move 2 curtains at the same time, but will change this to 2 motors for either side since the better half of me likes to control the curtains separately in case she only wants to close 1.
    Next to the automation there will also be a manual control switch be hidden behind the curtains in case the node is down and I'm not at home to fix the issue...
    all running on an ESP8266 (ESP-12e) unit to have it over WiFi and have enough io

    Currently the test setup is working fine and I'm ordering parts to start implementing this a bit bigger in the first room. the test setup I made with some old wood 1 meter of rail and a few old rugs as curtains...
    alt text

    I use a GT2 timing belt that is commently used for 3D printers and pully's are widely available as well as the belts in different lengths (I order them on rolls of 10 meters) and attached the timing belt to a carriage that is normally to pull the curtains with a rod.
    alt text

    the motor is mounted on a bracket with currently 2 end stop switches for open and close (but will be 1 switch for open and 1 for close in the final solution)
    alt text

    resulting in driving the curtain from 1 side
    alt text

    alt text

    electronics are working fine now and need to be ported to a test print and later to a real PCB design

    alt text

    alt text



  • Hello.

    To my project i use stepper motor JK42HS34-0404 and driver L298N.

    This is a wiring diagram.
    alt text

    This is a program code that I use

    #include <MySensor.h>  
    #include <SPI.h>
    #include <AccelStepper.h>
    #define HALFSTEP 4  // Stepper uses "Halfstep" mode
    #define CURTAIN_CLOSED 1000 // value when closed
    #define CURTAIN_OPEN 0 // value when open
    #define CHILD_ID 1   // Id of the sensor child
    // MySensors definitions
    MySensor gw;
    // Initialize message
    MyMessage msg(CHILD_ID, V_TRIPPED);
    
    
    // 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
    
    // Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
    AccelStepper stepper1(HALFSTEP, motorPin1, motorPin2, motorPin3, motorPin4);
    
    unsigned long current_position ; // remembers current position, need to be saved in EEPROM?
    
    void setup() {
      // MySensors
      gw.begin(); // fixed node 13
      gw.sendSketchInfo("Curtain control 13", "1.0"); // Send the sketch version information to the gateway and Controller
    
      //pinMode(DIGITAL_INPUT_SENSOR, INPUT);      // sets the motion sensor digital pin as input
      // Register all sensors to gw (they will be created as child devices)
      gw.present(CHILD_ID, S_COVER); // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
      
      stepper1.setMaxSpeed(500.0);
      stepper1.setAcceleration(700.0);
      stepper1.moveTo(CURTAIN_OPEN);
      
      current_position = CURTAIN_OPEN ;
      stepper1.moveTo(CURTAIN_OPEN);
    }//--(end setup )---
    
    void loop() {
      // if message = V_UP start moving until closed
      // if message = V_DOWN start moving back until open
      // if message = V_STOP stop moving
      // Test: Change direction when the stepper reaches the target position
      gw.process(); // check if message from controller
      if (stepper1.distanceToGo() == 0){
        if (stepper1.currentPosition() == CURTAIN_OPEN){
          stepper1.moveTo(CURTAIN_CLOSED);
          Serial.println("Curtain Open, now closing");
          gw.request(CHILD_ID, V_UP, 0); // request new values from controller  
        }
        else{
          stepper1.moveTo(CURTAIN_OPEN);
          Serial.println("Curtain Closed, now opening");
          gw.request(CHILD_ID, V_DOWN, 0); // request new values from controller   
        }
      }
      
      stepper1.run();
    }
    
    // This is called when a message is received 
    void incomingMessage(const MyMessage &message) {
      // We only expect few types of messages from controller, check which
      stepper1.moveTo(message.getInt() * CURTAIN_CLOSED/100);
      Serial.print("Message - valid: ");
      Serial.print(message.sensor);
      Serial.print(" , value: % ");
      Serial.println(message.getInt());
      switch (message.getInt()) {
      case 100:
        // Curtain should be opened
         Serial.print("Message - valid: ");
         Serial.print(message.sensor);
         Serial.print(", Message UP ");
         stepper1.moveTo(CURTAIN_OPEN);
         break;
      case 0:
        // Curtain should be closed
         Serial.print("Message - valid: ");
         Serial.print(message.sensor);
         Serial.print(", Message DOWN ");
         stepper1.moveTo(CURTAIN_CLOSED);
        break;
      case 50:
        // Curtain action should be stopped
         Serial.print("Message - valid: ");
         Serial.print(message.sensor);
         Serial.print(", Message STOP ");
        break;
      default: 
        // not recognizable message
         Serial.print("Message - valid: ");
         Serial.print(message.sensor);
         Serial.print(", Unrecognized ");
    
      }
     
    }
    

    When the program I have uploaded to my mysensors, the engine still turns, one left and one to the right and did not work the button blinds in Domoticz.

    I cant turn off my stepper motor in Domoticz.

    Stepper motor on MySensors in Domoticz – 01:25
    — Roman Kubat

    Please help me.



  • I write a program on library Stepper not AccelStepper.
    Now is working.

    Program code:

    // Enable debug prints to serial monitor
    #define MY_DEBUG 
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    // Enable repeater functionality for this node
    #define MY_REPEATER_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <Stepper.h>
    
    #define CURTAIN_CLOSED 1000  // wartosc gdy kurtyna zamknieta
    #define CURTAIN_OPEN -1000         // wartosc gdy kurtyna otwarta 
    #define CHILD_ID 1
    
    // definicje MySensors 
    
    MyMessage message(CHILD_ID, S_COVER);
    
    int in1Pin = 3;
    int in2Pin = 4;
    int in3Pin = 5;
    int in4Pin = 6;
    int lastState;
     
    // liczba kroków na jeden obrót
    #define stepsPerRevolution 200
    
    //ustawienie szybkości silnika 
    int motorSpeed = 40;
    
    Stepper myStepper(stepsPerRevolution, in1Pin, in2Pin, in3Pin, in4Pin);
    
    void setup() {
      
    // ustawienie pinów jako wyjście
      pinMode(in1Pin, OUTPUT);
      pinMode(in2Pin, OUTPUT);
      pinMode(in3Pin, OUTPUT);
      pinMode(in4Pin, OUTPUT);
    // ustawienie szybkosci silnika
      myStepper.setSpeed(motorSpeed);
    }
    
    void presentation()  
    { 
      // Wyslanie informacji o wersji programu
      sendSketchInfo("Program kurtyna", "1.0");
      // Register all sensors to gw (they will be created as child devices)
      present(CHILD_ID, S_COVER); // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    }
    
    void receive(const MyMessage &message) {
      // if message = V_UP start moving until closed
      if (message.type==V_UP) {
         myStepper.step(CURTAIN_OPEN);
         // Store state in eeprom
         saveState(message.sensor, message.getBool());
         request(CHILD_ID, V_UP, 0); // request new values from controller
            }
         
     if (message.type==V_DOWN) {
         myStepper.step(CURTAIN_CLOSED);
         // Store state in eeprom
         saveState(message.sensor, message.getBool());
         request(CHILD_ID, V_DOWN, 0); // request new values from controller
            } 
    }
    
    

    I use MySensors Library 2.0

    Youtube film

    Silnik krokowy z L298N - sterowanie z Domoticz – 00:40
    — Roman Kubat



  • your wiring diagram is not accessable:
    Error (403)
    It seems you don't belong here! You should probably sign in. Check out our Help Center and forums for help, or head back to home.

    i use a DC motor so can't help with stepper motors, but can you share an image of how you setup your rails?



  • Now the wiring diagram should be visible.



  • I write program with AccelStepper library

    / Enable debug prints to serial monitor
    #define MY_DEBUG
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    // Enable repeater functionality for this node
    #define MY_REPEATER_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <AccelStepper.h>      //import biblioteki AccelStepper 
    
    #define HALFSTEP 8
    #define CURTAIN_CLOSED 2000  // wartosc gdy kurtyna zamknieta
    #define CURTAIN_OPEN 0       // wartosc gdy kurtyna otwarta
    #define CHILD_ID 1
    
    // definicje MySensors
    
    MyMessage message(CHILD_ID, S_COVER);
    
    // Definicja pinow silnika
    #define IN1  3     // IN1 - zielony
    #define IN2  4     // IN2 - czarny
    #define IN3  5     // IN3 - niebieski
    #define IN4  6     // IN4 - czerwony
     
    AccelStepper stepper1(HALFSTEP, IN1, IN2, IN3, IN4);
    
    void setup()
    {
      stepper1.setMaxSpeed(200.0);
      stepper1.setAcceleration(1000.0);
      stepper1.setSpeed(200);
      stepper1.moveTo(CURTAIN_OPEN);
    }
    
    void presentation() 
    {
      // Wyslanie informacji o wersji programu
      sendSketchInfo("Program kurtyna", "1.0");
      // Register all sensors to gw (they will be created as child devices)
      present(CHILD_ID, S_COVER); // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    }
    
    void loop()
    {
      stepper1.run();  //Start
    }
    
    void receive(const MyMessage &message)
    {
      // if message = V_UP start moving until closed
      if (message.type==V_UP) {
         if (stepper1.distanceToGo() == 0){
             if (stepper1.currentPosition() == CURTAIN_OPEN){
                 stepper1.moveTo(CURTAIN_CLOSED);
         // Store state in eeprom
         saveState(message.sensor, message.getBool());
         request(CHILD_ID, V_UP, 0); // request new values from controller
             }
          }
       }
       if (message.type==V_DOWN) {
           stepper1.moveTo(CURTAIN_OPEN);
           // Store state in eeprom
           saveState(message.sensor, message.getBool());
           request(CHILD_ID, V_DOWN, 0); // request new values from controller   
        }
        if (message.type==V_STOP) {
            stepper1.setCurrentPosition(0);
            // Store state in eeprom
            saveState(message.sensor, message.getBool());
            request(CHILD_ID, V_STOP, 0); // request new values from controller
        }
    }
    
    

    I use MySensors library 2.0

    Now i working.



  • Hello,

    I trying to adopt the code for 28BYJ-48 stepper motor with ULN2003 driver board for roller blinds control. Code is working, but when the stepper motor is always on - it gets hot. Because of it also its consumes more energy. So thats why I want to turn on ULN2003 board only when new action is started, and after that it should be shuted down again. I could do it, because ULN2003 has On/Off jumper:
    alt text

    Question #1: I trying to use relay for that On/Off. Could I do it without relay and control On/Off jumper directly from arduino? Does relay is the best solution?

    Question #2: I suck at programming, but I trying to add theese lines to my code to control the relay SIL05-1A72-71D, which controls the ULN2003 driver board:
    int powerPin = 7; //before SETUP
    pinMode(powerPin, OUTPUT); // In SETUP
    digitalWrite(powerPin, LOW); // In SETUP
    digitalWrite(powerPin, HIGH); // In void receive function
    delay(CURTAIN_CLOSED); // In void receive function
    digitalWrite(powerPin, LOW); // In void receive function

    digitalWrite(powerPin, HIGH); - I think is in right position, but I don't know how to turn off relay when action is done. Please advise for coding, because I not good in it. Thank You! Below is the code:

    // Enable debug prints to serial monitor
    #define MY_DEBUG
    
    // Enable and select radio type attached
    #define MY_NODE_ID 10
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    // Enable repeater functionality for this node
    //#define MY_REPEATER_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <AccelStepper.h>      //import biblioteki AccelStepper 
    
    #define HALFSTEP 8
    #define CURTAIN_CLOSED 10000  // wartosc gdy kurtyna zamknieta
    #define CURTAIN_OPEN 0       // wartosc gdy kurtyna otwarta
    #define CHILD_ID 1
    
    int powerPin = 7;
    
    // definicje MySensors
    
    MyMessage message(CHILD_ID, S_COVER);
    
    // Definicja pinow silnika
    #define IN1  3     // IN1 - zielony
    #define IN2  4     // IN2 - czarny
    #define IN3  5     // IN3 - niebieski
    #define IN4  6     // IN4 - czerwony
     
    AccelStepper stepper1(HALFSTEP, IN1, IN3, IN2, IN4);
    
    void setup()
    {
      stepper1.setMaxSpeed(1000.0);
      stepper1.setAcceleration(100.0);
      stepper1.setSpeed(200);
      stepper1.moveTo(CURTAIN_OPEN);
      
      pinMode(powerPin, OUTPUT);
      digitalWrite(powerPin, LOW);
    }
    
    void presentation() 
    {
      // Wyslanie informacji o wersji programu
      sendSketchInfo("Roller blinds", "1.0");
      // Register all sensors to gw (they will be created as child devices)
      present(CHILD_ID, S_COVER); // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    }
    
    void loop()
    {
      stepper1.run();  //Start
    }
    
    void receive(const MyMessage &message)
    {
      // if message = V_UP start moving until closed
      if (message.type==V_UP) {
         digitalWrite(powerPin, HIGH);
         if (stepper1.distanceToGo() == 0){
             if (stepper1.currentPosition() == CURTAIN_OPEN){
                 stepper1.moveTo(CURTAIN_CLOSED);
         // Store state in eeprom
         saveState(message.sensor, message.getBool());
         request(CHILD_ID, V_UP, 0); // request new values from controller
             }
          }
       }
       if (message.type==V_DOWN) {
           digitalWrite(powerPin, HIGH);
           stepper1.moveTo(CURTAIN_OPEN);
           // Store state in eeprom
           saveState(message.sensor, message.getBool());
           request(CHILD_ID, V_DOWN, 0); // request new values from controller   
        }
        if (message.type==V_STOP) {
            digitalWrite(powerPin, HIGH);
            stepper1.setCurrentPosition(0);
            // Store state in eeprom
            saveState(message.sensor, message.getBool());
            request(CHILD_ID, V_STOP, 0); // request new values from controller
        }
     //   delay(CURTAIN_CLOSED);
     //   digitalWrite(powerPin, LOW);
    }
    
    


  • @jacikaas said in Curtain Control Node.:

    void receive(const MyMessage &message)
    {
    // if message = V_UP start moving until closed
    if (message.type==V_UP) {
    digitalWrite(powerPin, HIGH);
    if (stepper1.distanceToGo() == 0){
    if (stepper1.currentPosition() == CURTAIN_OPEN){
    stepper1.moveTo(CURTAIN_CLOSED);
    // Store state in eeprom
    saveState(message.sensor, message.getBool());
    request(CHILD_ID, V_UP, 0); // request new values from controller
    }
    }
    }
    if (message.type==V_DOWN) {
    digitalWrite(powerPin, HIGH);
    stepper1.moveTo(CURTAIN_OPEN);
    // Store state in eeprom
    saveState(message.sensor, message.getBool());
    request(CHILD_ID, V_DOWN, 0); // request new values from controller
    }
    if (message.type==V_STOP) {
    digitalWrite(powerPin, HIGH);
    stepper1.setCurrentPosition(0);
    // Store state in eeprom
    saveState(message.sensor, message.getBool());
    request(CHILD_ID, V_STOP, 0); // request new values from controller
    }
    // delay(CURTAIN_CLOSED);
    // digitalWrite(powerPin, LOW);
    }

    You can enable and disable outputs and try. In the following function:

    void receive(const MyMessage &message)
    
    //add the following at the begining of the function
    stepper1.enableOutputs ()
    //and add the following at the end in the same function.
    stepper1.disableOutputs ()
    

    I hope this helps haven't tried it myself.



  • @suresh-mali Thank You Suresh for Your answer!
    I disconnect relay and delete it lines from the code.

    I add lines to code like You said:

    void receive(const MyMessage &message)
    {
    stepper1.enableOutputs ();
     // rest of code
    stepper1.disableOutputs ();
    }
    

    It looks like it have to work because it is simple solution, but it doesn't... On driver board ULN2003, when motor is not rotating, A Led is always on. I think if command with disableOutputs would be activated, then the LED should not be on?



  • @jacikaas Ok.

    //Remove this line from loop function
    stepper1.run(); 
    
    //Change this line
    stepper1.moveTo(CURTAIN_CLOSED);
    //to
    stepper1.runToPosition(CURTAIN_CLOSED);
    

    I hope this works. Also make similar changes in block which closes curtains.
    Refernce article: https://www.pjrc.com/teensy/td_libs_AccelStepper.html



  • @suresh-mali
    I now reading about that funktions in http://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html#a344f58fef8cc34ac5aa75ba4b665d21c

    Did what You said, but getting error: no matching function for call to 'AccelStepper::runToPosition(int)'
    I paste here all code, but I think I did everything without mistakes:

    // Enable debug prints to serial monitor
    #define MY_DEBUG
    
    // Enable and select radio type attached
    #define MY_NODE_ID 10
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    // Enable repeater functionality for this node
    //#define MY_REPEATER_FEATURE
    
    #include <SPI.h>
    #include <MySensors.h>
    #include <AccelStepper.h>
    
    #define HALFSTEP 8
    #define CURTAIN_CLOSED 10000
    #define CURTAIN_OPEN 0
    #define CHILD_ID 1
    
    // int powerPin = 7;
    
    // definicje MySensors
    
    MyMessage message(CHILD_ID, S_COVER);
    
    // Definicja pinow silnika
    #define IN1  3     // IN1
    #define IN2  4     // IN2
    #define IN3  5     // IN3
    #define IN4  6     // IN4
     
    AccelStepper stepper1(HALFSTEP, IN1, IN3, IN2, IN4);
    
    void setup()
    {
      stepper1.setMaxSpeed(1000.0);
      stepper1.setAcceleration(100.0);
      stepper1.setSpeed(200);
      stepper1.runToPosition(CURTAIN_CLOSED);
    }
    
    void presentation() 
    {
      sendSketchInfo("Roller blinds", "1.0");
      present(CHILD_ID, S_COVER); // Window Cover sub-type, commands: V_UP, V_DOWN, V_STOP
    }
    
    void loop()
    {
      //stepper1.run();  //Start
    }
    
    void receive(const MyMessage &message)
    {
    stepper1.enableOutputs ();
      // if message = V_UP start moving until closed
      if (message.type==V_UP) {
         if (stepper1.distanceToGo() == 0){
             if (stepper1.currentPosition() == CURTAIN_OPEN){
                 stepper1.runToPosition(CURTAIN_CLOSED);
         // Store state in eeprom
         saveState(message.sensor, message.getBool());
         request(CHILD_ID, V_UP, 0); // request new values from controller
             }
          }
       }
       if (message.type==V_DOWN) {
           stepper1.moveTo(CURTAIN_OPEN);
           // Store state in eeprom
           saveState(message.sensor, message.getBool());
           request(CHILD_ID, V_DOWN, 0); // request new values from controller   
        }
        if (message.type==V_STOP) {
            stepper1.setCurrentPosition(0);
            // Store state in eeprom
            saveState(message.sensor, message.getBool());
            request(CHILD_ID, V_STOP, 0); // request new values from controller
        }
    stepper1.disableOutputs ();
    }
    
    


  • @jacikaas Sorry to revive an old thread, but did you get this resolved and if so how?

    I just 3D printed my first motorised roller blind mounts and have tested with arduino stepper only.

    Next is to go mysensors with it, but which way is best? Standard arduino stepper or accelstepper?



  • Okey. This is an old thread but I hope it's ok I keep it alive.
    I just printed som parts for my blinds.
    I use a stepper motor (28BYJ-48 with ULN2003 Driver board) and this sketch found in this thread.
    The sketch is awesome.
    To my quiestion.
    I want to run 2 blinds at the same time from ONE node. Synchronized or maybe individual.
    Is this possible?
    There isn't any digital pins over for this when using button(s).
    I think i saw someone using analog pin connected to the ULN2003 someware.
    Is it possible to run 2 motors on one board?
    Really hope somone can answer som of my question.
    What modifications is needed in the sketch if this is possible.
    This is the sketch I use.

    /*
     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_RF24
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>
    #include <MySensors.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 ;
    	}
      }
    

  • Contest Winner

    You can use an I/O expander like an MCP23017 which is a 16 port expander or a 8 port expander. They're cheap. Keep in mind they consume 1 mAmp. I'm currently working on a product to overcome that power issue for a battery powered Node. Will try to post about it in a while



  • @xydix
    Hello @xydix do you have the curtain working with the sketch above?
    I don't get the button working on pin 7.
    Witch button library do you use?
    I have the library witch is in the sketch, i only must change Bbutton.h in LC_Button.h.
    Is that maby my problem.
    I hope u can help me.
    Greatz Ton



  • @barduino said in Curtain Control Node.:

    @Suresh-Mali

    DC Motor, very interesting!

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

    Cheers

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



  • @ton-rijnaard
    Hi. This was a problem for me to. The reason is the library changed name and updates was made.
    I use the library version 0.9.
    I have like zero skills in arduino. If you use the old library the button will work with the sketch i posted a moth ago.
    Looke here: https://github.com/JChristensen/JC_Button/releases
    Maybe some of the other older releases will work. Don't remember why o choose V0.9
    Look at 2.0.0, "This is a major release that is not backwards-compatible with previous releases."



  • @xydix
    To answer my own question the stepper runs fine from the analog pins.
    Next step is to find some intersted in the same thing and see if we could get the sketch adjusted to run two steppers.
    I will try when i get some time but I guess I will have problem to clone the calibration and store-to-eerprom-part.



  • @AWI Sorry for butting in to an old thread, but automating my blinds is top priority!

    I've copied your sketch and have everything setup - however, I have eratic behaviour and a few questions:

    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 )---
    
    • Should the pinMode on buttonPin1 actually be an INPUT?

    • Can you please describe how to go about calibrating the system?

    • Does the //saveStateL(closedPositionEE, closedPosition);
      need uncommenting for one boot of the arduino and the blind closed - then the sketch re-uploading with it commented again?

    Your help is greatly appreciated as I have a twitching motor and feel I'm almost there!



  • @adds666 What i can tell it was a long time since @AWI was active here.
    I use this sketch with my first bild since a couple of days.
    For me it works like a charm. But in my case I control the motor from analog pins just to test and it seems OK.
    No problem with the button.
    The only thing I don't like, is the calibration.
    I my case, using this on a blind, when calibrating, first it goes down, then i press the button, the blind stops at once and change it's direction and goes up. But when i reach the top level and want to stop it and save the state, it stops slow and pass the point i pressed the button and then reverse to the right spot. Problem is, the blind can't go further because it is in the top position.
    If it would be the other way around it wouldn't be a problem.
    Maybe i have the motor one the "wrong" side of the blind but when it is down the "downkey" in my HA is greyed out.
    When it i up the "upkey" it greyed out so that seems right.

    What is not working in your case?
    What motor do you use? I use 28BYJ-48 with ULN2003 and had to set stepper1.setMaxSpeed(2000.0); to stepper1.setMaxSpeed(1000.0); because the motor "slipped" at a speed of 2000.
    Like i wrote earlier in the thread I tried to figure out how to "dublicate" the sketch and ute it for two motors.
    I really don't know where to start to get this done. It would be awesome to use one arduino with two blinds.



  • Hi @xydix , glad to hear you have the sketch working properly. Do you have the pinmode as OUTPUT or did you change to input?

    And do you still have the saveStateL(closedPositionEE, closedPosition); line commented out?

    If I change the pinMode to input, I get the system powered up and connected to the mysensors gateway fine - serial print repeats 'Button Release'. If I short press the button, the system does nothing. If I hold the button for 3000ms serial print reads 'Button Long Press' and the motor turns - however If I let go of the button, the motor stop and system returns to its 'button release' state.

    I'm not able to get the system in to calibration mode.



  • @adds666
    I am still in "testing mode".
    I had some problems with the node, it stoped receiving messages but did always work with the button.
    Yesterday i built a new node and uploaded the sketch and it seems to work better.
    Here is the sketch i use, I changed the pin for the button due to missing digital pins on my pcb.
    And I am back on using digital pins for the stepper driver for the same reason.
    I greyed out buttonpin 2. I don't think it is needed.
    I also greyed out the heartbeat-part as I had problems. Just for testing. Don't know if it is suitable when using Home Assistant.

    /*
     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 8                     // fixed node number
    // Enable and select radio type attached
    #define MY_RADIO_RF24
    #define MY_RF24_PA_LEVEL RF24_PA_HIGH
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>
    #include <MySensors.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 - Använd gammal version. testat med V0.9 och det funkar
    
    #define CHILD_ID 1                        // Id of the sensor child
    
    #define SN "Curtain control"
    #define SV "1.0"
    
    #define buttonPin1 A0                        // Arduino pin connected to buttonPin1
    //#define buttonPin2 A0                       // Arduino pin connected to buttonPin2 (fixed to ground)
    
    // Motor pin definitions
    #define motorPin1  2                        // IN1 on the ULN2003 driver 1
    #define motorPin2  3                        // 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(1000.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 ;
      }
      }
    


Suggested Topics

22
Online

11.2k
Users

11.1k
Topics

112.5k
Posts