154 lines
7.1 KiB
PowerShell
154 lines
7.1 KiB
PowerShell
|
|
#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 = '<Pdo Name="IO Inputs" Index="#x1a08" Flags="#x0020" SyncMan="3"/>'
|
||
|
|
$newPdoIn = @"
|
||
|
|
<Pdo Name="IO Inputs" Index="#x1a08" Flags="#x0020" SyncMan="3">
|
||
|
|
<Entry Name="IO Inputs" Index="#x6000" Sub="#x01">
|
||
|
|
<Type GUID="{18071995-0000-0000-0000-000300000400}">ARRAY [0..1023] OF BYTE</Type>
|
||
|
|
</Entry>
|
||
|
|
</Pdo>
|
||
|
|
"@
|
||
|
|
if ($content -match [regex]::Escape($oldPdoIn)) { $content = $content -replace [regex]::Escape($oldPdoIn), $newPdoIn; Log ' IO Inputs PDO fixed' }
|
||
|
|
|
||
|
|
$oldPdoOut = '<Pdo Name="IO Outputs" Index="#x1608" InOut="1" Flags="#x0020" SyncMan="2"/>'
|
||
|
|
$newPdoOut = @"
|
||
|
|
<Pdo Name="IO Outputs" Index="#x1608" InOut="1" Flags="#x0020" SyncMan="2">
|
||
|
|
<Entry Name="IO Outputs" Index="#x7000" Sub="#x01">
|
||
|
|
<Type GUID="{18071995-0000-0000-0000-000300000400}">ARRAY [0..1023] OF BYTE</Type>
|
||
|
|
</Entry>
|
||
|
|
</Pdo>
|
||
|
|
"@
|
||
|
|
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 '<AddressInfo>.*?</AddressInfo>') { $content = $content -replace '<AddressInfo>.*?</AddressInfo>', '' }
|
||
|
|
$addrBlock = @"
|
||
|
|
<AddressInfo>
|
||
|
|
<Pnp>
|
||
|
|
<DeviceDesc>$adapterName (TwinCAT-Intel PCI Ethernet Adapter - $($sel.InterfaceDescription))</DeviceDesc>
|
||
|
|
<DeviceName>\DEVICE\$adapterMac</DeviceName>
|
||
|
|
<DeviceData>$adapterMac</DeviceData>
|
||
|
|
</Pnp>
|
||
|
|
</AddressInfo>
|
||
|
|
"@
|
||
|
|
$content = $content -replace '(?s)(<Name>[^<]+</Name>\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 ==='
|