[CmdletBinding()] param( [ValidateSet('combined')] [string]$Profile = 'combined', [string]$Configuration = 'rev_d', [string]$RepositoryRoot = '', [string]$IarBuild = '', [string]$OutputDirectory = '', [ValidateSet(0, 1)] [int]$PositionControl = 1, [ValidateSet(0, 1)] [int]$Ventilation = 1, [ValidateSet(0, 1)] [int]$Heating = 1, [ValidateSet(0, 1)] [int]$RockingHorse = $PositionControl ) $ErrorActionPreference = 'Stop' if ($RockingHorse -eq 1 -and $PositionControl -eq 0) { throw 'Rocking-horse mode requires -PositionControl 1.' } $repositoryCandidate = if ($RepositoryRoot) { $RepositoryRoot } else { Join-Path $PSScriptRoot '..' } $resolvedRepository = (Resolve-Path -LiteralPath $repositoryCandidate).Path $firmwareRoot = Join-Path $resolvedRepository 'firmware\t2g' $repositoryFirmware = Join-Path $firmwareRoot 'Seat_Controller_demo' $repositoryProject = Join-Path $repositoryFirmware 'IAR\Seat_Controller_demo.ewp' $vendorCommon = Join-Path $firmwareRoot 'common' $vendorDevice = Join-Path $firmwareRoot 'tviibh8m' function Resolve-IarBuild { param([string]$ConfiguredPath) $candidates = @() if ($ConfiguredPath) { $candidates += $ConfiguredPath } if ($env:COGNITIVE_AGENT_IAR_BUILD) { $candidates += $env:COGNITIVE_AGENT_IAR_BUILD } $command = Get-Command iarbuild.exe -ErrorAction SilentlyContinue if ($command) { $candidates += $command.Source } $candidates += @( 'D:\Program Files\IAR Systems\Embedded Workbench 9.2\common\bin\iarbuild.exe', 'C:\Program Files\IAR Systems\Embedded Workbench 9.2\common\bin\iarbuild.exe' ) foreach ($candidate in $candidates) { if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) { return (Resolve-Path -LiteralPath $candidate).Path } } throw 'IAR iarbuild.exe was not found; pass -IarBuild or set COGNITIVE_AGENT_IAR_BUILD.' } foreach ($required in @($repositoryProject, $vendorCommon, $vendorDevice)) { if (-not (Test-Path -LiteralPath $required)) { throw "Self-contained firmware dependency is missing: $required" } } $resolvedIarBuild = Resolve-IarBuild -ConfiguredPath $IarBuild function Invoke-IarBuild { param([string]$ProjectPath) Write-Host "Building Seat_Controller_demo ($Configuration)..." & $resolvedIarBuild $ProjectPath -build $Configuration -log all if ($LASTEXITCODE -ne 0) { throw "IAR build failed with exit code $LASTEXITCODE" } } function Write-ImageSummary { param([string]$ImagePath) if (-not (Test-Path -LiteralPath $ImagePath -PathType Leaf)) { throw "Build completed but HEX image was not found: $ImagePath" } # Use the .NET primitive directly. Some locked-down PowerShell hosts do not # expose Get-FileHash even though the Utility module is otherwise present. $stream = [System.IO.File]::OpenRead($ImagePath) try { $algorithm = [System.Security.Cryptography.SHA256]::Create() try { $hashBytes = $algorithm.ComputeHash($stream) } finally { $algorithm.Dispose() } } finally { $stream.Dispose() } $hashValue = [System.BitConverter]::ToString($hashBytes).Replace('-', '').ToLowerInvariant() Write-Host "Seat_Controller_demo HEX: $ImagePath" Write-Host "Seat_Controller_demo SHA256: $hashValue" } function Copy-BuildArtifact { param( [string]$Source, [string]$Destination ) $lastError = $null for ($attempt = 1; $attempt -le 6; $attempt++) { try { Copy-Item -LiteralPath $Source -Destination $Destination -Force return } catch [System.IO.IOException] { $lastError = $_ if ($attempt -lt 6) { Start-Sleep -Milliseconds (150 * $attempt) } } } throw $lastError } # Preserve the IAR project's original ../../common and ../../tviibh8m layout # while keeping both vendor trees inside this repository. Building in a # temporary staging root avoids committing IAR Obj/BrowseInfo/List products. $temporaryParent = Join-Path $resolvedRepository '.codex_tmp' New-Item -ItemType Directory -Path $temporaryParent -Force | Out-Null $stagingRoot = Join-Path $temporaryParent ( 'seat-firmware-build-' + [guid]::NewGuid().ToString('N') ) $stagingRootFull = [System.IO.Path]::GetFullPath($stagingRoot) $temporaryParentFull = [System.IO.Path]::GetFullPath($temporaryParent) if (-not $stagingRootFull.StartsWith( $temporaryParentFull + [System.IO.Path]::DirectorySeparatorChar, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Firmware staging directory escaped repository temporary root: $stagingRootFull" } try { New-Item -ItemType Directory -Path $stagingRootFull | Out-Null $stagingProjectRoot = Join-Path $stagingRootFull 'Seat_Controller_demo' Copy-Item -LiteralPath $repositoryFirmware -Destination $stagingProjectRoot -Recurse New-Item -ItemType Junction -Path (Join-Path $stagingRootFull 'common') -Target $vendorCommon | Out-Null New-Item -ItemType Junction -Path (Join-Path $stagingRootFull 'tviibh8m') -Target $vendorDevice | Out-Null $featureProfilePath = Join-Path $stagingProjectRoot 'Config\seat_feature_profile.h' # Preserve dependency checks and derived switches (including THERMAL). $featureProfile = [System.IO.File]::ReadAllText($featureProfilePath) $featureValues = [ordered]@{ SEAT_FEATURE_POSITION_CONTROL = $PositionControl SEAT_FEATURE_VENTILATION = $Ventilation SEAT_FEATURE_HEATING = $Heating SEAT_FEATURE_ROCKING_HORSE = $RockingHorse } foreach ($entry in $featureValues.GetEnumerator()) { $pattern = '(?m)^#define\s+' + $entry.Key + '\s+\([01]\)' if (-not [regex]::IsMatch($featureProfile, $pattern)) { throw "Firmware feature switch is missing: $($entry.Key)" } $featureProfile = [regex]::Replace( $featureProfile, $pattern, "#define $($entry.Key) ($($entry.Value))" ) } [System.IO.File]::WriteAllText( $featureProfilePath, $featureProfile, [System.Text.UTF8Encoding]::new($false) ) Write-Host "Feature profile: position=$PositionControl ventilation=$Ventilation heating=$Heating rocking_horse=$RockingHorse" $stagedProject = Join-Path $stagingProjectRoot 'IAR\Seat_Controller_demo.ewp' Invoke-IarBuild -ProjectPath $stagedProject $stagedExe = Join-Path $stagingProjectRoot "IAR\$Configuration\Exe" $artifactOutput = if ($OutputDirectory) { [System.IO.Path]::GetFullPath($OutputDirectory) } else { Join-Path $repositoryFirmware "IAR\$Configuration\Exe" } New-Item -ItemType Directory -Path $artifactOutput -Force | Out-Null Copy-BuildArtifact -Source $featureProfilePath -Destination ( Join-Path $artifactOutput 'seat_feature_profile.h' ) foreach ($artifact in @('Seat_Controller_demo.hex', 'Seat_Controller_demo.out')) { $sourceArtifact = Join-Path $stagedExe $artifact if (-not (Test-Path -LiteralPath $sourceArtifact -PathType Leaf)) { throw "IAR build did not produce $sourceArtifact" } Copy-BuildArtifact -Source $sourceArtifact -Destination ( Join-Path $artifactOutput $artifact ) } Write-ImageSummary -ImagePath ( Join-Path $artifactOutput 'Seat_Controller_demo.hex' ) } finally { if (Test-Path -LiteralPath $stagingRootFull) { foreach ($dependencyLink in @('common', 'tviibh8m')) { $linkPath = Join-Path $stagingRootFull $dependencyLink if (Test-Path -LiteralPath $linkPath) { $link = Get-Item -LiteralPath $linkPath -Force if (($link.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) { throw "Refusing to remove non-junction dependency path: $linkPath" } [System.IO.Directory]::Delete($linkPath, $false) } } $remainingLinks = @( Get-ChildItem -LiteralPath $stagingRootFull -Recurse -Force -Attributes ReparsePoint -ErrorAction SilentlyContinue ) if ($remainingLinks.Count -ne 0) { throw "Refusing recursive cleanup because a reparse point remains in $stagingRootFull" } [System.IO.Directory]::Delete($stagingRootFull, $true) } } Write-Host 'Build complete. This script never flashes or starts a physical actuator.'