Siri enabled Smart Curtains

Using a Raspberry Pi as a MQTT server running homekit2mqtt controlling a Wemos D1 Mini as a controller for a steppermotor



Created by: Timothy Puglia, 2020-01-23


Fully functioning Homekit/Siri enabled smart curtains retrofitted to my existing curtain rail. Running homekit2mqtt on a Raspberry Pi 3 (https://github.com/hobbyquaker/homekit2mqtt) which controls a simple circuit that couldn’t be easier; 12V in over a very long speaker wire, hooked up to the a DRV8255 stepper driver and a MINIDC-DCSD2A to step down to 5V to power the Wemos and an end-stop if anything goes wrong.

Motor has rubber tape around the sides to reduce vibration (45dB opening, 52dB closing), although that does mean that the stepper motor shaft is at a slight angle. It doesn’t cause any issues however. It is all fitted in a 3D printed case, could be space optimised, but the project already took long enough. Everything mounted with industrial tape to the wall. It uses standard a standard GT2 pulley and belt on both ends.

I used ArduinoOTA to update and finetune the code. Also the homekit2mqtt landing page gives you control over a bunch of settings. Very neat. I have it set in homekit to open at sunrise on every day of the week except the weekend and Wednesday. It hasn’t failed on me once during the couple of months it is in use.

At the end of the video, the curtain does a quick correction to release the tension in the belt that builds up when opening. This makes sure it stays in the correct position when the power is removed from the stepper motor.

And we have light:

Here is the code:

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char *ssid = “Wifi SSID“;    // cannot be longer than 32 characters!
const char *pass = “written by an industrial design engineer, yes we can write code too :P;
const char* host = "rightCurtain";
//Local IP = 10.0.1.22

#define microStepPin D6
#define dirPin D3
#define stepPin D2
#define slpPin D1
#define endStopPin D5

int statusCurrentPosition, setTargetPosition;
int currentTimer;
int motorSpeed = 550;
int previousTimer = 0;
int startDelay = 2000;
int endStopVal;
int overShootValue = 4000;
int overShoot = 200;

bool trigger = false;

// Update these with values suitable for your network.
IPAddress server(10, 0, 1, 5);

#define BUFFER_SIZE 100

void callback(const MQTT::Publish& pub) {

  //  Callback is om over topics te luisteren
  String myMessage = String(pub.payload_string());
  //handle message arrived
  Serial.print(pub.topic());
  Serial.print(" => ");
  String myTopic = String(pub.topic());

  endStopVal = digitalRead(endStopPin);
  if (myTopic == host + (String)"/setTargetPosition" && endStopVal == HIGH)  {
    trigger = true;
    setTargetPosition = myMessage.toInt();
    Serial.println(pub.payload_string());
    previousTimer = currentTimer;
  }
}

WiFiClient wclient;
PubSubClient client(wclient, server);

void setup() {
  // Setup console
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(slpPin, OUTPUT);
  pinMode(endStopPin, INPUT);
  pinMode(microStepPin, OUTPUT);
  WiFi.mode(WIFI_STA);
  delay(5000);
  client.publish("rightCurtainAlert", "0");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_SPIFFS
      type = "filesystem";
    }

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");
    }
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);

    if (WiFi.waitForConnectResult() != WL_CONNECTED)
      return;
    Serial.println("WiFi connected");
  }

  if (WiFi.status() == WL_CONNECTED) {
    if (!client.connected()) {
      if (client.connect("arduinoClientRightWindowCurtain")) {
        client.set_callback(callback);
        client.subscribe("rightCurtain/#"); // change InTopic to your Topic description
      }
    }

    if (client.connected())
      client.loop();
  }

  ArduinoOTA.handle();

  int val = digitalRead(slpPin);
  if (val == HIGH) {
    digitalWrite(slpPin, LOW);
  }

  currentTimer = millis();
  if ((currentTimer - previousTimer) >= startDelay && trigger == true) {
    client.publish("rightCurtainAlert", "0");

    if (statusCurrentPosition < setTargetPosition) {
      digitalWrite(slpPin, HIGH);
      delay(100);
      digitalWrite(dirPin, HIGH);
      for (int i = statusCurrentPosition; i < setTargetPosition; i++) {
        endStopVal = digitalRead(endStopPin);
        if (endStopVal == LOW) {
          client.publish("rightCurtainAlert", "1");
          statusCurrentPosition = setTargetPosition;
          trigger = false;
          break;
        }
        digitalWrite(stepPin, HIGH);
        statusCurrentPosition += 1;
        Serial.println(statusCurrentPosition);
        delayMicroseconds(motorSpeed);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(motorSpeed);
        yield();
      }
      client.publish("rightCurtain/statusCurrentPosition", String (statusCurrentPosition));
      delay(1000);
      digitalWrite(slpPin, LOW);

    } else if (statusCurrentPosition > setTargetPosition) {
      digitalWrite(slpPin, HIGH);
      delay(1000);
      digitalWrite(dirPin, LOW);
      for (int i = statusCurrentPosition; i > setTargetPosition; i--) {
        endStopVal = digitalRead(endStopPin);
        if (endStopVal == LOW) {
          client.publish("rightCurtainAlert", "1");
          statusCurrentPosition = setTargetPosition;
          trigger = false;
          break;
        }
        digitalWrite(stepPin, HIGH);
        statusCurrentPosition -= 1;
        Serial.println(statusCurrentPosition);
        delayMicroseconds(motorSpeed);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(motorSpeed);
        yield();
      }
      client.publish("rightCurtain/statusCurrentPosition", String (statusCurrentPosition));
      delay(1000);
      digitalWrite(slpPin, LOW);
    }

    if (setTargetPosition > overShootValue && trigger == true) {
      minimumReset();
      delay(1000);
      digitalWrite(slpPin, LOW);
    }

    trigger = false;
    previousTimer = currentTimer;
  }
}

void minimumReset() {
  digitalWrite(slpPin, HIGH);
  delay(100);
  digitalWrite(dirPin, HIGH);

  for (int i = 0; i < overShoot; i++) {
    endStopVal = digitalRead(endStopPin);
    if (endStopVal == LOW) {
      client.publish("rightCurtainAlert", "1");
      trigger = false;
      break;
    }
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(motorSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(motorSpeed);
    yield();
  }

  if (trigger == true) {
    digitalWrite(dirPin, LOW);
    for (int i = 0; i < overShoot; i++) {
      endStopVal = digitalRead(endStopPin);
      if (endStopVal == LOW) {
        client.publish("rightCurtainAlert", "1");
        trigger = false;
        break;
      }
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(motorSpeed);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(motorSpeed);
      yield();
    }
  }
}