EtherNet/IP Emulator (Python)
ethernetip-emulator is a high level binary communications framework built around cpppo (pronounced ‘c’3*’p’‘o’ in Python) that exposes a set of tags and allows for the programatic execution .
Architecture
.
├───example
│ ├───basic_datatypes
│ │ └─── *.py
│ ├───mfg_line
│ │ └─── *.py
│ ├───orange_pi
│ │ └─── *.py
│ └───raspberry_pi
│ └─── *.py
├───src
│ └───ethernetip_emulator
│ ├───client
│ ├───server
│ │ ├───datatypes
│ │ │ ├───templates
│ │ │ │ └─── *.py
│ │ │ └─── *.py
│ │ └─── *.py
│ └─── *.py
└───tests
└───server
├───datatypes
│ ├───mock
│ │ └─── test_*.py
│ ├───templates
│ │ └─── test_*.py
│ └─── test_*.py
└─── test_*.py
Requirements
- Python 3.10+
cpppo(for Ethernet/IP attribute/device behavior)black| Development Dependency (for code formatting)coverage| Developer Dependency (for coverage reporting)RPi.GPIOorOPi.GPIO| Optional Dependency (for gpio i/o)web.py| Optional Dependency for WebApi
Getting Started
pip install ethernetip-emulator
A minimal project needs three files:
| File | Purpose |
|---|---|
tags.py |
Declare tags with @tag_registry.register |
actions.py |
React to tag changes with @actions.<type>.on_change |
__main__.py |
Start the server with device_controller |
# __main__.py
from ethernetip_emulator.server.device import AttributeDevice, apidict, device_controller
from ethernetip_emulator.server.tag_specs import tag_registry
import tags # registers all tags (can also just be imported in __init__.py)
import actions # registers all listeners (can also just be imported in __init__.py)
if __name__ == "__main__":
server_control = apidict(timeout=1.0)
AttributeDevice.set_server_control(server_control)
# To start the web server with web.py:
# AttributeDevice.build_web(host="0.0.0.0", port=8080)
# AttributeDevice.start_web()
with AttributeDevice._actions.bind(AttributeDevice):
device_controller(
argv=tag_registry.build_argv(base_args=["--print"]),
attribute_class=AttributeDevice,
server={"control": server_control},
)
Guides
Core
| Guide | Description |
|---|---|
| Tag Registry | Define tags with @tag_registry.register, use namespaced prefixes, and organise tags across files |
| Actions | Read and write tags at runtime, register on_change listeners, and understand key and defer |
| Device | Start and stop the server, understand how tag writes are intercepted, and apply startup defaults |
Datatypes
| Guide | Description |
|---|---|
| Datatypes Overview | All built-in types at a glance, and a step-by-step guide to creating custom datatypes |
| Basic Datatypes | Scalar types: BOOL, integers, floats, and strings — shared get_val / set_val / on_change API |
| Bool Array | BOOLARRAY — bit-level access, list operations, bulk mutations |
| Numeric Arrays | SINTARRAY · USINTARRAY · INTARRAY · UINTARRAY · DINTARRAY · UDINTARRAY · LINTARRAY · ULINTARRAY |
| Real Arrays | REALARRAY · LREALARRAY — same API as numeric arrays with floating-point zero tolerance (1e-9) |
| String Array | SSTRINGARRAY — list operations using "" as the empty slot sentinel |
| GPIO | GPIO — maps EtherNet/IP tags to physical hardware pins on a Raspberry Pi or Orange Pi |
| Counter | COUNTER — composite datatype that models a up/down counter with control tags |
| Timer | TIMER — composite datatype that models a timer with control tags |
Typical Workflow
┌─────────────────────────────────────────────────────┐
│ Your Project │
│ │
│ tags.py ──────► tag_registry │
│ │ │
│ ▼ │
│ datatypes.py ─► actions.datatype │
│ │ │
│ ▼ │
│ __main__.py ──► device_controller │
│ │ │
│ ▼ │
│ AttributeDevice │
│ (intercepts PLC writes) │
│ │ │
│ ▼ │
│ actions.py ──► on_change listeners │
│ │ │
│ ▼ │
│ actions.<type>.set_val / get_val │
└─────────────────────────────────────────────────────┘
- Tags are declared once at import time via
@tag_registry.register. - Custom Datatypes can be declared once at import time via
@actions.datatype. Although this is completely optional if you like the built-in datatypes device_controllerbuilds the tag list and starts the EtherNet/IP server.AttributeDeviceintercepts every PLC write and fires the matchingon_changelisteners.- Listeners use
actions.<type>helpers to read state, write back to tags, or trigger application logic.
Project Layout Convention
your_project/
├── __main__.py # server entrypoint
├── tags.py # tag definitions
├── actions.py # on_change handlers
└── datatypes/ # optional: custom datatype classes
├── __init__.py
└── mytype.py
For larger projects, split tags and actions into sub-modules and import them from a top-level __init__.py so all registrations are triggered before the server starts.
Protecting Attributes
AttributeDevice can make one or more attributes read-only. Protection is applied by attribute name and prevents writes handled by AttributeDevice.__setitem__; blocked writes are ignored and do not trigger on_set actions.
AttributeDevice.protect(*tag_names)marks the specified attributes as protected.AttributeDevice.unprotect(*tag_names)removes protection from the specified attributes.AttributeDevice.is_protected(tag_name)returns True when the attribute is currently protected; otherwise, it returns False.
from ethernetip_emulator.server.device import AttributeDevice
# Prevent writes to one or more attributes.
AttributeDevice.protect("Motor.Enabled", "Motor.Speed")
if AttributeDevice.is_protected("Motor.Speed"):
print("Motor.Speed is read-only")
# Allow writes again when the protected state is no longer needed.
AttributeDevice.unprotect("Motor.Speed", "Motor.Enabled")
Protection is maintained on the AttributeDevice class, so use the class methods to manage the protected attribute names. It remains active until the attributes are explicitly passed to unprotect.
Supporting the Development of EtherNet/IP Emulator
EtherNet/IP Emulator's development depends on your contributions. Right now it is just me working on this, so any contributions are welcome!
License
Copyright 2026 Merck KGaA, Darmstadt, Germany and/or its affiliates. All rights reserved.
Metadata
Release files for ethernetip-emulator 0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ethernetip_emulator-0.2.tar.gz | 27.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ethernetip_emulator-0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.9 kB
Release files / ethernetip_emulator-0.2.tar.gz
| Download URL | ethernetip_emulator-0.2.tar.gz |
|---|---|
| Size | 27.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f55d2bc51ab94faebe629851d951e60eb3301ae067cad86ea368ae1f12256326
|
|
BLAKE2b-256 checksum How to use checksums |
1e8775ca0451035476cadbb10cff6a71ab311324f447f1e9ef95290f12ee793f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / ethernetip_emulator-0.2-py3-none-any.whl
| Download URL | ethernetip_emulator-0.2-py3-none-any.whl |
|---|---|
| Size | 50.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2edee54d497ef36653b072d6cfa4a5c6ccc1143d20fa293c25091c7516c6f5d9
|
|
BLAKE2b-256 checksum How to use checksums |
185f3ea64d7d5475be5ed960b2e56fa539dfe0f8c3eef63cfc69f55a5f123c54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|