From c5ffbbd3037433cf3c75bdba649ce0dab78c9286 Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Sun, 28 Jun 2026 18:13:55 +1200 Subject: [PATCH] Edit esp-astroclockstepper1.yaml --- esphome/esp-astroclockstepper1.yaml | 225 ++++++++++++++++++++-------- 1 file changed, 165 insertions(+), 60 deletions(-) diff --git a/esphome/esp-astroclockstepper1.yaml b/esphome/esp-astroclockstepper1.yaml index 3d1a272..bfccb42 100644 --- a/esphome/esp-astroclockstepper1.yaml +++ b/esphome/esp-astroclockstepper1.yaml @@ -6,6 +6,7 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-astroclockstepper.yaml #:########################################################################################:# # VERSIONS: +# V1.4 2026-06-22 Added Motor Stop switch, swapped reverse buttons 3/4, and separated auto-position motor direction # V1.3 2026-06-22 Added SNTP year auto-positioning button and restored boot auto-position switch # V1.2 2026-06-22 Added DHCP IPv4 address flash sequence on onboard LED after WiFi connects # V1.1 2026-06-22 Added onboard D1 Mini LED flash on each motor step @@ -27,20 +28,23 @@ # Physical Buttons: # - Fast Forward: GPIO5 / D1 # - Slow Forward: GPIO4 / D2 -# - Fast Reverse: GPIO0 / D3 -# - Slow Reverse: GPIO16 / D0 +# - Slow Reverse: GPIO0 / D3 +# - Fast Reverse: GPIO16 / D0 # # Button board: # - Uses built-in 103 pull-up resistors, 10k ohm # - Buttons should pull the GPIO pin to GND when pressed # - Pull-ups must go to 3.3V, not 5V +# - Fast modes are on the two end buttons # # Onboard Step LED: # - GPIO2 / D4 # - Active-low on most D1 Mini boards #:########################################################################################:# # OPERATION NOTES: -# - Normal rotation starts automatically after boot. +# - Normal rotation starts automatically after boot only when Motor Stop is OFF. +# - Motor Stop is ON by default after every reboot and is not restored. +# - Motor Stop turns off all motor coils and pauses physical position counting. # - Default rotation is one full revolution every 365.2422 days. # - Default step count is 4096 steps per full revolution. # - Normal movement is extremely slow, about one step every 2.14 hours. @@ -55,8 +59,9 @@ # - A digit of 0 is represented as 10 flashes. # - SNTP is used for current year-position calculation. # - User should manually align the dial to year-zero, then press Zero Position Counter. -# - Auto Position Now moves forward only to the current calculated year position. -# - Auto Position On Boot is off by default, restored on power loss, and runs after boot/IP flash. +# - Auto Position Now moves forward logically to the current calculated year position. +# - Auto-position motor direction is separately adjustable from normal/manual movement. +# - Auto Position On Boot is off by default, restored on power loss, and runs after boot/IP flash if Motor Stop is OFF. #:########################################################################################:# # OFFLINE NOTES: # a) Home Assistant OFFLINE, but Network ONLINE @@ -64,16 +69,16 @@ # - HA entities unavailable until HA returns # b) WiFi/Network OFFLINE # - No HA API or web control available -# - Physical buttons continue to work -# - Normal motor timing continues from device uptime +# - Physical buttons continue to work if Motor Stop is OFF # - Fallback AP / captive portal should become available from network_common_dhcp.yaml # c) SNTP NOT SYNCED -# - Normal motor timing continues -# - Manual controls continue +# - Normal motor timing continues if Motor Stop is OFF +# - Manual controls continue if Motor Stop is OFF # - Auto Position Now will not run until SNTP time is valid # d) Device power loss / reboot # - Current position counter is restored from flash # - Auto Position On Boot setting is restored from flash +# - Motor Stop returns to ON after every reboot # - Physical position is assumed valid because the motor housing/dial is fixed #:########################################################################################:# @@ -91,7 +96,7 @@ substitutions: # Project Naming project_name: "zorruno.Astronomical Clock Stepper" - project_version: "v1.3" + project_version: "v1.4" # Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names) api_key: !secret esp-api_key @@ -110,10 +115,15 @@ substitutions: steps_per_rotation: "4096" normal_direction_name: "Clockwise" - # Use 1 for normal direction. - # Use -1 if the motor rotates the wrong way. + # Normal/manual motor direction. + # Use 1 or -1 if normal/manual movement rotates the wrong way. normal_direction_multiplier: "1" + # Auto-position motor direction. + # This is separate so auto-position can move clockwise while still counting logically forward. + # Set to -1 because auto-position was observed to be running anti-clockwise. + auto_position_motor_direction_multiplier: "-1" + # Manual Movement Speeds manual_fast_rotation_seconds: "30" manual_slow_rotation_seconds: "300" @@ -151,10 +161,12 @@ substitutions: # Physical Button Input Pins button_fast_forward_pin: GPIO5 button_slow_forward_pin: GPIO4 - button_fast_reverse_pin: GPIO0 - button_slow_reverse_pin: GPIO16 + button_slow_reverse_pin: GPIO0 + button_fast_reverse_pin: GPIO16 # Entity Naming + motor_stop_name: "Motor Stop" + web_fast_forward_name: "Web Fast Forward" web_slow_forward_name: "Web Slow Forward" web_fast_reverse_name: "Web Fast Reverse" @@ -203,9 +215,12 @@ packages: #common_webportal: !include common/webportal_common.yaml common_webportal: !include common/webportal_common_nopasswd.yaml + #### SNTP (Only use if you want/need accurate timeclocks) #### + #common_sntp: !include common/sntp_common.yaml + #### DIAGNOSTICS Sensors #### diag_basic: !include common/include_basic_diag_sensors.yaml - #diag_more: !include common/include_more_diag_sensors.yaml + diag_more: !include common/include_more_diag_sensors_nomqtt.yaml #diag_debug: !include common/include_debug_diag_sensors.yaml #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml @@ -370,6 +385,7 @@ globals: # 3 = Fast Reverse # 4 = Slow Reverse # 5 = Auto Position + # 6 = Motor Stopped - id: current_motion_mode type: int restore_value: no @@ -594,6 +610,11 @@ script: return; } + if (id(motor_stop_switch).state) { + ESP_LOGW("auto_position", "Motor Stop is ON. Auto position cancelled."); + return; + } + const int year = now.year; const int month = now.month; const int day_of_month = now.day_of_month; @@ -677,7 +698,7 @@ script: ESP_LOGI( "auto_position", - "Auto positioning started. Year: %d, day_of_year: %d/%d, target step: %d, current step: %d, moving forward %d steps.", + "Auto positioning started. Year: %d, day_of_year: %d/%d, target step: %d, current step: %d, moving forward %d logical steps.", year, day_of_year, days_in_year, @@ -689,7 +710,7 @@ script: #:########################################################################################:# # SWITCH: -# Web held-button controls and restored auto-position boot option +# Motor stop, web held-button controls, and restored auto-position boot option # https://esphome.io/components/switch/template.html #:########################################################################################:# switch: @@ -772,6 +793,47 @@ switch: optimistic: true restore_mode: RESTORE_DEFAULT_OFF + - platform: template + name: "${motor_stop_name}" + id: motor_stop_switch + icon: mdi:motor-off + optimistic: true + + # ON by default after every boot. State is not restored. + restore_mode: ALWAYS_ON + + turn_on_action: + - switch.turn_off: web_fast_forward_switch + - switch.turn_off: web_slow_forward_switch + - switch.turn_off: web_fast_reverse_switch + - switch.turn_off: web_slow_reverse_switch + - lambda: |- + id(web_fast_forward_active) = false; + id(web_slow_forward_active) = false; + id(web_fast_reverse_active) = false; + id(web_slow_reverse_active) = false; + + id(stepper_in1).turn_off(); + id(stepper_in2).turn_off(); + id(stepper_in3).turn_off(); + id(stepper_in4).turn_off(); + id(coils_are_on) = false; + + if (id(auto_position_active)) { + id(auto_position_active) = false; + id(auto_position_steps_remaining) = 0; + ESP_LOGW("motor_stop", "Motor Stop enabled. Auto positioning cancelled."); + } + + id(step_accumulator_ms) = 0.0; + id(current_motion_mode) = 6; + + turn_off_action: + - lambda: |- + id(step_accumulator_ms) = 0.0; + id(current_motion_mode) = 0; + ESP_LOGI("motor_stop", "Motor Stop disabled. Motor outputs enabled."); + #:########################################################################################:# # BINARY SENSORS: @@ -817,25 +879,6 @@ binary_sensor: id: physical_slow_forward_active value: "false" - - platform: gpio - name: "${physical_fast_reverse_name}" - id: physical_fast_reverse_button - pin: - number: ${button_fast_reverse_pin} - mode: INPUT - inverted: true - filters: - - delayed_on: 20ms - - delayed_off: 20ms - on_press: - - globals.set: - id: physical_fast_reverse_active - value: "true" - on_release: - - globals.set: - id: physical_fast_reverse_active - value: "false" - - platform: gpio name: "${physical_slow_reverse_name}" id: physical_slow_reverse_button @@ -855,6 +898,25 @@ binary_sensor: id: physical_slow_reverse_active value: "false" + - platform: gpio + name: "${physical_fast_reverse_name}" + id: physical_fast_reverse_button + pin: + number: ${button_fast_reverse_pin} + mode: INPUT + inverted: true + filters: + - delayed_on: 20ms + - delayed_off: 20ms + on_press: + - globals.set: + id: physical_fast_reverse_active + value: "true" + on_release: + - globals.set: + id: physical_fast_reverse_active + value: "false" + - platform: template name: "${auto_position_active_name}" id: auto_position_active_sensor @@ -1011,6 +1073,10 @@ text_sensor: icon: mdi:rotate-3d-variant update_interval: "${sensor_update_interval}" lambda: |- + if (id(motor_stop_switch).state) { + return {"Motor Stopped"}; + } + switch (id(current_motion_mode)) { case 1: return {"Fast Forward"}; @@ -1022,6 +1088,8 @@ text_sensor: return {"Slow Reverse"}; case 5: return {"Auto Position"}; + case 6: + return {"Motor Stopped"}; case 0: default: return {"Normal"}; @@ -1046,7 +1114,8 @@ interval: then: - lambda: |- const int STEPS_PER_ROTATION = ${steps_per_rotation}; - const int NORMAL_DIRECTION_MULTIPLIER = ${normal_direction_multiplier}; + const int NORMAL_MOTOR_DIRECTION_MULTIPLIER = ${normal_direction_multiplier}; + const int AUTO_POSITION_MOTOR_DIRECTION_MULTIPLIER = ${auto_position_motor_direction_multiplier}; const double NORMAL_STEP_INTERVAL_MS = (${rotation_days} * 24.0 * 60.0 * 60.0 * 1000.0) / ${steps_per_rotation}; @@ -1120,15 +1189,21 @@ interval: id(coils_are_on) = true; }; - auto do_one_step = [&](int direction, uint32_t now_ms) { - if (direction >= 0) { - id(current_phase_index) = (id(current_phase_index) + 1) % 8; + auto do_one_step = [&](int logical_direction, int motor_direction, uint32_t now_ms) { + // Logical direction updates the remembered year-position counter. + if (logical_direction >= 0) { id(current_position_step) = (id(current_position_step) + 1) % STEPS_PER_ROTATION; } else { - id(current_phase_index) = (id(current_phase_index) + 7) % 8; id(current_position_step) = (id(current_position_step) + STEPS_PER_ROTATION - 1) % STEPS_PER_ROTATION; } + // Motor direction updates the coil phase, allowing physical direction to be tuned separately. + if (motor_direction >= 0) { + id(current_phase_index) = (id(current_phase_index) + 1) % 8; + } else { + id(current_phase_index) = (id(current_phase_index) + 7) % 8; + } + set_coils_for_phase(id(current_phase_index)); id(last_step_ms) = now_ms; flash_step_led(now_ms); @@ -1152,6 +1227,37 @@ interval: fast_reverse_active || slow_reverse_active; + const uint32_t now_ms = millis(); + + if (id(last_loop_ms) == 0) { + id(last_loop_ms) = now_ms; + id(step_accumulator_ms) = 0.0; + + if (id(motor_stop_switch).state) { + id(current_motion_mode) = 6; + } else { + id(current_motion_mode) = 0; + } + + return; + } + + const uint32_t elapsed_ms = now_ms - id(last_loop_ms); + id(last_loop_ms) = now_ms; + + if (id(motor_stop_switch).state) { + if (id(auto_position_active)) { + id(auto_position_active) = false; + id(auto_position_steps_remaining) = 0; + ESP_LOGW("motor_stop", "Motor Stop is ON. Auto positioning cancelled."); + } + + all_coils_off(); + id(step_accumulator_ms) = 0.0; + id(current_motion_mode) = 6; + return; + } + if (any_manual_control_active && id(auto_position_active)) { id(auto_position_active) = false; id(auto_position_steps_remaining) = 0; @@ -1159,43 +1265,42 @@ interval: } int selected_mode = 0; - int selected_direction = NORMAL_DIRECTION_MULTIPLIER; + int selected_logical_direction = 1; + int selected_motor_direction = NORMAL_MOTOR_DIRECTION_MULTIPLIER; double selected_step_interval_ms = NORMAL_STEP_INTERVAL_MS; if (fast_forward_active) { selected_mode = 1; - selected_direction = NORMAL_DIRECTION_MULTIPLIER; + selected_logical_direction = 1; + selected_motor_direction = NORMAL_MOTOR_DIRECTION_MULTIPLIER; selected_step_interval_ms = FAST_STEP_INTERVAL_MS; } else if (slow_forward_active) { selected_mode = 2; - selected_direction = NORMAL_DIRECTION_MULTIPLIER; + selected_logical_direction = 1; + selected_motor_direction = NORMAL_MOTOR_DIRECTION_MULTIPLIER; selected_step_interval_ms = SLOW_STEP_INTERVAL_MS; } else if (fast_reverse_active) { selected_mode = 3; - selected_direction = -NORMAL_DIRECTION_MULTIPLIER; + selected_logical_direction = -1; + selected_motor_direction = -NORMAL_MOTOR_DIRECTION_MULTIPLIER; selected_step_interval_ms = FAST_STEP_INTERVAL_MS; } else if (slow_reverse_active) { selected_mode = 4; - selected_direction = -NORMAL_DIRECTION_MULTIPLIER; + selected_logical_direction = -1; + selected_motor_direction = -NORMAL_MOTOR_DIRECTION_MULTIPLIER; selected_step_interval_ms = SLOW_STEP_INTERVAL_MS; } else if (id(auto_position_active)) { selected_mode = 5; - selected_direction = NORMAL_DIRECTION_MULTIPLIER; + selected_logical_direction = 1; + selected_motor_direction = AUTO_POSITION_MOTOR_DIRECTION_MULTIPLIER; selected_step_interval_ms = AUTO_POSITION_STEP_INTERVAL_MS; + } else { + selected_mode = 0; + selected_logical_direction = 1; + selected_motor_direction = NORMAL_MOTOR_DIRECTION_MULTIPLIER; + selected_step_interval_ms = NORMAL_STEP_INTERVAL_MS; } - const uint32_t now_ms = millis(); - - if (id(last_loop_ms) == 0) { - id(last_loop_ms) = now_ms; - id(current_motion_mode) = selected_mode; - id(step_accumulator_ms) = 0.0; - return; - } - - const uint32_t elapsed_ms = now_ms - id(last_loop_ms); - id(last_loop_ms) = now_ms; - if (selected_mode != id(current_motion_mode)) { id(current_motion_mode) = selected_mode; id(step_accumulator_ms) = 0.0; @@ -1204,7 +1309,7 @@ interval: id(step_accumulator_ms) += (double) elapsed_ms; if (id(step_accumulator_ms) >= selected_step_interval_ms) { - do_one_step(selected_direction, now_ms); + do_one_step(selected_logical_direction, selected_motor_direction, now_ms); if (selected_mode == 5 && id(auto_position_active)) { id(auto_position_steps_remaining)--;