Ürünler
Servis ve Destek
Endüstrıler ve Çözümler
Şırket
versiyon 1.0
·
Son düzenleme tarihi 2025-08-27

Accessing BNI data via Modbus TCP using Python programming language

1. Introduction

The purpose of this technical application note is to explain the step necessary to access the Balluff IO-Link master configuration and its process input and output data using Python programming language via the Modbus / TCP fieldbus protocol.

Outline of available data is found on page 75 of the IO-Link master configuration manual table 6-2 – see below.

A screenshot of a computer programAI-generated content may be incorrect.

2. Setup

This chapter outlines both hardware and software setup

2.1. Hardware

This technical application note covers Balluff's Black and Silver line IO-Link master family with a firmware revision 1.2.0 or grater that supports multi protocol communications including Modbus/TCP

In the samples shown in this technical applicaition note following hardware was used:

  1. IO-Link master
    1. BNI00L3 p/n BNI XG3-508-0B5-R067 
      1. HW rev 1.0.1
      2. FW rev 1.2.0
    2. IO-Link master IP address: 192.168.1.1
  2. IO-Link devices connected to the IO-Link master
    1. Port 1 - Balluff Condition Monitoring Sensor
      1. BCM0003 p/n BCM R16E-004-CI02-01,5-S4 
      2. Process Data Profile - Vibration Velocity - 1
    2. Port 2 - Balluff Smart Light
      1. BNI0085 p/n BNI IOL-802-102-Z037 
      2. 5 Segment smart light with buzzer

 

2.2. Software

IDE (Integrated Development Environment) used:

PyCharm 2025.2
Build #PY-252.23892.439, built on August 4, 2025
Source revision: e7a5644c801f1
Licensed to Trial User
Subscription is active until September 10, 2025.
Runtime version: 21.0.7+6-b1038.58 amd64 (JCEF 122.1.9)
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Toolkit: sun.awt.windows.WToolkit
Windows 10.0
GC: G1 Young Generation, G1 Concurrent GC, G1 Old Generation
Memory: 2048M
Cores: 4
Registry:
  ide.experimental.ui=true

NOTE: Other IDEs can be used.

3. Testing communications with an IO-Link master and reading identification data

In this example code, we are going to open the modbus communications with the IO-Link master using the "client" function, read identification data in holding register starting address 10 and length of 4 words, and convert it to ASCII.

 

Sample code:


import struct
from pyModbusTCP.client import ModbusClient

client = ModbusClient(host="192.168.1.1", port=502, auto_open=True)

regs = client.read_holding_registers(10, 4)
product_order_code = b''.join(struct.pack('<H', r) for r in regs).decode('ascii').strip('\x00')

print(client.host, client.port,client.unit_id, client.open())
print("Raw_register_data:", regs)
print("Product order code:", product_order_code)

Result:

92.168.1.1 502 1 True
Raw_register_data: [20034, 12361, 19504, 51]
Product order code: BNI00L3

4. Reading IO-Link port direction

In this chapter we are reading IO-Link Port 1 direction.

Sample code can be modified to read port direction of other ports.

Reference the IO-Link configuration manual for more information:

 

Sample Code:

from pyModbusTCP.client import ModbusClient

# Create Modbus client
client = ModbusClient(host="192.168.1.1", port=502, auto_open=True)

# Read 1 register starting at 3001
regs = client.read_holding_registers(9101, 1)

if regs: # check if read succeeded
reg_val = regs[0] # get the first (and only) register value

# Extract low byte (bits 0-7)
byte0 = reg_val & 0xFF

# Extract high byte (bits 8-15)
byte1 = (reg_val >> 8) & 0xFF

print(f"Register: 0x{reg_val:04X}")
print(f"Byte 0 (low) : 0x{byte0:02X} ({byte0})")
print(f"Byte 1 (high): 0x{byte1:02X} ({byte1})")
else:
print("❌ Failed to read register")

if byte1 == 0x00:
print("port 1 - Deactivated")
else:
# code if condition1 is False
client.close()

if byte1 == 0x01:
print("port 1 - IOL Manual")
else:
# code if condition1 is False
client.close()

if byte1 == 0x02:
print("port 1 - IOL Auto")
else:
# code if condition1 is False
client.close()

if byte1 == 0x03:
print("port 1 - Digital IN")
else:
# code if condition1 is False
client.close()

if byte1 == 0x04:
print("port 1 - Digital OUT")
else:
client.close()

Result:

Register: 0x0201
Byte 0 (low) : 0x01 (1)
Byte 1 (high): 0x02 (2)
port 1 - IOL Auto

 

5. Setting IO-Link port direction

In this chapter we are setting (writing) IO-Link Port 1 direction.

Sample code can be modified to set (write) port direction of other ports.

Sample Code:

from pyModbusTCP.client import ModbusClient

client = ModbusClient(host="192.168.1.1", port=502, auto_open=True)

# Step 1: Read current value of register 9101 - IOL Port 1 Configuration
regs = client.read_holding_registers(9101, 1)

if regs:
current_val = regs[0]
print(f"Current register 9101: {current_val} (0x{current_val:04X})")

# Step 2: Replace ONLY high byte with decimal 2
low_byte = current_val & 0xFF # Keep low byte
high_byte = 2 # 0=deactivated, 1=IOL Manual, 2=IOL Autostart, 3=Digital Input, 4=Digital Output
new_val = (high_byte << 8) | low_byte # Combine

print(f"New value to write: {new_val} (0x{new_val:04X})")

# Step 3: Write back using Write Multiple Registers
success = client.write_multiple_registers(9101, [new_val])
print("✅ Write successful" if success else "❌ Write failed")

else:
print("❌ Failed to read register 9101")

Result:

Current register 9101: 513 (0x0201)
New value to write: 513 (0x0201)
✅ Write successful

6. Reading IO-Link Process Data

In this chapter we are going to read process data from Port 1 of the IO-Link master. This can be applied to other IO-Link master ports by adjusting the address of the starting register.

For reference:

Port 1 starting register address is 1100 and the data length is 34 words or 64 bytes, which includes 32 bytes of input process data and 32 bytes of output process data.

In the sample code below we are reading 32 bytes of input process data from a BCM0003 Balluff Condition Monitoring Sensor that is broken up into 8 slots of 4 bytes each (2 words), then we perform a byte swap, converting the data into a REAL value, and rounding it to a specific number of decimal spaces.

 

Sampe Code:

import struct


from pyModbusTCP.client import ModbusClient
client = ModbusClient(host='192.168.1.1', port=502, unit_id=255, auto_open=True)
#**************************************************************************************
def read_float32(client, address, unit=255):
regs = client.read_holding_registers(address, 2)
if not regs:
return None
swapped = [regs[1], regs[0]]
raw_bytes = struct.pack('<HH', swapped[0], swapped[1])
return struct.unpack('>f', raw_bytes)[0]
#**************************************************************************************

v_rms_x = read_float32(client, 1100)
v_rms_y = read_float32(client, 1102)
v_rms_z = read_float32(client, 1104)
v_peak_x = read_float32(client, 1106)
v_peak_y = read_float32(client, 1108)
v_peak_z = read_float32(client, 1110)
bcm_temp = read_float32(client, 1112)

v_rms_x = round(v_rms_x, 4)
v_rms_y = round(v_rms_y, 4)
v_rms_z = round(v_rms_z, 4)
v_peak_x = round(v_peak_x, 4)
v_peak_y = round(v_peak_y, 4)
v_peak_z = round(v_peak_z, 4)
bcm_temp = round(bcm_temp, 2)

print("BCM vRMS X:", v_rms_x)
print("BCM vRMS Y:", v_rms_y)
print("BCM vRMS Z:", v_rms_z)
print("BCM vPeak X:", v_peak_x)
print("BCM vPeak Y:", v_peak_y)
print("BCM vPeak Z:", v_peak_z)
print("BCM Temp:", bcm_temp)

Result:
BCM vRMS X: 0.0398
BCM vRMS Y: 0.038
BCM vRMS Z: 0.0582
BCM vPeak X: 0.1225
BCM vPeak Y: 0.1395
BCM vPeak Z: 0.1735
BCM Temp: 39.87

7. Writing IO-Link Process Data

In this chapter we are going to write IO-Link process data to control the function of a 5 segment smart ligth connected to Port 2 of the IO-Link master.

Sample code can be applied to other IO-Link devices.

NOTE: Register 1n17 (i.e. 1217 for Port 2) must to be set to 1 to validate IO-Link Process Data.

Sample Code:

def reverse_bits(word):
"""
Reverse the order of bits in a 16-bit word.
Example: 0b0000000000000001 -> 0b1000000000000000
"""
if not (0 <= word <= 0xFFFF):
raise ValueError("Word must be a 16-bit integer (0-65535)")

reversed_word = 0
for i in range(16):
if word & (1 << i):
reversed_word |= (1 << (15 - i))
return reversed_word
#*********************************************************************
def bits_to_word(bits):
"""
Convert a list of 16 bits (0 or 1) into a 16-bit integer (word).
bits[0] is the MSB, bits[15] is the LSB.
"""
if len(bits) != 16:
raise ValueError("Exactly 16 bits are required")
if any(bit not in (0, 1) for bit in bits):
raise ValueError("Bits must be 0 or 1")

word = 0
for i, bit in enumerate(bits):
word |= (bit << (15 - i)) # shift bit into position
return word
#*********************************************************************
SL_byte0_and_1 = [1, #Seg 1 color bit1
0, #Seg 1 color bit2
0, #Seg 1 color bit3
0, #Seg 1 blink
1, #Seg 2 color bit1
1, #Seg 2 color bit2
0, #Seg 2 color bit3
0, #Seg 2 blink
1, #Seg 3 color bit1
1, #Seg 3 color bit2
1, #Seg 3 color bit3
0, #Seg 3 blink
0, #Seg 4 color bit1
0, #Seg 4 color bit2
1, #Seg 4 color bit3
0, #Seg 4 blink
]
#*****************************************
SL_byte1_and_2 = [1, #Seg 5 color bit1
0, #Seg 5 color bit2
0, #Seg 5 color bit3
0, #Seg 1 blink
0, #Buzzer type bit 0
0, #Buzzer type bit 1
0, #not used for SL
0, #Buzzer state
1, #Segment mode
0, #Level mode
0, #Runlight mode
0, #Flexible mode
0, #Not used for SL
0, #Not sure for SL
0, #Sync start
0, #Sync impulse
]
Register_1 = bits_to_word(SL_byte0_and_1)
Register_1_reversed = reverse_bits(Register_1)

Register_2 = bits_to_word(SL_byte1_and_2)
Register_2_reversed = reverse_bits(Register_2)

from pyModbusTCP.client import ModbusClient

# Create Modbus client
client = ModbusClient(host="192.168.1.1", port=502, auto_open=True)


# NOTE: 1n01 Port n IOL Process Data Input Valid
# 1n01...1n16 16 words (32 bytes) of IOL Input process data
# 1n17 Port n IOL Process Data Output valid
# 1n18...1n33 16 words (32 bytes) of IOL Output process data
IOL_Process_data_output_valid = 1 #0=invalid and 1=valid
# Write 16 (32 bytes) registers starting at address 1200 IOL Port 2
client.write_multiple_registers(1217, [IOL_Process_data_output_valid,Register_1_reversed,Register_2_reversed,5,1])

# Read 16 (32 bytes) registers starting at address 1200 IOL Port 2
registers = client.read_holding_registers(1217, 5)

# Check and display values
if registers:
for i, value in enumerate(registers):
print(f"Register {1217 + i}: {value:016b}")
else:
print("❌ Failed to read registers")
#*********************************************************************************************************
Energy consumption labeling
Energy consumption labeling

EPREL - European Product Database for Energy Labeling

Herhangi bir sorunuz veya öneriniz var mı? Biz sizin emrinizdeyiz.

Teklifler, siparişler, teslimat süreleri gibi ticari konularla ilgili tüm sorularınız için iç satış departmanımız size destek vermekten mutluluk duyacaktır.

Bize doğrudan telefonla ulaşın: +90 216 265 12 00


Balluff Turkey Otomasyon Tic. Ltd. Şti.

PAKPEN PLAZA
Sahrayıcedid Mah. Halk Sokak No:40 Kat:6
Kadıköy/ 34734 İSTANBUL

Lütfen bizimle iletişime geçin:
[email protected]

Ücretsiz örnek ürün

Sepete ücretsiz bir örnek ürün eklemek için tüm normal ürünleri sepetten çıkarmamız gerekecek. Devam etmek istediğine emin misin