Edit esp-astroclockstepper1.yaml
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-astroclockstepper.yaml
|
||||
#:########################################################################################:#
|
||||
# VERSIONS:
|
||||
# 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
|
||||
# V1.0 2026-06-22 Initial Version
|
||||
@@ -52,6 +53,10 @@
|
||||
# - 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 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.
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
# a) Home Assistant OFFLINE, but Network ONLINE
|
||||
@@ -62,9 +67,14 @@
|
||||
# - Physical buttons continue to work
|
||||
# - Normal motor timing continues from device uptime
|
||||
# - Fallback AP / captive portal should become available from network_common_dhcp.yaml
|
||||
# c) Device power loss / reboot
|
||||
# - V1 does not restore exact astronomical position after reboot
|
||||
# - Re-align manually using the web or physical button controls
|
||||
# c) SNTP NOT SYNCED
|
||||
# - Normal motor timing continues
|
||||
# - Manual controls continue
|
||||
# - 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
|
||||
# - Physical position is assumed valid because the motor housing/dial is fixed
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
@@ -81,7 +91,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "zorruno.Astronomical Clock Stepper"
|
||||
project_version: "v1.2"
|
||||
project_version: "v1.3"
|
||||
|
||||
# Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names)
|
||||
api_key: !secret esp-api_key
|
||||
@@ -92,6 +102,9 @@ substitutions:
|
||||
sensor_update_interval: "1s"
|
||||
calculated_sensor_update_interval: "60s"
|
||||
|
||||
# Time Settings
|
||||
timezone_name: "Pacific/Auckland"
|
||||
|
||||
# Astronomical Clock Settings
|
||||
rotation_days: "365.2422"
|
||||
steps_per_rotation: "4096"
|
||||
@@ -105,6 +118,10 @@ substitutions:
|
||||
manual_fast_rotation_seconds: "30"
|
||||
manual_slow_rotation_seconds: "300"
|
||||
|
||||
# Auto Position Speed
|
||||
# Default is the same as Slow Forward: one full rotation every 300 seconds.
|
||||
auto_position_rotation_seconds: "300"
|
||||
|
||||
# Coil Release
|
||||
coil_release_ms: "500"
|
||||
|
||||
@@ -121,9 +138,9 @@ substitutions:
|
||||
# Suggested human-countable timing
|
||||
ip_flash_on_ms: "200"
|
||||
ip_flash_between_flash_gap_ms: "450"
|
||||
ip_flash_digit_gap_ms: "1000"
|
||||
ip_flash_octet_gap_ms: "4500"
|
||||
ip_flash_repeat_gap_ms: "10000"
|
||||
ip_flash_digit_gap_ms: "1500"
|
||||
ip_flash_octet_gap_ms: "3500"
|
||||
ip_flash_repeat_gap_ms: "8000"
|
||||
|
||||
# Stepper Output Pins
|
||||
stepper_in1_pin: GPIO14
|
||||
@@ -151,6 +168,11 @@ substitutions:
|
||||
zero_position_name: "Zero Position Counter"
|
||||
replay_ip_flash_name: "Replay IP Address Flash"
|
||||
|
||||
auto_position_now_name: "Auto Position Now"
|
||||
auto_position_on_boot_name: "Auto Position On Boot"
|
||||
auto_position_active_name: "Auto Position Active"
|
||||
sntp_time_valid_name: "SNTP Time Valid"
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# PACKAGES: Included Common Packages
|
||||
@@ -181,13 +203,8 @@ 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
|
||||
common_sntp: !include common/sntp_common_public.yaml
|
||||
|
||||
#### DIAGNOSTICS Sensors ####
|
||||
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||
diag_more: !include common/include_more_diag_sensors_nomqtt.yaml
|
||||
#diag_more: !include common/include_more_diag_sensors.yaml
|
||||
#diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||
@@ -210,6 +227,7 @@ esphome:
|
||||
priority: -100
|
||||
then:
|
||||
- output.turn_off: onboard_step_led
|
||||
|
||||
- logger.log:
|
||||
level: INFO
|
||||
format: "Astronomical Clock Stepper started. Normal rotation is %s days per full revolution."
|
||||
@@ -221,9 +239,38 @@ esphome:
|
||||
- wait_until:
|
||||
condition:
|
||||
wifi.connected:
|
||||
|
||||
- delay: 3s
|
||||
|
||||
- script.execute: start_ip_flash_script
|
||||
|
||||
# Give the IP flash state machine time to start.
|
||||
- delay: 100ms
|
||||
|
||||
# Wait for the boot IP flash sequence to finish before optional auto-positioning.
|
||||
- wait_until:
|
||||
condition:
|
||||
lambda: |-
|
||||
return !id(ip_flash_active);
|
||||
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: auto_position_on_boot_switch
|
||||
then:
|
||||
- globals.set:
|
||||
id: auto_position_on_boot_pending
|
||||
value: "true"
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return id(sntp_time).now().is_valid();
|
||||
then:
|
||||
- globals.set:
|
||||
id: auto_position_on_boot_pending
|
||||
value: "false"
|
||||
- script.execute: start_auto_position_script
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# ESP PLATFORM AND FRAMEWORK:
|
||||
@@ -234,7 +281,7 @@ esp8266:
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 5min
|
||||
flash_write_interval: 1min
|
||||
|
||||
mdns:
|
||||
disabled: false
|
||||
@@ -249,20 +296,42 @@ logger:
|
||||
baud_rate: 0
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# TIME:
|
||||
# SNTP time source for year-position calculation
|
||||
# https://esphome.io/components/time/sntp.html
|
||||
#:########################################################################################:#
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone_name}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return id(auto_position_on_boot_pending) && id(auto_position_on_boot_switch).state;
|
||||
then:
|
||||
- globals.set:
|
||||
id: auto_position_on_boot_pending
|
||||
value: "false"
|
||||
- script.execute: start_auto_position_script
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# GLOBALS:
|
||||
# Internal position, timing, mode, held-button state, and IP flash state
|
||||
# Internal position, timing, mode, held-button state, IP flash state, and auto-position state
|
||||
# https://esphome.io/components/globals.html
|
||||
#:########################################################################################:#
|
||||
globals:
|
||||
- id: current_position_step
|
||||
type: int
|
||||
restore_value: no
|
||||
restore_value: yes
|
||||
initial_value: "0"
|
||||
|
||||
- id: current_phase_index
|
||||
type: int
|
||||
restore_value: no
|
||||
restore_value: yes
|
||||
initial_value: "0"
|
||||
|
||||
- id: last_loop_ms
|
||||
@@ -300,6 +369,7 @@ globals:
|
||||
# 2 = Slow Forward
|
||||
# 3 = Fast Reverse
|
||||
# 4 = Slow Reverse
|
||||
# 5 = Auto Position
|
||||
- id: current_motion_mode
|
||||
type: int
|
||||
restore_value: no
|
||||
@@ -403,6 +473,27 @@ globals:
|
||||
restore_value: no
|
||||
initial_value: "0"
|
||||
|
||||
# Auto Position State
|
||||
- id: auto_position_active
|
||||
type: bool
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
- id: auto_position_on_boot_pending
|
||||
type: bool
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
- id: auto_position_target_step
|
||||
type: int
|
||||
restore_value: no
|
||||
initial_value: "0"
|
||||
|
||||
- id: auto_position_steps_remaining
|
||||
type: int
|
||||
restore_value: no
|
||||
initial_value: "0"
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# OUTPUT:
|
||||
@@ -490,10 +581,115 @@ script:
|
||||
${ip_flash_repeat_count}
|
||||
);
|
||||
|
||||
- id: start_auto_position_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: |-
|
||||
const int STEPS_PER_ROTATION = ${steps_per_rotation};
|
||||
|
||||
auto now = id(sntp_time).now();
|
||||
|
||||
if (!now.is_valid()) {
|
||||
ESP_LOGW("auto_position", "SNTP time is not valid yet. Auto position cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
const int year = now.year;
|
||||
const int month = now.month;
|
||||
const int day_of_month = now.day_of_month;
|
||||
|
||||
const bool is_leap_year =
|
||||
((year % 4 == 0) && (year % 100 != 0)) ||
|
||||
(year % 400 == 0);
|
||||
|
||||
const int days_in_year = is_leap_year ? 366 : 365;
|
||||
|
||||
const int days_before_month_common[12] = {
|
||||
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
|
||||
};
|
||||
|
||||
const int days_before_month_leap[12] = {
|
||||
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335
|
||||
};
|
||||
|
||||
int safe_month = month;
|
||||
|
||||
if (safe_month < 1) {
|
||||
safe_month = 1;
|
||||
}
|
||||
|
||||
if (safe_month > 12) {
|
||||
safe_month = 12;
|
||||
}
|
||||
|
||||
int safe_day_of_month = day_of_month;
|
||||
|
||||
if (safe_day_of_month < 1) {
|
||||
safe_day_of_month = 1;
|
||||
}
|
||||
|
||||
if (safe_day_of_month > 31) {
|
||||
safe_day_of_month = 31;
|
||||
}
|
||||
|
||||
const int day_of_year =
|
||||
(is_leap_year ? days_before_month_leap[safe_month - 1] : days_before_month_common[safe_month - 1]) +
|
||||
safe_day_of_month;
|
||||
|
||||
const uint32_t seconds_elapsed_this_year =
|
||||
((uint32_t) (day_of_year - 1) * 86400UL) +
|
||||
((uint32_t) now.hour * 3600UL) +
|
||||
((uint32_t) now.minute * 60UL) +
|
||||
((uint32_t) now.second);
|
||||
|
||||
const double seconds_in_year = (double) days_in_year * 86400.0;
|
||||
|
||||
int target_step =
|
||||
(int) (((double) seconds_elapsed_this_year / seconds_in_year) *
|
||||
(double) STEPS_PER_ROTATION);
|
||||
|
||||
target_step = target_step % STEPS_PER_ROTATION;
|
||||
|
||||
int current_step = id(current_position_step) % STEPS_PER_ROTATION;
|
||||
|
||||
if (current_step < 0) {
|
||||
current_step += STEPS_PER_ROTATION;
|
||||
}
|
||||
|
||||
const int steps_to_move =
|
||||
(target_step - current_step + STEPS_PER_ROTATION) % STEPS_PER_ROTATION;
|
||||
|
||||
id(auto_position_target_step) = target_step;
|
||||
id(auto_position_steps_remaining) = steps_to_move;
|
||||
|
||||
if (steps_to_move == 0) {
|
||||
id(auto_position_active) = false;
|
||||
ESP_LOGI(
|
||||
"auto_position",
|
||||
"Already at correct year position. Year: %d, target step: %d.",
|
||||
year,
|
||||
target_step
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
id(auto_position_active) = true;
|
||||
|
||||
ESP_LOGI(
|
||||
"auto_position",
|
||||
"Auto positioning started. Year: %d, day_of_year: %d/%d, target step: %d, current step: %d, moving forward %d steps.",
|
||||
year,
|
||||
day_of_year,
|
||||
days_in_year,
|
||||
target_step,
|
||||
current_step,
|
||||
steps_to_move
|
||||
);
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SWITCH:
|
||||
# Web held-button controls
|
||||
# Web held-button controls and restored auto-position boot option
|
||||
# https://esphome.io/components/switch/template.html
|
||||
#:########################################################################################:#
|
||||
switch:
|
||||
@@ -569,6 +765,13 @@ switch:
|
||||
id: web_slow_reverse_active
|
||||
value: "false"
|
||||
|
||||
- platform: template
|
||||
name: "${auto_position_on_boot_name}"
|
||||
id: auto_position_on_boot_switch
|
||||
icon: mdi:calendar-sync
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# BINARY SENSORS:
|
||||
@@ -652,6 +855,20 @@ binary_sensor:
|
||||
id: physical_slow_reverse_active
|
||||
value: "false"
|
||||
|
||||
- platform: template
|
||||
name: "${auto_position_active_name}"
|
||||
id: auto_position_active_sensor
|
||||
icon: mdi:calendar-sync
|
||||
lambda: |-
|
||||
return id(auto_position_active);
|
||||
|
||||
- platform: template
|
||||
name: "${sntp_time_valid_name}"
|
||||
id: sntp_time_valid_sensor
|
||||
icon: mdi:clock-check-outline
|
||||
lambda: |-
|
||||
return id(sntp_time).now().is_valid();
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# BUTTON:
|
||||
@@ -673,6 +890,12 @@ button:
|
||||
- globals.set:
|
||||
id: step_accumulator_ms
|
||||
value: "0.0"
|
||||
- globals.set:
|
||||
id: auto_position_active
|
||||
value: "false"
|
||||
- globals.set:
|
||||
id: auto_position_steps_remaining
|
||||
value: "0"
|
||||
- logger.log:
|
||||
level: INFO
|
||||
format: "Position counter zeroed."
|
||||
@@ -684,6 +907,13 @@ button:
|
||||
on_press:
|
||||
- script.execute: start_ip_flash_script
|
||||
|
||||
- platform: template
|
||||
name: "${auto_position_now_name}"
|
||||
id: auto_position_now_button
|
||||
icon: mdi:calendar-clock
|
||||
on_press:
|
||||
- script.execute: start_auto_position_script
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SENSOR:
|
||||
@@ -740,6 +970,34 @@ sensor:
|
||||
lambda: |-
|
||||
return (${rotation_days} * 24.0 * 60.0 * 60.0) / ${steps_per_rotation};
|
||||
|
||||
- platform: template
|
||||
name: "Auto Position Target Step"
|
||||
id: auto_position_target_step_sensor
|
||||
icon: mdi:target
|
||||
accuracy_decimals: 0
|
||||
update_interval: "${sensor_update_interval}"
|
||||
lambda: |-
|
||||
return id(auto_position_target_step);
|
||||
|
||||
- platform: template
|
||||
name: "Auto Position Steps Remaining"
|
||||
id: auto_position_steps_remaining_sensor
|
||||
icon: mdi:counter
|
||||
accuracy_decimals: 0
|
||||
update_interval: "${sensor_update_interval}"
|
||||
lambda: |-
|
||||
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
|
||||
update_interval: "${sensor_update_interval}"
|
||||
lambda: |-
|
||||
return ((double) id(auto_position_target_step) * ${rotation_days}) / ${steps_per_rotation};
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# TEXT SENSOR:
|
||||
@@ -762,6 +1020,8 @@ text_sensor:
|
||||
return {"Fast Reverse"};
|
||||
case 4:
|
||||
return {"Slow Reverse"};
|
||||
case 5:
|
||||
return {"Auto Position"};
|
||||
case 0:
|
||||
default:
|
||||
return {"Normal"};
|
||||
@@ -797,6 +1057,9 @@ interval:
|
||||
const double SLOW_STEP_INTERVAL_MS =
|
||||
(${manual_slow_rotation_seconds} * 1000.0) / ${steps_per_rotation};
|
||||
|
||||
const double AUTO_POSITION_STEP_INTERVAL_MS =
|
||||
(${auto_position_rotation_seconds} * 1000.0) / ${steps_per_rotation};
|
||||
|
||||
const uint32_t COIL_RELEASE_MS = ${coil_release_ms};
|
||||
const uint32_t STEP_LED_FLASH_MS = ${step_led_flash_ms};
|
||||
|
||||
@@ -883,6 +1146,18 @@ interval:
|
||||
const bool slow_reverse_active =
|
||||
id(physical_slow_reverse_active) || id(web_slow_reverse_active);
|
||||
|
||||
const bool any_manual_control_active =
|
||||
fast_forward_active ||
|
||||
slow_forward_active ||
|
||||
fast_reverse_active ||
|
||||
slow_reverse_active;
|
||||
|
||||
if (any_manual_control_active && id(auto_position_active)) {
|
||||
id(auto_position_active) = false;
|
||||
id(auto_position_steps_remaining) = 0;
|
||||
ESP_LOGW("auto_position", "Auto positioning cancelled by manual control.");
|
||||
}
|
||||
|
||||
int selected_mode = 0;
|
||||
int selected_direction = NORMAL_DIRECTION_MULTIPLIER;
|
||||
double selected_step_interval_ms = NORMAL_STEP_INTERVAL_MS;
|
||||
@@ -903,6 +1178,10 @@ interval:
|
||||
selected_mode = 4;
|
||||
selected_direction = -NORMAL_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_step_interval_ms = AUTO_POSITION_STEP_INTERVAL_MS;
|
||||
}
|
||||
|
||||
const uint32_t now_ms = millis();
|
||||
@@ -926,6 +1205,23 @@ interval:
|
||||
|
||||
if (id(step_accumulator_ms) >= selected_step_interval_ms) {
|
||||
do_one_step(selected_direction, now_ms);
|
||||
|
||||
if (selected_mode == 5 && id(auto_position_active)) {
|
||||
id(auto_position_steps_remaining)--;
|
||||
|
||||
if (id(auto_position_steps_remaining) <= 0) {
|
||||
id(auto_position_steps_remaining) = 0;
|
||||
id(auto_position_active) = false;
|
||||
id(current_motion_mode) = 0;
|
||||
|
||||
ESP_LOGI(
|
||||
"auto_position",
|
||||
"Auto positioning complete. Current step: %d.",
|
||||
id(current_position_step)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
id(step_accumulator_ms) -= selected_step_interval_ms;
|
||||
|
||||
// If the loop was badly delayed, avoid trying to catch up with a burst
|
||||
@@ -1077,3 +1373,51 @@ interval:
|
||||
id(onboard_step_led).turn_on();
|
||||
id(ip_flash_led_is_on) = true;
|
||||
id(ip_flash_next_action_ms) = now_ms + IP_FLASH_ON_MS;
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# MQTT:
|
||||
# Device-specific MQTT command triggers, in addition to common MQTT config
|
||||
# https://esphome.io/components/mqtt.html
|
||||
#:########################################################################################:#
|
||||
#mqtt:
|
||||
# on_message:
|
||||
# - topic: "${mqtt_command_topic}/fast_forward/set"
|
||||
# payload: "ON"
|
||||
# then:
|
||||
# - switch.turn_on: web_fast_forward_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/fast_forward/set"
|
||||
# payload: "OFF"
|
||||
# then:
|
||||
# - switch.turn_off: web_fast_forward_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/slow_forward/set"
|
||||
# payload: "ON"
|
||||
# then:
|
||||
# - switch.turn_on: web_slow_forward_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/slow_forward/set"
|
||||
# payload: "OFF"
|
||||
# then:
|
||||
# - switch.turn_off: web_slow_forward_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/fast_reverse/set"
|
||||
# payload: "ON"
|
||||
# then:
|
||||
# - switch.turn_on: web_fast_reverse_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/fast_reverse/set"
|
||||
# payload: "OFF"
|
||||
# then:
|
||||
# - switch.turn_off: web_fast_reverse_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/slow_reverse/set"
|
||||
# payload: "ON"
|
||||
# then:
|
||||
# - switch.turn_on: web_slow_reverse_switch
|
||||
#
|
||||
# - topic: "${mqtt_command_topic}/slow_reverse/set"
|
||||
# payload: "OFF"
|
||||
# then:
|
||||
# - switch.turn_off: web_slow_reverse_switch
|
||||
Reference in New Issue
Block a user