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)
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)
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.
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.1.1
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.1.1.tar.gz | 22.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ethernetip_emulator-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.6 kB
Release files / ethernetip_emulator-0.1.1.tar.gz
| Download URL | ethernetip_emulator-0.1.1.tar.gz |
|---|---|
| Size | 22.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
666d93bfd590bc277029a3677c5940ed7dd534656f105e896ac3d52172a0ace6
|
|
BLAKE2b-256 checksum How to use checksums |
1a50cbc2e72f4b10c176acdf617dc149fffeb8e7ff82eff6d79fe40df8e9e121
|
| 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.1.1-py3-none-any.whl
| Download URL | ethernetip_emulator-0.1.1-py3-none-any.whl |
|---|---|
| Size | 45.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a42e05ed83e5680d23e1db2910e345249c1eba5379ff01160fd4cfa6184e0a3c
|
|
BLAKE2b-256 checksum How to use checksums |
27aaf556883451420d7d83e121469670b5f2b641f9497cd8750d7e9c4efc4f07
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|