I used Claude to write this because I’m tired.
Next steps are making sure solar panel maintains the battery. Figuring out how to connect the motor to the Coolaro winding mechanism. Easy to roll down, requires more power to roll up.
Parts list
Motor & drive:
- StepperOnline 23HS30-2804S — NEMA 23, 1.8°/step, 2.8A/phase (same class of motor AutoShade’s own manual specifies — theirs is the 23HX30-2804S)
- TB6600 stepper driver
- Flex coupling: 6.35mm (motor shaft) to 0.325" ± 0.005" (Coolaroo worm gear shaft) — this is a nonstandard bore size; I used an 8mm-bore coupling and reamed it out slightly
Controller:
- Seeed XIAO ESP32C6 (pre-soldered), running ESPHome
Power system:
- SOLPERK 30W solar panel + MPPT controller kit
- 12V 7Ah SLA battery
- MOSFET switching module (opto-isolated, 3.3V-logic compatible) — gates power to the TB6600 so it’s only energized during actual moves, not sitting idle 24/7
- 12V-to-5V USB-C buck converter, powering the XIAO off the same battery
Enclosure:
- IP65 weatherproof junction box, cable glands, step drill bit for gland holes
Wiring
Motor → TB6600: Black→A+, Green→A-, Red→B+, Blue→B- (confirmed via multimeter continuity: Black-Green and Red-Blue are the two winding pairs)
TB6600 DIP switches: SW1=ON/SW2=OFF/SW3=OFF (4 microstep), SW4=OFF/SW5=OFF/SW6=ON (2.8A current)
XIAO → TB6600 signal side: GPIO1→PUL-, GPIO2→DIR-, GPIO21→ENA-, XIAO 3V3→PUL+/DIR+/ENA+ (common-anode driver — confirmed against the driver’s own silkscreen labels, which read “+5V” next to each + signal pin)
Power chain: Solar panel → MPPT controller → SLA battery → splits to (1) MOSFET module → TB6600 VCC, and (2) buck converter → XIAO USB-C. MOSFET’s negative side and the XIAO/driver signal ground all tie to one common ground network.
MOSFET control: XIAO GPIO23 → module’s control input, XIAO GND → module’s control ground (originally used GPIO4, which triggered an ESPHome strapping-pin warning and was moved to GPIO23)
ESPHome config
yaml
esphome:
name: front-porch-shade
esp32:
board: seeed_xiao_esp32c6
variant: esp32c6
framework:
type: esp-idf
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
ota:
platform: esphome
logger:
stepper:
- platform: a4988
id: shade_stepper
step_pin: GPIO1
dir_pin: GPIO2
sleep_pin: GPIO21
max_speed: 200 steps/s
acceleration: 200 steps/s^2
sensor:
- platform: template
name: "Stepper Current Position"
lambda: |-
return id(shade_stepper).current_position;
update_interval: 1s
number:
- platform: template
name: "Shade Position"
id: shade_target_ft
min_value: 0
max_value: 9
step: 0.25
optimistic: true
on_value:
then:
- script.execute:
id: move_shade
target_ft: !lambda "return x;"
button:
- platform: template
name: "Set Shade Zero (fully up)"
on_press:
- stepper.report_position:
id: shade_stepper
position: 0
- stepper.set_target:
id: shade_stepper
target: 0
- number.set:
id: shade_target_ft
value: 0
- platform: template
name: "Stop Shade"
on_press:
- stepper.set_target:
id: shade_stepper
target: !lambda "return id(shade_stepper).current_position;"
- number.set:
id: shade_target_ft
value: !lambda "return id(shade_stepper).current_position / 8496.0;"
switch:
- platform: gpio
pin: GPIO23
id: driver_power
name: "Driver Power"
script:
- id: move_shade
parameters:
target_ft: float
then:
- switch.turn_on: driver_power
- delay: 300ms
- stepper.set_target:
id: shade_stepper
target: !lambda "return target_ft * 8496;"
- wait_until:
condition:
lambda: "return id(shade_stepper).current_position == id(shade_stepper).target_position;"
timeout: 90s
- delay: 200ms
- switch.turn_off: driver_power
Position scaling: AutoShade’s own firmware (dug out of an old .ino they still host) uses 2124 full steps per foot of shade travel for this worm gear mechanism. At 4x microstepping that’s 8496 microsteps/ft, which is what the number entity’s lambda uses.
Current status
The battery powers the XIAO continuously (via buck converter) and the TB6600 on-demand (via the MOSFET switch, only energized during a move), and the shade responds correctly to position commands from Home Assistant.