Curtain Control Node.
-
@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.
-
DC Motor, very interesting!
Do you have end stops? The sketch doesn't open for some reason...
Cheers
-
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! -
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 -
@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"); } }I'm just curious, maybe its the arduino IDE version or something.
I'm using 1.6.4 and I get this:
However opening with a text editor is fine:
Cheers
-
I'm just curious, maybe its the arduino IDE version or something.
I'm using 1.6.4 and I get this:
However opening with a text editor is fine:
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.
-
@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:
-
@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 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 ; } } -
@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. -
@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
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``` -
@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```@Tore-André-Rosander are you using the development branch? The errors seem to indicate that is the case.
-
@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.
-
@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.
@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...
-
@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...
@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``` -
@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```@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.
-
@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 -
@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@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)