Virtual devices
Virtual devices let you present a Python-implemented device to the
rest of the LPF2 stack — most often to expose custom hardware to a
LEGO app through lpf2.hub_emulation, or to synthesise a
child device behind a port expander.
The pieces
port— an LPF2 port whose transport is a Python-side device. Behaves like any otherlpf2.port.device— the emulated device itself. Subclass to implement behaviour; unbound instances just log every call.lpf2.device_descriptor— declares the modes / versions the emulated device advertises.
Building a descriptor
import lpf2
d = lpf2.device_descriptor()
d.fwVersion = lpf2.version()
d.fwVersion.Major = 1
d.hwVersion = lpf2.version()
d.hwVersion.Major = 1
m = lpf2.mode()
m.name = "PWR"
m.min, m.max = -100, 100
m.SImin, m.SImax = -100, 100
m.PCTmin, m.PCTmax = -100, 100
m.format = 0 # int8
m.dataSets = 1
m.outMapping = 0x01 # accepts writes
d.modes = [m]
Subclassing device
Override any of startPower(), startSpeed(),
writeData(), setMode(), etc. Calling
super().method(...) invokes the C++ default (safely — the
trampoline blocks re-entry into Python), so use it for logging /
default behaviour:
class MyMotor(lpf2.virtual.device):
def startPower(self, pw):
print("MyMotor.startPower", pw)
super().startPower(pw)
Attaching and running
Both port and device add themselves to the
firmware update registry when constructed, so the C main loop
drives them automatically — no update() calls from Python are
needed (see Automatic polling).
port = lpf2.virtual.port()
dev = MyMotor(d)
port.attachDevice(dev)
# Optional write-intercept callback (fires before default logging):
dev.setWriteDataCallback(lambda mode, data, user: print(mode, data))
# Push a new input sample and fire value-change callbacks:
dev.setModeData(mode=0, buf=b"\\x2a")
Then plug the port into whatever consumes it — either the local port
manager (rare) or lpf2.hub_emulation (typical):
emu = lpf2.hub_emulation()
emu.setName("MyHub")
emu.attachPort(0, port)
emu.start()
GC pitfalls
The C++ side holds the port and descriptor; the port holds the device. Anchor the Python objects on your side too — otherwise the GC may free them:
self.port = port
self.dev = dev
self.desc = d