WIndow Roller Shutter - Position


  • Hardware Contributor

    Hello, I'm putting a automatic Window Roller Shutter at my home. I already have the system to get them up and down, and now is time to incorporate a MySensor Node. It's easy to get them up and down, just open and close a relay, but the hard part is to get the 10% open, or 50% closed (or open, depending on the point of view 🙂 ). I was thinking on timing the open and close speed and make the math from there... but that seems like it will get it wrong eventually. Another solution would be to have some kind of sensors that would trigger checkpoints.

    Any ideas? Any one implemented something like this?

    Thank you all


  • Hardware Contributor

    hi,

    what i did was to to check and recalculate/calibrate each time endstop was reached. I think most commercial devices work like this. I also had a current sensor (optional) to check motor current on endstops.
    you can take a look at



  • @soloam you could place a little magnet on the shaft of your roller shutter and read revolutions with a hall sensor. Then you will always know pretty accurate position of your sutter.


  • Hardware Contributor

    @scalz thank you for your replay, one qestion, what you use is somting like this?? :

    https://www.ebay.com/itm/181026550454?rmvSB=true
    https://www.ebay.com/itm/251558433854?rmvSB=true

    I like the idea of the auto calibration, It would make this a lot accurate!

    @monte thank you... that would be my second approach... probably an hybrid solution, use the magnets instead of the power sensor to determine the up and down positions.

    Thank You


  • Hardware Contributor

    Ok I found your project in openhardware.io nice work 😉

    Thank you



  • what type of motor are you using? If it was a stepper, just count steps, but I doubt it would be that. Maybe you could make some kind of closed loop system with a rotary encoder which should give you pretty accurate position every time.



  • Visit the following https://github.com/marvinroger/arduino-shutters

    I made one based on initial version of library, has been working for two years without any problems.



  • I have a similar situation: my house had manually operated roller shutters, where you had to keep the button pressed to open or close them.
    Importantly when the end stop is reached, the motor is not powered in the moving direction anymore (probably some integrated reed contact or something). If this is the case for you, my solution will work well:
    Just make a normal arduino relay (1 for every direction) and calibrate them with a timer. Then just make sure that you add some time for the opening direction (a few seconds), it will make sure that for every cycle any drift is compensated. This even worked for me when I had to reset the system with the rollers at a certain percentage => just move up and down a few times and it's ok again.
    In my system I have implemented the manual switches (which used to switch the 220V by connecting them with the 5V node source and switching dedicated input pins on the arduino. I have two nodes, one with 3 roller shutters and one with 1 shutter and 1 awning.
    (I think on the 3 up/down variant, there were not enough input pins for manual input, so I had all bound to one up-down switch).

    Here's my code (it has become somewhat messy, and if I remember correctly I changed from 100% open to 100% closed mode when changing from home assistant to openhab2):

    /**
       The MySensors Arduino library handles the wireless radio link and protocol
       between your home built sensors/actuators and HA controller of choice.
       The sensors forms a self healing radio network with optional repeaters. Each
       repeater and gateway builds a routing tables in EEPROM which keeps track of the
       network topology allowing messages to be routed to nodes.
    
       Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
       Copyright (C) 2013-2015 Sensnology AB
       Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
    
       Documentation: http://www.mysensors.org
       Support Forum: http://forum.mysensors.org
    
       This program is free software; you can redistribute it and/or
       modify it under the terms of the GNU General Public License
       version 2 as published by the Free Software Foundation.
    
     *******************************
    
       REVISION HISTORY
       Version 0.4 - October 13, 2017 - Powerpenguin
                     Added dedicated inputs for all manual switches
                     (This is possible for 2 up/down actuators: D2-D5 relay, D9-D13 nRF24, A0-A3=D14-D17: switches)
       Version 0.3 - October 9, 2017 - PowerPenguin
    
       DESCRIPTION
       Based on Dimmable LED Light using PWM from Henrik Ekblad
    
       Simple control of window shades which are already motorised and don't provide
       any more feedback, i.e. we want to send a target percentage from the controller
       to the shade which then moves as tgt_percentage * time_to_fully_close
       In the first implementation we assume that the starting state is fully open, so
       some "drift" is to be expected? Or "overshoot" opening by a margin to make sure
       100% corresponds to fully open? (yes: home assistant takes % open, not % closed)
    
       The sketch supports multiple blinds, i.e. multiple relays connected to the arduino.
    
       There are two relays per blind, for up/down respectively
    
       Also, additional pins are read for a push-button, to manually give the up or down signal
       (will start/stop moving one or covers simultaneously, based on btcidmap[])
    
       To avoid the relays coils from being powered in normal operation, and most relays are active low,
       high/low has been reversed (so high unless activated)
       (better buy active high relays!)
    
       The code has become somewhat messy, better to use interrupt on manual switch? Event based?
    
    */
    
    // Enable debug prints to serial monitor
    #define MY_DEBUG
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    #include <MySensors.h>
    
    #define SN "WindowShades"
    #define SV "0.4"
    
    #define PIN_BASE 2          // first of the Arduino pins attached to relay
    #define LOOP_DELAY 950      // loop delay in ms
    #define LOOP_DELAY_SMALL 30 // delay between transmissions for different sensors
    #define PIN_BASEA 14        // base of the analog pins to be used as digital input
    
    #define NCOV 2              // number of covers, each have an up and down relay; maximum is half the number of available digital i/o pins
    #define NCSW 2              // number of pairs (up/down) of manual override switches
    enum coverstates_enum {UP, DOWN, STOPPED};
    
    static bool btmanup_pressed[NCSW];
    static bool btmandn_pressed[NCSW];
    
    bool initialValueSent[NCOV];
    static int8_t currentLevel[NCOV];
    static int8_t targetLevel[NCOV];
    static int8_t time_open[NCOV];
    static int8_t time_close[NCOV];
    static unsigned long tstart[NCOV];
    static int8_t lstart[NCOV];
    
    // the manual button vs. CID mapping has to be specified manually, since sometimes we want one manual button to control more channels
    static int8_t btcidmap[NCOV] = {0, 1};
    
    static coverstates_enum state[NCOV]; // number of values must be NCOV
    
    MyMessage msgPct[NCOV];
    
    void setup() {
      // initialise all child actuators and related vars
    
      for (int8_t cid = 0; cid < NCOV; cid++) {
        msgPct[cid] = MyMessage(cid, V_PERCENTAGE);
        pinMode(PIN_BASE + 2 * cid, OUTPUT);
        pinMode(PIN_BASE + 2 * cid + 1, OUTPUT);
        state[cid] = STOPPED;
        initialValueSent[cid] = false;
        currentLevel[cid] = 100;
        targetLevel[cid] = 100;
        time_open[cid] = 29; // seconds to open, if not the same for all covers specify independently
        time_close[cid] = 27; // seconds to close
      }
      // specify times independently if not the same for all covers below
      time_close[0] = 40;
      time_open[0] = time_close[0] + 3; // overshoot to make sure we open completely, motor stops anyway
      time_close[1] = 26;
      time_open[1] = time_close[1] + 3; // overshoot to make sure we open completely, motor stops anyway  
      // time_close[2] = 22;
      // time_open[2] = time_close[2] + 3; // overshoot to make sure we open completely, motor stops anyway  
      
      for (int8_t i = 0; i < NCSW; i++) {
        pinMode(PIN_BASEA + 2 * i, OUTPUT);
        pinMode(PIN_BASE + 2 * i + 1, OUTPUT);
        btmanup_pressed[i] = false;
        btmandn_pressed[i] = false;
      }  
    }
    
    
    void presentation() {
      // Register the Cover with the controller
      sendSketchInfo(SN, SV);
      for (int cid = 0; cid < NCOV; cid++) {
        present(cid, S_COVER);
      }
    }
    
    
    void loop() {
    
      int8_t cid;
      int8_t delta;
      
      for (cid = 0; cid < NCOV; cid++) {
        if (!initialValueSent[cid]) {
          Serial.println("Sending initial value");
          send(msgPct[cid].set(currentLevel[cid]));
          Serial.println("Requesting initial value from controller");
          request(cid, V_PERCENTAGE);
          wait(LOOP_DELAY); // extra delay to give more time for initial handshake
        } else {
    
          // toggle moving if manual button pressed (since we arrive heare about once a second,
          // we don't need debounce, but we need to press the button up to 1 s to have a reaction
          btmanup_pressed[btcidmap[cid]] = (bool)digitalRead(PIN_BASEA + 2 * btcidmap[cid]);
          btmandn_pressed[btcidmap[cid]] = (bool)digitalRead(PIN_BASEA + 2 * btcidmap[cid] + 1);
          // if both pressed, reset (no reaction)
          if (btmanup_pressed[btcidmap[cid]] && btmandn_pressed[btcidmap[cid]]) btmanup_pressed[btcidmap[cid]] = btmandn_pressed[btcidmap[cid]] = false;
          
          if (btmanup_pressed[btcidmap[cid]]) {
            btmanup_pressed[btcidmap[cid]] = false;
            if (state[cid] == STOPPED) { 
              targetLevel[cid] = 100;
            } else { // if already moving, then stop by setting target to current
              currentLevel[cid];
            }
          }
          
          if (btmandn_pressed[btcidmap[cid]]) {
            btmandn_pressed[btcidmap[cid]] = false;
            if (state[cid] == STOPPED) { // if (reference) not moving, then make go up
              targetLevel[cid] = 0;
            } else { // if already moving, then stop by setting target to current
              currentLevel[cid];
            }      
          }
    
          
          if (currentLevel[cid] > targetLevel[cid]) {   // move down (close) => means decreasing pct!
     
            if (state[cid] != DOWN) { // we just started going down
              state[cid] = DOWN;
              tstart[cid] = millis();
              lstart[cid] = currentLevel[cid];
              // mstotgt[cid] =  (currentLevel[cid] - targetLevel[cid]) * 10 * time_close[cid]; // remaining time to target
            }
    
            digitalWrite(PIN_BASE + 2 * cid, 0);        // down pin
            digitalWrite(PIN_BASE + 2 * cid + 1, 1);    // up pin
            // level change over elpased time, don't exceed targetLevel (will lead to control settle behaviour)
            currentLevel[cid] = max(lstart[cid] - (millis() - tstart[cid]) / (10 * time_close[cid]), targetLevel[cid]); 
            Serial.print("Sending percentage feedback...");
            Serial.println(currentLevel[cid]);
            send(msgPct[cid].set(currentLevel[cid]));
          } else if (currentLevel[cid] < targetLevel[cid]) {   // move up (open)
     
            if (state[cid] != UP) { // we just started going up
              state[cid] = UP;
              tstart[cid] = millis();
              lstart[cid] = currentLevel[cid];
              // mstotgt[cid] =  (currentLevel[cid] - targetLevel[cid]) * 10 * time_close[cid]; // remaining time to target
            }
    
            digitalWrite(PIN_BASE + 2 * cid, 1);        // down pin
            digitalWrite(PIN_BASE + 2 * cid + 1, 0);    // up pin
            // level change over elpased time, don't exceed targetLevel (will lead to control settle behaviour)
            currentLevel[cid] = min(lstart[cid] + (millis() - tstart[cid]) / (10 * time_open[cid]), targetLevel[cid]); 
            Serial.print("Sending percentage feedback...");
            Serial.println(currentLevel[cid]);
            send(msgPct[cid].set(currentLevel[cid]));
          } else if (currentLevel[cid] == targetLevel[cid]) {  // we always stop when the level is reached
            // we don't change the state here, because we want to send a final level value to the controller first
            digitalWrite(PIN_BASE + 2 * cid, 1);        // down pin
            digitalWrite(PIN_BASE + 2 * cid + 1, 1);    // up pin
            if (state[cid] != STOPPED) send(msgPct[cid].set(currentLevel[cid]));
            state[cid] = STOPPED;
          }
          // Serial.println("Sending current level to controller");
          // Serial.println(currentLevel[cid]);
          // Serial.println("targetLevel = ");
          // Serial.println(targetLevel[cid]);
          // if (state[cid] != STOPPED) send(msgPct[cid].set(currentLevel[cid])); // send current estimated percentage back to gateway, until stopped state reached
          // if (currentLevel[cid] != targetLevel[cid]) state[cid] = STOPPED;
        }
        wait(LOOP_DELAY_SMALL);
      }
      wait(LOOP_DELAY);
    }
    
    
    void receive(const MyMessage &message) {
    
      // the message sets the target value for each sensor.
      // as long as the target is not reached, the approriate relay pin is kept high
      // and the controller is informed every LOOP_DELAY ms about the progress
    
      // Serial.println("Message received from gateway (cid):");
      // Serial.println(message.sensor);
    
      if (message.isAck()) {
        Serial.println("This is an ack from gateway");
      }
    
      switch (message.type) {
        case V_PERCENTAGE:
          if (!initialValueSent[message.sensor]) {
            Serial.println("Receiving initial value from controller");
            initialValueSent[message.sensor] = true;
          }
          targetLevel[message.sensor] = atoi(message.data);
          targetLevel[message.sensor] = targetLevel[message.sensor] > 100 ? 100 : targetLevel[message.sensor];
          targetLevel[message.sensor] = targetLevel[message.sensor] < 0   ? 0   : targetLevel[message.sensor];
          Serial.println("Received V_PERCENTAGE from controller");
          break;
    
        case V_UP:
          targetLevel[message.sensor] = 100;
          Serial.println("Received V_UP from controller");
          break;
    
        case V_DOWN:
          targetLevel[message.sensor] = 0;
          Serial.println("Received V_DOWN from controller");
          break;
        case V_STOP:
          Serial.println("Received V_STOP from controller");
          targetLevel[message.sensor] = currentLevel[message.sensor];
          break;
      }
    
    }
    

    Good luck!


 

296
Online

7.4k
Users

8.3k
Topics

89.6k
Posts