diff --git a/.github/workflows/record-missions.yml b/.github/workflows/record-missions.yml new file mode 100644 index 0000000..fd87b35 --- /dev/null +++ b/.github/workflows/record-missions.yml @@ -0,0 +1,45 @@ +name: Record missions + +# Re-records the replay tests' fixtures (src/test/missions/fixtures) under +# pHelmIvP. Download the "fixtures" artifact and commit it. +on: + workflow_dispatch: + inputs: + missions: + description: "Missions to record, separated by spaces (default: all of missions.txt)" + required: false + default: "" + +defaults: + run: + shell: bash + +env: + DEBIAN_FRONTEND: noninteractive + +jobs: + record: + runs-on: ubuntu-24.04 + container: ubuntu:24.04 + steps: + - name: Install git + run: apt-get update && apt-get install -y --no-install-recommends git ca-certificates + + - uses: actions/checkout@v5 + + - name: Init + run: | + git config --global --add safe.directory '*' + ./init.sh + # all of MOOS-IvP, not just the helm libraries + apt-get install -y --no-install-recommends make patch + + - name: Record + env: + MISSIONS: ${{ inputs.missions }} + run: src/test/missions/record.sh ${MISSIONS} + + - uses: actions/upload-artifact@v4 + with: + name: fixtures + path: src/test/missions/fixtures diff --git a/init.sh b/init.sh index 08ce4aa..9fdc3d0 100755 --- a/init.sh +++ b/init.sh @@ -30,7 +30,8 @@ apt_install() { } # moos-ivp's helm libraries (IVP_BUILD_HELM_LIBS_ONLY) need only CMake and a compiler -packages=(cmake ninja-build g++ git ca-certificates protobuf-compiler libprotobuf-dev curl gpg) +# xz-utils: the replay tests' recorded missions are compressed +packages=(cmake ninja-build g++ git ca-certificates protobuf-compiler libprotobuf-dev curl gpg xz-utils) $SUDO apt-get update apt_install "${packages[@]}" diff --git a/src/lib/helm/helm.cpp b/src/lib/helm/helm.cpp index 706af5a..c2881d6 100644 --- a/src/lib/helm/helm.cpp +++ b/src/lib/helm/helm.cpp @@ -48,6 +48,8 @@ struct Value bool is_string{false}; double d{0}; std::string s; + // posted by the helm's "initialize" + bool init{false}; }; Value make_value(const VarDataPair& pair) @@ -139,6 +141,7 @@ class Helm::Impl void start(double time); // mail + void apply_pending(double time); void apply(const Navigation& nav, double time); void apply(const Command& command, double time); void apply_variable(const std::string& var, const Value& value, double msg_time); @@ -341,37 +344,30 @@ const Output& Helm::Impl::iterate(double time) output_ = Output(); curr_time_ = time; - // the previous iteration's posts reach the helm first, as they reached the MOOSDB first + info_buffer_.setCurrTime(time); + ledger_.setCurrTimeUTC(time); + if (!started_) + { + // pHelmIvP posts its "initialize" variables before it registers for + // anything, so they replace whatever was posted before it started + if (config_warnings_.empty()) + apply_pending(time); + start(time); + } + + // The previous iteration's posts (or the startup's) reach the helm first, + // as they reached the MOOSDB first auto echo = std::move(posted_); posted_.clear(); const double echo_time = posted_time_ == 0 ? time : posted_time_; posted_time_ = time; - if (!started_) - start(time); - - info_buffer_.setCurrTime(time); - ledger_.setCurrTimeUTC(time); if (status_ != Status::MALCONFIG) { for (const auto& [var, value] : echo) if (registered_.count(var)) apply_variable(var, value, echo_time); - for (const auto& input : pending_) - { - if (const auto* nav = std::get_if(&input)) - apply(*nav, time); - else if (const auto* var = std::get_if(&input)) - { - Value value; - value.is_string = var->value_case() == Variable::kSval; - value.d = var->dval(); - value.s = var->sval(); - apply_variable(var->key(), value, seconds(var->time(), time)); - } - else - apply(std::get(input), time); - } + apply_pending(time); info_buffer_.setValue("COMMS_POLICY", comms_policy_, time); } pending_.clear(); @@ -434,6 +430,26 @@ const Output& Helm::Impl::iterate(double time) return output_; } +void Helm::Impl::apply_pending(double time) +{ + for (const auto& input : pending_) + { + if (const auto* nav = std::get_if(&input)) + apply(*nav, time); + else if (const auto* var = std::get_if(&input)) + { + Value value; + value.is_string = var->value_case() == Variable::kSval; + value.d = var->dval(); + value.s = var->sval(); + apply_variable(var->key(), value, seconds(var->time(), time)); + } + else + apply(std::get(input), time); + } + pending_.clear(); +} + void Helm::Impl::apply(const Navigation& nav, double time) { const auto& n = nav.nav(); @@ -519,6 +535,18 @@ void Helm::Impl::apply(const Command& command, double time) // HelmIvP::OnNewMail for one message void Helm::Impl::apply_variable(const std::string& var, const Value& value, double msg_time) { + // pHelmIvP has set the helm's own "initialize" values already, but still + // acts on them (e.g. initialize MOOS_MANUAL_OVERRIDE = false) + auto store = [&]() + { + if (value.init) + return; + if (value.is_string) + info_buffer_.setValue(var, value.s, msg_time); + else + info_buffer_.setValue(var, value.d, msg_time); + }; + const std::string sval = value.s; if (var == "MOOS_MANUAL_OVERRIDE" || var == "MOOS_MANUAL_OVERIDE") { @@ -526,7 +554,7 @@ void Helm::Impl::apply_variable(const std::string& var, const Value& value, doub set_manual_override(false); else if (toupper(sval) == "TRUE") set_manual_override(true); - info_buffer_.setValue(var, sval, msg_time); + store(); } // Status is always complete, so there's nothing to rejournal else if (var == "IVPHELM_STATE" || var == "DB_CLIENTS" || var == "RESTART_HELM" || @@ -548,14 +576,12 @@ void Helm::Impl::apply_variable(const std::string& var, const Value& value, doub info_buffer_.setValue(toupper(vname) + "_NAV_GROUP", ledger_.getGroup(vname)); info_buffer_.setValue(toupper(vname) + "_NAV_TYPE", ledger_.getType(vname)); } - info_buffer_.setValue(var, sval, msg_time); + store(); } else if (var == "HELM_MAP_CLEAR") refresh_pending_ = true; - else if (value.is_string) - info_buffer_.setValue(var, sval, msg_time); else - info_buffer_.setValue(var, value.d, msg_time); + store(); } void Helm::Impl::set_manual_override(bool on) @@ -668,8 +694,9 @@ void Helm::Impl::post_initial_variables(bool deferred) info_buffer_.setValue(var, value.s); else info_buffer_.setValue(var, value.d); - // the MOOSDB echoes these too, but pHelmIvP ignores that echo (HELM_VAR_INIT) add_posting(Posting::INITIALIZE, "", var, value); + value.init = true; + notify(var, value); } } } diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index b780187..70d964c 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -5,3 +5,13 @@ add_test(NAME helm_libs COMMAND check_helm_libs) add_executable(test_helm helm/test_helm.cpp) target_link_libraries(test_helm PRIVATE ivpico_helm) add_test(NAME helm COMMAND test_helm) + +add_executable(replay missions/replay.cpp) +target_link_libraries(replay PRIVATE ivpico_helm) +file(GLOB fixtures LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/missions/fixtures/*) +foreach(fixture ${fixtures}) + if(IS_DIRECTORY ${fixture}) + get_filename_component(mission ${fixture} NAME) + add_test(NAME replay_${mission} COMMAND replay ${fixture}) + endif() +endforeach() diff --git a/src/test/missions/README.md b/src/test/missions/README.md new file mode 100644 index 0000000..5e0b6e8 --- /dev/null +++ b/src/test/missions/README.md @@ -0,0 +1,19 @@ +# Mission replay tests + +*This section was written by Claude.* + +These tests check that the IvPico helm (`src/lib/helm`) does what pHelmIvP does, using MOOS-IvP's own missions. + +```mermaid +flowchart LR + record["record.sh
(all of MOOS-IvP, headless)"] -- "alog, .moos, .bhv" --> fixtures["fixtures/<mission>"] + fixtures --> replay["replay
(ctest: replay_<mission>)"] + replay -- "same inputs, same iteration times" --> helm["ivpico::helm::Helm"] + replay -- "compare" --> report["pass / fail per stream"] +``` + +- `missions.txt` lists the missions and the variables poked to start them. +- `record.sh [mission...]` builds all of MOOS-IvP from `external/moos-ivp` into `build/missions`, runs each mission under pHelmIvP with time warp, and keeps each vehicle's helm inputs and outputs in `fixtures/`. The "Record missions" workflow runs it on GitHub and uploads the fixtures as an artifact. +- `replay ` feeds the recorded inputs to the IvPico helm at pHelmIvP's iteration times and compares what each helm posted. `replay.cpp` describes what's compared and the tolerances. + +Re-record the fixtures whenever `external/moos-ivp` moves. diff --git a/src/test/missions/fixtures/s10_juliet/juliet.bhv b/src/test/missions/fixtures/s10_juliet/juliet.bhv new file mode 100644 index 0000000..677948c --- /dev/null +++ b/src/test/missions/fixtures/s10_juliet/juliet.bhv @@ -0,0 +1,91 @@ +//-------- FILE: juliet.bhv ------------- + +initialize DEPLOY = false +initialize RETURN = false +initialize GOTO_DEPTH = false + +//======================================================== +// Hierarchical Mode Declarations +// o ROOT o ROOT +// |--o INACTIVE |--o GOTO_DEPTH +// |--o ACTIVE |--o NOT GOTO_TDEPTH +// |--o RETURNING +// |--o LOITERING +//======================================================== + + +Set MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set MODE = RETURNING { + MODE = ACTIVE + RETURN = true +} LOITERING + +//---------------------------------------------- +Behavior = BHV_Loiter +{ + name = loiter + pwt = 100 + condition = MODE == LOITERING + updates = UP_LOITER + + speed = 1.3 + clockwise = best + radius = 4.0 + nm_radius = 15.0 + polygon = radial:: x=20, y=-80, radius=20, pts=8, snap=1, label=juliet_loiter + visual_hints = nextpt_color=red, nextpt_lcolor=khaki, edge_color=khaki + visual_hints = edge_size=1, vertex_size=4, vertex_color=white +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = MODE == RETURNING + updates = UPDATES_RETURN + perpetual = true + endflag = RETURNING = false + endflag = DEPLOY = false + + speed = 2.0 + radius = 2.0 + nm_radius = 8.0 + point = 0,0 +} + +//---------------------------------------------- +Behavior = BHV_GoToDepth +{ + name = bhv_go_to_depth + pwt = 1000 + condition = MODE == LOITERING + condition = GOTO_DEPTH = true + endflag = GOTO_DEPTH = false + + depth = 40,60:30,45:20,45 + repeat = 2 + perpetual = true + capture_flag = DEPTH_ACHIEVED + capture_delta = 0.5 +} + +//---------------------------------------------- +Behavior = BHV_ConstantDepth +{ + name = bhv_const_depth + pwt = 100 + duration = no-time-limit + condition = MODE==LOITERING + condition = GOTO_DEPTH != true + updates = DEPTH_VALUE + + depth = 50 + peakwidth = 8 + basewidth = 12 + summitdelta = 10 +} + diff --git a/src/test/missions/fixtures/s10_juliet/s10_juliet.alog.xz b/src/test/missions/fixtures/s10_juliet/s10_juliet.alog.xz new file mode 100644 index 0000000..868f4f0 Binary files /dev/null and b/src/test/missions/fixtures/s10_juliet/s10_juliet.alog.xz differ diff --git a/src/test/missions/fixtures/s10_juliet/s10_juliet.moos b/src/test/missions/fixtures/s10_juliet/s10_juliet.moos new file mode 100644 index 0000000..2466a22 --- /dev/null +++ b/src/test/missions/fixtures/s10_juliet/s10_juliet.moos @@ -0,0 +1,286 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./JULIET_27_10_2026_____07_50_51/JULIET_27_10_2026_____07_50_51._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930874517.89039 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: juliet.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = henry + +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uTimerScript @ NewConsole = false + Run = uProcessWatch @ NewConsole = false +} + +//------------------------------------------ +// uProcessWatch config block + +ProcessConfig = uProcessWatch +{ + AppTick = 4 + CommsTick = 4 + + watch_all = true + nowatch = uXMS* +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = APPCAST @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DEPTH_VALUE @ 0 NOSYNC + Log = DESIRED_DEPTH @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = GOTO_DEPTH @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = LOITER_ACQUIRE @ 0 NOSYNC + Log = LOITER_DIST_TO_POLY @ 0 NOSYNC + Log = LOITER_DIST_TO_VERT @ 0 NOSYNC + Log = LOITER_ETA_TO_POLY @ 0 NOSYNC + Log = LOITER_INDEX @ 0 NOSYNC + Log = LOITER_MODE @ 0 NOSYNC + Log = LOITER_REPORT @ 0 NOSYNC + Log = MODE @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = NAV_DEPTH @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = UPDATES_RETURN @ 0 NOSYNC + Log = UP_LOITER @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_POLYGON @ 0 NOSYNC + AppTick = 5 + CommsTick = 5 + + File = JULIET + PATH = ./ + SyncLog = false + AsyncLog = true + FileTimeStamp = true + + LogAuxSrc = true + WildCardLogging = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + start_x = 0 + start_y = 0 + start_speed = 0 + start_heading = 180 + + buoyancy_rate = 0.025 + max_depth_rate = 5 + max_depth_rate_speed = 2.0 + + default_water_depth = 400 + + prefix = NAV +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = juliet.bhv + verbose = quiet + domain = course:0:359:360 + domain = speed:0:4:41 + domain = depth:0:490:491 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + DEPTH_CONTROL = true + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + // Pitch PID controller + pitch_pid_kp = 1.5 + pitch_pid_kd = 1.0 + pitch_pid_ki = 0 + pitch_pid_integral_limit = 0 + + // ZPID controller + z_to_pitch_pid_kp = 0.12 + z_to_pitch_pid_kd = 0 + z_to_pitch_pid_ki = 0.004 + z_to_pitch_pid_integral_limit = 0.05 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + maxpitch = 15 + maxelevator = 13 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.65 + vehicle_shape_scale = 1.35 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + left_context[survey-point] = SURVEY_UPDATES = points = vname=$(VNAME), x=$(XPOS), y=$(YPOS), format=bowtie, label=delta, wid1=10, wid2=25, wid3=30, height=20 + +// left_context[survey-point] = SURVEY_UPDATES = points = vname=$(VNAME), x=$(XPOS), y=$(YPOS), format=lawnmower, label=delta, width=70, height=30, lane_width=8, rows=north-south, degs=80 + + left_context[survey-point] = SURVEY = true + + scope = SURVEY_UPDATES + scope = MVIEWER_LCLICK + scope = GPS_UPDATE_RECEIVED + scope = TIME_AT_SURFACE + + action = UP_LOITER="speed=0" + action = UP_LOITER="speed=1.2" + + button_one = DEPLOY # DEPLOY=true # MOOS_MANUAL_OVERRIDE=false + button_two = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN # RETURN=true + button_three = GTDEPTH-true # GOTO_DEPTH=true + button_four = GTDEPTH-false # GOTO_DEPTH=false +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + vessel_type = UUV +} + +//------------------------------------------ +// uTimerScript configuration block + +ProcessConfig = uTimerScript +{ + AppTick = 4 + CommsTick = 4 + + paused = false + reset_max = any + reset_time = end + condition = TIME_AT_SURFACE > 3 + upon_awake = restart + delay_start = 20:120 + script_name = GPS_SCRIPT + + event = var=GPS_UPDATE_RECEIVED, val="RCVD_$(COUNT)", time=0.5:1.0 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/s1_alpha/alpha.bhv b/src/test/missions/fixtures/s1_alpha/alpha.bhv new file mode 100644 index 0000000..7847455 --- /dev/null +++ b/src/test/missions/fixtures/s1_alpha/alpha.bhv @@ -0,0 +1,91 @@ +//-------- FILE: alpha.bhv ------------- + +initialize DEPLOY = false +initialize RETURN = false + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_survey + pwt = 100 + condition = RETURN = false + condition = DEPLOY = true + endflag = RETURN = true + + endflag = SUMMARY=speed=$[SPEED],time=$[DUR_RUN_TIME] + + configflag = CRUISE_SPD = $[SPEED] + + activeflag = INFO=$[OWNSHIP] + activeflag = INFO=$[BHVNAME] + activeflag = INFO=$[BHVTYPE] + + cycleflag = CINFO=$[OSX],$[OSY] + + wptflag = PREV=$(PX),$(PY) + wptflag = NEXT=$(NX),$(NY) + wptflag = TEST=$(X),$(Y) + wptflag = OSPOS=$(OSX),$(OSY) + wptflag_on_start = true + + updates = WPT_UPDATE + perpetual = true + + speed_alt = 1.2 + use_alt_speed = true + lead = 8 + lead_damper = 1 + lead_to_start = true + speed = 3 // meters per second + capture_line = true + capture_radius = 5.0 + slip_radius = 15.0 + efficiency_measure = all + polygon = 60,-40:60,-160:150,-160:180,-100:150,-40 + + order = normal + repeat = 0 + + visual_hints = nextpt_color=yellow + visual_hints = nextpt_vertex_size=8 + visual_hints = nextpt_lcolor=gray70 + visual_hints = vertex_color=dodger_blue, edge_color=white + visual_hints = vertex_size=5, edge_size=1 +} + +//---------------------------------------------- +Behavior=BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = RETURN = true + condition = DEPLOY = true + perpetual = true + updates = RETURN_UPDATE + endflag = RETURN = false + endflag = DEPLOY = false + endflag = MISSION = complete + + speed = 2.0 + capture_radius = 2.0 + slip_radius = 8.0 + points = 0,-20 + } + +//---------------------------------------------- +Behavior=BHV_ConstantSpeed +{ + name = const_speed + pwt = 200 + condition = SPD=true + condition = DEPLOY = true + perpetual = true + updates = SPEED_UPDATE + endflag = SPD = false + + speed = 0.5 + duration = 10 + duration_reset = CONST_SPD_RESET=true + +} + diff --git a/src/test/missions/fixtures/s1_alpha/s1_alpha.alog.xz b/src/test/missions/fixtures/s1_alpha/s1_alpha.alog.xz new file mode 100644 index 0000000..1f5500c Binary files /dev/null and b/src/test/missions/fixtures/s1_alpha/s1_alpha.alog.xz differ diff --git a/src/test/missions/fixtures/s1_alpha/s1_alpha.moos b/src/test/missions/fixtures/s1_alpha/s1_alpha.moos new file mode 100644 index 0000000..510d5f0 --- /dev/null +++ b/src/test/missions/fixtures/s1_alpha/s1_alpha.moos @@ -0,0 +1,315 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./MOOSLog_27_10_2026_____06_56_17/MOOSLog_27_10_2026_____06_56_17._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930841772.21312 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: alpha.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = alpha +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +// Forest Lake +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +// MIT Sailing Pavilion (use this one) +// LatOrigin = 42.358456 +// LongOrigin = -71.087589 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarineV22 @ NewConsole = false + Run = pMarinePIDV22 @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = uProcessWatch @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pRealm @ NewConsole = false +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = AIS_REPORT @ 0 NOSYNC + Log = AIS_REPORT_LOCAL @ 0 NOSYNC + Log = APPCAST @ 0 NOSYNC + Log = BHV_ABLE_FILTER @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = CINFO @ 0 NOSYNC + Log = CONST_SPD_RESET @ 0 NOSYNC + Log = CRUISE_SPD @ 0 NOSYNC + Log = CYCLE_INDEX @ 0 NOSYNC + Log = DB_CLIENTS @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = HELM_MAP_CLEAR @ 0 NOSYNC + Log = INFO @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_REJOURNAL @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = MOOS_MANUAL_OVERIDE @ 0 NOSYNC + Log = MOOS_MANUAL_OVERRIDE @ 0 NOSYNC + Log = NAV_DEPTH @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = NEXT @ 0 NOSYNC + Log = NODE_REPORT @ 0 NOSYNC + Log = NODE_REPORT_LOCAL @ 0 NOSYNC + Log = OSPOS @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = PREV @ 0 NOSYNC + Log = RESTART_HELM @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = RETURN_UPDATE @ 0 NOSYNC + Log = SPD @ 0 NOSYNC + Log = SPEED_UPDATE @ 0 NOSYNC + Log = SUMMARY @ 0 NOSYNC + Log = TEST @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_SEGLIST @ 0 NOSYNC + Log = WPT_EFF_DIST_ALL @ 0 NOSYNC + Log = WPT_EFF_DIST_LEG @ 0 NOSYNC + Log = WPT_EFF_SUMM_ALL @ 0 NOSYNC + Log = WPT_EFF_SUMM_LEG @ 0 NOSYNC + Log = WPT_EFF_TIME_ALL @ 0 NOSYNC + Log = WPT_EFF_TIME_LEG @ 0 NOSYNC + Log = WPT_INDEX @ 0 NOSYNC + Log = WPT_ODO @ 0 NOSYNC + Log = WPT_STAT @ 0 NOSYNC + Log = WPT_UPDATE @ 0 NOSYNC + AppTick = 8 + CommsTick = 8 + + AsyncLog = true + + // For variables that are published in a bundle on their + // first post, explicitly declare their logging request + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = REPORT @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = SUMMARY @ 0 NOSYNC + + LogAuxSrc = true + WildCardLogging = true + WildCardOmitPattern = *_STATUS + WildCardOmitPattern = DB_VARSUMMARY + WildCardOmitPattern = DB_RWSUMMARY + WildCardExclusionLog = true +} + +//------------------------------------------ +// uProcessWatch config block + +ProcessConfig = uProcessWatch +{ + AppTick = 4 + CommsTick = 4 + + watch_all = true + nowatch = uPokeDB* + nowatch = uQueryDB* + nowatch = uXMS* + nowatch = uMAC* +} + +//------------------------------------------ +// uSimMarineV22 config block + +ProcessConfig = uSimMarineV22 +{ + AppTick = 4 + CommsTick = 4 + + start_pos = x=0, y=-20, heading=180, speed=0 + prefix = NAV + turn_rate = 95 + thrust_map = 0:0, 20:1, 40:2, 60:3, 80:4, 100:5 + turn_spd_loss = 0.4 +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = alpha.bhv + domain = course:0:359:360 + domain = speed:0:4:41 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePIDV22 +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = false + + // SIM_INSTABILITY = 20 + + // Yaw PID controller + yaw_pid_kp = 0.9 + yaw_pid_kd = 0.3 + yaw_pid_ki = 0.3 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + //tiff_file = MIT_SP.tif + + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.65 + vehicle_shape_scale = 1.5 + hash_delta = 50 + hash_shade = 0.22 + hash_viewable = true + + trails_point_size = 1 + + //op_vertex = x=-83, y=-47, lwidth=1,lcolor=yellow,looped=true,group=moa + //op_vertex = x=-46.4, y=-129.2, lwidth=1,lcolor=yellow,looped=true,group=moa + //op_vertex = x=94.6, y=-62.2, lwidth=1,lcolor=yellow,looped=true,group=moa + //op_vertex = x=58, y=20, lwidth=1,lcolor=yellow,looped=true,group=moa + + // Appcast configuration + appcast_height = 75 + appcast_width = 30 + appcast_viewable = true + appcast_color_scheme = indigo + nodes_font_size = xlarge + procs_font_size = xlarge + appcast_font_size = large + + // datum_viewable = true + // datum_size = 18 + // gui_size = small + + // left_context[survey-point] = DEPLOY=true + // left_context[survey-point] = MOOS_MANUAL_OVERRIDE=false + // left_context[survey-point] = RETURN=false + + right_context[return] = DEPLOY=true + right_context[return] = MOOS_MANUAL_OVERRIDE=false + right_context[return] = RETURN=false + + scope = SUMMARY + scope = RETURN + scope = WPT_STAT + scope = VIEW_SEGLIST + scope = VIEW_POINT + scope = VIEW_POLYGON + scope = MVIEWER_LCLICK + scope = MVIEWER_RCLICK + + button_one = DEPLOY # DEPLOY=true + button_one = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN # RETURN=true + button_three = SLOWER # WPT_UPDATE=speed=1.5 + button_four = FASTER # WPT_UPDATE=speed=3.5 + + action = MENU_KEY=deploy # DEPLOY = true # RETURN = false + action+ = MENU_KEY=deploy # MOOS_MANUAL_OVERRIDE=false + action = RETURN=true + action = UPDATES_RETURN=speed=1.4 +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + + platform_type = kayak + platform_color = yellow + platform_length = 4 +} + +ProcessConfig = uTimerScript_record +{ + event = var=DEPLOY, val=true, time=10 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=10 +} + + diff --git a/src/test/missions/fixtures/s2_bravo/bravo.bhv b/src/test/missions/fixtures/s2_bravo/bravo.bhv new file mode 100644 index 0000000..20ba119 --- /dev/null +++ b/src/test/missions/fixtures/s2_bravo/bravo.bhv @@ -0,0 +1,72 @@ +//------------------------------------------------ +// File: bravo.bhv +// Name: M. Benjamin +// Date: Jan 1, 2010 +//------------------------------------------------ + +initialize DEPLOY = false +initialize RETURN = false + +set MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set MODE = SURVEYING { + MODE = ACTIVE + RETURN != true +} RETURNING + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_survey + pwt = 100 + condition = MODE == SURVEYING + endflag = RETURN = true + perpetual = true + + lead = 8 + lead_damper = 1 + speed = 2.0 // meters per second + radius = 4.0 + nm_radius = 10.0 + points = 60,-40:60,-160:150,-160:180,-100:150,-40 + repeat = 0 +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = MODE == RETURNING + perpetual = true + endflag = RETURN = false + endflag = DEPLOY = false + + speed = 2.0 + radius = 2.0 + nm_radius = 8.0 + point = 0,0 +} + +//* Constant Heading +Behavior=BHV_ConstantHeading +{ + name = const_hdg + pwt = 100 + perpetual = true + + condition = MODE==TURNING + updates = TURN_HEADING + endflag = CONDITIONS_SET = false + endflag = TURN_FINISH = true + endflag = TURN = finish + endflag = TURNING = false + + heading = 190 + duration = no-time-limit + complete_thresh = 4 + heading_mismatch_var = DBG_CH +} + diff --git a/src/test/missions/fixtures/s2_bravo/s2_bravo.alog.xz b/src/test/missions/fixtures/s2_bravo/s2_bravo.alog.xz new file mode 100644 index 0000000..6f56f3c Binary files /dev/null and b/src/test/missions/fixtures/s2_bravo/s2_bravo.alog.xz differ diff --git a/src/test/missions/fixtures/s2_bravo/s2_bravo.moos b/src/test/missions/fixtures/s2_bravo/s2_bravo.moos new file mode 100644 index 0000000..637ef5d --- /dev/null +++ b/src/test/missions/fixtures/s2_bravo/s2_bravo.moos @@ -0,0 +1,259 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./MOOSLog_27_10_2026_____07_40_01/MOOSLog_27_10_2026_____07_40_01._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930868015.6366 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: bravo.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = bravo + +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = uSimCurrent @ NewConsole = false + Run = pHelmIvP @ NewConsole = false +} + +//------------------------------------------ +// uSimCurrent config block + +ProcessConfig = uSimCurrent +{ + AppTick = 10 + CommsTick = 10 + + current_field = bravo.cfd +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = AIS_REPORT @ 0 NOSYNC + Log = AIS_REPORT_LOCAL @ 0 NOSYNC + Log = APPCAST @ 0 NOSYNC + Log = BHV_ABLE_FILTER @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = CYCLE_INDEX @ 0 NOSYNC + Log = DBG_CH @ 0 NOSYNC + Log = DB_CLIENTS @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = HELM_MAP_CLEAR @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_REJOURNAL @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = MODE @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = MOOS_MANUAL_OVERIDE @ 0 NOSYNC + Log = MOOS_MANUAL_OVERRIDE @ 0 NOSYNC + Log = NAV_DEPTH @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = NODE_REPORT @ 0 NOSYNC + Log = NODE_REPORT_LOCAL @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = RESTART_HELM @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = TURN_HEADING @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_SEGLIST @ 0 NOSYNC + Log = WPT_INDEX @ 0 NOSYNC + Log = WPT_ODO @ 0 NOSYNC + Log = WPT_STAT @ 0 NOSYNC + AppTick = 4 + CommsTick = 4 + + SyncLog = false + AsyncLog = true + + LogAuxSrc = true + WildCardLogging = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + start_x = 0 + start_y = 0 + start_speed = 0 + start_heading = 180 + prefix = NAV + //current_field = bravo.cfd +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = bravo.bhv + verbose = true + domain = course:0:359:360 + domain = speed:0:4:21 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = false + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + + // Non-zero SPEED_FACTOR overrides SPEED_PID + // DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.65 + vehicle_shape_scale = 1.5 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + // appcast settings + appcast_height = 75 + appcast_width = 30 + appcast_viewable = true + appcast_color_scheme = indigo + nodes_font_size = medium + procs_font_size = medium + appcast_font_size = small + + scope = VIEW_POINT + scope = VIEW_POLYGON + scope = VIEW_SEGLIST + + scope = NAV_X + scope = NAV_Y + scope = MVIEWER_LCLICK + scope = MVIEWER_RCLICK + + left_context = return_point + + button_one = DEPLOY # DEPLOY=true + button_one = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN # RETURN=true + + action = MENU_KEY=deploy # DEPLOY = true # RETURN = false + action+ = MENU_KEY=deploy # MOOS_MANUAL_OVERRIDE=false + action = RETURN=true + action = HSLINE=off + action = HSLINE=on + action = UPDATES_RETURN=speed=1.4 +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 4 + CommsTick = 4 + + platform_type = kayak + platform_length = 3 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/s3_charlie/charlie.bhv b/src/test/missions/fixtures/s3_charlie/charlie.bhv new file mode 100644 index 0000000..28bcdb7 --- /dev/null +++ b/src/test/missions/fixtures/s3_charlie/charlie.bhv @@ -0,0 +1,74 @@ +//------------------------------------------------ +// File: charlie.bhv +// Name: M. Benjamin +// Date: Jan 2nd, 2010 +//------------------------------------------------ + +initialize DEPLOY=false, RETURN = false +initialize LOITER=true, STATION_KEEP = false + +set MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set MODE = LOITERING { + MODE = ACTIVE + (LOITER = true) and (RETURN != true) + STATION_KEEP != true +} + +set MODE = RETURNING { + MODE = ACTIVE + RETURN = true + STATION_KEEP != true +} STATION-KEEPING + + +//---------------------------------------------- +Behavior = BHV_Loiter +{ + name = loiter + priority = 100 + condition = MODE==LOITERING + updates = UP_LOITER + + speed = 1.3 + clockwise = false + radius = 4.0 + nm_radius = 25.0 + polygon = format=radial, x=0, y=-75, radius=40, pts=6, snap=1 + visual_hints = nextpt_color=yellow, nextpt_lcolor=khaki + visual_hints = edge_color=white, vertex_color=invisible + visual_hints = edge_size=1, vertex_size=0, label=LOITER_POLYGON + visual_hints = nextpt_vertex_size=5 +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + priority = 100 + perpetual = true + updates = RETURN_UPDATES + condition = MODE==RETURNING + endflag = STATION_KEEP = true + + speed = 1.3 + radius = 3.0 + nm_radius = 15.0 + point = x=15, y=0 +} + +//---------------------------------------------- +Behavior = BHV_StationKeep +{ + name = station-keep + priority = 100 + condition = MODE==STATION-KEEPING + + center_activate = true + inner_radius = 5 + outer_radius = 10 + outer_speed = 1.0 + swing_time = 10 +} diff --git a/src/test/missions/fixtures/s3_charlie/s3_charlie.alog.xz b/src/test/missions/fixtures/s3_charlie/s3_charlie.alog.xz new file mode 100644 index 0000000..a02bf5b Binary files /dev/null and b/src/test/missions/fixtures/s3_charlie/s3_charlie.alog.xz differ diff --git a/src/test/missions/fixtures/s3_charlie/s3_charlie.moos b/src/test/missions/fixtures/s3_charlie/s3_charlie.moos new file mode 100644 index 0000000..f059384 --- /dev/null +++ b/src/test/missions/fixtures/s3_charlie/s3_charlie.moos @@ -0,0 +1,275 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./MOOSLog_27_10_2026_____07_42_08/MOOSLog_27_10_2026_____07_42_08._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930869283.83171 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: charlie.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = charlie + +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uTimerScript @ NewConsole = false +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = APPCAST @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = LOITER @ 0 NOSYNC + Log = LOITER_ACQUIRE @ 0 NOSYNC + Log = LOITER_DIST_TO_POLY @ 0 NOSYNC + Log = LOITER_DIST_TO_VERT @ 0 NOSYNC + Log = LOITER_ETA_TO_POLY @ 0 NOSYNC + Log = LOITER_INDEX @ 0 NOSYNC + Log = LOITER_MODE @ 0 NOSYNC + Log = LOITER_REPORT @ 0 NOSYNC + Log = MODE @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = RETURN_UPDATES @ 0 NOSYNC + Log = STATION_KEEP @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = UP_LOITER @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_POLYGON @ 0 NOSYNC + AppTick = 4 + CommsTick = 4 + + LogAuxSrc = true + WildCardLogging = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + start_x = 0 + start_y = -10 + start_speed = 0 + start_heading = 180 + prefix = NAV + max_deceleration = 0.1 + + dual_state = true +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = charlie.bhv + verbose = true + domain = course:0:359:360 + domain = speed:0:4:21 +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + + platform_type = kayak + platform_length = 3 + platform_group = normal + + alt_nav_prefix = NAV_GT + alt_nav_group = gnd_truth + + } +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = false + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + + // Non-zero SPEED_FACTOR overrides SPEED_PID + // DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// uTimerScript configuration block + +ProcessConfig = uTimerScript +{ + AppTick = 2 + CommsTick = 2 + + paused = false + reset_max = unlimited + reset_time = end + condition = WIND_GUSTS = true + delay_start = 30 + delay_reset = 10:60 + time_warp = 0.25:2.0 + + randvar = varname=ANG, min=0, max=359, key=at_reset + randvar = varname=MAG, min=1.5, max=3.5, key=at_reset + + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*0.2}", time=0 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*0.2}", time=2 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*0.2}", time=4 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*0.2}", time=6 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*0.2}", time=8 + + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*-0.2}", time=10 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*-0.2}", time=12 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*-0.2}", time=14 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*-0.2}", time=16 + event = var=DRIFT_VECTOR_ADD, val="$(ANG),{$(MAG)*-0.2}", Time=18 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + appcast_viewable = true + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -340 + zoom = 0.65 + vehicle_shape_scale = 2.0 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + scope = LOITER_REPORT + scope = LOITER_DIST_TO_POLY + scope = USM_FSUMMARY + scope = USM_DRIFT_VECTOR_ADD + scope = DB_CLIENTS + scope = CONVEX_GRID_CONFIG + + action = UP_LOITER = center_assign = 40,-40 + action+ = UP_LOITER = center_assign = x=100, y=-80 + + action = UP_LOITER = polygon = format=radial, x=0, y=-75, radius=40, pts=6 + action+ = UP_LOITER = polygon = format=ellipse, x=110, y=-75, degs=150, pts=14, major=80, minor=20 + + action = UP_LOITER = center_activate = true + action+ = UP_LOITER = center_activate = false + + action = WIND_GUSTS = true + action+ = WIND_GUSTS = false + + action = UP_LOITER=clockwise=true + action = UP_LOITER=clockwise=false + + button_one = DEPLOY # MOOS_MANUAL_OVERRIDE=false # DEPLOY=true + button_two = STATION_KEEP=false # LOITER=true # RETURN=false + button_two = RETURN # RETURN=true # STATION_KEEP=false + button_three = SKEEP:true # STATION_KEEP=true + button_four = SKEEP:false # STATION_KEEP=false +} + +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/s4_delta/delta.bhv b/src/test/missions/fixtures/s4_delta/delta.bhv new file mode 100644 index 0000000..3017e2a --- /dev/null +++ b/src/test/missions/fixtures/s4_delta/delta.bhv @@ -0,0 +1,135 @@ +//-------- FILE: delta.bhv ------------- + +initialize DEPLOY = false +initialize RETURN = false +initialize SURVEY = false +initialize PSURFACE = true + +//======================================================== +// Hierarchical Mode Declarations +// o ROOT o ROOT +// |--o INACTIVE |--o PSURFACING +// |--o ACTIVE |--o NOT_PSURFACING +// |--o RETURNING +// |--o LOITERING +// |--o SURVEYING +//======================================================== + + +Set MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set MODE = LOITERING { + MODE = ACTIVE + RETURN != true + SURVEY != true +} + +set MODE = RETURNING { + MODE = ACTIVE + RETURN = true +} SURVEYING + + +//---------------------------------------------- +Behavior = BHV_MaxDepth +{ + name = maxdepth + pwt = 200 + condition = MODE==ACTIVE + + max_depth = 7 + tolerance = 1 + duration = no-time-limit +} + +//---------------------------------------------- +Behavior = BHV_Loiter +{ + name = loiter + pwt = 100 + condition = MODE == LOITERING + updates = UP_LOITER + + speed = 1.3 + clockwise = best + radius = 4.0 + nm_radius = 15.0 + polygon = radial:: x=20, y=-80, radius=20, pts=8, snap=1, label=DUDLEY_LOITER + visual_hints = nextpt_color=red, nextpt_lcolor=khaki + visual_hints = edge_color=khaki, vertex_color=white + visual_hints = edge_size=1, vertex_size=4 +} + + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = MODE == RETURNING + updates = UPDATES_RETURN + perpetual = true + endflag = RETURNING = false + endflag = DEPLOY = false + + speed = 2.0 + radius = 2.0 + nm_radius = 8.0 + point = 0,0 +} + +//---------------------------------------------- +Behavior = BHV_PeriodicSurface +{ + name = bhv_periodic_surface + pwt = 1000 + condition = (MODE == LOITERING) + condition = PSURFACE = true + + period = 120 + zero_speed_depth = 0.5 + max_time_at_surface = 60 + ascent_speed = 1.0 + ascent_grade = fullspeed +} + +//---------------------------------------------- +Behavior = BHV_ConstantDepth +{ + name = bhv_const_depth + pwt = 100 + duration = no-time-limit + condition = MODE==ACTIVE + updates = DEPTH_VALUE + + depth = 50 + peakwidth = 8 + basewidth = 12 + summitdelta = 10 +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_survey + pwt = 100 + condition = MODE==SURVEYING + perpetual = true + updates = SURVEY_UPDATES + endflag = SURVEY = false + + endflag = SURVEY = false + lead = 8 + lead_damper = 1 + speed = 2.0 // meters per second + radius = 8.0 +// points = format=lawnmower, label=dudley_survey, x=80, y=-80, width=70, height=30, lane_width=8, rows=north-south, degs=30 + points = format=bowtie, label=dudley_bowtie, x=80, y=-80, wid1=10, wid2=25, wid3=30, height=20 + repeat = 10 + visual_hints = nextpt_color=red, nextpt_lcolor=khaki + visual_hints = vertex_color=yellow, line_color=white + visual_hints = vertex_size=2, edge_size=1 +} + diff --git a/src/test/missions/fixtures/s4_delta/s4_delta.alog.xz b/src/test/missions/fixtures/s4_delta/s4_delta.alog.xz new file mode 100644 index 0000000..426eb5d Binary files /dev/null and b/src/test/missions/fixtures/s4_delta/s4_delta.alog.xz differ diff --git a/src/test/missions/fixtures/s4_delta/s4_delta.moos b/src/test/missions/fixtures/s4_delta/s4_delta.moos new file mode 100644 index 0000000..656e2af --- /dev/null +++ b/src/test/missions/fixtures/s4_delta/s4_delta.moos @@ -0,0 +1,308 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./LOG_27_10_2026_____07_44_21/LOG_27_10_2026_____07_44_21._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930870614.74463 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: delta.moos +//------------------------------------------------- +ServerHost = localhost +ServerPort = 9000 +Community = henry +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uTimerScript @ NewConsole = false + Run = uProcessWatch @ NewConsole = false +} + +//------------------------------------------ +// uProcessWatch config block + +ProcessConfig = uProcessWatch +{ + AppTick = 4 + CommsTick = 4 + + watch_all = true + nowatch = uXMS* +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = AIS_REPORT @ 0 NOSYNC + Log = AIS_REPORT_LOCAL @ 0 NOSYNC + Log = APPCAST @ 0 NOSYNC + Log = BHV_ABLE_FILTER @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = DB_CLIENTS @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DEPTH_VALUE @ 0 NOSYNC + Log = DESIRED_DEPTH @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = GPS_UPDATE_RECEIVED @ 0 NOSYNC + Log = HELM_MAP_CLEAR @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_REJOURNAL @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = LOITER_ACQUIRE @ 0 NOSYNC + Log = LOITER_DIST_TO_POLY @ 0 NOSYNC + Log = LOITER_DIST_TO_VERT @ 0 NOSYNC + Log = LOITER_ETA_TO_POLY @ 0 NOSYNC + Log = LOITER_INDEX @ 0 NOSYNC + Log = LOITER_MODE @ 0 NOSYNC + Log = LOITER_REPORT @ 0 NOSYNC + Log = MODE @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = MOOS_MANUAL_OVERIDE @ 0 NOSYNC + Log = MOOS_MANUAL_OVERRIDE @ 0 NOSYNC + Log = NAV_DEPTH @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = NODE_REPORT @ 0 NOSYNC + Log = NODE_REPORT_LOCAL @ 0 NOSYNC + Log = PENDING_SURFACE @ 0 NOSYNC + Log = PERIODIC_ASCEND @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = PSF_AT_SURFACE_TIMEOUT @ 0 NOSYNC + Log = PSURFACE @ 0 NOSYNC + Log = RESTART_HELM @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = SURVEY @ 0 NOSYNC + Log = SURVEY_UPDATES @ 0 NOSYNC + Log = TIME_AT_SURFACE @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = UPDATES_RETURN @ 0 NOSYNC + Log = UP_LOITER @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_POLYGON @ 0 NOSYNC + AppTick = 5 + CommsTick = 5 + + File = LOG + PATH = ./ + SyncLog = true @ 0.2 + AsyncLog = true + FileTimeStamp = true + + LogAuxSrc = true + WildCardLogging = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + start_x = 0 + start_y = 0 + start_speed = 0 + start_heading = 180 + + buoyancy_rate = 0.025 + max_depth_rate = 5 + max_depth_rate_speed = 2.0 + + default_water_depth = 400 + + prefix = NAV +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = delta.bhv + verbose = quiet + domain = course:0:359:360 + domain = speed:0:4:41 + domain = depth:0:199:200 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = true + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //Pitch PID controller + pitch_pid_kp = 1.5 + pitch_pid_kd = 1.0 + pitch_pid_ki = 0 + pitch_pid_integral_limit = 0 + + //ZPID controller + z_to_pitch_pid_kp = 0.12 + z_to_pitch_pid_kd = 0 + z_to_pitch_pid_ki = 0.004 + z_to_pitch_pid_integral_limit = 0.05 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + maxpitch = 15 + maxelevator = 13 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.65 + vehicle_shape_scale = 1.35 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + left_context[survey-point] = SURVEY_UPDATES = points = vname=$(VNAME), x=$(XPOS), y=$(YPOS), format=bowtie, label=delta, wid1=10, wid2=25, wid3=30, height=20 + +// left_context[survey-point] = SURVEY_UPDATES = points = vname=$(VNAME), x=$(XPOS), y=$(YPOS), format=lawnmower, label=delta, width=70, height=30, lane_width=8, rows=north-south, degs=80 + + left_context[survey-point] = SURVEY = true + + scope = SURVEY_UPDATES + scope = MVIEWER_LCLICK + scope = GPS_UPDATE_RECEIVED + scope = TIME_AT_SURFACE + + action = DEPTH_VALUE="depth=50" + action = DEPTH_VALUE="depth=25" + action = DEPTH_VALUE="depth=7" + action = UP_LOITER="speed=0" + action = UP_LOITER="speed=1.2" + action = PSURFACE=true + action = PSURFACE=false + + button_one = DEPLOY # DEPLOY=true # MOOS_MANUAL_OVERRIDE=false + button_two = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN # RETURN=true + button_three = SURVEY-true # SURVEY=true + button_four = SURVEY-false # SURVEY=false +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + vessel_type = UUV +} + +//------------------------------------------ +// uTimerScript configuration block + +ProcessConfig = uTimerScript +{ + AppTick = 4 + CommsTick = 4 + + paused = false + reset_max = any + reset_time = end + condition = TIME_AT_SURFACE > 3 + upon_awake = restart + delay_start = 20:120 + script_name = GPS_SCRIPT + + event = var=GPS_UPDATE_RECEIVED, val="RCVD_$(COUNT)", time=0.5:1.0 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/s5_echo/echo.bhv b/src/test/missions/fixtures/s5_echo/echo.bhv new file mode 100644 index 0000000..9f9efb6 --- /dev/null +++ b/src/test/missions/fixtures/s5_echo/echo.bhv @@ -0,0 +1,69 @@ +//-------- FILE: echo.bhv ------------- + +initialize DEPLOY = false +initialize MOOS_MANUAL_OVERRIDE = false +initialize AUTO_SPAWN = false +initialize RETURN = false + + +set IVPHELM_MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set IVPHELM_MODE = SURVEYING { + IVPHELM_MODE = ACTIVE + RETURN != true +} RETURNING + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_survey + pwt = 100 + condition = IVPHELM_MODE == SURVEYING + endflag = RETURN = true + perpetual = true + + lead = 8 + lead_damper = 1 + speed = 2.0 // meters per second + radius = 8.0 + repeat = 100000 + points = 60,-40:60,-140:150,-140:170,-90:150,-40 + visual_hints = nextpt_color=yellow, nextpt_lcolor=aqua + visual_hints = vertex_color=yellow, line_color=white + visual_hints = vertex_size=2, edge_size=1 +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = IVPHELM_MODE == RETURNING + updates = UPDATES_RETURN + perpetual = true + endflag = RETURN = false + endflag = DEPLOY = false + + speed = 2.0 + radius = 2.0 + nm_radius = 8.0 + points = 0,0 +} + +//---------------------------------------------- +Behavior = BHV_BearingLine +{ + name = bng-line- + condition = IVPHELM_MODE == SURVEYING + updates = BEARING_POINT + templating = clone + duration_idle_decay = true + + bearing_point = 100,-100 + line_pct = 50 + show_pt = true +} + + diff --git a/src/test/missions/fixtures/s5_echo/s5_echo.alog.xz b/src/test/missions/fixtures/s5_echo/s5_echo.alog.xz new file mode 100644 index 0000000..b34aa3d Binary files /dev/null and b/src/test/missions/fixtures/s5_echo/s5_echo.alog.xz differ diff --git a/src/test/missions/fixtures/s5_echo/s5_echo.moos b/src/test/missions/fixtures/s5_echo/s5_echo.moos new file mode 100644 index 0000000..f35e68b --- /dev/null +++ b/src/test/missions/fixtures/s5_echo/s5_echo.moos @@ -0,0 +1,302 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./MOOSLog_27_10_2026_____07_46_32/MOOSLog_27_10_2026_____07_46_32._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930871923.37738 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: echo.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = echo +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block + +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uTimerScript @ NewConsole = false + Run = uXMS @ NewConsole = false + Run = uMemWatch @ NewConsole = false +} + +//------------------------------------------ +// uXMS config block + +ProcessConfig = uXMS +{ + AppTick = 4 + CommsTick = 4 + + var = IVPHELM_ITER + var = IVPHELM_DOMAIN + var = IVPHELM_BHV_CNT + var = IVPHELM_BHV_CNT_EVER + var = IVPHELM_ALLSTOP + var = IVPHELM_STATE +} + +//------------------------------------------ +// uMemWatch config block + +ProcessConfig = uMemWatch +{ + AppTick = 4 + CommsTick = 4 + + absolute_time_gap = 1 // In Seconds, Default is 1. + + watch_only = pHelmIvP,pMarineViewer +} + + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = AUTO_SPAWN @ 0 NOSYNC + Log = BEARING_POINT @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = CYCLE_INDEX @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODE @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = MOOS_MANUAL_OVERRIDE @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = UPDATES_RETURN @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_SEGLIST @ 0 NOSYNC + Log = WPT_INDEX @ 0 NOSYNC + Log = WPT_ODO @ 0 NOSYNC + Log = WPT_STAT @ 0 NOSYNC + AppTick = 4 + CommsTick = 4 + + SyncLog = false + AsyncLog = true + WildCardLogging = true + + LogAuxSrc = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + start_x = 0 + start_y = -10 + start_speed = 0 + start_heading = 180 + + prefix = NAV +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + behaviors = echo.bhv + verbose = quiet + domain = course:0:359:360 + domain = speed:0:4:21 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = false + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -240 + zoom = 0.65 + vehicle_shape_scale = 1.8 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + // Appcast configuration + appcast_height = 75 + appcast_width = 30 + appcast_viewable = true + appcast_color_scheme = indigo + nodes_font_size = medium + procs_font_size = medium + appcast_font_size = medium + + scope = PHELMIVP_MEM + scope = HVARS + scope = BCOUNT + scope = BEARING_POINT + scope = VIEW_SEGLIST + scope = VIEW_POINT + scope = VIEW_POLYGON + scope = NAV_X + scope = NAV_Y + scope = MVIEWER_LCLICK + scope = MVIEWER_RCLICK + + left_context[bng_point] = BEARING_POINT = name=bng-line$(X)-$(Y) # bearing_point=$(X),$(Y) + + button_one = DEPLOY # DEPLOY=true + button_one = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN # RETURN=true + button_three = AUTO_SPAWN # AUTO_SPAWN=true + + action = MENU_KEY=deploy # DEPLOY = true # RETURN = false + action+ = MENU_KEY=deploy # MOOS_MANUAL_OVERRIDE=false + action+ = RETURN=true + action = BEARING_POINT = show_pt=true + action+ = BEARING_POINT = show_pt=false + action = BEARING_POINT = line_pct=0 + action = BEARING_POINT = line_pct=25 + action = BEARING_POINT = line_pct=50 + action = BEARING_POINT = line_pct=75 + action+ = BEARING_POINT = line_pct=100 + action = BEARING_POINT = "name=bng-line # line_pct=0" + action = BEARING_POINT = "name=bng-line # line_pct=50" + action+ = BEARING_POINT = "name=bng-line # line_pct=100" + action = AUTO_SPAWN = true + action+ = AUTO_SPAWN = false +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + + vessel_type = KAYAK +} + +//------------------------------------------ +// uTimerScript config block + +ProcessConfig = uTimerScript +{ + AppTick = 4 + CommsTick = 4 + + paused = false + reset_max = nolimit + reset_time = end + condition = AUTO_SPAWN=true + + randvar = varname=FOO, min=1, max=800, key=at_reset + randvar = varname=DUR, min=25, max=50, key=at_reset + randvar = varname=X, min=0, max=150, key=at_reset + randvar = varname=Y, min=-150, max=0, key=at_reset + + event = var=BEARING_POINT, val="name=B$[TCOUNT] # bearing_point=$[X],$[Y] # duration=$[DUR] # line_pct=100", time=0:2 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/s7_golf/golf.bhv b/src/test/missions/fixtures/s7_golf/golf.bhv new file mode 100644 index 0000000..431f5ef --- /dev/null +++ b/src/test/missions/fixtures/s7_golf/golf.bhv @@ -0,0 +1,124 @@ +//-------- FILE: delta.bhv ------------- + +initialize DEPLOY = false +initialize RETURN = false +initialize SURVEY = false +initialize RETREAT_LOITER = false +initialize PSURFACE = true + +//======================================================== +// Hierarchical Mode Declarations +// o ROOT o ROOT +// |--o INACTIVE |--o PSURFACING +// |--o ACTIVE |--o NOT_PSURFACING +// |--o RETREAT_LOITER +// |--o SURVEYING +// |--o RETURNING +//======================================================== + + +Set MODE = ACTIVE { + DEPLOY = true +} INACTIVE + +set MODE = RETREAT_LOITER { + MODE = ACTIVE + SAFETY_RETREAT = true +} + +set MODE = SURVEYING { + MODE = ACTIVE + SURVEY = true +} RETURNING + + +//---------------------------------------------- +Behavior = BHV_Loiter +{ + name = loiter + pwt = 100 + condition = MODE == RETREAT_LOITER + updates = UP_RETREAT + perpetual = true + endflag = SAFETY_RETREAT = false + + post_suffix = A + speed = 1.3 + clockwise = false + radius = 4.0 + nm_radius = 15.0 + polygon = radial:: x=20, y=-80, radius=20, pts=8, snap=1, label=DUDLEY_LOITER + visual_hints = nextpt_color=red, nextpt_lcolor=khaki + visual_hints = edge_color=khaki, vertex_color=white + visual_hints = edge_size=1, vertex_size=4 +} + + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = MODE == RETURNING + updates = UPDATES_RETURN + perpetual = true + endflag = RETURNING = false + endflag = DEPLOY = false + + speed = 2.0 + radius = 2.0 + nm_radius = 8.0 + point = 0,0 +} + +//---------------------------------------------- +Behavior = BHV_PeriodicSurface +{ + name = bhv_periodic_surface + pwt = 1000 + condition = (MODE == LOITERING) + //condition = (MODE == LOITERING) or (MODE == RETURNING) + condition = PSURFACE = true + + period = 120 + zero_speed_depth = 2 + max_time_at_surface = 60 + ascent_speed = 1.0 + ascent_grade = fullspeed +} + +//---------------------------------------------- +Behavior = BHV_ConstantDepth +{ + name = bhv_const_depth + pwt = 100 + duration = no-time-limit + condition = MODE==ACTIVE + + depth = 15 +} + +//---------------------------------------------- +Behavior = BHV_Waypoint +{ + name = waypt_survey + pwt = 100 + condition = MODE==SURVEYING + perpetual = true + updates = SURVEY_UPDATES + endflag = SURVEY = false + + cycleflag = SURVEY = false + lead = 8 + lead_damper = 1 + speed = 2.0 // meters per second + radius = 8.0 + points = format=lawnmower, label=dudley_survey, x=80, y=-80, width=70, height=30, lane_width=8, rows=north-south, degs=30 + repeat = 1 + visual_hints = nextpt_color=red, nextpt_lcolor=khaki + visual_hints = vertex_color=yellow, line_color=white + visual_hints = vertex_size=2, edge_size=1 +} + + + diff --git a/src/test/missions/fixtures/s7_golf/s7_golf.alog.xz b/src/test/missions/fixtures/s7_golf/s7_golf.alog.xz new file mode 100644 index 0000000..f222d05 Binary files /dev/null and b/src/test/missions/fixtures/s7_golf/s7_golf.alog.xz differ diff --git a/src/test/missions/fixtures/s7_golf/s7_golf.moos b/src/test/missions/fixtures/s7_golf/s7_golf.moos new file mode 100644 index 0000000..1b359bf --- /dev/null +++ b/src/test/missions/fixtures/s7_golf/s7_golf.moos @@ -0,0 +1,293 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./LOG_27_10_2026_____07_48_44/LOG_27_10_2026_____07_48_44._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930873244.76507 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT CSAIL +// FILE: golf.moos +// DATE: Aug 2010 +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9000 +Community = henry + +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +LatOrigin = 43.825300 +LongOrigin = -70.330400 + +//------------------------------------------ +// Antler configuration block +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = uSimMarine @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePID @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uTimerScript @ NewConsole = false + Run = pSafetyRetreat @ NewConsole = false +} + +//------------------------------------------ +ProcessConfig = pSafetyRetreat +{ + AppTick = 4 + CommsTick = 4 + + polygon = radial:: x=-25, y=-100, radius=12, pts=8, snap=1 + polygon = radial:: x=150, y=-150, radius=12, pts=8, snap=1 + polygon = radial:: x=180, y=-60, radius=12, pts=8, snap=1 + polygon = radial:: x=60, y=0, radius=12, pts=8, snap=1 + duration = 90 + + retreat_cue_var = THREAT_DETECTED + retreat_message_var = UP_RETREAT + retreat_notify_var = SAFETY_RETREAT + verbose = true +} + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = APPCAST @ 0 NOSYNC + Log = BHV_ERROR @ 0 NOSYNC + Log = BHV_IPF @ 0 NOSYNC + Log = BHV_SETTINGS @ 0 NOSYNC + Log = BHV_STATUS @ 0 NOSYNC + Log = BHV_WARNING @ 0 NOSYNC + Log = CYCLE_INDEX @ 0 NOSYNC + Log = DEPLOY @ 0 NOSYNC + Log = DESIRED_DEPTH @ 0 NOSYNC + Log = DESIRED_HEADING @ 0 NOSYNC + Log = DESIRED_SPEED @ 0 NOSYNC + Log = GPS_UPDATE_RECEIVED @ 0 NOSYNC + Log = IVPHELM_ALLSTOP @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE @ 0 NOSYNC + Log = IVPHELM_BHV_ACTIVE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_CNT_EVER @ 0 NOSYNC + Log = IVPHELM_BHV_DISABLED_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE @ 0 NOSYNC + Log = IVPHELM_BHV_IDLE_CNT @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING @ 0 NOSYNC + Log = IVPHELM_BHV_RUNNING_CNT @ 0 NOSYNC + Log = IVPHELM_COMPLETED_PENDING @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_CREATE_CPU @ 0 NOSYNC + Log = IVPHELM_DOMAIN @ 0 NOSYNC + Log = IVPHELM_IPF_CNT @ 0 NOSYNC + Log = IVPHELM_ITER @ 0 NOSYNC + Log = IVPHELM_LEDGER_SUMMARY @ 0 NOSYNC + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = IVPHELM_LOOP_CPU @ 0 NOSYNC + Log = IVPHELM_MODESET @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = IVPHELM_STATEVARS @ 0 NOSYNC + Log = IVPHELM_SUMMARY @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_CACHED @ 0 NOSYNC + Log = IVPHELM_TOTAL_PCS_FORMED @ 0 NOSYNC + Log = IVPHELM_UPDATEVARS @ 0 NOSYNC + Log = LOITER_ACQUIRE_A @ 0 NOSYNC + Log = LOITER_DIST_TO_POLY_A @ 0 NOSYNC + Log = LOITER_ETA_TO_POLY_A @ 0 NOSYNC + Log = LOITER_INDEX_A @ 0 NOSYNC + Log = LOITER_MODE_A @ 0 NOSYNC + Log = LOITER_REPORT_A @ 0 NOSYNC + Log = MODE @ 0 NOSYNC + Log = MOOS_DEBUG @ 0 NOSYNC + Log = NAV_DEPTH @ 0 NOSYNC + Log = NAV_HEADING @ 0 NOSYNC + Log = NAV_SPEED @ 0 NOSYNC + Log = NAV_X @ 0 NOSYNC + Log = NAV_Y @ 0 NOSYNC + Log = PENDING_SURFACE @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + Log = PLOGGER_CMD @ 0 NOSYNC + Log = PSURFACE @ 0 NOSYNC + Log = RETREAT_LOITER @ 0 NOSYNC + Log = RETURN @ 0 NOSYNC + Log = RETURNING @ 0 NOSYNC + Log = SAFETY_RETREAT @ 0 NOSYNC + Log = SURVEY @ 0 NOSYNC + Log = SURVEY_UPDATES @ 0 NOSYNC + Log = TURN_RATE @ 0 NOSYNC + Log = UPDATES_RETURN @ 0 NOSYNC + Log = UP_RETREAT @ 0 NOSYNC + Log = VIEW_POINT @ 0 NOSYNC + Log = VIEW_POLYGON @ 0 NOSYNC + Log = WPT_ODO @ 0 NOSYNC + Log = WPT_STAT @ 0 NOSYNC + AppTick = 5 + CommsTick = 5 + + File = LOG + PATH = ./ + SyncLog = true @ 0.2 + AsyncLog = true + FileTimeStamp = true + + LogAuxSrc = true + WildCardLogging = true +} + +//------------------------------------------ +// uSimMarine config block + +ProcessConfig = uSimMarine +{ + AppTick = 10 + CommsTick = 10 + + START_X = 0 + START_Y = 0 + START_SPEED = 0 + START_HEADING = 180 + FLOAT_RATE = 0.05 + + PREFIX = NAV +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + AppTick = 4 + CommsTick = 4 + + Behaviors = golf.bhv + Verbose = quiet + Domain = course:0:359:360 + Domain = speed:0:4:21 + Domain = depth:0:500:501 +} + +//------------------------------------------ +// pMarinePID config block + +ProcessConfig = pMarinePID +{ + AppTick = 20 + CommsTick = 20 + + verbose = true + depth_control = true + + // Yaw PID controller + yaw_pid_kp = 0.5 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.0 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //Pitch PID controller + pitch_pid_kp = 0.5 + pitch_pid_kd = 1.0 + pitch_pid_ki = 0 + pitch_pid_integral_limit = 0 + + //ZPID controller + z_to_pitch_pid_kp = 0.12 + z_to_pitch_pid_kd = 0 + z_to_pitch_pid_ki = 0.004 + z_to_pitch_pid_integral_limit = 0.05 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + maxpitch = 15 + maxelevator = 13 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + tiff_file = forrest19.tif + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.65 + vehicle_shape_scale = 1.35 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + left_context[survey-point] = SURVEY_UPDATES = points = vname=$(VNAME), x=$(XPOS), y=$(YPOS), format=lawnmower, label=delta, width=70, height=30, lane_width=8, rows=north-south, degs=80 + left_context[survey-point] = SURVEY = true + + scope = UP_RETREAT + scope = SURVEY_UPDATES + scope = MVIEWER_LCLICK + scope = GPS_UPDATE_RECEIVED + scope = TIME_AT_SURFACE + + button_one = DEPLOY # DEPLOY=true + button_one = MOOS_MANUAL_OVERRIDE=false # RETURN=false # SURVEY=true + button_two = RETURN # RETURN=true + button_three = THREAT-DETECT # THREAT_DETECTED=true +} + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + + vessel_type = UUV +} + +//------------------------------------------ +// uTimerScript configuration block + +ProcessConfig = uTimerScript +{ + AppTick = 4 + CommsTick = 4 + + paused = false + reset_max = any + reset_time = end + condition = TIME_AT_SURFACE > 3 + upon_awake = restart + delay_start = 20:120 + script_name = GPS_SCRIPT + + event = var=GPS_UPDATE_RECEIVED, val="RCVD_$(COUNT)", time=0.5:1.0 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/fixtures/x1_legs/alpha.bhv b/src/test/missions/fixtures/x1_legs/alpha.bhv new file mode 100644 index 0000000..ade6836 --- /dev/null +++ b/src/test/missions/fixtures/x1_legs/alpha.bhv @@ -0,0 +1,117 @@ +//-------- FILE: alpha.bhv ------------- + +initialize DEPLOY = false +initialize RETURN = false +initialize AVOIDING = idle +initialize ZIGGING = false +//initialize DRIFT_X = 0.5 +//initialize DRIFT_Y = 0.7 + +//---------------------------------------------- +Behavior = BHV_LegRun +{ + name = legs + pwt = 100 + condition = RETURN = false + condition = DEPLOY = true + condition = ZIGGING = false + endflag = RETURN = true + endflag = EFLAG = endings=$[CTR2] + + updates = LEG_UPDATE + perpetual = true + + lead = 5 + lead_damper = 1 + speed = 4.0 // meters per second + capture_line = true + capture_radius = 2 + slip_radius = 15 + + vx1 = 70, -35 + vx2 = -35, -85 + + //vx1 = 100, -25 + //vx2 = -70, -95 + + //vx2 = -50, -120 + //vx1 = 150, -120 + + //points = 60,-40 : -40,-80 + repeat = 1 + + legflag = PCT_TO_NEXT=$[PCT_NP] + + turn1_dir = port + turn2_dir = star + turn1_rad = 20 + turn2_rad = 20 + turn1_bias = 100 + turn2_bias = 100 + + mid_pct = 10 + //midflag = ZIGGING=true + + visual_hints = nextpt_color=yellow + visual_hints = nextpt_vertex_size=8 + visual_hints = nextpt_lcolor=gray70 + visual_hints = vertex_color=dodger_blue, edge_color=white + visual_hints = vertex_size=6, edge_size=1 + visual_hints = turn_edge_color=white + visual_hints = turn_vertext_color=green + visual_hints = turn_label_color=off +} + +//---------------------------------------------- +Behavior = BHV_ZigZag +{ + name = zig + pwt = 100 + condition = RETURN = false + condition = DEPLOY = true + condition = ZIGGING = true + endflag = ZIGGING = false + + updates = ZIG_UPDATE + perpetual = true + + speed = 3.0 // meters per second + + stem_on_active = true + zig_first = star + max_zig_zags = 1 + zig_angle = 55 + + hdg_thresh = 2 + fierce_zigging = true + } + +//---------------------------------------------- +Behavior=BHV_Waypoint +{ + name = waypt_return + pwt = 100 + condition = RETURN = true + condition = DEPLOY = true + perpetual = true + updates = RETURN_UPDATE + endflag = RETURN = false + endflag = DEPLOY = false + endflag = MISSION = complete + + endflag = EFLAG = r_endings=$[CTR_02] + cycleflag = CFLAG = r_cyles=$[CTR_01] + wptflag = WFLAG = r_waypoints=$[CTR] + + lead_condition = AVOIDING != active + + lead = 8 + lead_damper = 1 + lead_to_start = true + + speed = 2.0 + capture_radius = 2.0 + slip_radius = 8.0 + point = 0,-20 + } + diff --git a/src/test/missions/fixtures/x1_legs/x1_legs.alog.xz b/src/test/missions/fixtures/x1_legs/x1_legs.alog.xz new file mode 100644 index 0000000..83111dc Binary files /dev/null and b/src/test/missions/fixtures/x1_legs/x1_legs.alog.xz differ diff --git a/src/test/missions/fixtures/x1_legs/x1_legs.moos b/src/test/missions/fixtures/x1_legs/x1_legs.moos new file mode 100644 index 0000000..a33e091 --- /dev/null +++ b/src/test/missions/fixtures/x1_legs/x1_legs.moos @@ -0,0 +1,295 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOG FILE: ./alpha_27_10_2026_____07_53_01/alpha_27_10_2026_____07_53_01._moos +%% FILE OPENED ON Thu Jan 1 00:00:00 1970 +%% LOGSTART 17930875818.86947 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +//------------------------------------------------- +// NAME: M. Benjamin, MIT MechE +// FILE: alpha.moos +//------------------------------------------------- + +ServerHost = localhost +ServerPort = 9005 +Community = alpha + +//---- Next Line was commented and replaced with a command line overload ---- // +//MOOSTimeWarp = 1 (default) +MOOSTimeWarp = 10 + +// Forest Lake +//LatOrigin = 43.825300 +//LongOrigin = -70.330400 + +// MIT Sailing Pavilion (use this one) +LatOrigin = 42.358456 +LongOrigin = -71.087589 + + +//------------------------------------------ +// Antler configuration block + +ProcessConfig = ANTLER +{ + Run = uTimerScript @ NewConsole = false ~ uTimerScript_record + MSBetweenLaunches = 200 + + Run = MOOSDB @ NewConsole = false + Run = pLogger @ NewConsole = false + Run = pMarineViewer @ NewConsole = false + Run = uSimMarineV22 @ NewConsole = false + Run = pNodeReporter @ NewConsole = false + Run = pMarinePIDV22 @ NewConsole = false + Run = pHelmIvP @ NewConsole = false + Run = uProcessWatch @ NewConsole = false + Run = pRealm @ NewConsole = false +} + +//------------------------------------------ +// pRealm config block + +ProcessConfig = pRealm +{ + AppTick = 4 + CommsTick = 4 + + hist_var = FEEDBACK_MSG + + msg_max_hist = 20 + + scope_set = name=events, vars=NEXT_POINT:PREV_POINT:RF_NEXT:RF_PREV:CURR_TIME + scope_set = name=events, vars=CYCLE_STATUS:INDEX + scope_set = name=events2, vars=WPTS_TOTAL:CYCLES_REMAINING:WPTS_REMAIN + scope_set = name=bflags, vars=CFLAG:WFLAG:EFLAG +} + + +//------------------------------------------ +// uProcessWatch config block + +ProcessConfig = uProcessWatch +{ + AppTick = 4 + CommsTick = 4 + + summary_wait = 5 + + nowatch = uXMS* + nowatch = uMAC* + nowatch = uPokeDB* + nowatch = uTermCommand* + watch_all = true +} + + +//------------------------------------------ +// pLogger config block + +ProcessConfig = pLogger +{ + Log = APPCAST @ 0 NOSYNC + Log = APP_LOG @ 0 NOSYNC + Log = IVPHELM_CPU @ 0 NOSYNC + Log = IVPHELM_REGISTER @ 0 NOSYNC + Log = IVPHELM_STATE @ 0 NOSYNC + Log = NODE_REPORT_LOCAL @ 0 NOSYNC + Log = PHELMIVP_PID @ 0 NOSYNC + Log = PHELMIVP_STATUS @ 0 NOSYNC + AppTick = 8 + CommsTick = 8 + + AsyncLog = true + + // For variables that are published in a bundle on their first post, + // explicitly declare their logging request + + Log = IVPHELM_LIFE_EVENT @ 0 NOSYNC + Log = APP_LOG @ 0 NOSYNC + + file = alpha + + LogAuxSrc = true + WildCardLogging = true + WildCardExclusionLog = true +} + +//------------------------------------------ +// uSimMarineV22 config block + +ProcessConfig = uSimMarineV22 +{ + AppTick = 8 + CommsTick = 8 + + app_logging = true + + drift_y = 0 + + + //start_x = 95 + //start_y = -30 + start_x = -20 + start_y = -20 + start_speed = 0 + start_heading = 180 + + max_acceleration = 0.25 + + //wind_conditions = spd=3.3, dir=180 + //polar_plot = 0,0: 20,40: 45,65: 90,80: 110,90: 135,83: 150,83: 165,60: 180,50 + + turn_spd_map_full_speed = 4 + turn_spd_map_null_speed = 0.5 + turn_spd_map_full_rate = 100 + turn_spd_map_null_rate = 2 + + prefix = NAV + turn_rate = 40 + thrust_map = 0:0, 20:1, 40:2, 60:3, 80:4, 100:5 +} + +//------------------------------------------ +// pHelmIvP config block + +ProcessConfig = pHelmIvP +{ + MaxAppTick = 10 + AppTick = 4 + CommsTick = 4 + + term_report_interval = 0 + + app_logging = true + + bhv_dir_not_found_ok = true + //ivp_behavior_dir = /Users/ddmikerb + + verbose = terse + + behaviors = alpha.bhv + domain = course:0:359:360 + domain = speed:0:4:21 +} + +//------------------------------------------ +// pMarinePIDV22 config block + +ProcessConfig = pMarinePIDV22 +{ + AppTick = 10 + CommsTick = 10 + + max_sat_hdg_debug = false + max_sat_spd_debug = true + + verbose = true + depth_control = false + + // SIM_INSTABILITY = 20 + + // Yaw PID controller + yaw_pid_kp = 1.2 + yaw_pid_kd = 0.0 + yaw_pid_ki = 0.3 + yaw_pid_integral_limit = 0.07 + + // Speed PID controller + speed_pid_kp = 1.0 + speed_pid_kd = 0.0 + speed_pid_ki = 0.0 + speed_pid_integral_limit = 0.07 + + //MAXIMUMS + maxrudder = 100 + maxthrust = 100 + + // A non-zero SPEED_FACTOR overrides use of SPEED_PID + // Will set DESIRED_THRUST = DESIRED_SPEED * SPEED_FACTOR + speed_factor = 20 +} + +//------------------------------------------ +// pMarineViewer config block + +ProcessConfig = pMarineViewer +{ + AppTick = 4 + CommsTick = 4 + + set_pan_x = -90 + set_pan_y = -280 + zoom = 0.95 + + //tiff_file = forrest19.tif + tiff_file = MIT_SP.tif + + // app_logging = true + + vehicle_shape_scale = 1.5 + hash_delta = 50 + hash_shade = 0.4 + hash_viewable = true + + trails_point_size = 1 + + //full_screen = true + + stale_report_thresh = 10 + stale_remove_thresh = 20 + + // Appcast configuration + appcast_height = 75 + appcast_width = 30 + appcast_viewable = true + appcast_color_scheme = indigo + nodes_font_size = xlarge + procs_font_size = xlarge + appcast_font_size = large + + //scope = LEGRUN_MODE + scope = PCT_TO_NEXT + + left_context[vehicle_pick] = PROX_POLY_VIEW_$[UP_VNAME_CLOSEST]=toggle + + button_one = DEPLOY # DEPLOY=true + button_one = MOOS_MANUAL_OVERRIDE=false # RETURN=false + button_two = RETURN:T # RETURN=true + button_three = RETURN:F # RETURN=false + button_four = DEPLOY:F # DEPLOY=false + + button_5 = LEG_60 # LEG_UPDATE=leg_length=60 + button_6 = LEG_70 # LEG_UPDATE=leg_length=70 + + button_7 = LEG_90 # LEG_UPDATE=leg_length=90 + button_8 = LEG_110 # LEG_UPDATE=leg_length=110 + + button_9 = LEG_MORE # LEG_UPDATE=leg_length_mod=10 + button_10 = LEG_LESS # LEG_UPDATE=leg_length_mod=-10 + + button_11 = RAD_MORE # LEG_UPDATE=turn_rad_mod=5 + button_12 = RAD_LESS # LEG_UPDATE=turn_rad_mod=-5 + + button_13 = ANG_MORE # LEG_UPDATE=leg_angle_mod=5 + button_14 = ANG_LESS # LEG_UPDATE=leg_angle_mod=-5 + } + +//------------------------------------------ +// pNodeReporter config block + +ProcessConfig = pNodeReporter +{ + AppTick = 2 + CommsTick = 2 + + app_logging = true + + platform_type = KAYAK + platform_length = 4 +} +ProcessConfig = uTimerScript_record +{ + condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE) + event = var=DEPLOY, val=true, time=5 + event = var=MOOS_MANUAL_OVERRIDE, val=false, time=5 +} + + diff --git a/src/test/missions/missions.txt b/src/test/missions/missions.txt new file mode 100644 index 0000000..8ec3f16 --- /dev/null +++ b/src/test/missions/missions.txt @@ -0,0 +1,15 @@ +# Single-vehicle MOOS-IvP missions recorded for the replay tests (record.sh). +# Each runs from ivp/missions/ with pAntler, and the pokes go in +# 5 s (mission time) after pHelmIvP starts. +# +# mission moos_file warp seconds pokes +s1_alpha alpha.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +s2_bravo bravo.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +s3_charlie charlie.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +s4_delta delta.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +s5_echo echo.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +# s7_golf parks soon after DEPLOY: no behavior decides course or speed (MissingDecVars) +s7_golf golf.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +s10_juliet juliet.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false +# x1_legs is MALCONFIG: its alpha.bhv uses BHV_LegRun parameters this MOOS-IvP rejects +x1_legs alpha.moos 10 600 DEPLOY=true MOOS_MANUAL_OVERRIDE=false diff --git a/src/test/missions/record.sh b/src/test/missions/record.sh new file mode 100755 index 0000000..fbce1d9 --- /dev/null +++ b/src/test/missions/record.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# Records MOOS-IvP missions under pHelmIvP for the replay tests: builds all of +# MOOS-IvP from external/moos-ivp, runs each mission in missions.txt headless, +# and keeps each vehicle's helm inputs and outputs in fixtures/. +# +# Usage: record.sh [mission...] (default: every mission in missions.txt) +# +# WORK_DIR (default build/missions) holds the MOOS-IvP build and the runs. +# MOOS_BUILD_ARGS are passed to build-moos.sh, e.g. --with-proj=off. +set -euo pipefail + +here=$(cd "$(dirname "$0")" && pwd) +root=$(cd "${here}/../../.." && pwd) +work=$(realpath -m "${WORK_DIR:-${root}/build/missions}") +moos_ivp=${work}/moos-ivp + +# A copy, so that building leaves the submodule clean +if [ ! -x "${moos_ivp}/ivp/bin/pHelmIvP" ]; then + rm -rf "${moos_ivp}" + mkdir -p "${moos_ivp}" + git -C "${root}/external/moos-ivp" archive HEAD | tar -x -C "${moos_ivp}" + (cd "${moos_ivp}" && ./build-moos.sh --minrobot ${MOOS_BUILD_ARGS:-} && ./build-ivp.sh --nogui) +fi +export PATH="${moos_ivp}/ivp/bin:${moos_ivp}/build/MOOS/MOOSCore/bin:${moos_ivp}/build/MOOS/MOOSEssentials/bin:${PATH}" + +# Keeps the log header, pHelmIvP's posts, and every post to a variable pHelmIvP +# registered, without the duplicate lines pLogger sometimes writes +trim() { + awk 'NR == FNR { if ($2 == "IVPHELM_REGISTER") registered[$4] = 1; next } + /^%%/ { print; next } + ($3 ~ /^pHelmIvP/ || ($2 in registered)) && !seen[$0]++' "$1" "$1" +} + +# Launches pLogger right after MOOSDB, so it logs pHelmIvP from the start, +# without omitting anything (e.g. BHV_STATUS), and adds a uTimerScript that pokes once pHelmIvP is up. uTimerScript's posts are +# timestamped and delivered like the other apps', where uPokeDB's reach +# pHelmIvP seconds after their timestamps under time warp. +prepare() { + local moos_file=$1 + shift + awk 'tolower($0) ~ /processconfig *= *antler/ { antler = 1 } + antler && /^[ \t]*Run *= *pLogger[ \t@]/ { next } + tolower($0) ~ /^[ \t]*wildcardomitpattern/ { next } + { print } + antler && /^[ \t]*{/ { print " Run = uTimerScript @ NewConsole = false ~ uTimerScript_record" } + antler && /^[ \t]*Run *= *MOOSDB[ \t@]/ { print " Run = pLogger @ NewConsole = false" } + antler && /^[ \t]*}/ { antler = 0 }' \ + "${moos_file}" > "${moos_file}.prepared" + { + echo "ProcessConfig = uTimerScript_record" + echo "{" + echo " condition = (IVPHELM_STATE = PARK) or (IVPHELM_STATE = DRIVE)" + local poke + for poke in "$@"; do + echo " event = var=${poke%%=*}, val=${poke#*=}, time=5" + done + echo "}" + } >> "${moos_file}.prepared" + mv "${moos_file}.prepared" "${moos_file}" +} + +# The log directories of a run, wherever its pLogger writes them +log_dirs() { + find "$1" -name '*.alog' -exec dirname {} \; | sort -u +} + +# pHelmIvP's posts and the variables it registered, from a first run's logs +helm_variables() { + awk '$1 !~ /^-?[0-9.]+$/ { next } + $3 ~ /^pHelmIvP/ { print $2 } + $2 == "IVPHELM_REGISTER" { print $4 }' "$@" | grep -E '^[A-Za-z_][A-Za-z0-9_]*$' | sort -u +} + +# Logs each variable from its first post on: pLogger's wildcard logging only +# finds a variable some time after its first post +add_logging() { + local moos_file=$1 variables=$2 + awk -v variables="${variables}" ' + tolower($0) ~ /processconfig *= *plogger/ { logger = 1 } + { print } + logger && /^[ \t]*{/ { + n = split(variables, vars, " ") + for (i = 1; i <= n; i++) print " Log = " vars[i] " @ 0 NOSYNC" + logger = 0 + }' "${moos_file}" > "${moos_file}.logged" + mv "${moos_file}.logged" "${moos_file}" +} + +# run [log variables] +run() { + local run=$1 moos_file=$2 warp=$3 seconds=$4 variables=${5:-} + rm -rf "${run}" + mkdir -p "$(dirname "${run}")" + cp -r "${moos_ivp}/ivp/missions/${mission}" "${run}" + ( + cd "${run}" + prepare "${moos_file}" "${pokes[@]}" + [ -n "${variables}" ] && add_logging "${moos_file}" "${variables}" + # job control puts pAntler in its own process group, to stop whatever it leaves behind + set -m + pAntler "${moos_file}" --MOOSTimeWarp="${warp}" > antler.log 2>&1 & + antler=$! + sleep $((seconds / warp)) + kill -INT "${antler}" + for _ in $(seq 20); do + kill -0 "${antler}" 2> /dev/null || break + sleep 1 + done + kill -KILL -- "-${antler}" 2> /dev/null || true + wait "${antler}" || true + ) +} + +record() { + local moos_file=$2 warp=$3 seconds=$4 + mission=$1 + shift 4 + pokes=("$@") + echo "Recording ${mission} (${seconds} s at warp ${warp}, twice)" + + run "${work}/runs/${mission}.discover" "${moos_file}" "${warp}" "${seconds}" + local variables + local logs=() + mapfile -t logs < <(find "${work}/runs/${mission}.discover" -name '*.alog' -o -name '*.xlog') + variables=$(helm_variables "${logs[@]}" | tr '\n' ' ') + run "${work}/runs/${mission}" "${moos_file}" "${warp}" "${seconds}" "${variables}" + + local fixture=${here}/fixtures/${mission} + rm -rf "${fixture}" + mkdir -p "${fixture}" + local log + for log in $(log_dirs "${work}/runs/${mission}"); do + local name + name=$(basename "${log}") + trim "${log}/${name}.alog" | xz -9 > "${fixture}/${mission}.alog.xz" + cp "${log}/${name}._moos" "${fixture}/${mission}.moos" + if ! xz -dc "${fixture}/${mission}.alog.xz" | grep "IVPHELM_REGISTER" > /dev/null; then + echo "warning: ${mission}'s log misses pHelmIvP's startup" >&2 + fi + for bhv in "${log}"/*._bhv; do + [ -e "${bhv}" ] || continue + cp "${bhv}" "${fixture}/$(basename "${bhv}" ._bhv).bhv" + done + # pHelmIvP only asks pLogger to copy its behavior files once it has started + if ! ls "${fixture}"/*.bhv > /dev/null 2>&1; then + cp "${work}/runs/${mission}"/*.bhv "${fixture}/" + fi + done +} + +missions=("$@") +while read -r mission moos_file warp seconds pokes; do + [[ -z "${mission}" || "${mission}" == \#* ]] && continue + if [ ${#missions[@]} -eq 0 ] || [[ " ${missions[*]} " == *" ${mission} "* ]]; then + # shellcheck disable=SC2086 + record "${mission}" "${moos_file}" "${warp}" "${seconds}" ${pokes} < /dev/null + fi +done < "${here}/missions.txt" diff --git a/src/test/missions/replay.cpp b/src/test/missions/replay.cpp new file mode 100644 index 0000000..c93f29b --- /dev/null +++ b/src/test/missions/replay.cpp @@ -0,0 +1,656 @@ +// Replays a MOOS-IvP mission recorded under pHelmIvP (see record.sh) against +// ivpico::helm::Helm, and checks that the helm makes the same decisions and +// posts the same things. +// +// Usage: replay [stream to print in full, e.g. "loiter LOITER_INDEX"] +// +// The fixture holds the vehicle's .moos file (for pHelmIvP's configuration), +// its .bhv files, and an alog trimmed to pHelmIvP's posts and the posts to +// every variable pHelmIvP registered. +// +// Open loop is enough: the helm's outputs don't change the recorded inputs. +// pHelmIvP posts IVPHELM_STATE at the start of every iteration, so those posts +// give the iteration times. Before each one, every recorded input posted at +// least `mail_latency` earlier goes to the helm as a Variable, and then the +// helm iterates at that time. Inputs pHelmIvP posted itself are left out, +// since the helm feeds its own posts back (as the MOOSDB does). +// +// Only the last value in each iteration is compared. Numbers inside values match within `number_tolerance` (relative, at least +// 0.01), since recorded inputs are rounded. +// +// What's compared, as sequences of changes, each change matched within +// `tolerance` iterations, and all but `max_mismatch` of them matching (a +// stream with fewer than 20 changes has to match exactly). Continuous values +// (distances, ETAs, track points) differ now and then, when pHelmIvP got a +// navigation update a little later or earlier than its timestamp says. +// - IVPHELM_STATE and IVPHELM_ALLSTOP against Status; +// - mode variables against MODE postings; +// - initialize posts against INITIALIZE postings; +// - each behavior's posts against its postings and behavior events; +// - IVPHELM_LIFE_EVENT against life events; +// - IVPHELM_BHV_ACTIVE against the ACTIVE behaviors in Status. +// DESIRED_* are compared iteration by iteration: within one step of the +// domain on all but `desired_max_mismatch` of the iterations where either +// helm decided, and never off for more than `desired_max_run` in a row. +// +// Prints a summary per stream, the first mismatches in each, and exits non-zero +// if any stream failed. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helm/helm.h" + +namespace fs = std::filesystem; +using ivp::interface::HelmConfig; +using ivp::interface::Status; +using ivpico::helm::Helm; +using ivpico::helm::Output; + +namespace +{ +constexpr int tolerance = 1; +constexpr double mail_latency = 0.005; +constexpr double number_tolerance = 1e-3; +constexpr double max_mismatch = 0.05; +constexpr double desired_max_mismatch = 0.01; +constexpr int desired_max_run = 3; +constexpr int report_mismatches = 5; + +std::string trim(const std::string& s) +{ + const auto begin = s.find_first_not_of(" \t\r\n"); + if (begin == std::string::npos) + return ""; + return s.substr(begin, s.find_last_not_of(" \t\r\n") - begin + 1); +} + +std::string lower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + return s; +} + +bool is_number(const std::string& s, double* value = nullptr) +{ + if (s.empty()) + return false; + char* end = nullptr; + const double d = std::strtod(s.c_str(), &end); + if (end != s.c_str() + s.size()) + return false; + if (value) + *value = d; + return true; +} + +struct Entry +{ + double time; + std::string var, source, value; +}; + +struct Alog +{ + double logstart{0}; + std::vector entries; +}; + +Alog read_alog(const fs::path& path) +{ + Alog alog; + std::unique_ptr pipe( + popen(("xz -dc '" + path.string() + "'").c_str(), "r"), pclose); + if (!pipe) + throw std::runtime_error("can't run xz on " + path.string()); + std::string text; + char buffer[65536]; + for (size_t n; (n = fread(buffer, 1, sizeof(buffer), pipe.get())) > 0;) text.append(buffer, n); + + std::istringstream lines(text); + for (std::string line; std::getline(lines, line);) + { + if (line.rfind("%%", 0) == 0) + { + std::istringstream header(line.substr(2)); + std::string key; + header >> key; + if (key == "LOGSTART") + header >> alog.logstart; + continue; + } + std::istringstream fields(line); + Entry e; + if (!(fields >> e.time >> e.var >> e.source)) + continue; + std::getline(fields, e.value); + e.value = trim(e.value); + alog.entries.push_back(e); + } + std::stable_sort(alog.entries.begin(), alog.entries.end(), + [](const Entry& a, const Entry& b) { return a.time < b.time; }); + return alog; +} + +// pHelmIvP's block of the .moos file, as HelmConfig +HelmConfig read_config(const fs::path& moos_file, const fs::path& dir) +{ + HelmConfig cfg; + std::ifstream in(moos_file); + bool in_block = false, in_helm = false; + std::vector helm_lines; + for (std::string line; std::getline(in, line);) + { + line = trim(line.substr(0, line.find("//"))); + if (line.empty()) + continue; + const auto eq = line.find('='); + const std::string key = lower(trim(line.substr(0, eq))); + const std::string value = eq == std::string::npos ? "" : trim(line.substr(eq + 1)); + + if (!in_block && key == "community") + cfg.set_ownship(value); + if (key == "processconfig") + { + in_helm = value == "pHelmIvP"; + continue; + } + if (line == "{") + { + in_block = true; + continue; + } + if (line == "}") + { + in_block = in_helm = false; + continue; + } + if (in_block && in_helm) + helm_lines.push_back(line); + } + + // MOOS's mission reader hands pHelmIvP its configuration in reverse order, + // which sets the order of the domain variables + for (auto it = helm_lines.rbegin(); it != helm_lines.rend(); ++it) + { + const std::string& line = *it; + const auto eq = line.find('='); + const std::string key = lower(trim(line.substr(0, eq))); + const std::string value = eq == std::string::npos ? "" : trim(line.substr(eq + 1)); + const bool truth = lower(value) == "true"; + if (key == "behaviors") + cfg.add_behavior_file((dir / fs::path(value).filename()).string()); + else if (key == "domain") + { + std::string spec = value; + std::replace(spec.begin(), spec.end(), ',', ':'); + std::vector parts; + std::istringstream ps(spec); + for (std::string p; std::getline(ps, p, ':');) parts.push_back(trim(p)); + auto* d = cfg.add_domain(); + d->set_name(parts.at(0)); + d->set_low(std::stod(parts.at(1))); + d->set_high(std::stod(parts.at(2))); + if (parts.at(3).rfind("delta=", 0) == 0) + d->set_delta(std::stod(parts.at(3).substr(6))); + else + d->set_points(std::stoul(parts.at(3))); + if (parts.size() > 4 && lower(parts[4]) == "optional") + d->set_optional(true); + } + else if (key == "park_on_allstop") + cfg.set_park_on_allstop(truth); + else if (key == "allow_park") + cfg.set_allow_park(truth); + else if (key == "start_in_drive" || key == "start_indrive" || key == "start_engaged" || + key == "active_start") + cfg.set_start_in_drive(truth); + else if (key == "goals_mandatory") + cfg.set_goals_mandatory(truth); + else if (key == "seed_random") + cfg.set_seed_random(truth); + else if (key == "helm_alias") + cfg.set_helm_alias(value); + else if (key == "nav_grace") + cfg.set_nav_grace(std::stod(value)); + else if (key == "contact_max_age") + cfg.set_contact_max_age(std::stod(value)); + else if (key == "contact_max_count") + cfg.set_contact_max_count(std::stoul(value)); + else if (key == "ok_skew") + cfg.set_ok_skew(lower(value) == "any" ? -1 : std::stod(value)); + else if (key == "pmgen") + { + auto* pm = cfg.mutable_platform_model(); + std::istringstream ps(value); + for (std::string p; std::getline(ps, p, '#');) + { + const auto peq = p.find('='); + const std::string pk = lower(trim(p.substr(0, peq))); + const std::string pv = trim(p.substr(peq + 1)); + if (pk == "alg") + pm->set_algorithm(lower(pv) == "dubins" ? HelmConfig::PlatformModel::DUBINS + : HelmConfig::PlatformModel::HOLONOMIC); + else if (pk == "radius" || pk == "rad") + pm->set_radius(std::stod(pv)); + else if (pk == "spoke_degs" || pk == "degs") + pm->set_spoke_degs(std::stod(pv)); + } + } + else + std::cout << " (pHelmIvP parameter not replayed: " << line << ")\n"; + } + return cfg; +} + +struct Change +{ + int tick; + std::string value; +}; + +using Streams = std::map>; + +// Keeps the last value in each iteration, and only changes +void add_change(Streams& streams, const std::string& stream, int tick, const std::string& value) +{ + auto& changes = streams[stream]; + if (!changes.empty() && changes.back().tick == tick) + { + changes.back().value = value; + if (changes.size() > 1 && changes[changes.size() - 2].value == value) + changes.pop_back(); + return; + } + if (changes.empty() || changes.back().value != value) + changes.push_back({tick, value}); +} + +// Splits "x=1.5,y=-2" into its text ("x=,y=") and its numbers (1.5, -2) +std::pair> split_numbers(const std::string& s) +{ + std::pair> parts; + for (size_t i = 0; i < s.size();) + { + const bool starts_number = std::isdigit(static_cast(s[i])) || + ((s[i] == '-' || s[i] == '.') && i + 1 < s.size() && + std::isdigit(static_cast(s[i + 1]))); + if (starts_number) + { + char* end = nullptr; + parts.second.push_back(std::strtod(s.c_str() + i, &end)); + i = end - s.c_str(); + } + else + parts.first += s[i++]; + } + return parts; +} + +bool same_value(const std::string& a, const std::string& b) +{ + if (a == b) + return true; + const auto pa = split_numbers(a), pb = split_numbers(b); + if (pa.first != pb.first || pa.second.size() != pb.second.size()) + return false; + for (size_t i = 0; i < pa.second.size(); ++i) + if (std::fabs(pa.second[i] - pb.second[i]) > + std::max(0.01, number_tolerance * std::fabs(pa.second[i]))) + return false; + return true; +} + +std::string number(double d) +{ + std::ostringstream os; + os << d; + return os.str(); +} + +std::string allstop_string(const Status& status) +{ + switch (status.allstop()) + { + case ivp::interface::CLEAR: return "clear"; + case ivp::interface::MANUAL_OVERRIDE: return "ManualOverride"; + case ivp::interface::BEHAVIOR_ERROR: return "BehaviorError"; + case ivp::interface::NOTHING_TO_DO: return "NothingToDo"; + case ivp::interface::NO_GOAL_BEHAVIOR: return "NoGoalBehavior"; + case ivp::interface::DISABLED_BY_STANDBY_HELM: return "DisabledByStandbyHelm"; + case ivp::interface::MISSING_DECISION_VARIABLES: + { + std::string s = "MissingDecVars:"; + for (int i = 0; i < status.missing_decision_variables_size(); ++i) + s += (i ? "," : "") + status.missing_decision_variables(i); + return s; + } + default: return ""; + } +} + +std::string behavior_event_var(ivp::interface::BehaviorEvent::Kind kind) +{ + using ivp::interface::BehaviorEvent; + switch (kind) + { + case BehaviorEvent::WARNING: + case BehaviorEvent::WARNING_RETRACTED: return "BHV_WARNING"; + case BehaviorEvent::ERROR: return "BHV_ERROR"; + case BehaviorEvent::EVENT: return "BHV_EVENT"; + case BehaviorEvent::CONFIG_WARNING: return "BHV_CONFIG_WARNING"; + case BehaviorEvent::BAD_CONFIG: return "BHV_BAD_CONFIG"; + case BehaviorEvent::STATUS: return "BHV_STATUS"; + case BehaviorEvent::SETTINGS: return "BHV_SETTINGS"; + default: return "BHV_UNKNOWN"; + } +} + +// "event=spawn,bname=ch_one,..." to "spawn ch_one" +std::string life_event(const std::string& spec) +{ + std::string event, bname; + std::istringstream fields(spec); + for (std::string f; std::getline(fields, f, ',');) + { + const auto eq = f.find('='); + const std::string key = trim(f.substr(0, eq)), value = trim(f.substr(eq + 1)); + if (key == "event") + event = value; + else if (key == "bname") + bname = value; + } + return event + " " + bname; +} + +// Compares two sequences of changes, matching values in order and ticks within tolerance +bool compare(const std::string& stream, const std::vector& moos, + const std::vector& ivpico) +{ + std::vector problems; + size_t i = 0, j = 0; + while (i < moos.size() && j < ivpico.size()) + { + if (same_value(moos[i].value, ivpico[j].value) && + std::abs(moos[i].tick - ivpico[j].tick) <= tolerance) + { + ++i, ++j; + continue; + } + problems.push_back("pHelmIvP iteration " + std::to_string(moos[i].tick) + ": \"" + + moos[i].value + "\", IvPico iteration " + + std::to_string(ivpico[j].tick) + ": \"" + ivpico[j].value + "\""); + // a different value on the same iteration is one mismatch; otherwise + // resynchronize on whichever comes first + if (moos[i].tick == ivpico[j].tick) + ++i, ++j; + else if (moos[i].tick < ivpico[j].tick) + ++i; + else + ++j; + } + for (; i < moos.size(); ++i) + problems.push_back("pHelmIvP iteration " + std::to_string(moos[i].tick) + ": \"" + + moos[i].value + "\", missing from IvPico"); + for (; j < ivpico.size(); ++j) + problems.push_back("IvPico iteration " + std::to_string(ivpico[j].tick) + ": \"" + + ivpico[j].value + "\", missing from pHelmIvP"); + + const bool ok = problems.size() <= max_mismatch * std::max(moos.size(), ivpico.size()); + std::cout << (ok ? " ok " : " FAIL ") << stream << " (" << moos.size() << " changes, " + << problems.size() << " off)\n"; + for (size_t k = 0; k < problems.size() && k < report_mismatches; ++k) + std::cout << " " << problems[k] << "\n"; + if (problems.size() > report_mismatches) + std::cout << " ... and " << problems.size() - report_mismatches << " more\n"; + return ok; +} + +bool compare_desired(const std::string& var, double step, bool wrap, + const std::map& moos, const std::map& ivpico) +{ + std::map, std::optional>> ticks; + for (const auto& [k, v] : moos) ticks[k].first = v; + for (const auto& [k, v] : ivpico) ticks[k].second = v; + + int mismatches = 0, run = 0, longest_run = 0; + std::vector examples; + for (const auto& [k, values] : ticks) + { + bool ok = values.first && values.second; + if (ok) + { + double diff = std::fabs(*values.first - *values.second); + if (wrap) + diff = std::min(diff, 360 - diff); + ok = diff <= step + 1e-6; + } + if (ok) + { + run = 0; + continue; + } + ++mismatches; + longest_run = std::max(longest_run, ++run); + if (examples.size() < report_mismatches) + examples.push_back("iteration " + std::to_string(k) + ": pHelmIvP " + + (values.first ? number(*values.first) : std::string("none")) + + ", IvPico " + + (values.second ? number(*values.second) : std::string("none"))); + } + const bool ok = ticks.empty() || (mismatches <= desired_max_mismatch * ticks.size() && + longest_run <= desired_max_run); + std::cout << (ok ? " ok " : " FAIL ") << var << " (" << ticks.size() << " iterations, " + << mismatches << " off, longest run " << longest_run << ")\n"; + for (const auto& e : examples) std::cout << " " << e << "\n"; + return ok; +} + +} // namespace + +int main(int argc, char* argv[]) +{ + if (argc != 2 && argc != 3) + { + std::cerr << "usage: replay [stream to print in full]\n"; + return 2; + } + const fs::path dir = fs::path(argv[1]).lexically_normal(); + fs::path moos_file, alog_file; + for (const auto& f : fs::directory_iterator(dir)) + { + if (f.path().extension() == ".moos") + moos_file = f.path(); + else if (f.path().string().size() > 8 && + f.path().string().substr(f.path().string().size() - 8) == ".alog.xz") + alog_file = f.path(); + } + std::cout << "Replaying " << (dir.has_filename() ? dir : dir.parent_path()).filename().string() + << "\n"; + + const HelmConfig cfg = read_config(moos_file, dir); + const Alog alog = read_alog(alog_file); + + auto is_helm = [](const Entry& e) { return e.source.rfind("pHelmIvP", 0) == 0; }; + auto aux = [](const Entry& e) + { return e.source.size() > 9 ? e.source.substr(9) : std::string(); }; + + std::vector ticks; + for (const auto& e : alog.entries) + if (is_helm(e) && e.var == "IVPHELM_STATE") + ticks.push_back(e.time); + auto tick_of = [&](double time) + { + return std::max(0, static_cast(std::upper_bound(ticks.begin(), ticks.end(), time) - + ticks.begin()) - + 1); + }; + + // pHelmIvP's side + Streams moos; + std::map> moos_desired; + for (const auto& e : alog.entries) + { + if (!is_helm(e)) + continue; + const int k = tick_of(e.time); + const std::string a = aux(e); + if (e.var == "IVPHELM_STATE") + add_change(moos, "IVPHELM_STATE", k, e.value); + else if (e.var == "IVPHELM_ALLSTOP") + add_change(moos, "IVPHELM_ALLSTOP", k, e.value); + else if (e.var == "IVPHELM_LIFE_EVENT") + moos["IVPHELM_LIFE_EVENT"].push_back({k, life_event(e.value)}); + else if (e.var == "IVPHELM_BHV_ACTIVE") + add_change(moos, "IVPHELM_BHV_ACTIVE", k, e.value); + else if (e.var.rfind("DESIRED_", 0) == 0 && a.empty()) + { + double d; + if (is_number(e.value, &d)) + moos_desired[e.var][k] = d; + } + else if (a == "HELM_MODE") + add_change(moos, "mode " + e.var, k, e.value); + else if (a == "HELM_VAR_INIT") + add_change(moos, "initialize " + e.var, k, e.value); + else if (a.find(':') != std::string::npos && e.var != "BHV_IPF") + add_change(moos, a.substr(a.find(':') + 1) + " " + e.var, k, e.value); + } + + // IvPico's side + Helm helm(cfg); + for (const auto& w : helm.config_warnings()) std::cout << " config warning: " << w << "\n"; + + Streams ivpico; + std::map> ivpico_desired; + size_t next_input = 0; + for (size_t k = 0; k < ticks.size(); ++k) + { + for (; next_input < alog.entries.size() && + alog.entries[next_input].time <= ticks[k] - mail_latency; + ++next_input) + { + const Entry& e = alog.entries[next_input]; + if (is_helm(e)) + continue; + ivp::interface::Variable v; + v.set_time(static_cast((alog.logstart + e.time) * 1e6)); + v.set_key(e.var); + double d; + if (is_number(e.value, &d)) + v.set_dval(d); + else + v.set_sval(e.value); + helm.handle(v); + } + + const Output& out = helm.iterate(alog.logstart + ticks[k]); + const int tick = static_cast(k); + add_change(ivpico, "IVPHELM_STATE", tick, Status::State_Name(out.status.state())); + if (out.status.allstop() != ivp::interface::ALLSTOP_UNKNOWN) + add_change(ivpico, "IVPHELM_ALLSTOP", tick, allstop_string(out.status)); + + std::string active; + for (const auto& b : out.status.behavior()) + if (b.run_state() == Status::Behavior::ACTIVE) + active += (active.empty() ? "" : ",") + b.name(); + // pHelmIvP posts the list only when it changes, starting from none + if (!ivpico["IVPHELM_BHV_ACTIVE"].empty() || !active.empty()) + add_change(ivpico, "IVPHELM_BHV_ACTIVE", tick, active); + + for (const auto& p : out.postings) + { + const std::string value = + p.value_case() == ivp::interface::Posting::kSval ? p.sval() : number(p.dval()); + switch (p.origin()) + { + case ivp::interface::Posting::MODE: + add_change(ivpico, "mode " + p.key(), tick, value); + break; + case ivp::interface::Posting::INITIALIZE: + add_change(ivpico, "initialize " + p.key(), tick, value); + break; + case ivp::interface::Posting::BEHAVIOR: + add_change(ivpico, p.behavior() + " " + p.key(), tick, value); + break; + default: break; + } + } + for (const auto& e : out.behavior_events) + if (!e.behavior().empty()) + add_change(ivpico, e.behavior() + " " + behavior_event_var(e.kind()), tick, + e.text()); + for (const auto& e : out.life_events) + { + const std::string type = lower(ivp::interface::LifeEvent::Type_Name(e.type())); + ivpico["IVPHELM_LIFE_EVENT"].push_back({tick, type + " " + e.behavior()}); + } + if (out.solution) + { + const auto& d = out.solution->desired(); + if (d.has_attitude() && d.attitude().has_heading()) + { + double h = std::fmod(d.attitude().heading() * 180 / M_PI, 360); + ivpico_desired["DESIRED_HEADING"][tick] = h < 0 ? h + 360 : h; + } + if (d.has_speed() && d.speed().has_value()) + ivpico_desired["DESIRED_SPEED"][tick] = d.speed().value(); + if (d.has_depth()) + ivpico_desired["DESIRED_DEPTH"][tick] = d.depth(); + if (d.has_altitude()) + ivpico_desired["DESIRED_ALTITUDE"][tick] = d.altitude(); + if (d.has_attitude() && d.attitude().has_pitch()) + ivpico_desired["DESIRED_PITCH"][tick] = d.attitude().pitch() * 180 / M_PI; + for (const auto& c : d.custom()) ivpico_desired[c.name()][tick] = c.value(); + } + } + std::cout << " " << ticks.size() << " iterations\n"; + + bool ok = true; + std::map names; + for (const auto& [name, changes] : moos) names[name] = true; + for (const auto& [name, changes] : ivpico) names[name] = true; + for (const auto& [name, unused] : names) ok = compare(name, moos[name], ivpico[name]) && ok; + + if (argc == 3) + { + std::cout << "pHelmIvP " << argv[2] << ":\n"; + for (const auto& c : moos[argv[2]]) std::cout << " " << c.tick << " " << c.value << "\n"; + std::cout << "IvPico " << argv[2] << ":\n"; + for (const auto& c : ivpico[argv[2]]) std::cout << " " << c.tick << " " << c.value << "\n"; + } + + std::map desired_vars; + for (const auto& [var, values] : moos_desired) desired_vars[var] = true; + for (const auto& [var, values] : ivpico_desired) desired_vars[var] = true; + for (const auto& [var, unused] : desired_vars) + { + double step = 0; + bool wrap = false; + for (const auto& d : cfg.domain()) + { + std::string name = "DESIRED_"; + for (char c : d.name()) name += static_cast(std::toupper(c)); + if (name == "DESIRED_COURSE") + name = "DESIRED_HEADING", wrap = true; + if (name == var) + step = d.resolution_case() == HelmConfig::Domain::kDelta + ? d.delta() + : (d.high() - d.low()) / std::max(1u, d.points() - 1); + } + ok = compare_desired(var, step, wrap, moos_desired[var], ivpico_desired[var]) && ok; + } + + std::cout << (ok ? "PASSED" : "FAILED") << "\n"; + return ok ? 0 : 1; +}