diff --git a/esphome/esp-astroclockstepper1.yaml b/esphome/esp-astroclockstepper1.yaml index ce9af11..b481505 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.7 2026-06-22 Rebuilt direction model so clockwise always equals forward and position count increase # V1.6 2026-06-22 Fixed auto-position cycle calculation to use rotation_days / solar-year cycle # V1.5 2026-06-22 Changed Motor Stop to OFF on boot and added shortest-path auto-positioning # V1.4 2026-06-22 Added Motor Stop switch, swapped reverse buttons 3/4, and separated auto-position motor direction @@ -28,10 +29,10 @@ # - GND: Common GND with ESP8266 # # Physical Buttons: -# - Fast Forward: GPIO5 / D1 -# - Slow Forward: GPIO4 / D2 -# - Slow Reverse: GPIO0 / D3 -# - Fast Reverse: GPIO16 / D0 +# - Fast Reverse / Anti-clockwise: GPIO5 / D1 / Button 1 +# - Slow Reverse / Anti-clockwise: GPIO4 / D2 / Button 2 +# - Slow Forward / Clockwise: GPIO0 / D3 / Button 3 +# - Fast Forward / Clockwise: GPIO16 / D0 / Button 4 # # Button board: # - Uses built-in 103 pull-up resistors, 10k ohm @@ -47,26 +48,28 @@ # - Normal rotation starts automatically after boot because Motor Stop defaults OFF. # - Motor Stop is OFF by default after every reboot and is not restored. # - Motor Stop ON turns off all motor coils and pauses physical position counting. -# - Default rotation is one full revolution every 365.2422 days. +# - Default rotation is one full revolution clockwise 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. +# - Clockwise physical movement is forward. +# - Forward movement increases Position Step and Position Days. +# - Anti-clockwise physical movement is reverse. +# - Reverse movement decreases Position Step and Position Days. # - Motor coils are powered only while stepping, then released. # - No holding torque is used. # - Web template switches mimic held physical buttons. # - Manual movement only applies while a physical button or web switch is held/on. -# - Manual forward increases the remembered year position. -# - Manual reverse decreases the remembered year position. # - Position counter wraps back to zero after one full rotation. +# - Zero Position Counter sets the current physical pointer location as Jan-zero. +# - Zero Position Counter does not reset the stepper coil phase. # - Onboard D1 Mini LED flashes briefly every time a motor step is issued. # - After WiFi connects, the onboard LED flashes the DHCP IPv4 address. # - IPv4 flash sequence is decimal digit based. # - 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 calculates the target using the configured 365.2422-day rotation cycle. # - Auto Position Now moves to the current calculated year position by the shortest path. # - Auto Position can move clockwise or anti-clockwise, whichever is quicker. -# - 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: @@ -102,9 +105,9 @@ substitutions: # Project Naming project_name: "zorruno.Astronomical Clock Stepper" - project_version: "v1.6" + project_version: "v1.7" - # Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names) + # Passwords & Secrets api_key: !secret esp-api_key ota_pass: !secret esp-ota_pass @@ -119,27 +122,22 @@ substitutions: # Astronomical Clock Settings rotation_days: "365.2422" steps_per_rotation: "4096" - normal_direction_name: "Clockwise" - # 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 the physical motor phase direction used when the logical year position increases. - # Reverse auto-position uses the opposite of this automatically. - # Set to -1 because auto-position was observed to be running anti-clockwise when this was 1. - auto_position_motor_direction_multiplier: "-1" - - # Auto Position Path - # true = move clockwise or anti-clockwise, whichever is quicker - # false = always move logically forward through the year - auto_position_use_shortest_path: "true" + # Physical Direction Calibration + # This is the motor phase direction that makes the needle move clockwise. + # Your testing showed the old reverse direction moved clockwise, so default is -1. + # If clockwise/anti-clockwise is still backwards, change this to 1. + clockwise_motor_direction_multiplier: "-1" # Manual Movement Speeds manual_fast_rotation_seconds: "30" manual_slow_rotation_seconds: "300" + # Auto Position Path + # true = move clockwise or anti-clockwise, whichever is quicker + # false = always move clockwise / forward through the year + auto_position_use_shortest_path: "true" + # Auto Position Speed # Default is the same as Slow Forward: one full rotation every 300 seconds. auto_position_rotation_seconds: "300" @@ -171,10 +169,12 @@ substitutions: stepper_in4_pin: GPIO15 # Physical Button Input Pins - button_fast_forward_pin: GPIO5 - button_slow_forward_pin: GPIO4 - button_slow_reverse_pin: GPIO0 - button_fast_reverse_pin: GPIO16 + # Button 1 and 2 physically move anti-clockwise, so they are reverse. + # Button 3 and 4 physically move clockwise, so they are forward. + button_fast_reverse_pin: GPIO5 + button_slow_reverse_pin: GPIO4 + button_slow_forward_pin: GPIO0 + button_fast_forward_pin: GPIO16 # Entity Naming motor_stop_name: "Motor Stop" @@ -359,6 +359,8 @@ globals: restore_value: yes initial_value: "0" + # Electrical stepper phase. + # This should not be reset when setting the calendar zero point. - id: current_phase_index type: int restore_value: yes @@ -525,8 +527,8 @@ globals: restore_value: no initial_value: "0" - # 1 = logical forward / clockwise year position increase - # -1 = logical reverse / anti-clockwise year position decrease + # 1 = clockwise / forward / increasing position + # -1 = anti-clockwise / reverse / decreasing position - id: auto_position_logical_direction type: int restore_value: no @@ -537,6 +539,16 @@ globals: restore_value: no initial_value: "0.0" + - id: auto_position_forward_steps + type: int + restore_value: no + initial_value: "0" + + - id: auto_position_reverse_steps + type: int + restore_value: no + initial_value: "0" + #:########################################################################################:# # OUTPUT: @@ -692,12 +704,15 @@ script: ((uint32_t) now.minute * 60UL) + ((uint32_t) now.second); - // The date/time gives seconds since the local year-zero mark. - // The dial cycle itself is the configured solar-year length, not 365 or 366 days. - double cycle_seconds = - fmod((double) seconds_elapsed_this_calendar_year, ROTATION_SECONDS); + // The display zero mark is Jan 1 local midnight. + // The dial cycle is the configured solar-year length, not 365 or 366 days. + double cycle_seconds = (double) seconds_elapsed_this_calendar_year; - if (cycle_seconds < 0.0) { + while (cycle_seconds >= ROTATION_SECONDS) { + cycle_seconds -= ROTATION_SECONDS; + } + + while (cycle_seconds < 0.0) { cycle_seconds += ROTATION_SECONDS; } @@ -730,6 +745,8 @@ script: id(auto_position_steps_remaining) = chosen_steps; id(auto_position_logical_direction) = chosen_logical_direction; id(auto_position_cycle_seconds) = cycle_seconds; + id(auto_position_forward_steps) = forward_steps; + id(auto_position_reverse_steps) = reverse_steps; if (chosen_steps == 0) { id(auto_position_active) = false; @@ -746,7 +763,7 @@ script: ESP_LOGI( "auto_position", - "Auto positioning started. Year: %d, day_of_year: %d, cycle_seconds: %.2f / %.2f, target step: %d, current step: %d, forward steps: %d, reverse steps: %d, chosen direction: %s, moving %d logical steps.", + "Auto positioning started. Year: %d, day_of_year: %d, cycle_seconds: %.2f / %.2f, target step: %d, current step: %d, forward steps: %d, reverse steps: %d, chosen direction: %s, moving %d steps.", year, day_of_year, cycle_seconds, @@ -755,7 +772,7 @@ script: current_step, forward_steps, reverse_steps, - chosen_logical_direction >= 0 ? "forward" : "reverse", + chosen_logical_direction >= 0 ? "clockwise/forward" : "anti-clockwise/reverse", chosen_steps ); @@ -896,10 +913,10 @@ switch: #:########################################################################################:# binary_sensor: - platform: gpio - name: "${physical_fast_forward_name}" - id: physical_fast_forward_button + name: "${physical_fast_reverse_name}" + id: physical_fast_reverse_button pin: - number: ${button_fast_forward_pin} + number: ${button_fast_reverse_pin} mode: INPUT inverted: true filters: @@ -907,30 +924,11 @@ binary_sensor: - delayed_off: 20ms on_press: - globals.set: - id: physical_fast_forward_active + id: physical_fast_reverse_active value: "true" on_release: - globals.set: - id: physical_fast_forward_active - value: "false" - - - platform: gpio - name: "${physical_slow_forward_name}" - id: physical_slow_forward_button - pin: - number: ${button_slow_forward_pin} - mode: INPUT - inverted: true - filters: - - delayed_on: 20ms - - delayed_off: 20ms - on_press: - - globals.set: - id: physical_slow_forward_active - value: "true" - on_release: - - globals.set: - id: physical_slow_forward_active + id: physical_fast_reverse_active value: "false" - platform: gpio @@ -953,10 +951,10 @@ binary_sensor: value: "false" - platform: gpio - name: "${physical_fast_reverse_name}" - id: physical_fast_reverse_button + name: "${physical_slow_forward_name}" + id: physical_slow_forward_button pin: - number: ${button_fast_reverse_pin} + number: ${button_slow_forward_pin} mode: INPUT inverted: true filters: @@ -964,11 +962,30 @@ binary_sensor: - delayed_off: 20ms on_press: - globals.set: - id: physical_fast_reverse_active + id: physical_slow_forward_active value: "true" on_release: - globals.set: - id: physical_fast_reverse_active + id: physical_slow_forward_active + value: "false" + + - platform: gpio + name: "${physical_fast_forward_name}" + id: physical_fast_forward_button + pin: + number: ${button_fast_forward_pin} + mode: INPUT + inverted: true + filters: + - delayed_on: 20ms + - delayed_off: 20ms + on_press: + - globals.set: + id: physical_fast_forward_active + value: "true" + on_release: + - globals.set: + id: physical_fast_forward_active value: "false" - platform: template @@ -1000,9 +1017,6 @@ button: - globals.set: id: current_position_step value: "0" - - globals.set: - id: current_phase_index - value: "0" - globals.set: id: step_accumulator_ms value: "0.0" @@ -1018,9 +1032,15 @@ button: - globals.set: id: auto_position_cycle_seconds value: "0.0" + - globals.set: + id: auto_position_forward_steps + value: "0" + - globals.set: + id: auto_position_reverse_steps + value: "0" - logger.log: level: INFO - format: "Position counter zeroed." + format: "Position counter zeroed. Current physical pointer location is now Jan-zero." - platform: template name: "${replay_ip_flash_name}" @@ -1101,6 +1121,16 @@ sensor: lambda: |- return id(auto_position_target_step); + - platform: template + name: "Auto Position Target Days" + id: auto_position_target_days_sensor + icon: mdi:calendar-clock + unit_of_measurement: "days" + accuracy_decimals: 4 + update_interval: "${sensor_update_interval}" + lambda: |- + return ((double) id(auto_position_target_step) * ${rotation_days}) / ${steps_per_rotation}; + - platform: template name: "Auto Position Steps Remaining" id: auto_position_steps_remaining_sensor @@ -1111,14 +1141,22 @@ sensor: return id(auto_position_steps_remaining); - platform: template - name: "Auto Position Target Days" - id: auto_position_target_days_sensor - icon: mdi:calendar-clock - unit_of_measurement: "days" - accuracy_decimals: 4 + name: "Auto Position Forward Steps" + id: auto_position_forward_steps_sensor + icon: mdi:rotate-right + accuracy_decimals: 0 update_interval: "${sensor_update_interval}" lambda: |- - return ((double) id(auto_position_target_step) * ${rotation_days}) / ${steps_per_rotation}; + return id(auto_position_forward_steps); + + - platform: template + name: "Auto Position Reverse Steps" + id: auto_position_reverse_steps_sensor + icon: mdi:rotate-left + accuracy_decimals: 0 + update_interval: "${sensor_update_interval}" + lambda: |- + return id(auto_position_reverse_steps); - platform: template name: "Auto Position Cycle Days" @@ -1171,7 +1209,7 @@ text_sensor: icon: mdi:rotate-right update_interval: "${calculated_sensor_update_interval}" lambda: |- - return {"${normal_direction_name}"}; + return {"Clockwise / Forward"}; - platform: template name: "Auto Position Direction" @@ -1184,10 +1222,10 @@ text_sensor: } if (id(auto_position_logical_direction) < 0) { - return {"Reverse / Anti-clockwise"}; + return {"Anti-clockwise / Reverse"}; } - return {"Forward / Clockwise"}; + return {"Clockwise / Forward"}; #:########################################################################################:# @@ -1200,8 +1238,7 @@ interval: then: - lambda: |- const int STEPS_PER_ROTATION = ${steps_per_rotation}; - const int NORMAL_MOTOR_DIRECTION_MULTIPLIER = ${normal_direction_multiplier}; - const int AUTO_POSITION_MOTOR_DIRECTION_MULTIPLIER = ${auto_position_motor_direction_multiplier}; + const int CLOCKWISE_MOTOR_DIRECTION_MULTIPLIER = ${clockwise_motor_direction_multiplier}; const double NORMAL_STEP_INTERVAL_MS = (${rotation_days} * 24.0 * 60.0 * 60.0 * 1000.0) / ${steps_per_rotation}; @@ -1275,15 +1312,22 @@ interval: id(coils_are_on) = true; }; - auto do_one_step = [&](int logical_direction, int motor_direction, uint32_t now_ms) { - // Logical direction updates the remembered year-position counter. + auto do_one_step = [&](int logical_direction, uint32_t now_ms) { + // logical_direction >= 0 means clockwise / forward / increase position. + // logical_direction < 0 means anti-clockwise / reverse / decrease position. + int motor_direction = + logical_direction >= 0 + ? CLOCKWISE_MOTOR_DIRECTION_MULTIPLIER + : -CLOCKWISE_MOTOR_DIRECTION_MULTIPLIER; + if (logical_direction >= 0) { - id(current_position_step) = (id(current_position_step) + 1) % STEPS_PER_ROTATION; + id(current_position_step) = + (id(current_position_step) + 1) % STEPS_PER_ROTATION; } else { - id(current_position_step) = (id(current_position_step) + STEPS_PER_ROTATION - 1) % STEPS_PER_ROTATION; + 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 { @@ -1352,28 +1396,23 @@ interval: int selected_mode = 0; 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_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_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_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_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; @@ -1383,14 +1422,10 @@ interval: selected_logical_direction = 1; } - selected_motor_direction = - selected_logical_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; } @@ -1402,7 +1437,7 @@ interval: id(step_accumulator_ms) += (double) elapsed_ms; if (id(step_accumulator_ms) >= selected_step_interval_ms) { - do_one_step(selected_logical_direction, selected_motor_direction, now_ms); + do_one_step(selected_logical_direction, now_ms); if (selected_mode == 5 && id(auto_position_active)) { id(auto_position_steps_remaining)--;