|
1 | 1 | --[[
|
2 | 2 | SoarNav: an advanced feature for hunting thermals by Marco Robustini.
|
3 |
| --- Version 0.9.1 - 2025/07/29 |
| 3 | +-- Version 0.9.2 - 2025/07/29 |
4 | 4 |
|
5 | 5 | In early versions of SOAR mode in Ardupilot, which I collaborated on with
|
6 | 6 | the author, when the glider exited THERMAL mode the heading pointed
|
@@ -124,7 +124,7 @@ Script Parameters (SNAV_*)
|
124 | 124 |
|
125 | 125 | --[[ SCRIPT INITIALIZATION ]]--
|
126 | 126 |
|
127 |
| -math.randomseed(tonumber(tostring(millis()))) |
| 127 | +math.randomseed(tonumber(tostring(millis() or 0))) |
128 | 128 |
|
129 | 129 | -- Enums for GCS message severity and script internal state
|
130 | 130 | local MAV_SEVERITY = {
|
@@ -480,18 +480,16 @@ local function calculate_bearing(lat1, lon1, lat2, lon2)
|
480 | 480 | return (brng + 360) % 360
|
481 | 481 | end
|
482 | 482 |
|
483 |
| -if not location_offset then |
484 |
| - function snav_snav_location_offset(lat, lon, brng, dist) |
485 |
| - local R = 6371000 |
486 |
| - local lat_rad, lon_rad = math.rad(lat), math.rad(lon) |
487 |
| - local brng_rad = math.rad(brng) |
488 |
| - local new_lat_rad = math.asin(math.sin(lat_rad) * math.cos(dist / R) + math.cos(lat_rad) * math.sin(dist / R) * math.cos(brng_rad)) |
489 |
| - local new_lon_rad = lon_rad + atan2_safe(math.sin(brng_rad) * math.sin(dist / R) * math.cos(lat_rad), math.cos(dist / R) - math.sin(lat_rad) * math.sin(new_lat_rad)) |
490 |
| - local new_loc = Location() |
491 |
| - new_loc:lat(math.floor(math.deg(new_lat_rad) * 1e7 + 0.5)) |
492 |
| - new_loc:lng(math.floor(math.deg(new_lon_rad) * 1e7 + 0.5)) |
493 |
| - return new_loc |
494 |
| - end |
| 483 | +local function location_offset(lat, lon, brng, dist) |
| 484 | + local R = 6371000 |
| 485 | + local lat_rad, lon_rad = math.rad(lat), math.rad(lon) |
| 486 | + local brng_rad = math.rad(brng) |
| 487 | + local new_lat_rad = math.asin(math.sin(lat_rad) * math.cos(dist / R) + math.cos(lat_rad) * math.sin(dist / R) * math.cos(brng_rad)) |
| 488 | + local new_lon_rad = lon_rad + atan2_safe(math.sin(brng_rad) * math.sin(dist / R) * math.cos(lat_rad), math.cos(dist / R) - math.sin(lat_rad) * math.sin(new_lat_rad)) |
| 489 | + local new_loc = Location() |
| 490 | + new_loc:lat(math.floor(math.deg(new_lat_rad) * 1e7 + 0.5)) |
| 491 | + new_loc:lng(math.floor(math.deg(new_lon_rad) * 1e7 + 0.5)) |
| 492 | + return new_loc |
495 | 493 | end
|
496 | 494 |
|
497 | 495 | -- Point-in-polygon check algorithm
|
@@ -663,8 +661,8 @@ local function manage_grid_initialization()
|
663 | 661 | local max_dist = p_max_dist:get() or 500
|
664 | 662 | local center_lat, center_lon = active_center:lat()/1e7, active_center:lng()/1e7
|
665 | 663 | local corner_dist = max_dist * 1.4142
|
666 |
| - local sw_corner_loc = snav_snav_location_offset(center_lat, center_lon, 225, corner_dist) |
667 |
| - local ne_corner_loc = snav_snav_location_offset(center_lat, center_lon, 45, corner_dist) |
| 664 | + local sw_corner_loc = location_offset(center_lat, center_lon, 225, corner_dist) |
| 665 | + local ne_corner_loc = location_offset(center_lat, center_lon, 45, corner_dist) |
668 | 666 | SoarNavGlobals.grid_bounds = {
|
669 | 667 | min_lat = sw_corner_loc:lat()/1e7, max_lat = ne_corner_loc:lat()/1e7,
|
670 | 668 | min_lon = sw_corner_loc:lng()/1e7, max_lon = ne_corner_loc:lng()/1e7
|
@@ -831,7 +829,7 @@ local function stop_and_record_thermal()
|
831 | 829 | local base_comp_time = safe_get(p_wind_comp, 60)
|
832 | 830 | local adaptive_comp_time = base_comp_time * math.max(0.5, math.min(2.0, avg_strength / 1.5))
|
833 | 831 | local offset_dist = wind_vel * adaptive_comp_time
|
834 |
| - local corrected_loc = snav_snav_location_offset(hotspot_lat, hotspot_lon, upwind_bearing, offset_dist) |
| 832 | + local corrected_loc = location_offset(hotspot_lat, hotspot_lon, upwind_bearing, offset_dist) |
835 | 833 | if corrected_loc then
|
836 | 834 | hotspot_lat, hotspot_lon = corrected_loc:lat()/1e7, corrected_loc:lng()/1e7
|
837 | 835 | end
|
|
983 | 981 |
|
984 | 982 | -- Generates a random waypoint, ensuring it's inside the defined area
|
985 | 983 | local function generate_target_around_point(center_lat, center_lon, radius_m)
|
986 |
| - local target_loc = snav_snav_location_offset(center_lat, center_lon, math.random() * 360, math.sqrt(math.random()) * radius_m) |
| 984 | + local target_loc = location_offset(center_lat, center_lon, math.random() * 360, math.sqrt(math.random()) * radius_m) |
987 | 985 | if not target_loc then return nil, nil end
|
988 | 986 | local candidate_lat, candidate_lon = target_loc:lat()/1e7, target_loc:lng()/1e7
|
989 | 987 | local active_center = get_active_center_location()
|
@@ -1134,7 +1132,7 @@ local function search_for_new_waypoint()
|
1134 | 1132 | local wind_dir_bearing = (math.deg(wind_heading_rad) + 180 + 360) % 360
|
1135 | 1133 | local drift_factor = math.min(SoarNavConstants.MAX_DRIFT_FACTOR, age_s / SoarNavConstants.MAX_DRIFT_AGE_S)
|
1136 | 1134 | drift_dist = wind_vel * age_s * drift_factor
|
1137 |
| - local drifted_loc = snav_snav_location_offset(center_lat, center_lon, wind_dir_bearing, drift_dist) |
| 1135 | + local drifted_loc = location_offset(center_lat, center_lon, wind_dir_bearing, drift_dist) |
1138 | 1136 | if drifted_loc then
|
1139 | 1137 | center_lat = drifted_loc:lat() / 1e7
|
1140 | 1138 | center_lon = drifted_loc:lng() / 1e7
|
@@ -1163,7 +1161,7 @@ local function search_for_new_waypoint()
|
1163 | 1161 | local max_dist = safe_get(p_max_dist, 500)
|
1164 | 1162 | local rand_dist = math.sqrt(math.random()) * max_dist
|
1165 | 1163 | local rand_bearing = math.random() * 360
|
1166 |
| - local fallback_loc = snav_snav_location_offset(center_point_lat, center_point_lon, rand_bearing, rand_dist) |
| 1164 | + local fallback_loc = location_offset(center_point_lat, center_point_lon, rand_bearing, rand_dist) |
1167 | 1165 | if fallback_loc then
|
1168 | 1166 | SoarNavGlobals.target_lat, SoarNavGlobals.target_lon = fallback_loc:lat()/1e7, fallback_loc:lng()/1e7
|
1169 | 1167 | new_wp_found = true
|
@@ -1391,12 +1389,14 @@ update_body = function()
|
1391 | 1389 | end
|
1392 | 1390 | end
|
1393 | 1391 | local rc_val = 0
|
1394 |
| - if SoarNavGlobals.detected_soaring_rc_channel then rc_val = rc:get_pwm(SoarNavGlobals.detected_soaring_rc_channel) end |
1395 |
| - local script_switch_high = rc_val and rc_val >= SoarNavConstants.rc_thresh_high |
| 1392 | + if SoarNavGlobals.detected_soaring_rc_channel then |
| 1393 | + rc_val = rc:get_pwm(SoarNavGlobals.detected_soaring_rc_channel) or 0 |
| 1394 | + end |
| 1395 | + local script_switch_high = rc_val >= SoarNavConstants.rc_thresh_high |
1396 | 1396 | local home = ahrs:get_home()
|
1397 | 1397 | local dist_from_home_3d = ahrs:get_relative_position_NED_home()
|
1398 | 1398 | local can_navigate = false
|
1399 |
| - if arming:is_armed() and script_switch_high and (current_mode == SoarNavConstants.mode_fbwb or current_mode == SoarNavConstants.mode_cruise) and home and dist_from_home_3d and ahrs:get_yaw() and SoarNavGlobals.grid_initialized then |
| 1399 | + if arming:is_armed() and script_switch_high and (current_mode == SoarNavConstants.mode_fbwb or current_mode == SoarNavConstants.mode_cruise) and home and dist_from_home_3d and ahrs:get_yaw_rad() and SoarNavGlobals.grid_initialized then |
1400 | 1400 | local min_alt = safe_get(p_soar_alt_min, 40)
|
1401 | 1401 | local current_alt = -dist_from_home_3d:z()
|
1402 | 1402 | if SoarNavGlobals.script_state == SCRIPT_STATE.NAVIGATING or SoarNavGlobals.script_state == SCRIPT_STATE.THERMAL_PAUSE then
|
@@ -1463,7 +1463,7 @@ update_body = function()
|
1463 | 1463 | log_gcs(MAV_SEVERITY.INFO, 1, string.format("Stuck detected. Repositioning upwind, adaptive distance: %.0fm", repo_dist_m))
|
1464 | 1464 | local wind_heading_rad = atan2_safe(wind_vec:y(), wind_vec:x())
|
1465 | 1465 | local upwind_bearing = (math.deg(wind_heading_rad) + 180 + 360) % 360
|
1466 |
| - local repo_loc = snav_snav_location_offset(current_loc:lat()/1e7, current_loc:lng()/1e7, upwind_bearing, repo_dist_m) |
| 1466 | + local repo_loc = location_offset(current_loc:lat()/1e7, current_loc:lng()/1e7, upwind_bearing, repo_dist_m) |
1467 | 1467 | if repo_loc then
|
1468 | 1468 | SoarNavGlobals.target_lat = repo_loc:lat()/1e7
|
1469 | 1469 | SoarNavGlobals.target_lon = repo_loc:lng()/1e7
|
@@ -1507,7 +1507,7 @@ update_body = function()
|
1507 | 1507 | end
|
1508 | 1508 | local curr_lat, curr_lon = loc:lat()/1e7, loc:lng()/1e7
|
1509 | 1509 | local target_heading = calculate_bearing(curr_lat, curr_lon, SoarNavGlobals.target_lat, SoarNavGlobals.target_lon)
|
1510 |
| - local current_heading = math.deg(ahrs:get_yaw()) |
| 1510 | + local current_heading = math.deg(ahrs:get_yaw_rad()) |
1511 | 1511 | local heading_error = (target_heading - current_heading + 540) % 360 - 180
|
1512 | 1512 | local last_grid_update_num = tonumber(tostring(SoarNavGlobals.last_grid_update_ms))
|
1513 | 1513 | if last_grid_update_num == 0 or (current_time_num - last_grid_update_num) > SoarNavConstants.grid_update_interval_ms then
|
|
0 commit comments