esphome: name: rotary-hall-sensor friendly_name: Rotary Hall Sensor esp32: board: esp32dev framework: type: esp-idf logger: api: encryption: key: "your api key here" ota: - platform: esphome password: "password" wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: "Rotary-Hall-Sensor" password: "password" captive_portal: i2c: sda: GPIO21 scl: GPIO22 scan: true sensor: - platform: mpu6050 address: 0x68 update_interval: 1s accel_x: {id: accel_x, internal: true} accel_y: {id: accel_y, internal: true} accel_z: {id: accel_z, internal: true} gyro_x: {id: gyro_x, internal: true} gyro_y: {id: gyro_y, internal: true} gyro_z: {id: gyro_z, internal: true} temperature: name: "MPU Temperature" - platform: template name: "Current Tilt" id: current_tilt unit_of_measurement: "°" accuracy_decimals: 1 update_interval: 1s lambda: |- return atan2(id(accel_y).state, id(accel_z).state) * (180.0 / M_PI); - platform: sun name: "Sun Elevation" type: elevation id: sun_elev - platform: sun name: "Sun Azimuth" type: azimuth id: sun_azim - platform: template name: "Desired Tilt" id: desired_tilt unit_of_measurement: "°" accuracy_decimals: 1 update_interval: 60s lambda: |- const float WIND_STOW_THRESHOLD = 30.0; const float WIND_STOW_TILT = 0.0; float elev = id(sun_elev).state; if (id(wind_speed).has_state() && id(wind_speed).state > WIND_STOW_THRESHOLD) { return WIND_STOW_TILT; } if (elev <= 0.0) return 0.0; float elev_rad = elev * (M_PI / 180.0); float azim_rad = id(sun_azim).state * (M_PI / 180.0); float tan_elev = tan(elev_rad); if (fabs(tan_elev) < 0.0001) tan_elev = 0.0001; float r = -atan(sin(azim_rad) / tan_elev) * (180.0 / M_PI); if (r > 53.0) r = 53.0; if (r < -56.0) r = -56.0; return r; - platform: adc pin: GPIO35 name: "Wind Sensor Voltage" id: wind_voltage unit_of_measurement: "v" attenuation: 12db update_interval: 1s filters: - lambda: |- float v = x - 0.14; if (v < 0) v = 0; return v; - sliding_window_moving_average: window_size: 10 send_every: 1 - platform: template name: "Wind Speed" id: wind_speed unit_of_measurement: "mph" icon: "mdi:weather-windy" accuracy_decimals: 1 update_interval: 5s lambda: |- if (std::isnan(id(wind_voltage).state)) return NAN; float mph = id(wind_voltage).state * 13.4; // ADJUST THIS CALIBRATION VALUE! return mph; - platform: internal_temperature id: esp_internal_temperature name: "Internal Temperature" binary_sensor: - platform: template name: "High Wind Stow" id: high_wind_stow device_class: problem lambda: |- if (std::isnan(id(wind_speed).state)) return false; return id(wind_speed).state > 30.0; - platform: gpio pin: number: GPIO27 mode: INPUT_PULLDOWN inverted: false name: "East Limit Switch" id: east_limit device_class: problem filters: - delayed_on: 300ms - delayed_off: 300ms on_press: then: - logger.log: "East limit hit - stopping East rotation" - switch.turn_off: rotate_east on_release: then: - logger.log: "East limit cleared" - platform: gpio pin: number: GPIO33 mode: INPUT_PULLDOWN inverted: false name: "West Limit Switch" id: west_limit device_class: problem filters: - delayed_on: 300ms - delayed_off: 300ms on_press: then: - logger.log: "West limit hit - stopping West rotation" - switch.turn_off: rotate_west on_release: then: - logger.log: "West limit cleared" switch: - platform: gpio pin: GPIO25 name: "Rotate West" id: rotate_west interlock: [rotate_east, rotate_west] - platform: gpio pin: GPIO26 name: "Rotate East" id: rotate_east interlock: [rotate_east, rotate_west] - platform: template name: "E-Stop" id: e_stop optimistic: true turn_on_action: - switch.turn_off: rotate_east - switch.turn_off: rotate_west - logger.log: "E-STOP ACTIVATED - all rotation stopped" turn_off_action: - logger.log: "E-STOP cleared - normal operation resumed" # MANUAL MODE SWITCH – turn ON to control motors manually - platform: template name: "Manual Mode" id: manual_mode optimistic: true turn_on_action: - logger.log: "Manual Mode ACTIVATED - auto tracking paused" turn_off_action: - logger.log: "Manual Mode OFF - auto tracking resumed" - platform: template name: "Rotation Fault" id: rotation_fault optimistic: true turn_on_action: - logger.log: "ROTATION FAULT TRIGGERED - check mechanics/motor" turn_off_action: - logger.log: "Rotation fault cleared" sun: latitude: 36.207681 longitude: -87.849238 time: - platform: homeassistant id: ha_time interval: - interval: 1s then: - lambda: |- static uint32_t last_movement_time = 0; // for 5-minute cooldown static uint32_t movement_start_time = 0; // for fault detection static float starting_tilt = 0.0; const uint32_t cooldown = 300000; // 5 minutes const uint32_t fault_timeout = 3000; // 3 seconds // E-stop override - highest priority if (id(e_stop).state) { id(rotate_east).turn_off(); id(rotate_west).turn_off(); last_movement_time = millis(); movement_start_time = 0; return; } // Manual Mode bypasses auto logic and cooldown if (id(manual_mode).state) { movement_start_time = 0; return; } if (!id(desired_tilt).has_state() || !id(current_tilt).has_state()) return; float target = id(desired_tilt).state; float current = id(current_tilt).state; float deadband = 4; // HIGH WIND STOW - always immediate, no cooldown if (id(wind_speed).has_state() && id(wind_speed).state > 12.0) { id(rotate_east).turn_off(); id(rotate_west).turn_off(); last_movement_time = millis(); movement_start_time = 0; return; } bool can_rotate_east = !id(east_limit).state; bool can_rotate_west = !id(west_limit).state; bool should_move = false; if (current > target + deadband) { if (can_rotate_west) { id(rotate_west).turn_on(); should_move = true; } else { id(rotate_west).turn_off(); } id(rotate_east).turn_off(); } else if (current < target - deadband) { if (can_rotate_east) { id(rotate_east).turn_on(); should_move = true; } else { id(rotate_east).turn_off(); } id(rotate_west).turn_off(); } else { // Reached target → start cooldown id(rotate_east).turn_off(); id(rotate_west).turn_off(); last_movement_time = millis(); movement_start_time = 0; return; } // === Cooldown Logic (only for auto mode) === if (millis() - last_movement_time < cooldown && !should_move) { id(rotate_east).turn_off(); id(rotate_west).turn_off(); return; } // === Fault Detection === if (should_move) { if (movement_start_time == 0) { movement_start_time = millis(); starting_tilt = current; } if (millis() - movement_start_time > fault_timeout) { float tilt_change = fabs(current - starting_tilt); if (tilt_change < 2) { id(rotate_east).turn_off(); id(rotate_west).turn_off(); id(rotation_fault).turn_on(); // Turn on fault ESP_LOGW("main", "Rotation fault: No tilt movement detected in 3 seconds!"); movement_start_time = 0; last_movement_time = millis(); // also trigger cooldown return; } } } else { movement_start_time = 0; // Auto-clear fault when not moving if (id(rotation_fault).state) { id(rotation_fault).turn_off(); } } // Reset cooldown timer while actively moving if (should_move) { last_movement_time = 0; }