Curtainator
We calculate the final price from your selected file. You review and accept it before payment.
Saved on this device.
Opens your curtains!
- Rating
- 0 / 5
- Likes
- 189
- Downloads
- 562
- Makes
- 0
- Views
- 12,124
- Reviews
- 0
Updated at source:
Model information
- Source
- Printables
- Category
- Other House Equipment
- License
- Creative Commons — Attribution — Noncommercial — Share Alike
This is the curtainator. It's designed to open curtains that use a rope loop on one end. I couldn't find any product like it so enjoy! I'll probably be making improvements to this as things break.
Components:
There are 3 main components of this print:
Wall Mount:
The wall mount was pretty specific to my setup. I wanted to reuse the drilled holes that the previous pulley used and I needed to get around a window sill. There's a slit in the bottom mount so the top mount can slide up and down to adjust the tension on the rope. This is kinda tricky to print so you'll probably want supports in there.
- bottom-wall-mount.stl
- top-wall-mount.stl
Gearbox:
The curtainator uses a 5:1 planetary gearbox to increase the torque of the stepper. Why a planetary gearbox? Because it looks super cool. I used 3 planets in my gearbox. The planetary gears turn a spur gear that is concave to hug the rope. It links up to another spur gear (which I'm calling the auxiliary gear) to “grab” the rope and keep it from slipping.
planetary gears:
- ring-gear.stl
- planet-gear.stl (x3)
- sun-gear.stl
rope spur gears (and related mounts):
- carrier-gear.stl
- carrier-shaft.stl (x3)
- carrier-arms.stl
- aux-gear.stl
- aux-gear-mount.stl
- aux-gear-shaft.stl
Bracket:
The bracket is what mounts to the stepper motor and keeps everything together. It uses 4 20mm M3 screws to combine the bottom bracket, ring gear, and top bracket into a fun curtainator sandwich. There's also a cap on the top that keeps the carrier gear centered.
- bracket-bottom.stl
- bracket-top.stl
- bracket-cap.stl
Printing:
I printed with prusament PLA and a 0.25 nozzle. Most things shouldn't need supports except for the bottom-wall-mount.stl and bracket-top.stl.
Non-3D printed components I used:
- A lot of M3 hex socket screws and nuts of varying lengths (6,8,12,16,20mm). The nuts should be hex, not square
- 5 608-2RS ball bearings
- Nema 17 Stepper motor. Mine was 2A with 59Ncm of torque, but get whatever works
- I also used an esp32 to control a TB6600 driver, but again get whatever works for you
- You'll also need a power supply, I used a 24v 5A power supply
All the components above were found on Amazon.
Automation?:
You can do this however you want, but I'll share what I did. I use an esp32 with esphome to control the TB6600 driver. The code for esphome is below. Once esphome is flashed, I can use home asssistant to automate the curtains however I like. Currently I open/close for sunrise/sunset and also when I start watching my TV.
substitutions:
deviceid: esp32_5
esphomename: esp32-5 # underscore in hostname is discouraged
devicename: Curtainsglobals:
- id: curtains_max
type: int
restore_value: no
initial_value: "56000"
- id: curtains_mostly_open
type: int
restore_value: no
initial_value: "46000"
esphome:
name: $esphomename
platform: ESP32
board: wemos_d1_mini32
on_boot:
priority: -100.0
then:
- stepper.report_position:
id: curtains_stepper
position: !lambda return id(curtains).position * id(curtains_max);
- stepper.set_target:
id: curtains_stepper
target: !lambda return id(curtains).position * id(curtains_max);
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).current_position;
wifi:
ssid: !secret wifi_smart_ssid
password: !secret wifi_password
fast_connect: true
domain: .home
Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: $deviceid
captive_portal:
api:
ota:
web_server:
port: 80
Enable logging to ESPHome
logger:
Disable logging to serial
baud_rate: 0
level: debug
pins that connect the esp32 to the driver
stepper:
- platform: a4988
id: curtains_stepper
step_pin: GPIO16
dir_pin: GPIO17
sleep_pin:
number: GPIO21
inverted: true
max_speed: 5000 steps/s
acceleration: 5000 steps/s^2
deceleration: 5000 steps/s^2
report the position of the curtain (in steps) to home assistant for debugging
sensor:
- platform: template
name: "Curtains Position"
id: position_sensor
pull in the status of the door sensor from home assistant
binary_sensor:
- platform: homeassistant
name: "Curtain sensor"
entity_id: binary_sensor.lumi_lumi_sensor_magnet_aq2_on_off
id: curtain_sensor
filters:
- invert:
internal: true
on_press:
then:
- stepper.report_position:
id: curtains_stepper
position: 0
- stepper.set_target:
id: curtains_stepper
target: 0
- cover.template.publish:
id: curtains
position: 0.0
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).target_position;
cover:
- platform: template
name: "Curtains"
id: curtains
device_class: curtain
has_position: true
open_action:
the first open command will only open partially, the second will open all the way
- if:
condition:
- lambda: return id(curtains_stepper).target_position >= id(curtains_mostly_open);
then:
- stepper.set_target:
id: curtains_stepper
target: !lambda return id(curtains_max);
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).target_position;
- if:
condition:
- lambda: return id(curtains_stepper).target_position < id(curtains_mostly_open);
then:
- stepper.set_target:
id: curtains_stepper
target: !lambda return id(curtains_mostly_open);
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).target_position;
close_action:
- if:
condition:
- binary_sensor.is_off: "curtain_sensor"
then:
- stepper.set_target:
id: curtains_stepper
target: -1000
stop_action:
- stepper.set_target:
id: curtains_stepper
target: !lambda return id(curtains_stepper).current_position;
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).current_position;
- cover.template.publish:
id: curtains
position: !lambda return (float)id(curtains_stepper).current_position / (float)id(curtains_max);
position_action:
- stepper.set_target:
id: curtains_stepper
target: !lambda return pos * id(curtains_max);
- sensor.template.publish:
id: position_sensor
state: !lambda return id(curtains_stepper).target_position;
if we only open the curtains partially, publish that
on_open:
- if:
condition:
- lambda: return id(curtains_stepper).target_position == id(curtains_mostly_open);
then:
- cover.template.publish:
id: curtains
position: !lambda return (float)id(curtains_mostly_open) / (float)id(curtains_max);
optimistic: true
assumed_state: true