Introduction: Smart Balcony Shade Using NFC

About: I’m a software engineer, a father and a master of fixing things that aren’t really broken.

These past few months I spent mostly at home, as everyone else, and decided to take on a project to improve my time there. I was spending a lot of time in our balcony and got really annoyed by lowering and raising the shade every day. I had to do something!

Automating shades/blinds is a common project, but pretty much all the examples I found so far operated a bedroom window shade by pulling a chain or rotating a small twist shaft. My balcony shade is big and heavy, 8 feet wide and weighs 8 pounds, and it uses a crank and a wand to operate, not the typical mechanism other projects automated. I decided to challenge myself to the task!

I found a strong motor, designed a circuit and programmed a microcontroller to run the show. I mounted a switch box with up/down buttons and used NFC to support “shade presets”. Finally, I got everything connected to the internet and respond to voice commands from Google Assistant.

It was quite a journey. At the end, I had a polished Shade Controller that works reliably and safe for everyone to use, including my kids.

Here's a quick overview of what the next steps will be about:

  • Finding a suitable motor and figuring out how to attach it to the shade.
  • Building the circuit and testing the prototype in my balcony.
  • Taking automation to the next level, by using NFC (Near-Field Communication).
  • Designing a PCB and improving the appearance of the shade controller.
  • Supporting voice commands using Google Assistant.
  • Tips and lessons learned

Supplies

The source files are available on Github, including the Fritzing diagrams, PCB schematic and Gerber files.

The full parts list:

Step 1: Finding a Suitable Motor

My balcony shade is pretty heavy, which made the motor selection especially important. I had to find something strong enough to move this shade.

There are ways I could use to measure the force needed to move the shade (like using a luggage scale), but I ended up consulting my mechanical engineer brother, who helped me pick a motor that should be strong enough for the job. I found a Servo motor that's strong by itself, but it also comes inside a gearbox that increases its torque (and I could use all the torque I can get!). The gearbox has 2 gears in a 2:1 ratio, which produces half the speed of the original servo motor, but with twice the torque. To close or open the shade I need to rotate the crank 60 times, so with the 31 RPM the motor can do - it’ll take about 2 minutes. That’s reasonable enough.

Connecting the motor to the crank and rotating it was also not trivial. I took some measurements, but I don't have a 3D printer so I can't just print a perfect adapter. Luckily, after some online searching I found a bracket that fits perfectly! To celebrate, I did an initial test to verify the motor can actually move the shade in both directions:

Sweet!

Step 2: Mounting the Motor

It's important to mount the motor in a way that produces a smooth rotation of the crank. Aligning the motor's rotation with the crank's rotation was found to be quite a challenge. Any misalignment between the rotation planes of the motor and the crank could cause friction that damages the shade's mechanism or the motor.

Since I don't have a 3D printer, I had to improvise a setup with existing structural components. I found ServoCity, a well-organized website that sells parts for projects like this. On the site, I found and ordered a bunch of different structural components that might help me. I used various metal plates and brackets to mount the motor in different ways, testing for each one the crank's rotation and making adjustments. After some trial and error, and a bunch of new holes in my wall, I eventually found an arrangement that worked.

Step 3: Building the Shade Controller Circuit

I wanted to start small - building a circuit with 2 buttons that can spin the motor in both directions. That was a great excuse to start tinkering with Arduino, something I wanted to do for a while.

The code is pretty simple - each button is connected to a pin on the Arduino, and the code always reads from those pins to detect when a button is pressed.

void loop() {
  // Reading the value in the pins connected to the buttons.
  // Normally returns HIGH, while pressing the button it returns LOW
  btnUpCurr = digitalRead(PIN_UP_BTN);
  btnDownCurr = digitalRead(PIN_DOWN_BTN);

  // The UP button is pressed 
  if (btnUpCurr == LOW && btnUpPrev == HIGH) {
    // If the motor is already spinning - stop it
    if (raisingShade || loweringShade) {
      stopShade();
    } else {
      raiseShade();
    }
  }  

  // The DOWN button is pressed  
  if (btnDownCurr == LOW && btnDownPrev == HIGH) {
    if (raisingShade || loweringShade) {
      stopShade();
    } else {
      lowerShade();
    }
  }
}

The motor is also connected to a pin on the Arduino, controlling it is done with an API that's part of the Servo Arduino library.

void lowerShade() {
  myServo.attach(PIN_SERVO);
  // Value smaller than 1500 will spin the motor counter-clockwise
  myServo.writeMicroseconds(850);  
  loweringShade = true;
  raisingShade = false;
}

void raiseShade() {
  myServo.attach(PIN_SERVO);
  // Value larger than 1500 will spin the motor clockwise
  myServo.writeMicroseconds(2100);  
  raisingShade = true;
  loweringShade = false;
}

Internally, the servo library is controlling the motor by sending it signals in "different formats", which contain the position and direction the motor should be spinning. For more on that, check out how Pulse-Width Modulation works.

After testing and verifying this simple circuit works, I decided to make the switch from an Arduino board to a much smaller and powerful microcontroller - the ESP32.

Developing to ESP32 is very similar to an Arduino board, since both use the Arduino IDE and share many core libraries. ESP32 supports WiFI, which will be useful in a later step, but its small size is also a big factor and contributes to a smaller footprint by my shade controller. In the next step, I'll use the ESP32 at the core of the first prototype.

Step 4: Creating the First Prototype

In order to test the shade controller in my balcony, I created a more sustainable circuit on a perfboard, instead of the breadboard I was using so far. I updated the circuit to work with a single battery and soldered all the components on the perfboard (see diagram). I also added a 2-button remote, for a better accessibility when trying to operate the shade from my balcony chair.

The motor can take up to 7.4V, according to its spec, which is why I'm using a 7.4V battery that's connected directly to the motor (Higher voltage = more torque). However, the ESP32 is more sensitive and could be damaged by a voltage too high. To be on the safe side, I added a DC step-down convertor (a.k.a Buck Convertor or BEC - Battery Eliminator Circuit) which converts the battery’s 7.4V to a steady 5V that powers the ESP32.

I used the prototype for a few weeks and it was great! Operating the shade was super convenient and the motor raised that heavy shade like a champ!

There was only one problem - a full battery lasted only 5 days. It's a pretty strong battery, with high capacity, so I did some calculations to better understand what's happening: The motor draws between 200mA-3000mA while running, but the majority of time the ESP32 is on standby and draws 50mA. The battery I have is 6200mAh, meaning it can power a 50mA device for 124 hours (6200 / 50 = 124), which is a little over 5 days. This aligns with what I saw in practice and explains why the battery doesn't last that long.

I decided that the best solution is to simply ditch the battery. I added a barrel-jack port to my circuit and got a power adapter that supplies 7.5V, now I don't need to worry about sitting in the sun because I forgot to charge the battery in time.

Here's the prototype in action:

Step 5: Supporting Automatic Stops Using NFC

It takes a single button press to start lowering/raising the shade, but I still need to wait and press the button again in order to stop it. I wanted to take this automation to the next level and have the shade controller stop automatically when the shade is at position. But how will it know when to stop?

There are a few ways the shade controller could know when to automatically stop. The simplest one is to measure the duration it takes to fully open/close the shade, then use that as a time limit every time it raises/lowers the shade. This is not a very reliable way because the duration could skew over time and require frequent calibrations (e.g strong winds is one possible cause). Another option is to stick 2 magnets on the shade itself, one at the top and the other at the bottom, then use a Hall effect sensor to detect these magnets and stop the motor from rolling the shade past these points. While this could work for simple use cases, it won’t prevent moving the shade past the boundaries the magnets created. For example, pressing “down” will lower the shade until the magnet is reached, but afterwards pressing “down” again would lower the shade further, past the lowest position allowed.

I decided to try an interesting approach - using NFC to read position markers on the shade. The problem with the magnets approach is the fact they aren’t distinguishable, so the shade controller can't know which magnet is caught by the Hall effect sensor. Using an NFC reader, the shade controller can read NFC tags placed on the shade in different places and decide whether to stop or continue raising/lowering the shade. The NFC tags are distinguishable by their ID, but they also allow writing data in them, which can be used for storing in each NFC tag the position it represents (e.g. "100% open", "50% open").

The PN532 is the most popular NFC/RFID chip. Pretty much all the mobile phones who support NFC use this chip, used for things like wireless payments or sharing data between nearby devices. I connected the PN532 to the main shade controller circuit using the I2C communication protocol (see diagram).

Working with the PN532 NFC module was relatively straightforward. I used the Adafruit PN532 library, which has a great API to communicate with the NFC module, but there was a noticeable problem - the command for waiting for an NFC tag is a blocking call. That means that while the ESP32 waits for NFC tags - it can't do anything, like reacting to button presses. Since I wanted to be able to stop the shade by pressing a button, I looked for a solution. To overcome this, I added a couple of new API methods to the open-source PN532 library, which provide the same functionality but in a non-blocking way. I updated the shade controller to listen for NFC tags, using the new API, while the shade is lowered/raised and still be responsive to the buttons:

  // The DOWN button is pressed
  if (btnDownCurr == LOW && btnDownPrev == HIGH) {
    if (raisingShade || loweringShade) {
      stopShade();
    } else {
      lowerShade();

      // Puts the NFC reader on a listening mode, a NON-BLOCKING call
      nfc.startPassiveTargetIDDetection(PN532_MIFARE_ISO14443A);
      listeningToNFC = true;
    }
  }

During the time it waits for NFC tags, the shade controller constantly monitors the value of the IRQ pin on the NFC module (the yellow wire on the diagram above). A change in that pin is a way for the PN532 to signal the microcontroller it has some response, so when IRQ goes from HIGH to LOW - we should try reading the detected NFC tag/card info.

  irqCurr = digitalRead(PN532_IRQ);

  // If the NFC reader is currently listening and we’re notified via the
  // IRQ line that a card was detected
  if (listeningToNFC && irqCurr == LOW && irqPrev == HIGH) {
    // Read the detected NFC tag's info
    success = nfc.readDetectedPassiveTargetID(uid, &uidLength);

    if (success) {
      // If the tag is valid, we'll read its ID
      uint32_t cardId = getCardId(uid, uidLength);
      if (cardId == CARDID_0_PERCENT) {
        if (loweringShade) {
          // Found the "0% open" position tag while lowering the shade, 
          // we can stop because the shade is now fully closed
          stopShade();
          listeningToNFC = false;
        }
      } else if (cardId == CARDID_100_PERCENT) {
        // Similar implementation as above.
        // Stop raising the shade when the CARDID_100_PERCENT tag is detected, 
        // since the shade is now fully open.
      }
    }
  }<br>

The new API methods I added to the PN532 library were contributed back to the open-source library and are now available publicly (see the pull request I made in Github for more info).

With these changes, the up/down buttons are always responsive and the motor can be stopped either by pressing any of the buttons or when the NFC reader is reading the appropriate NFC tag. The next video demonstrate this in my development environment, note how I use a different NFC tag to stop each spin direction:

Step 6: Mounting the NFC Module

To test this NFC-based positioning system on my balcony shade, I needed to hang the NFC module close to the shade. In order to read an NFC tag, the NFC module needs to be up to 2 inches away from it.

I got a plastic sheet from Amazon, mounted it on the wall using L brackets and attached the NFC module on its back side. The shade rests on this plastic sheet as it moves up or down, giving the NFC module a chance to read any NFC tags that are on the shade.

Using another instructable, I learned how to curve the lower part of the plastic sheet. This helps the shade move more smoothly.

For my first test, I attached an NFC tag on the shade using 2 of my daughter’s hair clips (I couldn’t find a stapler ¯\_(ツ)_/¯ ), and verified the shade controller stops the motor after detecting the NFC tag. Here's how that test went:

Step 7: Hardware Facelift

Before adding more software features, I decided to spend some time polishing the hardware side, making it more user friendly and sustainable.

First thing I did is replacing the remote with a waterproof switch box. I organized all the wires inside cable channels to protect them from the rain, and more importantly - my kids. Besides the robustness of the new switch box, it’s also a lot nicer than that hacky-looking remote.

I also moved the main circuit and the NFC module to reside in the same waterproof box. I hung that box on the plastic sheet, so the NFC module can be close enough to read NFC tags attached to the shade. After this change I was able to remove the old main-circuit box that was mounted on the wall, and by that reducing the shade controller's footprint in my balcony,

While I was doing that, I figured I could reduce the footprint even further by trimming the plastic sheet, since I don't really need it to be this big. I took a hacksaw and removed a significant chunk of it.

Now I have the same shade controller, just better looking and with a much smaller footprint. This will be useful when I’ll need to explain all this to my wife

Step 8: Migrating to a PCB

Now that I’m done with hardware changes, at least for the near future, a PCB would look a lot nicer and reduces the amount of loose wires that could be damaged accidentally.

I never designed PCBs before, but it was simpler than I thought. I found EasyEDA, which is an online tool for designing PCBs. I created a schematic design for my circuit and used it to generate a PCB design. The design has all the components connected to each other, so I just moved components around to arrange them a little better, added labels and other small tweaks. When I was done, I ordered the PCBs directly from the tool (there are 6 colors to choose from!) and after about a week I got the PCBs.

Soldering all the components didn't take long and the result is a slick new shade controller circuit.

Step 9: Controlling the Shade From the Web

This is the change that upgrades the shade controller to be a “smart shade controller”.

Before we can get Google Assistant to send commands to the shade controller, we need to have a way to communicate with it. The ESP32 has WiFi support and can connect to the home router for internet access. The most common protocol for communicating with IoT and embedded devices is MQTT - a lightweight messaging protocol. The concept is simple - have a server (called MQTT Broker) that manages different feeds (called "topics"), and each client will receive the messages sent to the feeds it's subscribed to.

To set up MQTT for my shade controller, I can create an MQTT server in my local network (using Mosquitto) or use a cloud service such as Adafruit IO and Blynk. My needs are very simple and the free tier of a cloud service should suffice. I picked Adafruit IO, set up my account and added a new feed for my project.

I updated my code to use the Adafruit IO library in order to subscribe to a specific feed, called "shade-open", which represents how much the shade should be open. A value of "100" posted to that feed, means the shade should be 100% open.

AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS);

// Adafruit IO feed for how much the shade should be open (either 0% or 100%)
AdafruitIO_Feed *balconyShade = io.feed("shade-open");

boolean connectingInProgress = false;

void loop() {
  // Keep the connection alive, if already connected.
  // Times-out after 400ms to keep the microcontroller responsive.
  aio_status_t ioStatus = io.run(400, true);

  // Connect to Adafruit IO, if connected then subscribe to the feed
  if (ioStatus < AIO_CONNECTED && !connectingInProgress) {
    connectingInProgress = true;
    io.connect();       
  } else if (ioStatus >= AIO_CONNECTED && connectingInProgress) {
    connectingInProgress = false;
	
    // Subscribe to the "shade-open" feed
    balconyShade->onMessage(handleShadeLevelMessage);
  }
  // ...
}<br>

The connectivity to the cloud service is maintained by the io.run() call, which is part of the main loop function. When a new message is posted to the "shade-open" feed, by some other client who's also connected to the cloud service, then the callback function handleShadeLevelMessage() is called.

// This function is called when a new message is sent to the “shade-open” feed.
void handleShadeLevelMessage(AdafruitIO_Data *data) {
  // Got a new message with the percent the shade should be open 
  int percentOpen = atoi(data->value());
  
  // If we're asked to open the shade to 100%, then raise it. 
  // Open to 0% means closing/lowering the shade.
  if (percentOpen == 100) {
    raiseShade();
  } else if (percentOpen == 0) {
    lowerShade();
  }
  // ...
}

The callback function reads the new message and raises or lowers the shade accordingly.

After adding this integration with Adafruit IO, I went to their web console and posted messages to the "shade-open" feed, in order to test the integration works. And it did! The shade controller moved the shade based on the message I posted. The next step will add a much simpler way to post message to that feed.

Step 10: Supporting Voice Commands Via Google Assistant

This was probably the easiest part of this entire project.

The shade controller can already take commands from the web using MQTT, so if we want Google Assistant to control it - we need to connect the two.

IFTTT (If-this-then-that) is a great tool for connecting different web services and products to each other. It already supports integrations with Google Assistance and Adafruit IO, so all I needed to do is authenticate to both services and create rules (called “Applets”) to communicate between them.

For controlling the shade controller, I created an applet that reacts to a Google Assistant command for controlling the shade, and sends a message to the "shade-open" feed on my Adafruit IO account. e.g. when I say “OK Google, Open the balcony shade”, IFTTT will send the message "100" to that feed. The shade controller receives this message through Adafruit IO and raises the shade until it reaches the NFC tag that signals the shade is 100% open.

Here’s everything in action:

Nice!

Step 11: Tips and Hard Learned Lessons

I learned a lot of new things in this project, not all about electronics, and I think the best way to learn is to simply get your hands dirty and dig deeper as problems arise. I figured it’s worth mentioning a few quick tips from my experience working on this project, some I learned the hard way:

While I knew I wanted to add NFC support from the beginning, I went with an iterative approach - started with a simple circuit and made changes in steps. This might take a bit longer than completing the vision in one go, but it helps seeing and fixing problems early, before they become bigger and harder to pinpoint. The battery life problem is a good example for something I didn’t put too much thought into, but I was able to fix it before getting into the more complex NFC issues that came later.

I also recommend having a simple way to use the circuit in a development environment. After I mounted the motor and the switch box, I was still able to take the main circuit to my desk and debug problems using a simpler motor and remote. I also added a flag in the code for disabling the NFC, so I won’t have to connect an NFC module in my development environment unless I need to. This was really convenient and greatly improved my development process.

Breadboards can be unreliable, take that into account when testing your circuits. I spent hours on a problem with the NFC module, which turned out to be a result of jumper cables that got momentarily disconnected and caused weird state issues. If the circuit is not expected to change much, consider doing it on a perfboard.

Soldering is an important skill and having the right tools can make a big difference. My circuits looked a lot more professional and less frustrating to make after I got me a helping hand - a soldering tool with arms for holding the circuit and other components while soldering. It also includes a strong light and magnifying glass.

One more soldering tip - some components are just hard to solder straight (I'm looking at you headers!). I learned I can use sticky mounting tabs to steadily position the components while soldering them. Really useful and made my circuits look more professional.

There was a lot of drilling in this project, and I learned a lot about types of surfaces and drills, but I think the highlight is getting to know the Step Drill for drilling perfect round holes in plastic boxes. I found out about this awesome drill bit after spending way too much time drilling holes in the switch box, for inserting the 2 up/down buttons. The Step drill can make the same holes in seconds, and perfectly round too. I think it's a must for projects like this.

Step 12: Final Thoughts and Resources

It was really fun working on this project and I hope my experience helps others undertake similar projects.

I wrote a more detailed blog post about my experience, where I expand on the technical details of the Shade Controller and the decisions leading me to designing it this way.

The full source code is available in GitHub, along with all the diagrams and PCB designs (including Gerber files):

https://github.com/Udinic/smartShadeController

Thanks for reading and stay creative!

P.S. If you liked this article - please consider voting for it below :)