B&R PVI (Process Visualization Interface) — Complete Technical Reference
A comprehensive guide for programmatic PLC access via B&R’s PVI middleware.
Table of Contents
- What PVI Is and How It Works
- The PVI API: C/C++, .NET, COM
- Connecting to a CP1584 via PVI
- Reading and Writing Variables
- Browsing the Variable Namespace Without Project Files
- Pulling Diagnostic Data from a Running CP1584
- Error Handling: Error Codes and Recovery
- Data Types: B&R Types to PVI/C Types
- Callbacks and Event Handling
- Performance Considerations
- Python PVI Bindings
- Using PVI from Linux
- PVI vs OPC-UA: When to Use Which
- PVI License Requirements
- Example Code for Common Diagnostic Tasks
1. What PVI Is and How It Works
Overview
PVI (Process Visualization Interface) is B&R Industrial Automation’s proprietary communication middleware. It abstracts physical connections (Ethernet, serial, CAN, USB) behind a uniform API, enabling PC-based software to discover controllers, access variables symbolically, read/write process data, and manage controller lifecycle.
All higher-level B&R products consume PVI internally — OPC servers, B&R HMI panels, mapp View, Automation Studio’s online functionality, and PVI Transfer.
Client-Server Architecture
PVI uses a client-server principle allowing PVI applications to be operated remotely:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ PVI Client │ TCP/ │ PVI Manager │ ANSL/ │ B&R Controller │
│ (Application) │◄──────► │ (PviMan.exe) │◄───────►│ (CP1584, X20, │
│ - C API DLL │ │ Windows service │ INA │ APC, PowerPanel│
│ - .NET DLL │ │ or process │ │ Automation RT) │
│ - COM objects │ │ Routes/lines │ │ Port 11160/ANSL │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Key Components
| Component | Location | Function |
|---|---|---|
| PviMan.exe (PVI Manager) | Windows host | Central service: manages line configurations, routes traffic to controllers. Runs as Windows service or process. |
| PVIServer | Automation Runtime (controller) | Exposes process variables from controller tasks to the network. |
| PVI Client Libraries | Application (DLLs) | C/C++ DLL (PviCom.dll), .NET assemblies (BR.AN.PviServices), COM objects. |
| PVI OPC Server | Windows host | Translates OPC DA / UA calls into PVI calls. |
| PVI Monitor | Windows host | GUI tool for monitoring connections, configuring logging, snapshots. |
PVI Lines (Protocols)
PVI routes communication through “lines,” each a different protocol driver:
| Line | Driver Code | Protocol | Use Case |
|---|---|---|---|
| ANSL | LNANSL | Automation Net Standard Language (TCP) | Primary modern protocol for variable access to AR ≥ V4.08 controllers. |
| INA2000 | LNINA | INA2000 (UDP, legacy) | Legacy protocol for older controllers. 255-byte payload per frame limit. REMOVED in PVI 6.x — see critical note below. |
| SNMP | LNSNMP | SNMP v1 | Network configuration (IP, subnet, gateway) of B&R PLCs. |
| MODBUS | LNMODBUS | Modbus TCP | Communication with third-party Modbus devices. |
| NET2000 | LNNET | NET2000 | B&R’s legacy proprietary fieldbus. |
| ADI | LNADI | Automation Device Interface | Direct local access on B&R IPCs (no network). |
| DCAN | LNDCAN | Device CAN | CAN bus communication. |
⚠️ CRITICAL: PVI 6.x Drops INA2000 Support
Starting with PVI version 6 (bundled with Automation Studio 6), B&R completely removed INA2000 protocol support. PVI 6.x supports only ANSL and SNMP lines. This means:
- Any existing code, scripts, or tools using
LNINAwill fail with missing DLL errors on AS6 installations.- The INA2000 DLL (
PviIna.dll) is no longer shipped and cannot be side-loaded.- ANSL is backward-compatible with all AR ≥ V4.08 controllers, including the CP1584 — so migration is straightforward: replace
LNINAwithLNANSLin your connection code.- If you are using a recovered AS4 project and upgrading to AS6, search the entire project for “INA” or “LNINA” and replace with ANSL equivalents.
Migration checklist for existing PVI code:
1. Find all occurrences of: "LNINA", "INA2000", "PviIna" 2. Replace driver code: LNINA → LNANSL 3. Replace protocol string: "INA" → "ANSL" 4. Update connection: UDP port → TCP port (default 1212) 5. Test ANSL connectivity: PVI Manager → Add Line → ANSL → your PLC IPSee firmware-version-mgmt.md §5.3.1 for full AS4→AS6 migration details.
PVI Object Hierarchy
PVI organizes objects in a strict hierarchy. All objects must be created parent-first:
PVI (root)
└── Line (e.g., LNANSL)
└── Device (e.g., TCP/IP interface)
└── CPU/Station (e.g., PLC at 192.168.1.50)
├── Task
│ ├── Variable (process variable)
│ └── Variable ...
├── Module
└── CPU-level Variables (global scope)
PVI Architecture Sources
- B&R PVI Online Help
- B&R Community: How-to guide — all about Automation Net PVI
- B&R PVI Development Setup Download
- Industrial Monitor Direct: B&R PLC Communication Guide
2. The PVI API: C/C++, .NET, COM
C/C++ API (Native)
The C API is the foundational interface exposed through PviCom.dll (on Windows). It uses a flat function-based API with string-based object creation and callback-based event handling.
Key functions:
// Initialization / Shutdown
DWORD PviInitialize( DWORD dwFlags );
DWORD PviUninitialize( void );
DWORD PviInitializeEx( DWORD dwFlags, char *szPviManName ); // connect to specific PVI Manager
// Object Management
DWORD PviCreate( LPSTR lpszParent, LPSTR lpszName, LPSTR lpszAccess, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
DWORD PviCreateLink( LPSTR lpszParent, LPSTR lpszName, LPSTR lpszAccess, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
DWORD PviDelete( LPSTR lpszIdent, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
// Variable Read / Write
DWORD PviRead( LPSTR lpszIdent, LPSTR lpszAccess, LPVOID pBuffer, DWORD dwLength, DWORD dwTimeout, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
DWORD PviWrite( LPSTR lpszIdent, LPSTR lpszAccess, LPVOID pBuffer, DWORD dwLength, DWORD dwTimeout, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
DWORD PviReadValue( LPSTR lpszIdent, LPVOID pBuffer, DWORD dwLength, DWORD dwTimeout );
DWORD PviWriteValue( LPSTR lpszIdent, LPVOID pBuffer, DWORD dwLength, DWORD dwTimeout );
// Information / Status
DWORD PviGetInfo( LPSTR lpszIdent, LPSTR lpszAccess, LPVOID pBuffer, DWORD dwLength, LPDWORD lpdwRetLen, DWORD dwTimeout );
DWORD PviGetList( LPSTR lpszIdent, LPSTR lpszMask, LPSTR lpszAccess, LPVOID pBuffer, DWORD dwLength, LPDWORD lpdwRetLen, DWORD dwTimeout );
// Callbacks
DWORD PviSetCallbackMode( LPSTR lpszIdent, PFN_PVI_COMPLETION pfnCompletion, DWORD dwMode, DWORD dwUserData );
DWORD PviSetCallbackX( LPSTR lpszIdent, PFN_PVI_COMPLETION_X pfnCompletionX, DWORD dwMode, DWORD dwUserData );
// Error
LPSTR PviGetErrorText( DWORD dwError, LPSTR lpszBuffer, DWORD dwLength );
DWORD PviGetLastError( void );
// ActiveX / COM (legacy)
DWORD PviCreateAx( LPSTR lpszParent, LPSTR lpszName, LPSTR lpszAccess, PFN_PVI_COMPLETION pfnCompletion, DWORD dwUserData );
Callback signature:
typedef void (CALLBACK *PFN_PVI_COMPLETION)(
LPSTR lpszIdent, // object identifier
DWORD dwError, // error code (0 = success)
DWORD dwEvent, // event type (PVI_EVENT_LINK, etc.)
DWORD dwInfo1,
DWORD dwInfo2,
DWORD dwUserData
);
// Extended callback (PVI 4.x+)
typedef void (CALLBACK *PFN_PVI_COMPLETION_X)(
LPSTR lpszIdent,
DWORD dwError,
LPSTR lpszEvent,
DWORD dwInfo1,
LPSTR lpszInfo2,
DWORD dwUserData
);
Headers and libraries are provided in the PVI Development Setup:
C:\BrAutomation\PVI\V4.xx\Include\PviCom.hC:\BrAutomation\PVI\V4.xx\Lib\x64\PviCom.lib
.NET API (BR.AN.PviServices)
The .NET wrapper (BR.AN.PviServices.dll) provides an object-oriented interface. It is a GUI variable interface that uses the Windows Message Loop to trigger events.
Key classes:
using BR.AN.PviServices;
// Hierarchy
PviConnection connection = new PviConnection(); // connects to Pvi Manager
connection.Root // PVI root object
Line line = new Line(connection.Root, "LNANSL", "CD=LNANSL");
Device device = new Device(line, "TCP", "CD=/IF=TcpIp");
Cpu cpu = new Cpu(device, "myPLC", "CD=/IP=192.168.1.50");
Task task = new Task(cpu, "myTask");
Variable variable = new Variable(task, "myVar");
// Read/Write
variable.ReadValue();
variable.WriteValue(42);
// Event-driven updates
variable.ValueChanged += (sender, e) => {
Console.WriteLine($"Value: {variable.Value}");
};
// CPU diagnostics
cpu.Connected += (sender, e) => { /* connected */ };
cpu.ErrorChanged += (sender, e) => { /* handle errors */ };
// CPU info
string cpuStatus = cpu.Status.RunState; // "Run", "Stop", "Init", ...
DateTime cpuTime = cpu.Time;
// Start/stop tasks
task.Start();
task.Stop();
task.Cycle(3); // single-cycle mode, N cycles
task.Resume(); // resume single-cycle
// Connection lifecycle
connection.Start(); // begins event loop
connection.Stop(); // shuts down
Thread safety note:
BR.AN.PviServicesis NOT thread-safe. All modifications to PVI object members must be done on the main UI thread. UseInvoke/BeginInvokeif updating from background threads.
COM API (Legacy)
PVI also exposes a COM/ActiveX interface via PviCreateAx(). This is primarily for legacy VB6 applications and is not recommended for new development. The COM interface wraps the same underlying C API.
PVI Services Version Compatibility
| PVI Version | AR Compatibility | API Changes |
|---|---|---|
| PVI 3.x | AR 3.x | INA2000 primary, ANSL emerging |
| PVI 4.x | AR 4.x+ | ANSL primary, INA2000 legacy. Extended callbacks. |
| PVI 5.x | AR 5.x | Improved .NET API, 64-bit support |
| PVI 6.x | AR 6.x | ANSL and SNMP only (INA removed) |
PVI API Sources
3. Connecting to a CP1584 via PVI
Target Overview: CP1584
The CP1584 is a B&R Power Panel with an integrated controller and display (Automation Runtime). It exposes PVI via ANSL (TCP) and legacy INA2000 (UDP).
Connection Parameters
| Parameter | Value | Notes |
|---|---|---|
| Protocol | ANSL (recommended) | Automation Net Standard Language |
| Default TCP Port | 11160 | Default INA2000 transport port. Note: Some B&R documentation and tools (e.g., ANSL discovery) reference port 11169. Both ports may be active depending on AR version and configuration. Port 11160 is the INA2000/PVI TCP port; port 11169 is the ANSL COMT port. Try both if one doesn’t respond. |
| OPC UA Port | 4840 | If OPC UA is enabled (AR ≥ 4.0) |
| INA2000 Port | 11160 (UDP) | Legacy; 297-byte frame limit (255 payload + 42 headers) |
ANSL Connection String Format
/IF=TcpIp /IP=<ip_address> [/PORT=<port>] [/SLOT=<slot>] [/PASS=<password>]
C/C++ Example: Connect to CP1584
#include "PviCom.h"
DWORD err;
// Initialize PVI (connects to local PVI Manager)
err = PviInitialize(0);
if (err != 0) {
char errMsg[256];
PviGetErrorText(err, errMsg, sizeof(errMsg));
printf("PviInitialize failed: %s\n", errMsg);
return -1;
}
// Create ANSL line
err = PviCreate("PVI", "LNANSL", "CD=LNANSL", myCallback, 0);
// Create TCP device
err = PviCreate("LNANSL", "TCP", "CD=/IF=TcpIp", myCallback, 0);
// Create CPU station (CP1584)
err = PviCreate("LNANSL/TCP", "CP1584",
"CD=/IP=192.168.1.50 /PORT=11160",
myCallback, 0);
// Wait for PVI_EVENT_LINK (connection established)
.NET Example: Connect to CP1584
using BR.AN.PviServices;
var connection = new PviConnection();
var line = new Line(connection.Root, "LNANSL", "CD=LNANSL");
var device = new Device(line, "TCP", "CD=/IF=TcpIp");
var cpu = new Cpu(device, "CP1584", "CD=/IP=192.168.1.50 /PORT=11160");
cpu.ErrorChanged += (sender, e) => {
if (cpu.ErrorCode == 11020) {
Console.WriteLine("Unable to establish connection");
} else if (cpu.ErrorCode != 0) {
throw new PviException(cpu.ErrorCode);
} else {
Console.WriteLine("Connected to " + cpu.Name);
}
};
connection.Start();
Python (Pvi.py) Example: Connect to CP1584
from pvi import *
pviConnection = Connection()
line = Line(pviConnection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
def cpuErrorChanged(error: int):
if error == 11020:
print("Unable to establish connection")
pviConnection.stop()
elif error != 0:
raise PviError(error)
else:
print(f"Connected to CPU, AR-Version: {cpu.version}")
cpu.errorChanged = cpuErrorChanged
pviConnection.start()
SNMP Line: Network Configuration
SNMP is used for IP configuration of B&R devices (set IP, subnet, gateway):
line = Line(pviConnection.root, 'LNSNMP', CD='LNSNMP')
device = Device(line, 'SNMP', CD='/IF=SNMP /IP=192.168.1.50 /CM=public')
Connection Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| “Unable to establish connection” (error 11020) | Wrong IP, controller not reachable, ANSL disabled | Verify IP with ping; check ANSL enabled in project |
| PVI line shows “Station not responding” | Subnet mismatch, firewall blocking port 11160 | Check subnet; open TCP 11160 |
| Timeout on high-latency links (>300ms) | INA2000’s request-response protocol has high latency sensitivity | Switch to ANSL which is TCP-based and more latency-tolerant |
| Error 4806 | Requested data not found | Variable doesn’t exist or isn’t exposed; check variable name |
Connecting to CP1584 Sources
4. Reading and Writing Variables
Variable Access Paths
Variables are addressed hierarchically:
LNANSL/TCP/CP1584/<TaskName>/<VariableName>
For global (CPU-scope) variables:
LNANSL/TCP/CP1584/<VariableName>
C/C++: Synchronous Read/Write
// Synchronous read (blocks until complete)
float value;
DWORD err = PviReadValue("LNANSL/TCP/CP1584/myTask/myRealVar",
&value, sizeof(float), 5000); // 5s timeout
// Synchronous write
int newValue = 42;
err = PviWriteValue("LNANSL/TCP/CP1584/myTask/myIntVar",
&newValue, sizeof(int), 5000);
C/C++: Asynchronous Read/Write
// Asynchronous read — callback fires when data arrives
err = PviRead("LNANSL/TCP/CP1584/myTask/myRealVar",
"CV=0", // access string (Control Value, format 0 = raw)
&buffer, sizeof(buffer), 5000,
readCompleteCallback, userData);
// Asynchronous write
err = PviWrite("LNANSL/TCP/CP1584/myTask/myIntVar",
"CV=0",
&newValue, sizeof(newValue), 5000,
writeCompleteCallback, userData);
C/C++: Access Strings
The access string parameter (lpszAccess) controls formatting:
| Access Code | Meaning |
|---|---|
CV=0 | Raw binary value |
CF=<format> | Format string for display |
RF=<ms> | Refresh rate in milliseconds (for cyclic reads) |
HY=<threshold> | Hysteresis — only report changes above threshold |
VT=<type> | Variable type override |
VL=<bytes> | Variable length |
VN=<count> | Array dimension count |
.NET: Read/Write with Events
// Read with auto-refresh
var tempVar = new Variable(task, "gHeating.status.actTemp");
tempVar.RF = 200; // refresh every 200ms
tempVar.HY = 0.5; // hysteresis 0.5°C
tempVar.ValueChanged += (sender, e) => {
Console.WriteLine($"Temperature: {tempVar.Value}");
};
// Direct write
var setpoint = new Variable(task, "gHeating.setpoint");
setpoint.WriteValue(75.0f);
Python (Pvi.py): Read/Write
from pvi import *
pviConnection = Connection()
line = Line(pviConnection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
task = Task(cpu, 'myLogic')
# Read a variable with auto-refresh (200ms)
temperature = Variable(task, 'gHeating.status.actTemp', RF=200, HY=0.5)
temperature.valueChanged = lambda v: print(f'Temp = {v}')
# Write a variable
setpoint = Variable(task, 'gHeating.setpoint')
def onConnected(error):
if error == 0:
setpoint.value = 75.0
print(f'Wrote setpoint: {setpoint.value}')
task.errorChanged = onConnected
pviConnection.start()
Reading/Writing All IEC Data Types (Python Example)
from datetime import datetime, time, date, timedelta
from pvi import *
# Writing all basic IEC types (from Pvi.py basics1.py example)
variableList = (
('myBOOL', True),
('myUSINT', 123),
('mySINT', -123),
('myUINT', 1234),
('myINT', -1234),
('myUDINT', 1234567),
('myDINT', -124567),
('myREAL', 1.234e2),
('myLREAL', -3.1415e100),
('mySTRING', b'hello, world'),
('myWSTRING', 'hello, world'),
('myDT', datetime.fromisoformat('2011-11-04T00:05:23')),
('myTIME', timedelta(minutes=3, seconds=45, milliseconds=33)),
('myTOD', time(hour=14, minute=30, second=13)),
('myDATE', date.today()),
('myINTArray', (1, 2, 3, 4, 5, 6, 7)),
('mySTRINGArray', (b'Huey', b'Dewey', b'Louie')),
('myREALArray', 100.0), # broadcast single value to all elements
)
for name, value in variableList:
variable = Variable(task, name)
variable.value = value # write
print(f'{name}: {variable.value}') # read back
variable.kill()
ANSL Read Path Size Threshold
From the B&R community documentation:
- Variables > 64 kB are read asynchronously
- Variables ≤ 64 kB are read synchronously
This has implications for large arrays and structs — plan for callback-based handling.
Reading and Writing Variables Sources
5. Browsing the Variable Namespace Without Project Files
PVI can enumerate all objects on a controller without needing the Automation Studio project. This is critical for diagnostics, generic HMI tools, and data discovery.
C/C++: PviGetList
// Get list of tasks on a CPU
char buffer[8192];
DWORD retLen;
DWORD err = PviGetList(
"LNANSL/TCP/CP1584", // CPU ident
"*Task*", // mask: list all tasks
"C=Info", // access: return info objects
buffer, sizeof(buffer),
&retLen,
5000
);
// Parse the returned buffer — contains formatted list of task objects
.NET: Cpu.ExternalObjects
// Wait for CPU connection, then enumerate
cpu.Connected += (s, e) => {
var allObjects = cpu.ExternalObjects;
var modules = allObjects.Where(o => o.Type == "Module").Select(o => o.Name);
var tasks = allObjects.Where(o => o.Type == "Task").Select(o => o.Name);
var globalVars = allObjects.Where(o => o.Type == "Pvar").Select(o => o.Name);
foreach (var taskName in tasks) {
var task = new Task(cpu, taskName);
// task.ExternalObjects gives task-local variables
}
};
Python (Pvi.py): List All Objects on a CPU
from pvi import *
from pprint import pprint
pviConnection = Connection()
line = Line(pviConnection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
def cpuErrorChanged(error):
if error == 0:
allObjects = cpu.externalObjects
# Modules
moduleNames = [o['name'] for o in allObjects if o['type'] == 'Module']
for name in moduleNames:
module = Module(cpu, name)
print(f"Module: {name}, Status: {module.status}")
module.kill()
# Tasks
taskNames = [o['name'] for o in allObjects if o['type'] == 'Task']
for name in taskNames:
task = Task(cpu, name)
print(f"Task: {name}, Status: {task.status}")
task.kill()
# Global variables (CPU scope)
globalVarNames = [o['name'] for o in allObjects if o['type'] == 'Pvar']
for name in globalVarNames:
var = Variable(cpu, name)
print(f"Global Var: {name}, Type: {var.dataType}, Value: {var.value}")
var.kill()
# Task-local variables
for taskName in taskNames:
task = Task(cpu, taskName)
taskVars = [o['name'] for o in task.externalObjects if o['type'] == 'Pvar']
print(f"\nTask '{taskName}' variables:")
for vname in taskVars:
print(f" {vname}")
task.kill()
pviConnection.stop()
elif error:
raise PviError(error)
cpu.errorChanged = cpuErrorChanged
pviConnection.start()
ANSL UIF Library: Browsing on Linux
When using the ANSL UIF library (Linux native), variable browsing works at the task level:
// Get module list
ANSLI_CPU_ListModules(identCpu); // returns list of modules
ANSLI_CPU_ListTasks(identCpu); // returns list of tasks
// Get variables (IMPORTANT: use TASK, not CPU for variables)
ANSLI_TASK_ListVariables(identTask); // returns task-local variables
ANSLI_CPU_ListVariables(identCpu); // NOTE: may not be supported in all ANSL expansions
Important: According to B&R community,
ANSLI_CPU_ListVariablesmay not be supported in ANSL expansion I. UseANSLI_TASK_ListVariableswith a connected task ident instead.
Browsing Variable Namespace Sources
6. Pulling Diagnostic Data from a Running CP1584
CPU Status Information
from pvi import *
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
def onConnected(error):
if error == 0:
# PVI version
print(f"PVI Version: {pviConnection.root.version}")
# AR version
print(f"AR Version: {cpu.version}")
# CPU run state
print(f"Run State: {cpu.status.get('RunState', 'unknown')}")
# CPU clock
print(f"CPU Time: {cpu.time.isoformat()}")
# CPU type info
print(f"CPU Type: {cpu.cpuInfo.get('CT', 'unknown')}")
# Installed tasks
print(f"Tasks: {len(cpu.tasks)}")
Task Status and Control
from datetime import datetime, timedelta
task = Task(cpu, 'brewing')
def stateMachine(init):
# Read task status
print(f"Task status: {task.status}")
print(f"Task version: {task.version}")
# Task control operations
task.stop() # stop the task
task.cycle(3) # set single-cycle mode, 3 cycles
task.resume() # execute a single cycle
task.start() # restart normal cyclic execution
pviConnection.start(stateMachine)
Module Status
allObjects = cpu.externalObjects
moduleNames = [o['name'] for o in allObjects if o['type'] == 'Module']
for name in moduleNames:
module = Module(cpu, name)
status = module.status # dict with module-specific status fields
print(f"Module {name}: {status}")
module.kill()
System Dump / Profiler Data
Pvi.py includes utilities for parsing B&R system dump files:
from pvi.utils import BrProfilerDataFile
import csv
profilerData = BrProfilerDataFile('path/to/BuR_SDM_profiling_*.pd')
# Summary info
print("Summary:", profilerData.info)
print("Cyclic tasks:", profilerData.cyclicTasks)
print("System tasks:", profilerData.systemTasks)
# Export to CSV
with open('profiler.csv', 'w', newline='') as f:
writer = csv.writer(f, delimiter=';')
writer.writerow(["Nr", "time", "object name", "ident", "event", "description"])
for n, entry in enumerate(profilerData.entries):
writer.writerow((n+1, entry.microseconds, entry.objectName,
hex(entry.objectIdent), hex(entry.event),
entry.eventDescription))
SNMP-Based Diagnostics
Use the SNMP line to read network configuration:
line = Line(pviConnection.root, 'LNSNMP', CD='LNSNMP')
device = Device(line, 'SNMP', CD='/IF=SNMP /IP=192.168.1.50 /CM=public')
# Read device info (IP settings, MAC address, firmware version, etc.)
Diagnostic Data Sources
7. Error Handling: Error Codes and Recovery
Error Code Categories
PVI error codes are returned as DWORD values. Common error ranges:
| Range | Category |
|---|---|
| 0 | Success |
| 1–99 | General PVI errors |
| 100–999 | PVI Manager / system errors |
| 1000–1999 | Line/driver errors |
| 2000–3999 | Connection errors |
| 4000–4999 | Variable access errors |
| 5000–5999 | Memory/buffer errors |
| 10000–11999 | ANSL-specific errors |
Common Error Codes
| Error Code | Meaning | Typical Cause | Recovery |
|---|---|---|---|
| 0 | Success | — | — |
| 11020 | Unable to establish connection | Target unreachable, wrong IP, ANSL disabled | Verify IP, check ANSL configuration |
| 4806 | Requested data not found | Variable doesn’t exist or not exposed | Check variable name/path |
| 12050 | PVI is locked | License issue — 2-hour trial expired | Restart PVI Manager or obtain license |
| 1056 | Can’t start PVI Manager service | Service already running, timing issue | Use PVI Registry Changer to switch to process mode |
| 5882 | Function block error in C library | B&R library function issue in custom C library | Check library version compatibility |
| various | Object creation failed | Parent not connected yet | Wait for parent PVI_EVENT_LINK callback |
Getting Error Text
// C API
char errMsg[512];
PviGetErrorText(errorCode, errMsg, sizeof(errMsg));
printf("Error %lu: %s\n", errorCode, errMsg);
# Pvi.py
raise PviError(error_code) # automatically formats error text
Recovery Strategies
-
Reconnection logic: PVI does not auto-reconnect. Implement retry logic:
cpu.errorChanged = lambda err: reconnect_cpu() if err == 11020 else None -
PVI Manager restart: If PVI locks up (2-hour trial), you must stop and restart the PVI Manager. This also requires restarting any application using PVI (including Automation Studio).
-
Object re-creation: If a connection drops, delete and re-create the PVI objects in the hierarchy.
PVI Logging for Debugging
Enable detailed logging via PVI Monitor (Options > PVI Diagnostics):
Key components to log:
| Component | Log File | Purpose |
|---|---|---|
PviMan.exe | PviMan.log | General PVI Manager activity |
PviMan.exe/LnAnsl.dll | LnAnsl.log | ANSL protocol tracing |
PviMan.exe/LnAnsl.dll/Ansl | LnAnslA.log | ANSL layer details |
PviMan.exe/LnAnsl.dll/Communication | LnAnslC.log | ANSL communication traces |
PviMan.exe/LnIna2.dll | LnIna2.log | INA protocol (legacy) |
PviMan.exe/Icomm.dll | Icomm.log | Internal communication |
pg.exe/PviCom.dll/Client | PviClient.log | Client-side PVI calls |
Set Break Function to stop logging automatically on a specific error:
Use Break Function: True
Expression: E=4806
Turn-off global logging mode: True
Error Handling Sources
- B&R Community: PVI Exception handling, Logging
- B&R Community: PVI System error 1056
- B&R Community: Error 5882
8. Data Types: B&R Types to PVI/C Types
IEC 61131-3 to C/C++ Type Mapping
| IEC Data Type | B&R Name | C Type | Size (bytes) | Range |
|---|---|---|---|---|
BOOL | BOOL | unsigned char / bool | 1 | 0 or 1 |
SINT | SINT | signed char / int8_t | 1 | -128 to 127 |
USINT | USINT | unsigned char / uint8_t | 1 | 0 to 255 |
BYTE | BYTE | unsigned char / uint8_t | 1 | 0 to 255 |
INT | INT | short / int16_t | 2 | -32768 to 32767 |
UINT | UINT | unsigned short / uint16_t | 2 | 0 to 65535 |
WORD | WORD | unsigned short / uint16_t | 2 | 0 to 65535 |
DINT | DINT | int / int32_t | 4 | -2^31 to 2^31-1 |
UDINT | UDINT | unsigned int / uint32_t | 4 | 0 to 2^32-1 |
DWORD | DWORD | unsigned int / uint32_t | 4 | 0 to 2^32-1 |
LINT | LINT | long long / int64_t | 8 | -2^63 to 2^63-1 |
ULINT | ULINT | unsigned long long / uint64_t | 8 | 0 to 2^64-1 |
REAL | REAL | float | 4 | IEEE 754 single precision |
LREAL | LREAL | double | 8 | IEEE 754 double precision |
TIME | TIME | int32_t (milliseconds) | 4 | T#0S to T#24D_23H_59M_59S_999MS |
DATE | DATE | int32_t (days since 1970-01-01) | 4 | D#1970-01-01 to D#2106-02-06 |
TIME_OF_DAY | TOD | int32_t (ms since midnight) | 4 | TOD#00:00:00 to TOD#23:59:59.999 |
DATE_AND_TIME | DT | struct { date; time } | 8 | Combined date + time |
STRING[n] | STRING | char[n+1] | n+1 | Null-terminated, max 80 chars default |
WSTRING[n] | WSTRING | wchar_t[n+1] | 2*(n+1) | UTF-16LE, max 80 chars default |
Struct and Array Handling
| Type | PVI Behavior |
|---|---|
| Structs | Transferred as raw byte block. Structure layout (alignment, member offsets) must be known to the client. ANSL UIF cannot provide struct format information — alignment must match. |
| Arrays | Contiguous memory. Access individual elements with array index in variable name or write a slice. Pvi.py supports tuple broadcast to set all elements to one value. |
| Nested structs | Treated as flat byte arrays over the wire. Client must know the exact memory layout including any padding/alignment. |
B&R-Specific Alignment Rules
B&R follows standard C struct alignment rules on the target (typically PowerPC or ARM alignment). When reading/writing structs via C or ctypes, ensure:
- Use
#pragma packor equivalent to match B&R alignment - Account for target-specific alignment (PowerPC uses 4-byte alignment for 32-bit types)
- Test with known values before deploying
Python Type Mapping (Pvi.py)
Pvi.py automatically maps between Python and IEC types:
| IEC Type | Python Type |
|---|---|
| BOOL | bool |
| SINT/USINT/BYTE | int |
| INT/UINT/WORD | int |
| DINT/UDINT/DWORD | int |
| REAL | float |
| LREAL | float |
| STRING | bytes |
| WSTRING | str |
| TIME | datetime.timedelta |
| DATE | datetime.date |
| TOD | datetime.time |
| DT | datetime.datetime |
| Array | tuple |
9. Callbacks and Event Handling
PVI Event Types (C API)
| Event | Description |
|---|---|
PVI_EVENT_LINK | Object connected/linked successfully |
PVI_EVENT_UNLINK | Object disconnected |
PVI_EVENT_READ | Asynchronous read completed |
PVI_EVENT_WRITE | Asynchronous write completed |
PVI_EVENT_DATA | Cyclic data update (when RF is set) |
PVI_EVENT_ERROR | Error occurred on this object |
PVI_EVENT_INFO | Information message |
Setting Callbacks in C
// Set completion callback for a variable (receives all events)
err = PviSetCallbackX(
"LNANSL/TCP/CP1584/myTask/myVar",
myVariableCallback, // PFN_PVI_COMPLETION_X
PVI_CB_MODE_ALL, // mode: receive all events
userData
);
// Callback function
void CALLBACK myVariableCallback(
LPSTR lpszIdent,
DWORD dwError,
LPSTR lpszEvent, // event name string
DWORD dwInfo1,
LPSTR lpszInfo2, // additional info
DWORD dwUserData
) {
if (dwError != 0) {
// handle error
return;
}
if (strcmp(lpszEvent, "PVI_EVENT_DATA") == 0) {
// Variable value updated — read new value
float value;
PviReadValue(lpszIdent, &value, sizeof(value), 0);
printf("Variable %s = %f\n", lpszIdent, value);
}
else if (strcmp(lpszEvent, "PVI_EVENT_LINK") == 0) {
printf("Variable %s linked\n", lpszIdent);
}
}
.NET Events
// Variable-level events
variable.ValueChanged += (sender, e) => { /* value changed */ };
variable.ErrorChanged += (sender, e) => { /* error on variable */ };
// CPU-level events
cpu.Connected += (sender, e) => { /* connection established */ };
cpu.ErrorChanged += (sender, e) => { /* error on CPU connection */ };
// Task events
task.StatusChanged += (sender, e) => { /* task run state changed */ };
task.ErrorChanged += (sender, e) => { /* error on task */ };
Python (Pvi.py) Callbacks
# Variable change callback with refresh
temperature = Variable(task, 'gHeating.status.actTemp', RF=200, HY=0.5)
def onTempChange(value):
print(f'Temperature changed to {value}')
temperature.valueChanged = onTempChange
# CPU error callback
cpu.errorChanged = lambda err: print(f'CPU error: {err}') if err else print('Connected')
# Connection lifecycle
def runtimeMonitor(init):
"""Called every PVI cycle — implement state machines here"""
pass
pviConnection.start(runtimeMonitor)
Cyclic Refresh vs Polled Read
| Approach | Latency | CPU Load | Best For |
|---|---|---|---|
| Cyclic refresh (RF parameter) | Deterministic (set interval) | Low (PVI handles it) | Continuous monitoring, HMI displays |
| Polled read (explicit PviRead) | Variable (depends on poll rate) | Higher (client-driven) | On-demand diagnostics, conditional reads |
| Event-based (PVI_EVENT_DATA with HY) | Near-instant on change | Lowest | State monitoring, alarm detection |
Hysteresis (HY): Only triggers callback when value changes by more than the threshold. Reduces network traffic and callback frequency.
# Only notify if temperature changes by > 0.5°C
temperature = Variable(task, 'actTemp', RF=100, HY=0.5)
Callbacks and Event Handling Sources
10. Performance Considerations
Latency Benchmarks
| Communication Path | Typical Latency | Notes |
|---|---|---|
| PVI ANSL (local LAN) | 5–20 ms | TCP-based, 1–2 round trips |
| PVI INA2000 (local LAN) | 10–50 ms | UDP-based, 297-byte frame limit adds overhead |
| PVI ANSL (high latency, >300ms) | Degraded but functional | ANSL handles it; INA does not |
| OPC UA (on-controller) | 10–50 ms | Additional protocol overhead |
| OPC UA (via PVI OPC Server) | 20–100 ms | Extra hop through PVI Manager |
Refresh Rate Limits
| Parameter | Recommended Range | Hard Limit |
|---|---|---|
| Variable refresh (RF) | 100–1000 ms | ~10 ms floor (depends on CPU and variable count) |
| Hysteresis (HY) | Application-dependent | 0 = report every change |
| Cyclic data trending | 10–100 ms | Very fast trending requires dedicated C# companion (see br-automation-pvi MCP) |
CPU Load Impact
The OPC UA server on a mid-range X20 CPU (e.g., X20CP3584) consumes approximately 0.3–0.8 ms per 100 variables per cycle. Budget accordingly when the controller also runs motion and I/O tasks.
For PVI (ANSL) direct access, the load is lower than OPC UA since there’s no serialization overhead.
Sizing Formula for Variable Load
ServerLoad ≈ N_variables × (1000 / Refresh_ms) × Payload_bytes / 1024^2 [MB/s]
Example: 500 variables at 100 ms refresh, 8-byte payloads:
500 × 10 × 8 = 40,000 B/s ≈ 0.038 MB/s (negligible on Ethernet, but CPU-bound)
Best Practices
- Batch reads: Read multiple variables in a single PVI cycle rather than individual synchronous calls.
- Use hysteresis: Set HY to filter out noise and reduce callbacks.
- Limit refresh rate: 200 ms is a good default for HMI; 1000 ms for logging/scada.
- Avoid large struct reads in tight loops: Structs > 64 kB use the async path, adding latency.
- INA2000 limitations: Max 255 bytes per UDP frame. Use ANSL for any data exceeding this.
- High-frequency trending: For 10–100 ms trending, use a C# companion service or specialized data logger rather than direct PVI polling from Python/interpreted languages.
Performance Considerations Sources
11. Python PVI Bindings
Pvi.py (Recommended)
Repository: https://github.com/hilch/Pvi.py
Install: pip install pvipy
License: MIT
Docs: https://hilch.github.io/Pvi.py/
Pvi.py is a comprehensive Python wrapper that handles the complexity of the C API. Features:
- Object-oriented hierarchy matching PVI’s object model
- Automatic type conversion between Python and IEC types
- Callback-based event handling
- Variable browsing, CPU diagnostics, task control
- System dump / profiler data parsing
- SNMP support for network configuration
Requirements: PVI Development Setup must be installed on the machine (provides the underlying DLLs).
Limitations:
- Writing to basic datatypes only (no direct struct write support). Workaround: write to simple wrapper variables in the PLC.
- Not thread-safe (inherent PVI limitation).
Wrapping the C API with ctypes (Manual Approach)
For environments where Pvi.py can’t be used, you can wrap PviCom.dll directly:
import ctypes
from ctypes import wintypes, c_char_p, c_uint, c_void_p, POINTER, WINFUNCTYPE
# Load PVI DLL
pvicom = ctypes.WinDLL("PviCom.dll")
# Define callback type
PFN_PVI_COMPLETION = WINFUNCTYPE(
None, # return void
c_char_p, # lpszIdent
c_uint, # dwError
c_uint, # dwEvent
c_uint, # dwInfo1
c_uint, # dwInfo2
c_uint # dwUserData
)
# PviInitialize
pvicom.PviInitialize.argtypes = [c_uint]
pvicom.PviInitialize.restype = c_uint
pvicom.PviUninitialize.argtypes = []
pvicom.PviUninitialize.restype = c_uint
# PviCreate
pvicom.PviCreate.argtypes = [c_char_p, c_char_p, c_char_p,
PFN_PVI_COMPLETION, c_uint]
pvicom.PviCreate.restype = c_uint
# PviReadValue
pvicom.PviReadValue.argtypes = [c_char_p, c_void_p, c_uint, c_uint]
pvicom.PviReadValue.restype = c_uint
# PviWriteValue
pvicom.PviWriteValue.argtypes = [c_char_p, c_void_p, c_uint, c_uint]
pvicom.PviWriteValue.restype = c_uint
# Initialize
err = pvicom.PviInitialize(0)
print(f"PviInitialize: {err}")
# Callback
@PFN_PVI_COMPLETION
def myCallback(ident, error, event, info1, info2, userdata):
ident_str = ident.decode('ascii') if ident else ""
print(f"Callback: {ident_str} Error={error} Event={event}")
# Create objects (simplified — real code needs to wait for LINK events)
pvicom.PviCreate(b"PVI", b"LNANSL", b"CD=LNANSL", myCallback, 0)
pvicom.PviCreate(b"LNANSL", b"TCP", b"CD=/IF=TcpIp", myCallback, 0)
pvicom.PviCreate(b"LNANSL/TCP", b"PLC", b"CD=/IP=192.168.1.50", myCallback, 0)
# Read a float
value = ctypes.c_float()
err = pvicom.PviReadValue(b"LNANSL/TCP/PLC/myTask/myFloat",
ctypes.byref(value), ctypes.sizeof(value), 5000)
print(f"Value: {value.value}")
pvicom.PviUninitialize()
Other Third-Party Tools
| Tool | Description | Link |
|---|---|---|
| brsnmp | Python CLI for PVI-SNMP commands (list/search PLCs, change IP) | github.com/hilch/brsnmp |
| br-automation-pvi MCP | MCP server bridging B&R PLCs with AI assistants (Claude, etc.) | mcpmarket.com/server/br-automation-pvi |
| ListAllBurPLCs | Lists all B&R PLCs on network | Listed in awesome-B-R |
| brwatch | Service tool: watch, change, log variables, set IP addresses | Listed in awesome-B-R |
| BRLibToHelp | Python tool to parse B&R Automation Studio libraries and generate CHM help files | github.com/br-automation-community/BRLibToHelp |
| VS Code B&R Extension | VS Code extension for building B&R Automation Studio projects | github.com/br-automation-community/vscode-brautomationtools |
| AS6 Migration Tools | Open-source scripts for detecting deprecated libraries/FBs when migrating AS4 to AS6 | github.com/br-automation-community/as6-migration-tools |
| B&R SBOM Generator | Scans AS6 projects and generates CycloneDX 1.5 SBOMs per configuration | github.com/br-automation-community |
| OPCUA_Simple | Demonstration of OPC UA client-server communication with B&R PLC | github.com/rparak/OPCUA_Simple |
Python PVI Bindings Sources
- Pvi.py on GitHub
- awesome-B-R: B&R Frameworks and Libraries
- B&R Community: Python PVI wrapper discussion
12. Using PVI from Linux
Option 1: ANSL UIF Library (Native C/C++)
The AnslUIF library is the official path for Linux PLC communication. It is a C/C++ library included in the PVI Development Setup.
Location in PVI package: C:\BrAutomation\PVI\V4.12\AN\ (documentation, libraries, samples)
Supported platforms: Linux x86, Linux x64
Capabilities:
- ANSL TCP communication with B&R PLCs (AR ≥ V4.08)
- Connect, enumerate modules/tasks/variables
- Read and write variables
- No PVI Manager dependency (direct TCP connection)
Limitations:
- No TLS support — ANSL communication is unencrypted
- No struct format information — only basic data type info. Client must know struct layout and alignment.
ANSLI_CPU_ListVariablesmay not be supported in ANSL expansion I; useANSLI_TASK_ListVariablesinstead.
Key functions:
ANSLI_Initialize()
ANSLI_Deinitialize()
ANSLI_CPU_Connect(identCpu, ...) // connect to CPU
ANSLI_CPU_ListModules(identCpu) // list modules
ANSLI_CPU_ListTasks(identCpu) // list tasks
ANSLI_CPU_GetInfo(identCpu, ...) // CPU info
ANSLI_TASK_Connect(identCpu, taskName, ...)
ANSLI_TASK_ListVariables(identTask) // list task-local variables
ANSLI_TASK_GetInfo(identTask, ...)
ANSLI_PVAR_Read(identVar, buffer, size) // read variable
ANSLI_PVAR_Write(identVar, buffer, size) // write variable
Option 2: Pvi.py via WINE (Not Recommended)
The Pvi.py wrapper depends on the Windows PVI Manager and PviCom.dll. Running through WINE is theoretically possible but not documented and likely fragile due to the Windows service model of PviMan.exe.
Option 3: OPC UA from Linux (Recommended Alternative)
For Linux-based systems, the recommended path is OPC UA (no PVI needed):
- Enable OPC UA on the B&R controller (AR ≥ 4.0)
- Use Python’s
asyncuaoropcualibraries to connect directly - No Windows dependency
import asyncio
import asyncua
async def main():
client = asyncua.Client(url="opc.tcp://192.168.1.50:4840")
await client.connect()
var = await client.get_node("ns=2;s=gHeating.status.actTemp")
value = await var.read_value()
print(f"Temperature: {value}")
await client.disconnect()
asyncio.run(main())
Option 4: PVI Manager on Windows, Client on Linux
Run the PVI Manager on a Windows machine, then connect remotely from a Linux client using the PVI client DLL over the network. This requires the PVI client/server remote feature, which routes PVI client calls through the PVI Manager’s TCP interface.
Using PVI from Linux Sources
- B&R Community: ANSL UIF Library details
- B&R Community: ANSL UIF variable listing
- awesome-B-R: OPC-UA samples for B&R
13. PVI vs OPC-UA: When to Use Which
Decision Matrix
| Criterion | PVI | OPC UA | Raw TCP/UDP |
|---|---|---|---|
| Standardization | B&R proprietary | IEC 62541 (international standard) | None |
| Encryption / Auth | None | Yes (certificates, user tokens, SecurityPolicies) | Application layer |
| Typical Latency | 5–20 ms | 10–50 ms | Application-defined |
| SCADA Integration | Via PVI OPC bridge (extra hop) | Native (any OPC UA client) | Custom driver |
| Platform Support | Windows native, Linux via ANSL UIF | Cross-platform (any OPC UA SDK) | Any |
| Effort to expose new variable | Low (auto-exposed via PVI) | Low (mark as OPC UA accessible in AS) | High (custom code in PLC) |
| Cross-vendor compatibility | B&R only | Any OPC UA server/client | Custom |
| Real-time deterministic | No | No | Optional |
| Overhead | Low | Medium (XML/Binary encoding) | Lowest |
| Max throughput | High (raw binary) | Medium | Highest |
When to Use PVI
- Integrating with B&R HMI panels that use PVI internally
- Using B&R PVI Transfer for firmware deployment
- Need SNMP-based network configuration of B&R devices
- Accessing module-level diagnostics not exposed via OPC UA
- Tools that need to start/stop tasks or perform CPU control actions
- Legacy controllers (AR < 4.0) that don’t support OPC UA
- Lowest latency access where OPC UA overhead is unacceptable
When to Use OPC UA
- New projects — OPC UA is the modern standard
- Linux-based clients — no PVI Manager dependency
- Multi-vendor SCADA integration (Ignition, UA Expert, etc.)
- Security required — encrypted communication with certificate auth
- Eliminating Windows dependency — OPC UA runs directly on the controller
- Non-B&R consumers — any OPC UA client can connect
Both Can Coexist
Both protocols can run simultaneously on the same B&R controller:
- PVI/ANSL on TCP port 11160
- OPC UA on TCP port 4840
PVI vs OPC-UA Sources
14. PVI License Requirements
License Overview
PVI requires a license on non-B&R IPCs (standard PCs). Without a license, PVI runs in trial mode for 2 hours. After this, all PVI-based programs stop working. PVI Manager must be stopped and restarted to reset.
Important: The 2-hour trial is per PVI Manager session, not per application. All PVI clients on the machine are affected.
License Products
| Order Code | Type | Notes |
|---|---|---|
| 0TG1000.01 | Technology Guard (USB Dongle) | Hardware dongle for PVI license |
| 0TG1000.02 | Technology Guard (USB Dongle, newer) | Requires PVI ≥ 4.1.06 / 4.2.02 |
| 1TG0500.01 | Software Container License | No physical dongle; software-based (PVI ≥ 4.9) |
| 1TG0500.02 | Technology Guard License | Used with 0TG1000.01/02 dongle |
| 5S0500.99 | Mass License (Software DLL) | Implemented as BrLcmode.dll in PVI bin folder. One-time purchase for unlimited devices. Requires negotiation with B&R sales. |
License Check
The PVI Manager checks the license via:
- Hardware ADI driver — installed on B&R IPCs, exempts them from license requirement
- USB dongle (Technology Guard)
- Software container (1TG0500.01)
- Software DLL (BrLcmode.dll for mass licensing)
License holder information is displayed in the PviMonitor window.
Exemptions
| Environment | License Required? |
|---|---|
| B&R IPC (with ADI driver) | No |
| B&R Windows CE devices | No (PVI Manager doesn’t check) |
| Automation Studio | No (bundled license) |
| PVI Transfer Tool | No (bundled license) |
| Standard PC (non-B&R) | Yes |
| B&R Panel without ADI | Yes |
Verifying License
# Run license checker (PVI ≥ 4.9)
C:\BrAutomation\PVI\V4.x\Bin\BrSecChk.exe
# Under "Configuration", ensure "Search Technology Guarding License" is enabled
32-bit vs 64-bit
The 32-bit BrLcmode.dll is not compatible with a 64-bit PVI Manager. If migrating from 32-bit to 64-bit, a new 5S0500.99 order must be placed.
Changing the PVI Manager Version
- Open PVI Monitor with administrator rights
- Options > PVI Registry Changer
- Select the
PviMan.exeof the desired PVI version - Click OK
PVI License Sources
15. Example Code for Common Diagnostic Tasks
15.1 Complete Diagnostic Connection (Python)
"""
Connect to a B&R CP1584, read CPU status, list all tasks,
enumerate global variables, and read task-local variables.
"""
from pvi import *
from pprint import pprint
import json
class PlcDiagnostics:
def __init__(self, ip_address, cpu_name='CP1584'):
self.ip = ip_address
self.cpu_name = cpu_name
self.connection = Connection()
self._setup()
self.result = {}
def _setup(self):
self.line = Line(self.connection.root, 'LNANSL', CD='LNANSL')
self.device = Device(self.line, 'TCP', CD='/IF=TcpIp')
self.cpu = Cpu(self.device, self.cpu_name,
CD=f'/IP={self.ip}')
def _on_cpu_ready(self, error):
if error == 11020:
print(f"ERROR: Cannot reach {self.ip}")
self.connection.stop()
return
if error != 0:
raise PviError(error)
print(f"=== Connected to {self.cpu_name} at {self.ip} ===")
self.result['cpu_info'] = {
'pvi_version': self.connection.root.version,
'ar_version': self.cpu.version,
'run_state': self.cpu.status.get('RunState', 'unknown'),
'cpu_time': self.cpu.time.isoformat(),
'cpu_type': self.cpu.cpuInfo.get('CT', 'unknown'),
}
print(f"AR Version: {self.result['cpu_info']['ar_version']}")
print(f"Run State: {self.result['cpu_info']['run_state']}")
self._enumerate_objects()
def _enumerate_objects(self):
all_objects = self.cpu.externalObjects
modules = [o['name'] for o in all_objects if o['type'] == 'Module']
tasks = [o['name'] for o in all_objects if o['type'] == 'Task']
global_vars = [o['name'] for o in all_objects if o['type'] == 'Pvar']
self.result['modules'] = []
for name in modules:
m = Module(self.cpu, name)
self.result['modules'].append({name: dict(m.status)})
m.kill()
self.result['tasks'] = []
for name in tasks:
t = Task(self.cpu, name)
info = {name: {'status': dict(t.status), 'version': t.version}}
task_vars = [o['name'] for o in t.externalObjects if o['type'] == 'Pvar']
info[name]['variables'] = task_vars
self.result['tasks'].append(info)
t.kill()
self.result['global_variables'] = []
for name in global_vars:
v = Variable(self.cpu, name)
info = {'name': name, 'type': str(v.dataType), 'value': str(v.value)}
self.result['global_variables'].append(info)
v.kill()
print(f"\nModules: {[m for m in modules]}")
print(f"Tasks: {[t for t in tasks]}")
print(f"Global Variables ({len(global_vars)}): {global_vars}")
with open('diagnostics.json', 'w') as f:
json.dump(self.result, f, indent=2, default=str)
print("\nDiagnostics saved to diagnostics.json")
self.connection.stop()
def run(self):
self.cpu.errorChanged = self._on_cpu_ready
self.connection.start()
if __name__ == '__main__':
diag = PlcDiagnostics('192.168.1.50')
diag.run()
15.2 Monitor Variable Changes with Logging (Python)
"""
Monitor temperature and pressure variables from a CP1584.
Log changes to CSV with timestamps.
"""
import csv
import time
from datetime import datetime
from pvi import *
PLC_IP = '192.168.1.50'
CSV_FILE = 'plc_log.csv'
variables_to_monitor = [
('gHeating.status.actTemp', 200, 0.5), # (name, refresh_ms, hysteresis)
('gHeating.status.pressure', 500, 0.1),
('gMotor.speed.actual', 100, 1.0),
('gMotor.status.running', 500, 0), # BOOL, report every change
]
connection = Connection()
line = Line(connection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD=f'/IP={PLC_IP}')
task = Task(cpu, 'mainlogic')
csv_file = open(CSV_FILE, 'w', newline='')
writer = csv.writer(csv_file)
writer.writerow(['timestamp', 'variable', 'value'])
def make_callback(var_name):
def callback(value):
ts = datetime.now().isoformat()
writer.writerow([ts, var_name, value])
csv_file.flush()
print(f'{ts} | {var_name} = {value}')
return callback
pvi_vars = []
def on_task_ready(error):
if error == 0:
for name, rf, hy in variables_to_monitor:
v = Variable(task, name, RF=rf, HY=hy)
v.valueChanged = make_callback(name)
pvi_vars.append(v)
task.errorChanged = on_task_ready
try:
connection.start()
finally:
csv_file.close()
15.3 Batch Read Multiple Variables (C/C++)
#include <stdio.h>
#include "PviCom.h"
#define TIMEOUT_MS 3000
#define NUM_VARS 5
typedef struct {
const char *name;
void *buffer;
size_t size;
} PviVar;
volatile int completed = 0;
void CALLBACK readCallback(LPSTR ident, DWORD error, DWORD event,
DWORD info1, DWORD info2, DWORD userData) {
if (event == 4) { // PVI_EVENT_READ
if (error == 0) {
int idx = (int)userData;
printf("Read %s: OK\n", ((PviVar*)vars)[idx].name);
} else {
printf("Read failed: error %lu\n", error);
}
completed++;
}
}
void batchRead() {
PviVar vars[NUM_VARS] = {
{"LNANSL/TCP/CP1584/mainlogic/gHeating.status.actTemp", &(float){0}, sizeof(float)},
{"LNANSL/TCP/CP1584/mainlogic/gHeating.status.pressure", &(float){0}, sizeof(float)},
{"LNANSL/TCP/CP1584/mainlogic/gMotor.speed.actual", &(double){0}, sizeof(double)},
{"LNANSL/TCP/CP1584/mainlogic/gMotor.status.running", &(unsigned char){0}, 1},
{"LNANSL/TCP/CP1584/mainlogic/gMachine.cycleCount", &(int){0}, sizeof(int)},
};
completed = 0;
for (int i = 0; i < NUM_VARS; i++) {
PviRead(vars[i].name, "CV=0", vars[i].buffer, vars[i].size,
TIMEOUT_MS, readCallback, i);
}
while (completed < NUM_VARS) {
// Wait for all reads to complete (application-specific message loop)
}
}
15.4 CPU Control: Warm Start / Cold Start (Python)
"""
Read CPU state and perform control actions.
"""
from pvi import *
connection = Connection()
line = Line(connection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
def on_connected(error):
if error != 0:
raise PviError(error)
state = cpu.status.get('RunState', 'unknown')
print(f"Current run state: {state}")
if state == 'Run':
# Stop all cyclic tasks for controlled shutdown
for task_name in cpu.tasks:
task = Task(cpu, task_name)
task.stop()
print(f"Stopped task: {task_name}")
task.kill()
# To restart, use cpu methods:
# cpu.warmStart() — warm restart (retain data)
# cpu.coldStart() — cold restart (clear retained data)
# These may require specific access parameters
connection.stop()
cpu.errorChanged = on_connected
connection.start()
15.5 Browse and Export Variable List to CSV (Python)
"""
Export complete variable namespace to CSV.
"""
import csv
from pvi import *
connection = Connection()
line = Line(connection.root, 'LNANSL', CD='LNANSL')
device = Device(line, 'TCP', CD='/IF=TcpIp')
cpu = Cpu(device, 'CP1584', CD='/IP=192.168.1.50')
def on_ready(error):
if error != 0:
raise PviError(error)
with open('variable_export.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Scope', 'Name', 'Type', 'Size', 'Value'])
# Global variables
for obj in cpu.externalObjects:
if obj['type'] == 'Pvar':
v = Variable(cpu, obj['name'])
writer.writerow(['Global', obj['name'], str(v.dataType),
v.descriptor.get('VL', '?'), str(v.value)])
v.kill()
# Task-local variables
for obj in cpu.externalObjects:
if obj['type'] == 'Task':
task = Task(cpu, obj['name'])
for var_obj in task.externalObjects:
if var_obj['type'] == 'Pvar':
v = Variable(task, var_obj['name'])
writer.writerow([obj['name'], var_obj['name'],
str(v.dataType),
v.descriptor.get('VL', '?'), str(v.value)])
v.kill()
task.kill()
print("Exported to variable_export.csv")
connection.stop()
cpu.errorChanged = on_ready
connection.start()
Source URLs Summary
| Resource | URL |
|---|---|
| B&R PVI Online Help | https://help.br-automation.com/#/en/4/automationnet/pvi/pvi.htm |
| B&R PVI Development Setup | https://www.br-automation.com/en/downloads/software/automation-netpvi/pvi-development-setup/ |
| B&R Community: PVI How-to Guide | https://community.br-automation.com/t/how-to-guide-all-about-automation-net-pvi/4846 |
| B&R Community: ANSL UIF Variable Listing | https://community.br-automation.com/t/reading-variable-list-using-ansl-uif-library/8180 |
| B&R Community: Python PVI Wrapper | https://community.br-automation.com/t/python-pvi-wrapper-question-regarding-pvi-py/4996 |
| Industrial Monitor Direct: Communication Guide | https://industrialmonitordirect.com/blogs/knowledgebase/br-plc-communication-pvi-opc-and-tcpip-integration |
| Pvi.py (GitHub) | https://github.com/hilch/Pvi.py |
| Pvi.py Documentation | https://hilch.github.io/Pvi.py/ |
| Pvi.py Examples | https://github.com/hilch/Pvi.py/tree/main/examples |
| awesome-B-R (Curated List) | https://github.com/hilch/awesome-B-R |
| brsnmp (GitHub) | https://github.com/hilch/brsnmp |
| br-automation-pvi MCP Server | https://mcpmarket.com/server/br-automation-pvi |
| B&R Community Main | https://community.br-automation.com |
16. Security: PVI Credential Logging Vulnerability (CVE-2026-0936)
16.1 Advisory Details
| Field | Value |
|---|---|
| CVE | CVE-2026-0936 |
| B&R Advisory | SA26P001 |
| CISA Advisory | ICSA-26-125-02 |
| CVSS v3.1 | 5.0 (Medium) |
| CWE | CWE-532 (Insertion of Sensitive Information into Log File) |
| Affected | PVI client < 6.5.0 |
| Fixed In | PVI 6.5.0 (bundled with AS 6.5) |
16.2 What the Vulnerability Does
When PVI client logging is enabled, credentials (passwords, connection parameters, authentication tokens) are written in plaintext to the PVI log file. An authenticated local attacker with read access to the log directory can extract these credentials and use them to access the PLC or other B&R devices.
16.3 Practical Impact for CP1584 Engineers
- Risk is LOW if logging is disabled (default state) — no credentials are written anywhere
- Risk is HIGH if logging was enabled for troubleshooting — plaintext PLC passwords may be sitting in log files on engineering workstations
- The vulnerability is client-side only — it does NOT affect the PLC’s PVI server component or ANSL protocol
16.4 Mitigation Steps
- Check if PVI logging is enabled on all engineering workstations:
- PVI logging is not enabled by default — it must be explicitly activated
- Check for PVI log files (typically in the PVI application directory or a user-specified path)
- Delete any existing PVI log files if they contain connection history:
Get-ChildItem -Path "C:\Program Files*\B&R*\Pvi*" -Filter "*.log" -Recurse Get-ChildItem -Path "$env:LOCALAPPDATA\B&R" -Filter "*.log" -Recurse - Restrict log file access via NTFS permissions if logging must remain enabled:
icacls "C:\path\to\pvi\logs" /inheritance:r /grant:r "%USERNAME%:(OI)(CI)F" - Upgrade PVI to 6.5.0 (bundled with Automation Studio 6.5) — this version removes credential information from log output
- Rotate PLC passwords if PVI logging was previously enabled and log files were accessible to other users
16.5 Interaction with Access Recovery
When performing credential recovery on an undocumented CP1584 (see access-recovery.md), be aware that previous engineers may have left PVI log files on shared workstations that contain the PLC’s credentials. Search engineering PCs for PVI log files as a credential discovery method — but also ensure any discovered log files are secured or deleted to prevent credential leakage.
Cross-References
- opcua.md – OPC-UA as alternative to PVI for PLC access
- cp1584-forensics.md – using PVI for forensic data extraction
- python-diagnostics.md – Python PVI wrapper usage
- network-architecture.md – ANSL network requirements for PVI
- diagnostics-sdm.md – ANSL port 11169 and SDM vs PVI
- ftp-web-interface.md – FTP as alternative to PVI for file access
- plc-to-plc.md – PVI for inter-PLC data exchange
- access-recovery.md – PVI access without credentials
- iiot-retrofit.md – PVI to MQTT/OPC-UA bridges
- execution-model.md – how PVI tasks interact with cyclic tasks
- cybersecurity-hardening.md – PVI security hardening and CVE-2026-0936
- ar-rtos.md – CVE table including SA26P001/PVI credential leak
Document generated from web research. Some details are based on community reports and may vary with PVI/AR version. Always verify against the official B&R documentation for your specific version.
Key Findings
- PVI is the primary programmatic interface to B&R PLCs — it provides read/write access to process variables, alarm events, and logger data from any Windows PC. No AS project required for variable access.
- Connection requires the ANSL protocol on port 11169 — PVI communicates via the Automation Network Service Layer, which must be reachable. The PLC must be in RUN or SERVICE mode (not BOOT).
- Variable enumeration via CmpObject is the most valuable PVI capability for undocumented machines — it reveals all global variables, their data types, and memory addresses without needing the original project.
- PVI supports both synchronous and asynchronous data transfer — synchronous calls block the calling thread; asynchronous use callbacks and are preferred for continuous monitoring.
- Python wrappers for PVI exist — the community has developed Python bindings via Ctypes that expose PVI functionality to Python scripts. See python-diagnostics.md for complete examples.
- PVI cannot change CPU configuration — it can read/write runtime variables but cannot modify IP addresses, task configuration, or module settings. Use AS or FTP for configuration changes.
- The PVI Transfer Tool is separate from PVI runtime — it handles CF card creation and mass deployment, not real-time variable access.