I've been hacking a bit on some Logix stuff, just wanted to share something.
- serve up an L5X file as Logix device you can connect to
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
- or programmatically define the Logix tags you want to serve up
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.
L5X support is a little janky. Predefined/builtin Rockwell types don't work yet.
