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
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trust-lsp.runtime.controlEndpoint": "tcp://127.0.0.1:9901"
}
17 changes: 14 additions & 3 deletions TwinCatVariableViewer/TwinCatVariableViewer/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,23 @@

<Grid x:Name="RootGrid" Background="{StaticResource BackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="35"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="AMS Net ID:" Foreground="{StaticResource ForegroundBrush}" VerticalAlignment="Center" Margin="10,0,6,0"/>
<TextBox x:Name="TextBoxAmsNetId" Grid.Column="1" Height="20" Text="127.0.0.1.1.1" VerticalAlignment="Center" Margin="0,0,5,0" Background="{StaticResource BackgroundLightBrush}" Foreground="{StaticResource ForegroundBrush}" KeyUp="TextBoxAmsNetId_OnKeyUp"/>
<Button x:Name="ButtonConnect" Grid.Column="2" Content="Connect" Height="20" Margin="0,0,10,0" Click="ButtonConnect_OnClick"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="60"/>
Expand All @@ -60,7 +71,7 @@
<Button Grid.Column="1" Content="Filter" Height="20" Margin="5,10,5,0" Click="ButtonFilter_OnClick"/>
<ComboBox x:Name="ComboBoxPlc" Grid.Column="2" Height="20" Margin="5,10,10,0" SelectionChanged="ComboBoxPlc_OnSelectionChanged"/>
</Grid>
<ListView x:Name="SymbolListView" Grid.Row="1" SelectionMode="Single" Style="{x:Null}" Margin="10,5,10,10" Background="{StaticResource BackgroundLightBrush}"
<ListView x:Name="SymbolListView" Grid.Row="2" SelectionMode="Single" Style="{x:Null}" Margin="10,5,10,10" Background="{StaticResource BackgroundLightBrush}"
ItemContainerStyle="{StaticResource ListViewItemStyle}" ItemsSource="{Binding SymbolListViewItems}">
<ListView.View>
<GridView>
Expand Down Expand Up @@ -141,7 +152,7 @@
</ListView.View>
</ListView>

<Grid Grid.Row="2" HorizontalAlignment="Right" Width="{Binding ActualWidth, ElementName=RootGrid}">
<Grid Grid.Row="3" HorizontalAlignment="Right" Width="{Binding ActualWidth, ElementName=RootGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
Expand Down
17 changes: 15 additions & 2 deletions TwinCatVariableViewer/TwinCatVariableViewer/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,17 @@ public MainWindow()

private void ConnectPlc()
{
string amsNetId = TextBoxAmsNetId?.Text?.Trim();
if (string.IsNullOrEmpty(amsNetId)) amsNetId = "127.0.0.1.1.1";

_activePlc = 0;
_activePorts = GetActivePlcPorts("127.0.0.1.1.1", 851);
_activePorts = GetActivePlcPorts(amsNetId, 851);
_plcConnections.Clear();
ComboBoxPlc.Items.Clear();
SymbolListViewItems?.Clear();
foreach (int port in _activePorts)
{
PlcConnection plcCon = new PlcConnection(new AmsAddress($"127.0.0.1.1.1:{port}"));
PlcConnection plcCon = new PlcConnection(new AmsAddress($"{amsNetId}:{port}"));
plcCon.PlcConnectionError += PlcOnPlcConnectionError;
plcCon.Connect();
if (plcCon.Connected)
Expand Down Expand Up @@ -547,6 +550,16 @@ private void UpdateDumpStatus(string text, Color fontColor)
if (!isLoaded) Application.Current.Dispatcher.Invoke(() => SplashScreen.LoadingStatus(text));
}

private void ButtonConnect_OnClick(object sender, RoutedEventArgs e)
{
ConnectPlc();
}

private void TextBoxAmsNetId_OnKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter) ConnectPlc();
}

private void ButtonReconnect_OnClick(object sender, RoutedEventArgs e)
{
ConnectPlc();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>TwinCatVariableViewer</RootNamespace>
<AssemblyName>TwinCatVariableViewer</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
Expand Down
127 changes: 127 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Arquitectura del proyecto

## Visión general

TwinCAT Variable Viewer es una aplicación WPF (.NET Framework 4.8) que se conecta a un PLC Beckhoff TwinCAT 3 mediante el protocolo ADS (Automation Device Specification) para leer y visualizar variables en tiempo real.

## Estructura de ficheros

```
TwinCatVariableViewer/
├── TwinCatVariableViewer.sln
└── TwinCatVariableViewer/
├── App.xaml / App.xaml.cs # Punto de entrada de la aplicación
├── MainWindow.xaml # UI principal
├── MainWindow.xaml.cs # Lógica de la ventana principal
├── PlcConnection.cs # Gestión de la conexión ADS con el PLC
├── SymbolInfo.cs # Modelo de datos para cada variable
├── Tc3Symbols.cs # Lectura y parseo recursivo de símbolos
├── Converters.cs # Conversores de binding WPF
├── SplashScreen.xaml/.cs # Pantalla de carga inicial
└── Properties/
└── AssemblyInfo.cs # Versión y metadatos del ensamblado
DLLs/
└── TwinCAT.Ads.dll # Librería ADS de Beckhoff (referencia local)
```

## Clases principales

### `PlcConnection`

Encapsula una conexión ADS a un puerto concreto del PLC.

| Miembro | Tipo | Descripción |
|---|---|---|
| `Session` | `AdsSession` | Sesión ADS activa |
| `Connection` | `AdsConnection` | Conexión activa a la sesión |
| `Symbols` | `List<ISymbol>` | Lista plana de todas las variables leídas |
| `Connected` | `bool` | Estado actual de la conexión |
| `Connect()` | método | Abre sesión, comprueba estado del PLC y carga símbolos |
| `Disconnect()` | método | Cierra la conexión limpiamente |

**Eventos expuestos:**
- `PlcConnectionError` — se dispara cuando ocurre un error de conexión ADS

---

### `Tc3Symbols`

Clase estática con dos responsabilidades:

1. **`AddSymbolRecursive`** — recorre el árbol de símbolos del PLC de forma recursiva. Expande arrays (elemento a elemento) y structs (miembro a miembro) hasta llegar a valores primitivos. Filtra tipos no legibles como referencias (`REFERENCE TO`) y tipos internos de Beckhoff (`TC2_MC2.*`).

2. **`GetSymbolValue`** — lee el valor actual de un símbolo concreto a través de la conexión ADS.

---

### `SymbolInfo`

Modelo de datos que implementa `INotifyPropertyChanged`, usado como item del `ListView`.

| Propiedad | Tipo | Descripción |
|---|---|---|
| `Path` | `string` | Ruta completa de la variable (ej. `MAIN.miVar`) |
| `Type` | `string` | Nombre del tipo de dato (ej. `BOOL`, `REAL`) |
| `Size` | `int` | Tamaño en bytes |
| `IndexGroup` | `long` | Grupo de índice ADS |
| `IndexOffset` | `long` | Offset de índice ADS |
| `IsStatic` | `bool` | Si la variable es estática |
| `CurrentValue` | `string` | Valor actual como texto |

---

### `MainWindow`

Controlador principal de la UI. Coordina:

- **Conexión**: instancia `PlcConnection` por cada puerto ADS activo encontrado (a partir del puerto 851)
- **Refresco en tiempo real**: `DispatcherTimer` a 200 ms que actualiza únicamente las filas visibles del `ListView` (virtualización manual usando el `ScrollViewer`)
- **Dump**: lee todos los valores de una vez con `SumSymbolRead` en bloques de 500 y escribe XML y/o CSV
- **Filtrado**: filtra la lista por texto en la ruta de la variable

---

### `Converters.cs`

Conversores WPF para bindings:

| Clase | Descripción |
|---|---|
| `BoolToValueConverter` | `bool` → `0` o `1` |
| `ValueToVisibilityConverter` | `bool` → `Visibility`. Soporta parámetro `Invert` |
| `IndexConverter` | `ListViewItem` → índice numérico (para la columna `#`) |

---

## Flujo de datos

```
Inicio
├─ CheckLibrary("tcadsdll.dll") ← verifica que TwinCAT ADS está instalado
├─ ConnectPlc()
│ ├─ Lee AMS Net ID del TextBox (default: 127.0.0.1.1.1)
│ ├─ GetActivePlcPorts() ← prueba puertos 851, 852... hasta 5 fallos seguidos
│ └─ PlcConnection.Connect() por cada puerto activo
│ ├─ AdsSession + AdsConnection
│ ├─ ReadState() ← verifica Run o Stop
│ └─ GetSymbols() → Tc3Symbols.AddSymbolRecursive() (árbol → lista plana)
├─ DispatcherTimer (200ms)
│ └─ GetSymbolValue() para cada fila visible en pantalla
└─ ButtonDumpData
├─ SumSymbolRead (bloques de 500 variables)
├─ Escribe VariableDump_<puerto>.xml
└─ Escribe VariableDump_<puerto>.csv
```

## Protocolo ADS

ADS (Automation Device Specification) es el protocolo de comunicación propietario de Beckhoff. Cada dispositivo se identifica por:

- **AMS Net ID**: dirección de 6 bytes (ej. `192.168.1.10.1.1`). El local siempre es `127.0.0.1.1.1`.
- **Puerto ADS**: número que identifica el runtime PLC (851 = PLC1, 852 = PLC2, etc.)

Para conectar a un PLC remoto es necesario configurar una **ruta ADS** en el sistema local mediante TwinCAT System Manager.
71 changes: 71 additions & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Guía de compilación

## Requisitos

- **Visual Studio 2022** (cualquier edición, incluida Community)
- Workload: **.NET desktop development**
- **.NET Framework 4.8 Developer Pack** — [descargar](https://dotnet.microsoft.com/download/dotnet-framework/net48)
- **MSBuild** (incluido con Visual Studio)

---

## Compilar desde Visual Studio

1. Abre `TwinCatVariableViewer\TwinCatVariableViewer.sln`
2. Selecciona la configuración deseada en la barra de herramientas: `Debug` o `Release`
3. Pulsa **F6** (Build Solution) o **Ctrl+Shift+B**

El ejecutable se genera en:
- Debug: `TwinCatVariableViewer\bin\Debug\TwinCatVariableViewer.exe`
- Release: `TwinCatVariableViewer\bin\Release\TwinCatVariableViewer.exe`

---

## Compilar desde terminal (MSBuild)

```powershell
$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
# Ajusta la ruta si usas Community o Enterprise:
# "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"

& $msbuild "TwinCatVariableViewer\TwinCatVariableViewer.sln" `
/p:Configuration=Release `
/p:Platform="Any CPU" `
/v:minimal
```

Salida esperada:
```
TwinCatVariableViewer -> ...\bin\Release\TwinCatVariableViewer.exe
```

---

## Dependencias

| Dependencia | Versión | Origen |
|---|---|---|
| `TwinCAT.Ads.dll` | 4.2.109.0 | Incluida en `DLLs/` (referencia local) |
| .NET Framework | 4.8 | Instalado en el sistema |
| `tcadsdll.dll` | — | Instalada por TwinCAT en el sistema (no incluida en el repo) |

> La `TwinCAT.Ads.dll` del directorio `DLLs/` es la referencia en tiempo de compilación. En tiempo de ejecución, la app requiere también `tcadsdll.dll` del sistema, que se instala con TwinCAT ADS Runtime.

---

## Cambio de versión del target framework

El proyecto usa **.NET Framework 4.8**. Si necesitas cambiarlo, edita el fichero `.csproj`:

```xml
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
```

Versiones probadas: 4.8. Versión original del proyecto: 4.5 (requería instalar el Developer Pack de 4.5).

---

## Notas de compilación

- La advertencia `CS1668` sobre `Npcap-SDK\Lib\x64` es de la variable de entorno `LIB` del sistema y no afecta al build.
- El proyecto es WPF, por lo que solo compila en Windows (no es cross-platform).
77 changes: 77 additions & 0 deletions docs/connection-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Guía de conexión

## Requisitos previos

- **TwinCAT ADS** instalado en el PC donde se ejecuta la aplicación (mínimo TwinCAT ADS Runtime, no hace falta TwinCAT XAE completo)
- La DLL `tcadsdll.dll` debe estar accesible en el sistema. La aplicación la busca automáticamente al arrancar

---

## Conexión local (mismo PC que el runtime)

Es la configuración por defecto. El PLC corre en el mismo PC que la aplicación.

1. Asegúrate de que el runtime TwinCAT está en estado **Run** o **Stop**
2. Deja el campo **AMS Net ID** con el valor por defecto: `127.0.0.1.1.1`
3. Pulsa **Connect**

La aplicación detectará automáticamente todos los puertos PLC activos (PLC1=851, PLC2=852, etc.).

---

## Conexión remota (PLC en otra máquina)

### Paso 1 — Obtener el AMS Net ID del PLC remoto

En el PC donde corre el runtime TwinCAT:

- Haz clic derecho en el icono de TwinCAT en la barra de tareas → **Properties**
- El AMS Net ID aparece en la pestaña **AMS Router** (formato `x.x.x.x.1.1`, ej. `192.168.1.10.1.1`)

O bien, desde TwinCAT XAE:
- **System → Routes → Local** → columna *AmsNetId*

### Paso 2 — Añadir una ruta ADS en el PC local

En el PC donde se ejecuta la aplicación:

1. Abre **TwinCAT XAE** o **TwinCAT System Manager**
2. Ve a **System → Routes**
3. Haz clic en **Add Route** (o **Static Routes → Add**)
4. Rellena:
- **Route Name**: nombre descriptivo (ej. `MiPLC`)
- **AMS Net ID**: el Net ID del PLC remoto (ej. `192.168.1.10.1.1`)
- **Transport**: TCP/IP
- **Address**: IP del PLC remoto (ej. `192.168.1.10`)
5. Acepta. Si se pide contraseña, la por defecto en TwinCAT es en blanco o `1`

> **Nota**: también hay que añadir la ruta en el sentido inverso (desde el PLC hacia el PC local). En muchos entornos esto se hace automáticamente al aceptar la conexión.

### Paso 3 — Conectar desde la aplicación

1. En el campo **AMS Net ID** escribe el Net ID del PLC remoto (ej. `192.168.1.10.1.1`)
2. Pulsa **Connect** (o Enter)

---

## Múltiples runtimes PLC

Si el PLC tiene más de un runtime (PLC1, PLC2...), la aplicación los detecta automáticamente y añade una entrada en el desplegable de la esquina superior derecha. Puedes cambiar entre ellos sin reconectar.

---

## Solución de problemas

| Síntoma | Causa probable | Solución |
|---|---|---|
| `Did not find tcadsdll.dll` | TwinCAT ADS no instalado | Instalar TwinCAT ADS Runtime |
| `No active PLCs found` | El runtime no está en Run/Stop, o el AMS Net ID es incorrecto | Verificar estado del PLC y el Net ID |
| `Timeout` o error de conexión | Ruta ADS no configurada, firewall bloqueando puerto 48898 | Añadir ruta ADS, abrir puerto UDP/TCP 48898 |
| `PLC state: Disconnected` | El PLC perdió la conexión durante la sesión | Pulsar **Reconnect** |

### Puertos de red usados por ADS

| Puerto | Protocolo | Uso |
|---|---|---|
| 48898 | TCP y UDP | Comunicación AMS/ADS principal |
| 801 | — | Puerto local TwinCAT (no requiere apertura en firewall) |
Loading