Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. What PVI Is and How It Works
  2. The PVI API: C/C++, .NET, COM
  3. Connecting to a CP1584 via PVI
  4. Reading and Writing Variables
  5. Browsing the Variable Namespace Without Project Files
  6. Pulling Diagnostic Data from a Running CP1584
  7. Error Handling: Error Codes and Recovery
  8. Data Types: B&R Types to PVI/C Types
  9. Callbacks and Event Handling
  10. Performance Considerations
  11. Python PVI Bindings
  12. Using PVI from Linux
  13. PVI vs OPC-UA: When to Use Which
  14. PVI License Requirements
  15. 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

ComponentLocationFunction
PviMan.exe (PVI Manager)Windows hostCentral service: manages line configurations, routes traffic to controllers. Runs as Windows service or process.
PVIServerAutomation Runtime (controller)Exposes process variables from controller tasks to the network.
PVI Client LibrariesApplication (DLLs)C/C++ DLL (PviCom.dll), .NET assemblies (BR.AN.PviServices), COM objects.
PVI OPC ServerWindows hostTranslates OPC DA / UA calls into PVI calls.
PVI MonitorWindows hostGUI tool for monitoring connections, configuring logging, snapshots.

PVI Lines (Protocols)

PVI routes communication through “lines,” each a different protocol driver:

LineDriver CodeProtocolUse Case
ANSLLNANSLAutomation Net Standard Language (TCP)Primary modern protocol for variable access to AR ≥ V4.08 controllers.
INA2000LNINAINA2000 (UDP, legacy)Legacy protocol for older controllers. 255-byte payload per frame limit. REMOVED in PVI 6.x — see critical note below.
SNMPLNSNMPSNMP v1Network configuration (IP, subnet, gateway) of B&R PLCs.
MODBUSLNMODBUSModbus TCPCommunication with third-party Modbus devices.
NET2000LNNETNET2000B&R’s legacy proprietary fieldbus.
ADILNADIAutomation Device InterfaceDirect local access on B&R IPCs (no network).
DCANLNDCANDevice CANCAN 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 LNINA will 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 LNINA with LNANSL in 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 IP

See 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


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.h
  • C:\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.PviServices is NOT thread-safe. All modifications to PVI object members must be done on the main UI thread. Use Invoke/BeginInvoke if 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 VersionAR CompatibilityAPI Changes
PVI 3.xAR 3.xINA2000 primary, ANSL emerging
PVI 4.xAR 4.x+ANSL primary, INA2000 legacy. Extended callbacks.
PVI 5.xAR 5.xImproved .NET API, 64-bit support
PVI 6.xAR 6.xANSL 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

ParameterValueNotes
ProtocolANSL (recommended)Automation Net Standard Language
Default TCP Port11160Default 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 Port4840If OPC UA is enabled (AR ≥ 4.0)
INA2000 Port11160 (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

SymptomCauseFix
“Unable to establish connection” (error 11020)Wrong IP, controller not reachable, ANSL disabledVerify IP with ping; check ANSL enabled in project
PVI line shows “Station not responding”Subnet mismatch, firewall blocking port 11160Check subnet; open TCP 11160
Timeout on high-latency links (>300ms)INA2000’s request-response protocol has high latency sensitivitySwitch to ANSL which is TCP-based and more latency-tolerant
Error 4806Requested data not foundVariable 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 CodeMeaning
CV=0Raw 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_ListVariables may not be supported in ANSL expansion I. Use ANSLI_TASK_ListVariables with 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:

RangeCategory
0Success
1–99General PVI errors
100–999PVI Manager / system errors
1000–1999Line/driver errors
2000–3999Connection errors
4000–4999Variable access errors
5000–5999Memory/buffer errors
10000–11999ANSL-specific errors

Common Error Codes

Error CodeMeaningTypical CauseRecovery
0Success
11020Unable to establish connectionTarget unreachable, wrong IP, ANSL disabledVerify IP, check ANSL configuration
4806Requested data not foundVariable doesn’t exist or not exposedCheck variable name/path
12050PVI is lockedLicense issue — 2-hour trial expiredRestart PVI Manager or obtain license
1056Can’t start PVI Manager serviceService already running, timing issueUse PVI Registry Changer to switch to process mode
5882Function block error in C libraryB&R library function issue in custom C libraryCheck library version compatibility
variousObject creation failedParent not connected yetWait 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

  1. Reconnection logic: PVI does not auto-reconnect. Implement retry logic:

    cpu.errorChanged = lambda err: reconnect_cpu() if err == 11020 else None
    
  2. 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).

  3. 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:

ComponentLog FilePurpose
PviMan.exePviMan.logGeneral PVI Manager activity
PviMan.exe/LnAnsl.dllLnAnsl.logANSL protocol tracing
PviMan.exe/LnAnsl.dll/AnslLnAnslA.logANSL layer details
PviMan.exe/LnAnsl.dll/CommunicationLnAnslC.logANSL communication traces
PviMan.exe/LnIna2.dllLnIna2.logINA protocol (legacy)
PviMan.exe/Icomm.dllIcomm.logInternal communication
pg.exe/PviCom.dll/ClientPviClient.logClient-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


8. Data Types: B&R Types to PVI/C Types

IEC 61131-3 to C/C++ Type Mapping

IEC Data TypeB&R NameC TypeSize (bytes)Range
BOOLBOOLunsigned char / bool10 or 1
SINTSINTsigned char / int8_t1-128 to 127
USINTUSINTunsigned char / uint8_t10 to 255
BYTEBYTEunsigned char / uint8_t10 to 255
INTINTshort / int16_t2-32768 to 32767
UINTUINTunsigned short / uint16_t20 to 65535
WORDWORDunsigned short / uint16_t20 to 65535
DINTDINTint / int32_t4-2^31 to 2^31-1
UDINTUDINTunsigned int / uint32_t40 to 2^32-1
DWORDDWORDunsigned int / uint32_t40 to 2^32-1
LINTLINTlong long / int64_t8-2^63 to 2^63-1
ULINTULINTunsigned long long / uint64_t80 to 2^64-1
REALREALfloat4IEEE 754 single precision
LREALLREALdouble8IEEE 754 double precision
TIMETIMEint32_t (milliseconds)4T#0S to T#24D_23H_59M_59S_999MS
DATEDATEint32_t (days since 1970-01-01)4D#1970-01-01 to D#2106-02-06
TIME_OF_DAYTODint32_t (ms since midnight)4TOD#00:00:00 to TOD#23:59:59.999
DATE_AND_TIMEDTstruct { date; time }8Combined date + time
STRING[n]STRINGchar[n+1]n+1Null-terminated, max 80 chars default
WSTRING[n]WSTRINGwchar_t[n+1]2*(n+1)UTF-16LE, max 80 chars default

Struct and Array Handling

TypePVI Behavior
StructsTransferred 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.
ArraysContiguous 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 structsTreated 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:

  1. Use #pragma pack or equivalent to match B&R alignment
  2. Account for target-specific alignment (PowerPC uses 4-byte alignment for 32-bit types)
  3. Test with known values before deploying

Python Type Mapping (Pvi.py)

Pvi.py automatically maps between Python and IEC types:

IEC TypePython Type
BOOLbool
SINT/USINT/BYTEint
INT/UINT/WORDint
DINT/UDINT/DWORDint
REALfloat
LREALfloat
STRINGbytes
WSTRINGstr
TIMEdatetime.timedelta
DATEdatetime.date
TODdatetime.time
DTdatetime.datetime
Arraytuple

9. Callbacks and Event Handling

PVI Event Types (C API)

EventDescription
PVI_EVENT_LINKObject connected/linked successfully
PVI_EVENT_UNLINKObject disconnected
PVI_EVENT_READAsynchronous read completed
PVI_EVENT_WRITEAsynchronous write completed
PVI_EVENT_DATACyclic data update (when RF is set)
PVI_EVENT_ERRORError occurred on this object
PVI_EVENT_INFOInformation 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

ApproachLatencyCPU LoadBest 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 changeLowestState 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 PathTypical LatencyNotes
PVI ANSL (local LAN)5–20 msTCP-based, 1–2 round trips
PVI INA2000 (local LAN)10–50 msUDP-based, 297-byte frame limit adds overhead
PVI ANSL (high latency, >300ms)Degraded but functionalANSL handles it; INA does not
OPC UA (on-controller)10–50 msAdditional protocol overhead
OPC UA (via PVI OPC Server)20–100 msExtra hop through PVI Manager

Refresh Rate Limits

ParameterRecommended RangeHard Limit
Variable refresh (RF)100–1000 ms~10 ms floor (depends on CPU and variable count)
Hysteresis (HY)Application-dependent0 = report every change
Cyclic data trending10–100 msVery 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

  1. Batch reads: Read multiple variables in a single PVI cycle rather than individual synchronous calls.
  2. Use hysteresis: Set HY to filter out noise and reduce callbacks.
  3. Limit refresh rate: 200 ms is a good default for HMI; 1000 ms for logging/scada.
  4. Avoid large struct reads in tight loops: Structs > 64 kB use the async path, adding latency.
  5. INA2000 limitations: Max 255 bytes per UDP frame. Use ANSL for any data exceeding this.
  6. 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

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

ToolDescriptionLink
brsnmpPython CLI for PVI-SNMP commands (list/search PLCs, change IP)github.com/hilch/brsnmp
br-automation-pvi MCPMCP server bridging B&R PLCs with AI assistants (Claude, etc.)mcpmarket.com/server/br-automation-pvi
ListAllBurPLCsLists all B&R PLCs on networkListed in awesome-B-R
brwatchService tool: watch, change, log variables, set IP addressesListed in awesome-B-R
BRLibToHelpPython tool to parse B&R Automation Studio libraries and generate CHM help filesgithub.com/br-automation-community/BRLibToHelp
VS Code B&R ExtensionVS Code extension for building B&R Automation Studio projectsgithub.com/br-automation-community/vscode-brautomationtools
AS6 Migration ToolsOpen-source scripts for detecting deprecated libraries/FBs when migrating AS4 to AS6github.com/br-automation-community/as6-migration-tools
B&R SBOM GeneratorScans AS6 projects and generates CycloneDX 1.5 SBOMs per configurationgithub.com/br-automation-community
OPCUA_SimpleDemonstration of OPC UA client-server communication with B&R PLCgithub.com/rparak/OPCUA_Simple

Python PVI Bindings Sources


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_ListVariables may not be supported in ANSL expansion I; use ANSLI_TASK_ListVariables instead.

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

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.

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 asyncua or opcua libraries 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


13. PVI vs OPC-UA: When to Use Which

Decision Matrix

CriterionPVIOPC UARaw TCP/UDP
StandardizationB&R proprietaryIEC 62541 (international standard)None
Encryption / AuthNoneYes (certificates, user tokens, SecurityPolicies)Application layer
Typical Latency5–20 ms10–50 msApplication-defined
SCADA IntegrationVia PVI OPC bridge (extra hop)Native (any OPC UA client)Custom driver
Platform SupportWindows native, Linux via ANSL UIFCross-platform (any OPC UA SDK)Any
Effort to expose new variableLow (auto-exposed via PVI)Low (mark as OPC UA accessible in AS)High (custom code in PLC)
Cross-vendor compatibilityB&R onlyAny OPC UA server/clientCustom
Real-time deterministicNoNoOptional
OverheadLowMedium (XML/Binary encoding)Lowest
Max throughputHigh (raw binary)MediumHighest

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 CodeTypeNotes
0TG1000.01Technology Guard (USB Dongle)Hardware dongle for PVI license
0TG1000.02Technology Guard (USB Dongle, newer)Requires PVI ≥ 4.1.06 / 4.2.02
1TG0500.01Software Container LicenseNo physical dongle; software-based (PVI ≥ 4.9)
1TG0500.02Technology Guard LicenseUsed with 0TG1000.01/02 dongle
5S0500.99Mass 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:

  1. Hardware ADI driver — installed on B&R IPCs, exempts them from license requirement
  2. USB dongle (Technology Guard)
  3. Software container (1TG0500.01)
  4. Software DLL (BrLcmode.dll for mass licensing)

License holder information is displayed in the PviMonitor window.

Exemptions

EnvironmentLicense Required?
B&R IPC (with ADI driver)No
B&R Windows CE devicesNo (PVI Manager doesn’t check)
Automation StudioNo (bundled license)
PVI Transfer ToolNo (bundled license)
Standard PC (non-B&R)Yes
B&R Panel without ADIYes

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

  1. Open PVI Monitor with administrator rights
  2. Options > PVI Registry Changer
  3. Select the PviMan.exe of the desired PVI version
  4. 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

ResourceURL
B&R PVI Online Helphttps://help.br-automation.com/#/en/4/automationnet/pvi/pvi.htm
B&R PVI Development Setuphttps://www.br-automation.com/en/downloads/software/automation-netpvi/pvi-development-setup/
B&R Community: PVI How-to Guidehttps://community.br-automation.com/t/how-to-guide-all-about-automation-net-pvi/4846
B&R Community: ANSL UIF Variable Listinghttps://community.br-automation.com/t/reading-variable-list-using-ansl-uif-library/8180
B&R Community: Python PVI Wrapperhttps://community.br-automation.com/t/python-pvi-wrapper-question-regarding-pvi-py/4996
Industrial Monitor Direct: Communication Guidehttps://industrialmonitordirect.com/blogs/knowledgebase/br-plc-communication-pvi-opc-and-tcpip-integration
Pvi.py (GitHub)https://github.com/hilch/Pvi.py
Pvi.py Documentationhttps://hilch.github.io/Pvi.py/
Pvi.py Exampleshttps://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 Serverhttps://mcpmarket.com/server/br-automation-pvi
B&R Community Mainhttps://community.br-automation.com

16. Security: PVI Credential Logging Vulnerability (CVE-2026-0936)

16.1 Advisory Details

FieldValue
CVECVE-2026-0936
B&R AdvisorySA26P001
CISA AdvisoryICSA-26-125-02
CVSS v3.15.0 (Medium)
CWECWE-532 (Insertion of Sensitive Information into Log File)
AffectedPVI client < 6.5.0
Fixed InPVI 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

  1. 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)
  2. 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
    
  3. 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"
    
  4. Upgrade PVI to 6.5.0 (bundled with Automation Studio 6.5) — this version removes credential information from log output
  5. 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


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

  1. 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.
  2. 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).
  3. 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.
  4. PVI supports both synchronous and asynchronous data transfer — synchronous calls block the calling thread; asynchronous use callbacks and are preferred for continuous monitoring.
  5. 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.
  6. 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.
  7. The PVI Transfer Tool is separate from PVI runtime — it handles CF card creation and mass deployment, not real-time variable access.