# Logix Tag Simulator

**URL:** <https://forum.inductiveautomation.com/t/logix-tag-simulator/116350>\
**Category:** General Discussion\
**Created:** [July 10, 2026, 1:44pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350 "2026-07-10T13:44:24Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 1:44pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/1 "2026-07-10T13:44:24Z")

</div>

I've been hacking a bit on some Logix stuff, just wanted to share something.

1. serve up an L5X file as Logix device you can connect to

```python
kevin@Kevins-MacBook-Pro ~/Desktop> docker run --rm \
                                          --name logix-l5x-server \
                                          --network logix-test \
                                          --mount type=bind,source="$PWD/Example.L5X",target=/data/controller.L5X,readonly \
                                          logix-l5x-server:latest \
                                          /data/controller.L5X
Logix server loaded /data/controller.L5X
TCP endpoint: /[0:0:0:0:0:0:0:0]:44818
UDP discovery: /[0:0:0:0:0:0:0:0]:44818

```

1. or programmatically define the Logix tags you want to serve up

```kotlin
private object Types {

  data class Motor(val speed: Float, val temperature: Float)

  val motor = logixStruct("Motor") {
    val speed: LogixMemberRef<Float> = real("Speed", Motor::speed)
    val temperature: LogixMemberRef<Float> = real("Temperature", Motor::temperature)

    decode { Motor(this[speed], this[temperature]) }
  }
}

suspend fun main() {
  val heartbeat = MutableStateFlow(0)
  val motor = MutableStateFlow(Types.Motor(speed = 1_200f, temperature = 42.5f))

  val model = logixModel {
    tags {
      for (i in 1..3) {
        dint("DInt$i", initial = i)
      }

      dint("Heartbeat")
      tag("Motor1", Types.motor, initial = motor.value)
    }
  }

  val server = logixServer(model) {
    bindings {
      bind("Heartbeat", LogixTypes.DINT, heartbeat.asLogixValue())
      bind("Motor1", Types.motor, motor.asLogixValue())
    }
  }

  server.start().getOrThrow().also {
    println("Logix server listening on ${it.tcpAddress}; browse Heartbeat and Motor1")
  }

  try {
    coroutineScope {
      launch {
        while (currentCoroutineContext().isActive) {
          delay(1.seconds)
          heartbeat.update { it + 1 }
          motor.update { it.copy(speed = (it.speed + 25f) % 1_800f) }
        }
      }
      awaitCancellation()
    }
  } finally {
    server.stop().getOrThrow()
  }
}

```

works with IA Logix driver and @pturmel's EtherNet/IP module.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/2/c20aa288eeb5be6a709f859c59c41ed1bca35046.png)

L5X support is a little janky. Predefined/builtin Rockwell types don't work yet.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 10, 2026, 1:52pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/2 "2026-07-10T13:52:50Z")

</div>

> [@Kevin.Herron](#):
>
> Predefined/builtin Rockwell types don't work yet

If you look into my EtherNet/IP driver jars you can find definitions for all of the predefined types up to V35 as resources.

Package `com.automation_pros.enip1.driver.init`.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 1:57pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/3 "2026-07-10T13:57:11Z")

</div>

I'll take a look to see what you did. Did you go firmware-by-firmware version up to 35 to see if they changed in between or anything like that?

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 10, 2026, 2:00pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/4 "2026-07-10T14:00:46Z")

</div>

Yes, extract and diff. And there are differences between L7x and L8x series.

There's a checkbox in my Generic Client type to exhaustively probe all 4095 possible structure IDs, whether used or not. So they end up in the diagnostic exports. Post-processed for convenience in the Host Device type.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 2:05pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/5 "2026-07-10T14:05:54Z")

</div>

That sounds like a painfully monotonous process 😬

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 10, 2026, 2:12pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/6 "2026-07-10T14:12:52Z")

</div>

Changing the firmware in my lab hardware is the monotonous part. Then tweaking the target Studio 5000 to make sure there are tags of each type to help distinguish public type names (Studio 5k and L5X) from internal type names (as discovered in controller browse).

Once probed to get the driver diagnostics, it's easy.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 2:13pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/7 "2026-07-10T14:13:21Z")

</div>

> [@pturmel](#):
>
> Changing the firmware in my lab hardware is the monotonous part.

yeah that's the part I mean.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 4:00pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/8 "2026-07-10T16:00:11Z")

</div>

The main impetus here is so that I can do something like this:

```kotlin
  val server = logixServer {
    interfaceIdentity =
        EnipServerIdentity(
            serialNumber = 0x4E_534C_54u,
            productName = "$CONTROLLER_COUNT-slot test chassis",
        )

    repeat(CONTROLLER_COUNT) { slot ->
      controller(slot = slot, model = testModel) {
        identity =
            LogixEndpoint.DEFAULT_IDENTITY.copy(
                serialNumber = (slot + 1).toUInt(),
                productName = "test controller slot $slot",
            )
      }
    }
  }

```

to spin up 256 or whatever instances of something I can connect to, then maybe containerize it and multiply it further, to test the scalability of the current (and a future) Logix driver.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 10, 2026, 4:38pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/9 "2026-07-10T16:38:38Z")

</div>

You can already automate creation of 100 slots with my Host Device (with direct manipulation of resources in v8.3, or the new system.config functions) in an Ignition gateway. Each slot gets its own serial number automatically. I'd be happy let you internally use dev keys for my module for this sort of thing.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 10, 2026, 4:44pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/10 "2026-07-10T16:44:12Z")

</div>

Thanks for the offer... not necessary any more, that code above isn't theoretical 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![michael.flagler](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/michael.flagler/32/26272_2.png) [@michael.flagler](https://forum.inductiveautomation.com/u/michael.flagler)\
**Post date:** [July 13, 2026, 1:54am UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/11 "2026-07-13T01:54:41Z")

</div>

This would be an amazing tool to have even if I can't write to tags just for testing (although we have a couple licenses of Logix Echo now to test things with). v38 on the L9x processors though have gotten me all frustrated since I can't stand PlantPAx and am forced to use the P\_PID blocks on the new processors since they got rid of the PIDE blocks on those newer processors.

Definitely one thing that matters the most that I think a lot of users run into is the lack of optimization of UDTs/AOIs. I've ran lots of tests on our own standardized AOIs to try to strike a balance between performance and usability (avoiding arrays and putting both a "Data" UDT for faster data and a "Meta" UDT for rarely changing strings like descriptions and engineering units in our AOIs so we're not creating multiple tags per AOI instance). We're getting 60-70k tags per PLC instance running just fine with various scan groups based on usage. Meta is 60s direct, live values are 1s direct, and setpoints, configurations vary around 2-5s direct with some being 60s/2s leased. If I could get everyone to get Phil's driver, I'd switch it all over to 1s direct.

Edit: (Except for the new PlantPAx P\_PID, which we're going to experiment with on this first project using them to see how much it kills performance.) And another thing with those blocks....they literally have internal tags that are only accessible externally but not visible inside the PLC (all the internal operator tags..OCmd, OSet, etc) which in my opinion is horrible practice due to a progammer looking at logic should be able to see everything.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [July 13, 2026, 1:57am UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/12 "2026-07-13T01:57:01Z")

</div>

I'll be doing the AOI reverse engineering at some point. I've laid out / planned the "experiment", just don't have time to actually do it, and it doesn't make sense until plans firm up to actually do the new driver.

No amount of reverse engineering or alternative drivers will help you if you're stuck with AOIs you can't edit to remove the tags that aren't externally accessible, though.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 13, 2026, 2:02am UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/13 "2026-07-13T02:02:17Z")

</div>

> [@Kevin.Herron](#):
>
> if you're stuck with AOIs you can't edit to remove the tags that aren't externally accessible

Or worse, built-in instructions (not just new PlantPax) with similarly inaccessible members. ☹

---

<div class="post-metadata">

**Author:** ![bschroeder](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bschroeder/32/13260_2.png) [@bschroeder](https://forum.inductiveautomation.com/u/bschroeder)\
**Post date:** [July 13, 2026, 12:16pm UTC](https://forum.inductiveautomation.com/t/logix-tag-simulator/116350/14 "2026-07-13T12:16:07Z")

</div>

I solved this sorta here: [Ignition & Logix Best Practice Tips for better communications - #83 by pturmel](https://forum.inductiveautomation.com/t/ignition-logix-best-practice-tips-for-better-communications/60386/83)

Basically we have a UDT in Ignition that represents the data we want to use. The in the PLC we have a Wrapper AOI that wraps the internal AOI/Instruction/ETC... and maps the data to a PLC UDT that matches the Ignition UDT. Anything that needs to write the PLC side is done via the divert write tag actors in @pturmel toolkit.
