Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Images/HostSim/include/platform_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,25 @@ float platform_adc_get_offset_v_a(void);

bool platform_get_encoder_angle(float* angle_deg);
float platform_get_encoder_angle_latest(void);
float platform_get_motor_rpm(void);
float platform_get_dc_link_voltage(void);
float platform_phase_voltage_u(void);
float platform_phase_voltage_v(void);
float platform_phase_voltage_w(void);
float platform_get_throttle_a(void);
float platform_get_throttle_b(void);
bool platform_get_throttle_valid(void);
float platform_get_motor_temperature(void);
float platform_get_inverter_temperature(uint8_t channel);

/* Digital IO stubs. */
bool platform_digital_read(uint8_t pin);
void platform_digital_write(uint8_t pin, bool value);

/* CAN bus stubs (bus 1 = A, 2 = B). */
bool platform_can_send(uint8_t bus, uint32_t id, bool ext,
const uint8_t* data, uint8_t dlc);
int platform_can_rx(uint8_t bus, uint32_t id, uint8_t* data, uint32_t* seq_out);
void platform_sample_application_sensors(void);

void platform_raise_fault(uint32_t source, uint8_t reason);
Expand Down
53 changes: 38 additions & 15 deletions Images/HostSim/scripts/run_spwm_live.ps1
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
# Emit the SPWM demo graph, build HostSim, and launch live + NodeGUI.
# Emit a graph into HostSim, build it, and launch live + NodeGUI.
# Defaults to the SPWM demo graph for backward compatibility with the demo menu.
param(
[switch]$NoGui,
[switch]$ForceEmit
[switch]$ForceEmit,
[string]$Graph = "",
[string]$Scenario = ""
)

$ErrorActionPreference = "Stop"

$hostSimRoot = Split-Path $PSScriptRoot -Parent
$repoRoot = Split-Path (Split-Path $hostSimRoot -Parent) -Parent
$graph = Join-Path $hostSimRoot "graphs\spwm_demo_graph.json"
$scenario = Join-Path $hostSimRoot "scenarios\spwm_demo.json"

if ([string]::IsNullOrEmpty($Graph)) {
$Graph = Join-Path $hostSimRoot "graphs\spwm_demo_graph.json"
}
if (-not (Test-Path $Graph)) { throw "Graph not found: $Graph" }

$graphName = [System.IO.Path]::GetFileNameWithoutExtension($Graph)

if ([string]::IsNullOrEmpty($Scenario)) {
$scenarioBase = $graphName
if ($scenarioBase.EndsWith("_graph")) {
$scenarioBase = $scenarioBase.Substring(0, $scenarioBase.Length - 6)
}
$candidate = Join-Path $hostSimRoot "scenarios\${scenarioBase}.json"
if (Test-Path $candidate) {
$Scenario = $candidate
} else {
$Scenario = Join-Path $hostSimRoot "scenarios\default_motor.json"
}
}
if (-not (Test-Path $Scenario)) { throw "Scenario not found: $Scenario" }

$graph = $Graph
$scenario = $Scenario

function To-WslPath([string]$p) {
$full = (Resolve-Path $p).Path
Expand All @@ -35,13 +60,13 @@ function Clear-EmittedTree([string]$repoRoot, [string]$relativePath) {
wsl -d Ubuntu -u root -- bash -lc "cd $wslRepo && rm -rf $relativePath" 2>$null
}

$emittedRel = "build/hostsim_spwm_emitted"
$buildDir = Join-Path $repoRoot "build\hostsim_spwm_emitted_build"
$emittedRel = "build/hostsim_${graphName}_emitted"
$buildDir = Join-Path $repoRoot "build\hostsim_${graphName}_emitted_build"

Write-Host "Stopping running HostSim / NodeGUI (unlocks emit output)..."
Stop-SimApps

$emitted = Join-Path $repoRoot "build\hostsim_spwm_emitted"
$emitted = Join-Path $repoRoot "build\hostsim_${graphName}_emitted"
$exe = Join-Path $buildDir "Debug\host_sim.exe"
if (-not (Test-Path $exe)) { $exe = Join-Path $buildDir "host_sim.exe" }

Expand All @@ -59,7 +84,7 @@ if ($needEmit) {
Clear-EmittedTree $repoRoot $emittedRel
Remove-Item -Recurse -Force $buildDir -ErrorAction SilentlyContinue

Write-Host "Emitting SPWM graph..."
Write-Host "Emitting ${graphName} graph into HostSim..."
$emitCmd = "cd $wslRepo && ${emitter} --base-src Images/HostSim --graph $wslGraph --output $emittedRel --verbosity info"
wsl -d Ubuntu -u root -- bash -lc $emitCmd
if ($LASTEXITCODE -ne 0) { throw "RTECodeEmitter failed" }
Expand All @@ -72,12 +97,12 @@ if ($needEmit) {
$exe = Join-Path $buildDir "Debug\host_sim.exe"
if (-not (Test-Path $exe)) { $exe = Join-Path $buildDir "host_sim.exe" }
} else {
Write-Host "Using existing emitted SPWM build (pass -ForceEmit to rebuild)."
Write-Host "Using existing emitted ${graphName} build (pass -ForceEmit to rebuild)."
}

if (-not (Test-Path $exe)) { throw "host_sim.exe not found in $buildDir" }

Write-Host "Starting HostSim SPWM live..."
Write-Host "Starting HostSim live for ${graphName}..."
Start-Process -FilePath $exe -ArgumentList $scenario, "--live", "--realtime", "1.0" -WorkingDirectory $emitted

if (-not $NoGui) {
Expand All @@ -87,11 +112,9 @@ if (-not $NoGui) {
Start-Process -FilePath (Join-Path $guiWd "NodeGUI.exe") `
-ArgumentList $graphArg, "--tcp", "127.0.0.1:14608", "--protocol", "ivp" `
-WorkingDirectory $guiWd
Write-Host "NodeGUI opened with SPWM graph + live telemetry."
Write-Host "NodeGUI opened with ${graphName} graph + live telemetry."
}

Write-Host ""
Write-Host "Live controls:"
Write-Host " Throttle A -> modulation index (0..1)"
Write-Host " Throttle B -> electrical frequency map (1..20 Hz)"
Write-Host "Plot: duty_u/v/w (slow), pwm_gate_u/v/w + pwm_v_uv (scope), i_a/b/c"
Write-Host "Scenario: $scenario"
Write-Host "Live telemetry: 127.0.0.1:14608 (IVP)"
66 changes: 52 additions & 14 deletions Images/HostSim/scripts/run_spwm_live.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Linux equivalent of run_spwm_live.ps1
# Emit SPWM graph, build HostSim, launch live TCP server + NodeGUI.
# Linux HostSim live launcher.
# Emits the requested graph into HostSim, builds it, and starts the live TCP server.
# Defaults to the SPWM demo graph for backward compatibility with the demo menu.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand All @@ -9,17 +10,56 @@ REPO_ROOT="$(cd "${HOSTSIM_ROOT}/../.." && pwd)"

NO_GUI=0
FORCE_EMIT=0
GRAPH=""
SCENARIO=""
while [[ $# -gt 0 ]]; do
case "$1" in
--no-gui) NO_GUI=1 ;;
--force-emit) FORCE_EMIT=1 ;;
--graph)
GRAPH="${2:-}"
shift
;;
--scenario)
SCENARIO="${2:-}"
shift
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
shift
done

GRAPH="${HOSTSIM_ROOT}/graphs/spwm_demo_graph.json"
SCENARIO="${HOSTSIM_ROOT}/scenarios/spwm_demo.json"
if [[ -z "${GRAPH}" ]]; then
GRAPH="${HOSTSIM_ROOT}/graphs/spwm_demo_graph.json"
fi

if [[ ! -f "${GRAPH}" ]]; then
echo "Graph not found: ${GRAPH}" >&2
exit 1
fi

GRAPH_NAME="$(basename "${GRAPH}" .json)"

if [[ -z "${SCENARIO}" ]]; then
# Prefer a scenario that matches the graph name (stripping a trailing _graph suffix);
# fall back to the generic motor scenario.
SCENARIO_BASE="${GRAPH_NAME}"
if [[ "${SCENARIO_BASE}" == *_graph ]]; then
SCENARIO_BASE="${SCENARIO_BASE%_graph}"
fi
CANDIDATE="${HOSTSIM_ROOT}/scenarios/${SCENARIO_BASE}.json"
if [[ -f "${CANDIDATE}" ]]; then
SCENARIO="${CANDIDATE}"
else
SCENARIO="${HOSTSIM_ROOT}/scenarios/default_motor.json"
fi
fi

if [[ ! -f "${SCENARIO}" ]]; then
echo "Scenario not found: ${SCENARIO}" >&2
exit 1
fi

EMITTER="${RTE_EMITTER:-${REPO_ROOT}/build/Source/RTECodeEmitter/RTECodeEmitter}"
NODEGUI="${REPO_ROOT}/build/Source/NodeGUI/NodeGUI"

Expand All @@ -29,8 +69,8 @@ if [[ ! -x "${EMITTER}" ]]; then
exit 1
fi

EMITTED_REL="build/hostsim_spwm_emitted"
BUILD_DIR="${REPO_ROOT}/build/hostsim_spwm_emitted_build"
EMITTED_REL="build/hostsim_${GRAPH_NAME}_emitted"
BUILD_DIR="${REPO_ROOT}/${EMITTED_REL}_build"

cleanup_apps() {
# Use exact-name matching so we do not kill the shell running this script
Expand All @@ -53,7 +93,7 @@ fi
if [[ "${need_emit}" -eq 1 ]]; then
rm -rf "${REPO_ROOT}/${EMITTED_REL}" "${BUILD_DIR}"

echo "Emitting SPWM graph..."
echo "Emitting ${GRAPH_NAME} graph into HostSim..."
"${EMITTER}" \
--base-src "${HOSTSIM_ROOT}" \
--graph "${GRAPH}" \
Expand All @@ -63,15 +103,15 @@ if [[ "${need_emit}" -eq 1 ]]; then
cmake -S "${REPO_ROOT}/${EMITTED_REL}" -B "${BUILD_DIR}"
cmake --build "${BUILD_DIR}" -j"$(nproc)"
else
echo "Using existing emitted SPWM build (pass --force-emit to rebuild)."
echo "Using existing emitted ${GRAPH_NAME} build (pass --force-emit to rebuild)."
fi

if [[ ! -x "${EXE}" ]]; then
echo "host_sim not found in ${BUILD_DIR}" >&2
exit 1
fi

echo "Starting HostSim SPWM live..."
echo "Starting HostSim live for ${GRAPH_NAME}..."
nohup "${EXE}" "${SCENARIO}" --live --realtime 1.0 >/dev/null 2>&1 &

if [[ "${NO_GUI}" -eq 0 ]]; then
Expand All @@ -82,11 +122,9 @@ if [[ "${NO_GUI}" -eq 0 ]]; then
fi
sleep 1
nohup "${NODEGUI}" "${GRAPH}" --tcp 127.0.0.1:14608 --protocol ivp >/dev/null 2>&1 &
echo "NodeGUI opened with SPWM graph + live telemetry."
echo "NodeGUI opened with ${GRAPH_NAME} graph + live telemetry."
fi

echo ""
echo "Live controls:"
echo " Throttle A -> modulation index (0..1)"
echo " Throttle B -> electrical frequency map (1..20 Hz)"
echo "Plot: duty_u/v/w (slow), pwm_gate_u/v/w + pwm_v_uv (scope), i_a/b/c"
echo "Scenario: ${SCENARIO}"
echo "Live telemetry: 127.0.0.1:14608 (IVP)"
66 changes: 66 additions & 0 deletions Images/HostSim/src/platform_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,41 @@ float platform_get_encoder_angle_latest(void) {
return hostsim::g_motor->ThetaElectricalDeg();
}

float platform_get_motor_rpm(void) {
float omega_e = 0.0f;
int pole_pairs = 7;
if (hostsim::g_plant) {
omega_e = hostsim::g_plant->OmegaElectricalRadPerSec();
} else if (hostsim::g_motor) {
omega_e = hostsim::g_motor->OmegaElectricalRadPerSec();
} else {
return 0.0f;
}
if (hostsim::g_motor) {
pole_pairs = hostsim::g_motor->Params().pole_pairs;
}
return omega_e * 60.0f / (hostsim::kTwoPi * static_cast<float>(pole_pairs));
}

float platform_get_dc_link_voltage(void) {
return hostsim::GetSimContext().vdc_v;
}

float platform_phase_voltage_u(void) {
const auto& c = hostsim::GetSimContext();
return c.duty_u * c.vdc_v / 100.0f;
}

float platform_phase_voltage_v(void) {
const auto& c = hostsim::GetSimContext();
return c.duty_v * c.vdc_v / 100.0f;
}

float platform_phase_voltage_w(void) {
const auto& c = hostsim::GetSimContext();
return c.duty_w * c.vdc_v / 100.0f;
}

float platform_get_throttle_a(void) {
return hostsim::GetSimContext().throttle_a;
}
Expand All @@ -191,12 +222,47 @@ float platform_get_throttle_b(void) {
return hostsim::GetSimContext().throttle_b;
}

bool platform_get_throttle_valid(void) {
const float a = platform_get_throttle_a();
const float b = platform_get_throttle_b();
if (a < 0.0f || a > 1.0f || b < 0.0f || b > 1.0f) return false;
return std::fabs(a - b) < 0.1f;
}

float platform_get_motor_temperature(void) { return 25.0f; }
float platform_get_inverter_temperature(uint8_t channel) {
(void)channel;
return 25.0f;
}

bool platform_digital_read(uint8_t pin) {
(void)pin;
return false;
}

void platform_digital_write(uint8_t pin, bool value) {
(void)pin;
(void)value;
}

bool platform_can_send(uint8_t bus, uint32_t id, bool ext,
const uint8_t* data, uint8_t dlc) {
(void)bus;
(void)id;
(void)ext;
(void)data;
(void)dlc;
return false;
}

int platform_can_rx(uint8_t bus, uint32_t id, uint8_t* data, uint32_t* seq_out) {
(void)bus;
(void)id;
(void)data;
(void)seq_out;
return -1;
}

void platform_sample_application_sensors(void) {}

void platform_raise_fault(uint32_t source, uint8_t reason) {
Expand Down
10 changes: 8 additions & 2 deletions Source/NodeGUI/src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,9 @@ void MainWindow::StartBuildCommand(BuildCommand command) {
<< QStringLiteral("-ExecutionPolicy") << QStringLiteral("Bypass")
<< QStringLiteral("-File") << QString::fromStdString(psScript.string())
<< QStringLiteral("-ForceEmit")
<< QStringLiteral("-KeepGui");
<< QStringLiteral("-KeepGui")
<< QStringLiteral("-Graph")
<< absoluteGraphPath;
} else {
AppendBuildLog(
QStringLiteral("\n[error] Firmware build/flash on Windows is not included in this "
Expand All @@ -975,7 +977,11 @@ void MainWindow::StartBuildCommand(BuildCommand command) {
if (command == BuildCommand::BuildSimulation) {
program = QStringLiteral("bash");
const std::filesystem::path shScript = projectRoot / "Images" / "HostSim" / "scripts" / "run_spwm_live.sh";
arguments << QString::fromStdString(shScript.string()) << QStringLiteral("--force-emit") << QStringLiteral("--no-gui");
arguments << QString::fromStdString(shScript.string())
<< QStringLiteral("--force-emit")
<< QStringLiteral("--no-gui")
<< QStringLiteral("--graph")
<< absoluteGraphPath;
} else {
const std::filesystem::path script = projectRoot / "Tools" / "build_flash_graph.sh";
if (!std::filesystem::is_regular_file(script)) {
Expand Down
Loading