Skip to main content

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.GPIO or OPi.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       │
└─────────────────────────────────────────────────────┘
  1. Tags are declared once at import time via @tag_registry.register.
  2. Custom Datatypes can be declared once at import time via @actions.datatype. Although this is completely optional if you like the built-in datatypes
  3. device_controller builds the tag list and starts the EtherNet/IP server.
  4. AttributeDevice intercepts every PLC write and fires the matching on_change listeners.
  5. 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)

Source distribution for ethernetip-emulator 0.2
File Size Uploaded
ethernetip_emulator-0.2.tar.gz 27.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ethernetip-emulator 0.2
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2 This release

2 release files

0.1.1

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page