diff --git a/content/guides/creating-an-application-agent.md b/content/guides/creating-an-application-agent.md new file mode 100644 index 0000000000000000000000000000000000000000..7bdb2b544f723e6a307b792c66d88d8d618c28ce --- /dev/null +++ b/content/guides/creating-an-application-agent.md @@ -0,0 +1,254 @@ ++++ +title = "Creating an Application Agent" +weight = 100 +toc = true + +date = "2022-04-26" + ++++ + +An agent is a non graphical program which runs in the background. Agents can be used for playing music or computing expensive operations like indexing large databases, for example. + +Following is a step-by-step guide for creating an agent. This guide is based on the [agent sample application](https://git.apertis.org/git/sample-applications/helloworld-agentapp.git) available in the Apertis repos. It will be handy to have the sample application code open while reading through this guide. + +Our sample application is composed of two classes which will be explained in more detail below: + - `HlwAgent`: A wrapper class that manages the process itself and the D-Bus object + - `HlwTickBoard`: A child class that implements the D-Bus interface + +Let's get started! + +# Agents are GApplications + +The [`GApplication`](https://developer.gnome.org/gio/stable/GApplication.html) is the recommended base class for agents, just as it is for graphical applications. +One of the advantages of being derived from `GApplication` is that most of the work of registering with D-Bus is handled by the class, so you need only override a few virtual functions in order to get a running agent. + +The `HlwAgent` class is the entry point for running the agent. This class is defined as: + +``` +G_DEFINE_TYPE (HlwAgent, hlw_agent, G_TYPE_APPLICATION) +``` + +Create and launch an `HlwAgent`: + +``` + app = G_APPLICATION (g_object_new (HLW_TYPE_AGENT, + "application-id", "org.apertis.HelloWorld.Agent", + "flags", G_APPLICATION_IS_SERVICE, + NULL)); + + g_application_run (app, argc, argv); +``` + +The `application_id` property of the GApplication is used to provide the D-Bus bus name. This id should be unique so it is recommend that you use `@BUDNLE_ID@.Agent`. `GApplicationFlags` should be set to `G_APPLICATION_IS_SERVICE`. See [the description](https://developer.gnome.org/gio/stable/GApplication.html#GApplicationFlags) of flags for more information. + +In the `HlwAgent` class, the `dbus_register` and `dbus_unregister` virtual functions should be overridden to catch whether the requested D-Bus name is available to use, and`activate` should be overridden as well, which is a mandatory virtual function for all applications and agents. + +``` +static void +hlw_agent_class_init (HlwAgentClass *klass) +{ + GApplicationClass *app_class = G_APPLICATION_CLASS (klass); + + ... + + app_class->activate = hlw_agent_activate; + app_class->dbus_register = hlw_agent_dbus_register; + app_class->dbus_unregister = hlw_agent_dbus_unregister; + + ... +} +``` + +To cause `HlwAgent` to run in the background and prevent termination by the ending of the main function, the reference count should be held and released properly. In a graphical application, it's clear that `g_application_hold ()` should be called when the window appears and `g_application_release ()` when the window disappears, but unlike a graphical program, it's a bit less clear when to manage reference count in an agent. Fortunately, in the `dbus_register` function, we can assume that the agent is ready to export extra objects on the bus, making it a good place to take the reference with `g_application_hold ()`, and in `dbus_unregister` we can release the reference count with `g_application_release ()`. + +``` +static gboolean +hlw_agent_dbus_register (GApplication *app, + GDBusConnection *connection, + const gchar *object_path, + GError **error) +{ + ... + + g_application_hold (app); + + ... +} + +static void +hlw_agent_dbus_unregister (GApplication *app, + GDBusConnection *connection, + const gchar *object_path) +{ + ... + + g_application_release (app); + + ... +} +``` + +Once the `activate` virtual function is overridden, we will have a runnable agent skeleton. + +# D-Bus Interface + +Using the `GApplication` class allows our agent to run as stand-alone non-graphical program, but it isn't yet able to interact with the outside world. It is recommended that D-Bus be used to communicate with other processes on the system. D-Bus code and documentation can be easily created by `gdbus-codegen`. For more details of the usages of the generator, refer to [gdbus-codegen](https://developer.gnome.org/gio/stable/gdbus-codegen.html). + +## D-Bus XML Schema + +To make our agent application D-Bus aware, let's introduce a simple XML D-Bus interface. + +Our interface will contain the following: + - method: ToggleTick + - property: CurrentTick + +By calling `ToggleTick`, an auto-incremented tick count function is enabled or disabled. Once the function is enabled, the property, `CurrentTick`, is increased by 1 every second. + +``` +<node name="/org/apertis/helloworld/agent/tickboard"> + <interface name="org.apertis.HelloWorld.Agent.TickBoard"> + <method name="ToggleTick"/> + <property name="CurrentTick" type="i" access="read"/> + </interface> +</node> +``` + +To generate the D-Bus code at make time, we introduce a simple rule to the Automake `Makefile.am`. + +``` +helloworld-agent/%.c: helloworld-agent/%.xml Makefile + $(AM_V_GEN)$(GDBUS_CODEGEN) \ + --c-namespace=HlwDBus \ + --interface-prefix=org.apertis.HelloWorld.Agent \ + --generate-c-code helloworld-agent/$* $< +``` + +Note that `--interface-prefix` should be the same as the `application-id` of the agent. + +## D-Bus function implementation + +The D-Bus skeleton is generated by `gdbus-codegen` according to the XML schema. Now we need to create actual behaviors for when the D-Bus APIs are called. In our example, the `HlwDBusTickBoardSkeleton` object and the `HlwDBusTickBoard` interface are created. Although there are various approaches to implementing this interface, being a child of the D-Bus skeleton object will help show how the generated virtual functions should be filled in. + +``` +G_DEFINE_TYPE_WITH_CODE (HlwTickBoard, hlw_tick_board, + HLW_DBUS_TYPE_TICK_BOARD_SKELETON, + G_IMPLEMENT_INTERFACE (HLW_DBUS_TYPE_TICK_BOARD, + hlw_tick_board_tick_board_iface_init)) +``` + +Next, `handle_toggle_tick` of `HlwDbusTickBoardIface` should be overridden. + +``` +static gboolean +hlw_tick_board_handle_toggle_tick (HlwDBusTickBoard *object, + GDBusMethodInvocation *invocation) +{ + ... + + hlw_dbus_tick_board_complete_toggle_tick (object, invocation); + + return TRUE; +} + +static void +hlw_tick_board_tick_board_iface_init (HlwDBusTickBoardIface *iface) +{ + iface->handle_toggle_tick = hlw_tick_board_handle_toggle_tick; +} +``` + +## Exporting the interface to D-Bus + +Once the implementation of the D-Bus interface is completed, it needs to be exported on the bus. `HlwAgent` is already registered on the bus so the `HlwTickBoard`, which is a child object of the D-Bus skeleton, can be easily exported in the `dbus_register` function. + +``` +static gboolean +hlw_agent_dbus_register (GApplication *app, + GDBusConnection *connection, + const gchar *object_path, + GError **error) +{ + gboolean ret; + + ... + + /* chain up */ + ret = G_APPLICATION_CLASS (hlw_agent_parent_class)->dbus_register ( + app, + connection, + object_path, + error); + + if (ret && + !g_dbus_interface_skeleton_export ( + G_DBUS_INTERFACE_SKELETON (self->tick_board), + connection, + "/org/apertis/HelloWorld/Agent/TickBoard", + error)) + { + g_warning ("Failed to export TickBoard D-Bus interface (reason: %s)", + (*error)->message); + } + + return ret; +} +``` + +Note that it is recommended that an application registers as above before exporting any other interfaces to D-Bus. + +# Flatpak + + +Apertis migrated to flatpak based app distribution from v2022 release onwards, and it provides modular [reference Flatpak runtime](https://gitlab.apertis.org/infrastructure/apertis-flatpak-runtime) to meet the different specifications. Headless Runtime is to meet all headless specifications and hmi Runtime is to meet gtk based application development needs. + +## Creation of manifest file for Helloworld-simple-agent + +Each application [manifest](https://docs.flatpak.org/en/latest/manifests.html) file should specify basic information about the application that is to be built, including the app-id, runtime, runtime-version, sdk and command parameters + +[Create the manifest](https://gitlab.apertis.org/sample-applications/helloworld-simple-agent/-/blob/apertis/v2023dev1/flatpak-recipe.yaml) file by adding all the required fields. + +## Steps for building sources + +- Add flatpak repository to pull the required runtime. + +``` +$ flatpak --user remote-add apertis https://images.apertis.org/flatpak/repo/apertis.flatpakrepo +``` + +- Installation steps + +``` +flatpak --user install \ + org.apertis.headless.Platform \ + org.apertis.headless.Sdk \ +``` +{{% notice warning %}} +During installation you may be prompted for which version to install. Due to +incompatibilities between flatpak versions, it is highly recommended you select +the version corresponding to the Apertis version your system is running. +{{% /notice %}} + +- `flatpak-builder` will be used build the applications + +``` +$ cd helloworld-simple-agent +$ flatpak-builder --repo=repo --force-clean build-dir flatpak-recipe.yaml +``` + +- Install + +``` +$ cd helloworld-simple-agent +$ flatpak --if-not-exists --user remote-add --no-gpg-verify helloworld-simple-agent repo/ +$ flatpak install -y helloworld-simple-agent org.apertis.headless.helloworld-simple-agent +``` +- Running the agent + +``` +$ cd helloworld-simple-agent +$ flatpak run org.apertis.headless.helloworld-simple-agent + +``` +- Expected output + +helloworld-simple-agent-Message: ip: Registered D-Bus (path: /org/apertis/HelloWorld/SimpleAgent) \ No newline at end of file