#Requires -Version 5.1 <# .SYNOPSIS Deploy Primary-side TwinCAT project - scan hardware, fix PDO, link PLC, activate, start runtime. #> param( [string]$Solution = 'C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary\TwinCATProject1.sln', [string]$Tsproj = 'C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary\TwinCATProject1.tsproj', [string]$TargetNetId = '169.254.176.217.1.1', [string]$LogDir = 'C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary\Logs', [switch]$SkipScan ) $ErrorActionPreference = 'Continue' $LogFile = Join-Path $LogDir "deploy-primary.log" New-Item -ItemType Directory -Force -Path $LogDir | Out-Null function Log($m) { $line = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + " $m" Add-Content -Path $LogFile -Value $line -Encoding utf8 -ErrorAction SilentlyContinue Write-Host $line } function LogSection($m) { Log ''; Log ('='*65); Log " $m"; Log ('='*65) } Log '=== tc-deploy-primary start ===' # ---- Step 1: Fix PDO in tsproj ---- LogSection 'Step 1/5: Fix PDO in tsproj' $content = [System.IO.File]::ReadAllText($Tsproj, [System.Text.Encoding]::UTF8) $oldPdoIn = '' $newPdoIn = @" ARRAY [0..1023] OF BYTE "@ if ($content -match [regex]::Escape($oldPdoIn)) { $content = $content -replace [regex]::Escape($oldPdoIn), $newPdoIn; Log ' IO Inputs PDO fixed' } $oldPdoOut = '' $newPdoOut = @" ARRAY [0..1023] OF BYTE "@ if ($content -match [regex]::Escape($oldPdoOut)) { $content = $content -replace [regex]::Escape($oldPdoOut), $newPdoOut; Log ' IO Outputs PDO fixed' } # Fix SM lengths for EL6695 $content = $content -replace '001c00006400000003000000000000000000001c64010000', '001c00040000000003000000000000000000001c64010000' $content = $content -replace '008e02002000010004000000000000000200008e20010000', '008e02040000010004000000000000000200008e20010000' [System.IO.File]::WriteAllText($Tsproj, $content, [System.Text.Encoding]::UTF8) Log ' tsproj PDO fix saved' # ---- Step 2: Find adapter and write Pnp binding ---- LogSection 'Step 2/5: Discover network adapter + Pnp binding' $adapters = Get-NetAdapter -Physical | Where-Object { $_.Status -eq 'Up' } $sel = $adapters | Where-Object { $_.InterfaceDescription -like '*TwinCAT*' } | Select-Object -First 1 if (-not $sel) { $sel = $adapters | Where-Object { $_.InterfaceDescription -like '*Intel*' } | Select-Object -First 1 } if (-not $sel) { $sel = $adapters[0] } $adapterMac = ($sel.MacAddress -replace '-', '').ToLower() $adapterName = $sel.Name Log " Adapter: $adapterName MAC=$adapterMac" if ($content -match '.*?') { $content = $content -replace '.*?', '' } $addrBlock = @" $adapterName (TwinCAT-Intel PCI Ethernet Adapter - $($sel.InterfaceDescription)) \DEVICE\$adapterMac $adapterMac "@ $content = $content -replace '(?s)([^<]+\s*)', "`$1`t`t`t`t$addrBlock`n`t`t`t`t" [System.IO.File]::WriteAllText($Tsproj, $content, [System.Text.Encoding]::UTF8) Log ' Pnp binding written' if ($SkipScan) { Log ' -SkipScan: skipping hardware scan, using tsproj as-is' } else { # ---- Step 3: Open DTE, reload devices, activate ---- LogSection 'Step 3/5: Open DTE + reload devices' try { $dte = New-Object -ComObject TcXaeShell.DTE.17.0 } catch { $dte = New-Object -ComObject TcXaeShell.DTE.15.0 } $dte.MainWindow.Visible = $true try { $dte.SuppressUI = $false } catch { } $dte.Solution.Open($Solution) for ($i = 1; $i -le 120; $i++) { Start-Sleep -Seconds 1; if ($dte.Solution.IsOpen) { Log "IsOpen after ${i}s"; break } } $sm = $null for ($i = 1; $i -le 30; $i++) { try { $sm = $dte.Solution.Projects.Item(1).Object; if ($sm) { Log 'SysManager ready'; break } } catch {}; Start-Sleep -Seconds 3 } Start-Sleep -Seconds 15 try { $sm.SetTargetNetId($TargetNetId); Log 'SetTargetNetId OK' } catch { Log "SetTargetNetId err: $_" } # Activate to go online (needed before reload) try { $dte.SuppressUI = $true } catch { } try { $sm.ActivateConfiguration(); Log 'ActivateConfiguration OK' } catch { Log "Activate err: $_" } Start-Sleep -Seconds 10 # Reload devices to scan actual hardware topology $bytes = @(0xE9,0x87,0x8D,0xE6,0x96,0xB0,0xE5,0x8A,0xA0,0xE8,0xBD,0xBD,0xE8,0xAE,0xBE,0xE5,0xA4,0x87) $reloadCmd = 'TwinCAT.' + [System.Text.Encoding]::UTF8.GetString($bytes) try { $dte.ExecuteCommand($reloadCmd); Log "Reload devices OK" } catch { Log "Reload err: $_" } Start-Sleep -Seconds 10 # ---- Step 4: Link PLC variables to IO ---- LogSection 'Step 4/5: Link PLC variables to IO' try { # Find PLC instance tree item $plcInstance = $sm.LookupTreeItem('TIPC^PlcSync^PlcSync Instance') if ($plcInstance) { Log "PLC instance found: $($plcInstance.Name)" # Find Outputs and Inputs sub-items for ($j = 1; $j -le [int]$plcInstance.ChildCount; $j++) { try { $child = $plcInstance.Child($j) Log " Child: $($child.Name)" for ($k = 1; $k -le [int]$child.ChildCount; $k++) { try { $var = $child.Child($k); Log " Var: $($var.Name) type=$($var.ItemType)" } catch {} } } catch {} } } else { Log ' PLC instance not found in tree' } } catch { Log " Tree lookup err: $($_.Exception.Message)" } # ---- Step 5: Activate and start ---- LogSection 'Step 5/5: Activate + start runtime' try { $dte.SuppressUI = $true } catch { } try { $sm.ActivateConfiguration(); Log 'ActivateConfiguration OK' } catch { Log "Activate err: $_" } Start-Sleep -Seconds 10 try { $sm.StartRestartTwinCAT(); Log 'StartRestartTwinCAT executed' } catch { Log "StartRestart err: $_" } $adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll' Add-Type -Path $adsDll -ErrorAction SilentlyContinue function Get-TcState { try { $c = New-Object TwinCAT.Ads.TcAdsClient; $c.Connect($TargetNetId, 10000); $s = $c.ReadState(); $c.Dispose(); return "$($s.AdsState)" } catch { return "ERR" } } for ($i = 1; $i -le 12; $i++) { Start-Sleep -Seconds 5; $st = Get-TcState Log "state +$(($i)*5)s: $st" if ($st -eq 'Run') { break } } try { $dte.Quit() } catch {} Log "Final state: $(Get-TcState)" } LogSection 'Deploy complete' Log '=== tc-deploy-primary end ==='