Syntalos 2.x → 3.0 Porting
Syntalos 3.0 contains a small number of breaking changes that affect projects authored against the 2.x series. This page summarizes what changed and how to migrate existing setups.
At a glance
- Stream types are now protocol-agnostic:
LineCommand/LineReadingreplaceFirmataControl/FirmataData. - Hardware addressing in
Line*is ID-based only:line_idreplacespin_id+pin_name. - Python port helper APIs for
Line*are redesigned assyl.HwOutputLine/syl.HwInputLine. - Python modules declare their ports in code via
register_input_port()/register_output_port(); the[ports]table inmodule.tomlis gone. - Signal block types are renamed to include their precision, and
SignalBlockF32is now a 32-bit float. - The “Firmata User Control” (
firmata-userctl) module has been renamed to “Manual Line Control” (hwline-userctl). - Public C/C++ APIs (libsyntalos-mlink, -datactl) no longer depend on Qt. Out-of-tree C++ modules need to be ported and rebuilt against the new headers.
1. Porting Python modules & PyScript scripts
Port types
Anywhere your Python module’s port editor had FirmataControl or
FirmataData selected, switch to LineCommand / LineReading.
Replacing the convenience helpers
The 2.x API put convenience methods directly on OutputPort:
firmata_register_digital_pin, firmata_submit_digital_value,
firmata_submit_digital_pulse. All three are removed in 3.0. In their
place, two small Python classes — HwOutputLine and HwInputLine — capture
the identity of a hardware line and expose the operations valid for it.
Before (2.x):
import syntalos_mlink as syl
oport_fm = syl.get_output_port('firmatactl-out')
def start():
# register two pins
oport_fm.firmata_register_digital_pin(7, 'switch', is_output=False, is_pullup=True)
oport_fm.firmata_register_digital_pin(8, 'led1', is_output=True)
def trigger():
oport_fm.firmata_submit_digital_pulse('led1', duration_msec=50)
oport_fm.firmata_submit_digital_value('led1', False)After (3.0):
import syntalos_mlink as syl
oport_fm = syl.get_output_port('firmatactl-out')
# Capture identity once. Construction is side-effect-free.
switch = syl.HwInputLine(oport_fm, line_id=7, pullup=True)
led = syl.HwOutputLine(oport_fm, line_id=8)
def start():
# Explicitly register at every run
switch.send_mode()
led.send_mode()
def trigger():
led.pulse_msec(duration_msec=50)
led.set_value(False)Notes:
send_mode()must be called at the start of every run. Constructing theHwOutputLine/HwInputLineonce in module scope and re-registering instart()is the recommended pattern.- The
line_idis the only identity — there is nopin_nameanymore. If you used names to disambiguate readings, match onreading.line_id == my_line.line_idinstead. HwOutputLine(port, line_id, analog=True)exposesset_analog_value(code)for DAC-style outputs. Callingset_valueorpulseon an analog line (orset_analog_valueon a digital line) raises a clearSyntalosPyError.
Reading line readings
Field renames in the inbound type:
| 2.x | 3.0 |
|---|---|
data.pin_id | data.line_id |
data.pin_name | removed — match data.line_id against your HwInputLine.line_id |
data.is_digital | removed — the receiver already knows |
data.value | data.value (now an unsigned 32-bit integer) |
Before (2.x):
def on_new_firmata_data(data):
if data is None or not data.is_digital:
return
if data.pin_name != 'switch':
return
handle_switch(data.value)After (3.0):
def on_new_line_reading(data):
if data is None:
return
if data.line_id != switch.line_id:
return
handle_switch(data.value)Low-level constructors
| 2.x | 3.0 |
|---|---|
syl.new_firmatactl_with_id_name(kind, pin_id, name) | syl.new_line_command(kind, line_id, value=0) |
syl.new_firmatactl_with_id(kind, pin_id) | syl.new_line_command(kind, line_id, value=0) |
syl.new_firmatactl_with_name(kind, name) | removed — use line_id |
Most user code should not need these; HwOutputLine / HwInputLine cover the
common operations. If you construct raw commands, note that line setup is now
LineCommandKind.SET_MODE plus LineModeFlags:
cmd = syl.new_line_command(syl.LineCommandKind.SET_MODE, line_id=8)
cmd.flags = syl.LineModeFlags.IS_OUTPUT
oport_fm.submit(cmd)Pulse durations live in cmd.duration_usec; cmd.value is only the line value
or analog code. Device-specific payloads use cmd.extra together with
syl.LineCommandKind.DEVICE_SPECIFIC.
Declaring ports in Python modules
For standalone Python modules (not PyScript snippets), ports are now declared
in code at module construction time using register_input_port() /
register_output_port() on the SyntalosLink object. The legacy
[ports] table in module.toml is no longer read — remove it.
import syntalos_mlink as syl
class MyModule:
def __init__(self, modLink: syl.SyntalosLink):
self._iport = modLink.register_input_port(
'frames-in', 'Frames', syl.DataType.Frame)
self._oport = modLink.register_output_port(
'rows-out', 'Indices', syl.DataType.TableRow)2. Stream data type changes
Signal block type renames
All signal block data types have been renamed for clarity and to reflect their exact bit width:
| 2.x | 3.0 |
|---|---|
FloatSignalBlock | SignalBlockF32 |
IntSignalBlock | SignalBlockI32 |
This affects C++ type names, Python classes such as syl.SignalBlockF32(), and
the Python syl.DataType.* constants.
The SignalBlockU16 is new in 3.0 and has no 2.x equivalent.
Float precision change
The precision of SignalBlockF32 (formerly FloatSignalBlock) has changed from
double (64-bit) to float (32-bit).
This has some algorithmic advantages and matches what many DAQ systems output.
Both SignalBlockI32 and SignalBlockF32 are now consistently 32-bit, which
also allows further signal block types to be added more easily in the future.
Reach out to us if you have a use case that needs higher-precision floating point numbers as a stream data type!
3. Porting C++ modules
All public Syntalos libraries that out-of-tree modules link against —
libsyntalos-mlink, libsyntalos-datactl and the data-streaming fabric —
have been ported away from Qt and now depend only on GLib, the C/C++
standard library and OpenCV/Eigen3. This makes the libraries easier to consume
from non-Qt codebases and from Python, but it changes every public type that
previously used Qt containers or string types.
Out-of-tree C++ modules from the 2.x series will not load against 3.0 and need to be rebuilt against the new headers. The most common adjustments:
QString/QByteArrayin public APIs are replaced bystd::stringandSyntalos::ByteVector.- Stream metadata containers no longer use
QVariantHash; use the new metadata helpers indatactlinstead. - Any code that handled
FirmataControl/FirmataDataneeds to switch toLineCommand/LineReading, identical in spirit to the Python port above. - Signal block C++ types follow the same rename as in section 2
(
FloatSignalBlock→SignalBlockF32,IntSignalBlock→SignalBlockI32).
The bundled module sources, the example-mlink template, and the
cpp-workbench snippets are all up-to-date and make good references when
porting your own modules.
4. Renamed modules
The “Firmata User Control” module has been renamed to “Manual Line Control”
to reflect that it now operates on the protocol-agnostic Line* stream types
and is no longer tied to Firmata specifically:
| 2.x module id | 3.0 module id |
|---|---|
firmata-userctl | hwline-userctl |
When opening a 2.x project that referenced firmata-userctl, Syntalos will
report the old module as missing. Just add the new module instead.