Python library for Keyence PLC communication
Project description
PyKeyence Library
키엔스 장비를 파이썬으로 제어하기 위한 라이브러리입니다. 현재는 키엔스 PLC 장치와의 통신을 지원하며, UDP를 통해 ASCII 프로토콜로 데이터를 송수신합니다.
Features
- Simple Client Interface: Easy-to-use client for reading and writing PLC data
- Real-time Monitoring: Monitor PLC values with automatic change detection
- Heartbeat Support: Keep-alive functionality for PLC connections
- Mock Server: Built-in mock PLC server for testing and development
- Thread-safe: Safe for use in multi-threaded applications
- Enhanced Data Conversion Utilities: Improved utilities for string-to-decimal conversion with endian support and PLC-specific formatting
- Flexible Data Handling: Automatic handling of odd-length data with zero-padding and PLC-compatible 5-digit format
- Unicode String Parsing: Built-in support for parsing PLC continuous data into unicode strings
Installation
- pip 을 사용하는 경우.
# Install the package from PyPI
pip install pykeyence
- git 을 사용하는 경우
# Clone the repository
git clone git@github.com:CREFLEINC/pykeyence.git
cd pykeyence
# Install dependencies using uv (recommended)
uv sync
Quick Start
Basic PLC Communication
from pykeyence_plc_link import KeyencePlcClient
# Connect to PLC
client = KeyencePlcClient(host="192.168.0.10", port=8501)
# Read data from PLC
value = client.read("DM100") # Returns list[str]
print(f"DM100 value: {value}")
# Write data to PLC (supports both integer and list of integers)
success = client.write("DM100", 42) # Single integer
if success:
print("Write successful")
# Write multiple values
success = client.write("DM100", [42, 100, 255]) # List of integers
if success:
print("Multiple write successful")
Real-time Monitoring
from pykeyence_plc_link import KeyencePlcClient, PlcMonitor
def on_value_changed(new_value):
print(f"Value changed: {new_value}")
def on_disconnected():
print("PLC disconnected!")
client = KeyencePlcClient(host="192.168.0.10", port=8501)
monitor = PlcMonitor(
client=client,
address="DM100",
polling_interval_ms=100,
on_changed_callback=on_value_changed,
on_disconnected_callback=on_disconnected
)
monitor.start()
Heartbeat Implementation
from pykeyence_plc_link import KeyencePlcClient, Heartbeat
client = KeyencePlcClient(host="192.168.0.10", port=8501)
heartbeat = Heartbeat(
client=client,
address="DM200", # Heartbeat register
interval_ms=1000
)
heartbeat.start()
Data Handling and Conversion
PLC Data Format
The library now supports PLC-specific data formatting where data is automatically converted to 5-digit zero-padded strings (e.g., "00001", "00100"):
from pykeyence_plc_link.client import KeyencePlcClient
client = KeyencePlcClient(host="192.168.0.10", port=8501)
# Data is automatically formatted as 5-digit strings for PLC compatibility
client.write("DM100", 1) # Becomes "00001" internally
client.write("DM100", 100) # Becomes "00100" internally
client.write("DM100", 99999) # Becomes "99999" internally
Enhanced Data Conversion Utilities
Use the improved CharConverter class for manual data conversion:
from pykeyence_plc_link import CharConverter
# Convert string to 16-bit decimal (returns 5-digit formatted string)
decimal_value = CharConverter.string_to_16bit_decimal("AB", "little")
print(f"AB (little endian): {decimal_value}") # Output: "16961"
decimal_value = CharConverter.string_to_16bit_decimal("AB", "big")
print(f"AB (big endian): {decimal_value}") # Output: "16706"
# Convert 16-bit decimal back to string
string_value = CharConverter.decimal_16bit_to_string(16961)
print(f"16961 -> {string_value}") # Output: AB
Unicode String Parsing
New utility function for parsing PLC continuous data into unicode strings:
from pykeyence_plc_link import decode_plc_data_to_unicode
# Example with BCR data
bcr_data = ['12662', '13108', '12333', '12336', '13108', '12098', '13362', '13616', '12337', '12335', '12336', '13366']
result = decode_plc_data_to_unicode(bcr_data, byteorder="little")
print(f"BCR string: {result}") # Output: "V143-00043B/240510/00064"
Endian Support
The library supports both little and big endian byte orders:
from pykeyence_plc_link.data import WriteCommand
# Little endian (default)
cmd_little = WriteCommand(address="DM100", data="AB", byteorder="little")
result_little = cmd_little.encode()
# Result: b"WR DM100 16961\r\n"
# Big endian
cmd_big = WriteCommand(address="DM100", data="AB", byteorder="big")
result_big = cmd_big.encode()
# Result: b"WR DM100 16706\r\n"
Supported Commands
Read Commands
- Single Read:
RD DM100\r\n- Read one register - Multiple Read:
RDS DM100 10\r\n- Read multiple consecutive registers
Write Commands
- Single Write:
WR DM100 00042\r\n- Write single value (5-digit format) - Multiple Write:
WRS DM100 5 00001 00002 00003 00004 00005\r\n- Write multiple values (5-digit format)
Project Structure
pykeyence/
├── src/pykeyence_plc_link/
│ ├── client.py # Main PLC client implementation
│ ├── data.py # Command builders, data structures, CharConverter utilities, and decode_plc_data_to_unicode function
│ ├── protocol.py # UDP protocol implementation
│ ├── monitor.py # Real-time monitoring functionality
│ ├── heartbeat.py # Heartbeat implementation
│ └── mock/ # Mock PLC server for testing
├── examples/
│ ├── plc_client.py # Basic client usage
│ ├── plc_monitor.py # Monitoring example
│ └── plc_heartbeat.py # Heartbeat example
├── tests/
│ └── test_*.py # Test files
└── pyproject.toml # Project configuration
API Reference
KeyencePlcClient
class KeyencePlcClient(PlcClientInterface):
def __init__(self, host: str, port: int)
def read(self, address: str, count: int = 1) -> str
def write(self, address: str, data: Union[int, list[int]]) -> bool
Methods:
read(address, count=1): Read data from PLC registerwrite(address, data): Write data to PLC register (supports both integer and list of integers)
PlcMonitor
class PlcMonitor(threading.Thread):
def __init__(self,
client: PlcClientInterface,
address: str,
count: int = 1,
polling_interval_ms: int = 1000,
on_changed_callback=None,
on_disconnected_callback=None)
def start()
def stop()
Parameters:
client: PLC client instanceaddress: Register address to monitorcount: Number of registers to monitorpolling_interval_ms: Polling interval in millisecondson_changed_callback: Callback function when value changeson_disconnected_callback: Callback function when connection is lost
Heartbeat
class Heartbeat(threading.Thread):
def __init__(self,
client: PlcClientInterface,
address: str,
interval_ms: int = 1000,
on_disconnected_callback: callable = None)
def start()
def stop()
Parameters:
client: PLC client instanceaddress: Register address for heartbeatinterval_ms: Heartbeat interval in millisecondson_disconnected_callback: Callback function when connection is lost
CharConverter
class CharConverter:
@staticmethod
def string_to_16bit_decimal(data: str, byteorder: str = "little") -> str
@staticmethod
def decimal_16bit_to_string(data: int) -> str
Methods:
string_to_16bit_decimal(data, byteorder="little"): Convert string to 16-bit decimal (returns 5-digit formatted string)decimal_16bit_to_string(data): Convert 16-bit decimal to string
decode_plc_data_to_unicode
def decode_plc_data_to_unicode(data_list: list[str], byteorder: str = "little") -> str
Parameters:
data_list: List of 5-digit number strings from PLC (e.g., ["00086", "00049"])byteorder: Byte order ("little" or "big"), defaults to "little"
Returns:
- Parsed unicode string from PLC data
WriteCommand
@dataclass
class WriteCommand:
address: str
data: str
cr: str = "\r\n"
byteorder: str = "little"
def encode(self) -> bytes
Features:
- Automatic zero-padding for odd-length data
- Support for both little and big endian
- Single and multiple data write support
- PLC-compatible 5-digit data formatting
ReadCommand
@dataclass
class ReadCommand:
address: str
count: int = 1
def encode(self) -> bytes
ReceivedData
@dataclass
class ReceivedData:
data: bytes
def decode(self) -> str
Complete Examples
Basic Client Usage
from pykeyence_plc_link.client import KeyencePlcClient
from pykeyence_plc_link.mock.mock_keyence_plc_server import MockKeyencePlcServer
import time
# Start mock server for testing
mock_server = MockKeyencePlcServer(ip="127.0.0.1", port=8501)
mock_server.start()
time.sleep(1)
# Create client
client = KeyencePlcClient(host="127.0.0.1", port=8501)
# Write and read data (now supports integers directly)
value = 42
client.write("DM100", value)
print(f"Wrote value '{value}' to DM100.")
res = client.read("DM100")
print(f"Read value from DM100: {res}")
# Write multiple values
values = [100, 200, 300]
client.write("DM100", values)
print(f"Wrote multiple values to DM100: {values}")
# Cleanup
mock_server.stop()
Real-time Monitoring
import time
from pykeyence_plc_link.mock.mock_keyence_plc_server import MockKeyencePlcServer
from pykeyence_plc_link.client import KeyencePlcClient
from pykeyence_plc_link.monitor import PlcMonitor
# Start mock server
mock_server = MockKeyencePlcServer(ip="127.0.0.1", port=8501)
mock_server.start()
time.sleep(1)
def on_status_changed(new_value: str):
print(f"PLC 상태 변경 감지: {new_value}")
def on_disconnected():
print("PLC와의 연결이 끊어졌습니다.")
# Create client and monitor
client = KeyencePlcClient(host="127.0.0.1", port=8501)
monitor = PlcMonitor(
client=client,
address="DM100",
count=1,
polling_interval_ms=10,
on_changed_callback=on_status_changed,
on_disconnected_callback=on_disconnected
)
monitor.start()
time.sleep(1)
# Simulate value changes
mock_server.memory["DM100"] = "00000"
time.sleep(1)
mock_server.memory["DM100"] = "00042"
time.sleep(1)
# Cleanup
mock_server.stop()
monitor.stop()
Heartbeat Implementation
import time
from pykeyence_plc_link.mock.mock_keyence_plc_server import MockKeyencePlcServer
from pykeyence_plc_link.client import KeyencePlcClient
from pykeyence_plc_link.heartbeat import Heartbeat
# Start mock server
mock_server = MockKeyencePlcServer(ip="127.0.0.1", port=8501)
mock_server.start()
time.sleep(1)
# Create client and heartbeat
client = KeyencePlcClient(host="127.0.0.1", port=8501)
heartbeat = Heartbeat(
client=client,
address="DM100",
interval_ms=1000
)
heartbeat.start()
# Monitor heartbeat for 5 seconds
start_time = time.time()
while time.time() - start_time < 5:
time.sleep(0.1)
print(f"Heartbeat value: {mock_server.memory['DM100']}")
# Cleanup
heartbeat.stop()
mock_server.stop()
Advanced Data Handling with New Features
from pykeyence_plc_link.data import WriteCommand, ReadCommand, CharConverter, decode_plc_data_to_unicode
# Create commands manually
write_cmd = WriteCommand(address="DM100", data="ABC", byteorder="little")
read_cmd = ReadCommand(address="DM100", count=5)
# Encode commands
write_bytes = write_cmd.encode()
read_bytes = read_cmd.encode()
print(f"Write command: {write_bytes}")
print(f"Read command: {read_bytes}")
# Use CharConverter for custom conversions
decimal_value = CharConverter.string_to_16bit_decimal("XY", "big")
string_value = CharConverter.decimal_16bit_to_string(decimal_value)
print(f"XY -> {decimal_value} -> {string_value}")
# Example: Process BCR data step by step
bcr_chars = ["V1", "43", "-0", "00", "43", "B/", "24", "05", "10", "/0", "00", "64"]
encoded_data = [CharConverter.string_to_16bit_decimal(char) for char in bcr_chars]
print(f"Encoded BCR data: {encoded_data}")
# Parse PLC continuous data into unicode string
plc_data = ['12662', '13108', '12333', '12336', '13108', '12098', '13362', '13616', '12337', '12335', '12336', '13366']
parsed_string = decode_plc_data_to_unicode(plc_data, byteorder="little")
print(f"Parsed BCR data: {parsed_string}")
# Parse back to string
decoded_string = decode_plc_data_to_unicode(encoded_data, byteorder="little")
print(f"Decoded BCR string: {decoded_string}")
Development
Running Tests
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=src
# Run specific test file
uv run pytest tests/test_pykeyence_plc_link_data.py
# Run with verbose output
uv run pytest -v
Using Mock Server
For development and testing, use the built-in mock PLC server:
from pykeyence_plc_link.mock.mock_keyence_plc_server import MockKeyencePlcServer
# Start mock server
mock_server = MockKeyencePlcServer(ip="127.0.0.1", port=8501)
mock_server.start()
# Your client code here...
# Stop mock server
mock_server.stop()
Code Style
This project uses standard Python development tools:
# Format code
black src/ tests/
# Lint code
flake8 src/ tests/
# Type checking
mypy src/
Configuration
Default Settings
- Port: 8501 (Keyence PLC default UDP port)
- Timeout: 1 second for UDP operations
- Encoding: ASCII (as required by Keyence protocol)
- Protocol: UDP with
\r\ncommand termination - Default Byte Order: Little endian
- Auto-padding: Odd-length data automatically padded with '0'
- PLC Data Format: 5-digit zero-padded strings (e.g., "00001", "00100")
PLC Address Format
- DM Registers:
DM100,DM200, etc. - Other registers: Follow Keyence naming convention
Data Conversion Settings
- String Length: Must be 2 characters or less for direct conversion
- Auto-padding: Single characters and odd-length strings automatically padded
- Endian Support: Both little and big endian byte orders supported
- PLC Format: Data automatically formatted as 5-digit strings for PLC compatibility
Protocol Details
This library implements the Keyence ASCII protocol over UDP:
- Commands end with
\r\n(CRLF) - Responses are ASCII encoded
- Success responses start with data or "OK"
- Error responses start with "E" followed by error code
- Data is automatically converted between ASCII strings and 16-bit decimal values
- PLC data is formatted as 5-digit zero-padded strings for compatibility
Data Processing Flow
- Input Validation: Check data length and format
- Auto-padding: Add '0' to odd-length data
- Conversion: Convert 2-character chunks to 16-bit decimal
- PLC Formatting: Format data as 5-digit strings for PLC compatibility
- Command Generation: Create appropriate PLC command
- Transmission: Send command via UDP
- Response Processing: Handle PLC response and convert back to string
Error Handling
- Invalid String Length: Raises
ValueErrorfor strings longer than 2 characters in direct conversion - Invalid Byte Order: Raises
ValueErrorfor unsupported endian values - Network Errors: Handled gracefully with timeout and retry mechanisms
- PLC Errors: Parsed and reported through client methods
- Data Format Errors: Validates 5-digit format for PLC data
Requirements
- Python 3.8+
- No external dependencies for core functionality
- pytest, pytest-cov, pytest-mock for development
Release Notes
Version 0.1.11 (Latest)
- Mock Server Default Value Fix: Fixed default return value in MockKeyencePlcServer from "12336" to "00000" for better testing consistency
- Improved Testing Environment: Enhanced mock server behavior to return standard zero values for uninitialized registers
Version 0.1.9
- Major Data Handling Improvements: Enhanced PLC data compatibility with 5-digit formatting
- Class Renaming:
TwoCharConverter→CharConverterfor better clarity - New Utility Function: Added
decode_plc_data_to_unicodefor parsing PLC continuous data - Enhanced Client Interface: Client now supports both integer and list of integers for write operations
- Improved Data Processing: Better separation of string parsing and data conversion logic
- PLC Format Support: Automatic conversion to PLC-compatible 5-digit string format
- Better Error Handling: Enhanced validation for data format and conversion operations
Version 0.1.5
- Added DataConverter utility class for data conversion
- Improved data handling with automatic zero-padding
- Enhanced endian support for both little and big endian
- Better separation of concerns between data classes and conversion logic
- Comprehensive test coverage for all new features
Version 0.1.0
- Initial release
- Basic PLC communication
- Monitoring and heartbeat functionality
- Mock server for testing
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pykeyence-0.1.12.tar.gz.
File metadata
- Download URL: pykeyence-0.1.12.tar.gz
- Upload date:
- Size: 13.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.10.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf2e5ed37376b482a5dfdc266ba23c145adbd67c9b022f911773f9305af4994c
|
|
| MD5 |
e458c2e48ff1dc896b717679dbf05ab2
|
|
| BLAKE2b-256 |
f275cbd22e3759d5896c3ed3fbf0fb28cef3fcf760557549b046a3e054518b18
|
File details
Details for the file pykeyence-0.1.12-py3-none-any.whl.
File metadata
- Download URL: pykeyence-0.1.12-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.10.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9943953fa6c975539ceaa22bea537315a38e25ec4dec7946167f8c2d95e63891
|
|
| MD5 |
0f811b29456700744b638f59296c8a06
|
|
| BLAKE2b-256 |
18d36a0936d3103853cffea0343301aad4fbd30c97dba90637c0547b7e937034
|