10.5. Migrations between different Versions

10.5.1. Towards ln 2.x

10.5.1.1. Standard Message Definitions

10.5.1.1.1. ln/pyservice

ln/pyservice was changed and renamed to ln2/pyservice. see ln/pyobject below.

10.5.1.1.2. ln/pyobject

ln/pyobject was changed and renamed to ln2/pyobject. the data field changed its data-type from char* to uint8_t* as it is often used to transport a binary python pickle of some object (if is_pickle == 1). in case is_pickle == 0 the data field will transport an utf-8 encoded string of a python-repr of that object.

To address interoperability issues between different versions of LN and pickled pyobjects, you can set the LN_PYOBJECT_PICKLE_PROTOCOL environment variable. Supported pickle protocols range from 0 to 5, with the default being protocol 4 (introduced in Python 3.4). Protocols >3 are incompatible with earlier versions. To use the highest supported protocol version, specify a negative protocol value. (from help(pickle) for py3.9)

10.5.1.1.3. ln/file_services/{write,read_from}_file

These message definitions used char* fields to transport possibly binary data. they were fixed and renamed to ln/file_services2/{write,read_from}_file respectively.

10.5.1.2. Python Binding

The python binding can now be used with python3. see Python3 Migration.

10.5.1.3. Deprecated config-directives

10.5.1.3.1. Instance flag use_old_arch_names

This flag was not used for anything.

10.5.1.4. Deprecated API that was removed

10.5.1.4.1. ln_wait_for_service_requests() and ln_handle_service_requests()

// old, removed API:
while(keep_running) {
    if(ln_wait_for_service_requests(clnt, 1))
        ln_handle_service_requests(clnt); // handles all pending service requests
}
// new:
while(keep_running)
    ln_wait_and_handle_service_group_requests(clnt, NULL, 1); // every second we check `keep_running`

C++ binding:

// old, removed API:
while(true)
    if(clnt.wait_for_service_requests())
        clnt.handle_service_requests();
// new:
while(true)
    clnt.wait_and_handle_service_group_requests(std::nullptr);

Python binding:

# old, removed API:
while True:
    if self.clnt.wait_for_service_requests(): # (block until request)
        self.clnt.handle_service_requests()
# new:
while True:
    self.clnt.wait_and_handle_service_group_requests()
    # or: self.clnt.wait_and_handle_service_group_requests(None)
    # or: self.clnt.wait_and_handle_service_group_requests(None, -1)

10.5.1.4.2. ln_enable_async_service_handling()

(C++ binding: ln::client::enable_async_service_handling())

This removed API enabled service request handling from the LN client’s internal async thread. Do not use this service-handling model in new code. Use explicit direct handling with ln_wait_and_handle_service_group_requests() when requests should be handled serially by the caller, or use ln_handle_service_group_in_thread_pool() when handlers should run in a service-group thread pool.

// old, removed API:
ln_enable_async_service_handling(clnt, 1);
// new:
ln_handle_service_group_in_thread_pool(clnt, NULL, "main thread pool");

10.5.1.4.3. ln::client::unregister_service_provider()

(python binding: ln.client.unregister_service_provider())

// old, removed API:
clnt.unregister_service_provider(time_service);
// new:
clnt.release_service(time_service);

The old function was used in ln_generate-generated header files. But it was always discoraged to distribute those generated headers. Users should generate those headers with their buildsystem for whatever messages they want to use.

10.5.1.4.4. WITH_DEPRECATED_GET_TIME

User of ln.h could define that macro to get a get_time() function. Instead use ln_get_time() for wall time or ln_get_monotonic_time() for a monotonic time source.

10.5.2. Towards LN 1.3.3

10.5.2.1. API changes

10.5.2.1.1. lnrpc_{client, server} & gtk_multi_waiter

These are submodules from the links_and_nodes-python package which were designed to work with PyGTK (old gtk2 wrapper).

They are still available, but no longer unconditionally imported into the links_and_nodes namespace.

# old, removed API:
import links_and_nodes as ln
...
multi_waiter = ln.gtk_multi_waiter(...)
my_lnrpc_client = ln.lnrpc_client(...)

# new:
import links_and_nodes as ln
from links_and_nodes.gtk_multi_waiter import *
from links_and_nodes.lnrpc_client import *
...
multi_waiter = gtk_multi_waiter(...)
my_lnrpc_client = lnrpc_client(...)

To ease transition for this backward-incompatible change, you can instead set the LN_WANT_PYGTK environment variable to 1 before doing import links_and_nodes to get the old behavior (which will no longer work with LN versions >= 2).

Example in an ln-config file:

process old, pygtk using program
add environment: LN_WANT_PYGTK=1
command: python2 ...
add flags: forward_x11
...

Example in the conanfile of your program:

from conans import ConanFile

class my_program(ConanFile):
    ...
    requires = [
        "links_and_nodes_python/[~1.3]@common/stable",
        ...
    ]
    ...
    def package_info(self):
        self.env_info.LN_WANT_PYGTK = "1"

Instead of this PyGTK implementation, for new code you should try to use a combination of links_and_nodes.MainloopMultiWaiter and some implementation of links_and_nodes_base.MainloopInterface – when using Gtk3 you could use links_and_nodes_base.GLibMainloop as shown here:

import sys

import gi
gi.require_version('GLib', '2.0')
from gi.repository import GLib

import links_and_nodes as ln

class glib_subscriber_test_app:
    def __init__(self, mainloop):
        self.clnt = ln.client("glib_subscribe", sys.argv)
        self.port1 = self.clnt.subscribe("topic1", "uint32_t")
        self.port2 = self.clnt.subscribe("topic2", "double")

        self.multi_waiter = ln.MainloopMultiWaiter(self.clnt, mainloop)
        self.multi_waiter.add_port(self.port1, self.on_port1)
        self.multi_waiter.add_port(self.port2, self.on_port2, "test arg1", 42)

    def on_port1(self, port):
        print("port1 did publish %r" % port.packet.data)
        return True # return True to keep receiving updates

    def on_port2(self, port, arg1, arg2):
        print("port2 did publish %r" % port.packet.value)
        return True

if __name__ == "__main__":
    mainloop = ln.GLibMainloop() # implements ln.MainloopInterface for GLib
    app = glib_subscriber_test_app(mainloop)

    print("ready")
    mainloop.run()

or, if you don’t want to use Gtk at all, you could use links_and_nodes_base.SelectMainloop or create your own MainloopInterface implementation.

10.5.2.1.1.1. Why this breaking Change?

Because the version jump to LN 1.3 was intended to enable use of the new Gtk3 (via gobject-introspection, like import gi; from gi.repository import Gtk).

The fact that import gobject is done when importing links_and_nodes represents a bug introduced when LN version 1.3.0 was released.

The problem is that it is not allowed to import old PyGTK modules and the new Gtk3 modules into the same python interpreter:

>>> import gtk
>>> import gi; from gi.repository import Gtk
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/python/osl153-x86_64/python2/stable/1.4.4/lib/python2.7/site-packages/gi/__init__.py", line 39, in <module>
    raise ImportError(_static_binding_error)
ImportError: When using gi.repository you must not import static modules like "gobject". Please change all occurrences of "import gobject" to "from gi.repository import GObject". See: https://bugzilla.gnome.org/show_bug.cgi?id=709183

old, PyGtk modules include gtk, gdk, gobject, glib – see Porting from PyGTK 2 to PyGI GTK 3 for more information on how to port your program.

10.5.3. Towards LN 1.2.3

10.5.3.1. API Changes

10.5.3.1.1. flat-md

there is a new ln_get_message_definition_v21() which provides the “flat-md”-representation that is used by links_and_nodes_simulink/ln_publish_subscribe_sfun.

C++ programs can simply use the new member-overload ln::client::get_message_definition() with 5 arguments.

python ln.client.get_message_definition() now returns the 4-tuple md, msg_size, hash, flat_md.