Create a task and a 'service'
Last updated on 2025-08-22 | Edit this page
Estimated time: 30 minutes
Overview
Questions
- What are tasks and what can they do?
- Can I run a task as a (background) ‘service’ process?
Objectives
- Learn how to create tasks with (customized) task runners
- Learn how to make a background ‘service’ process with
qmi_proc
Setting up a task and a “service”
Next, we want to demonstrate QMI tasks and how they can be used in
setting up services, i.e. tasks running as background processes, on your
PC. This needs now somewhat more complex configuration of the
qmi.conf
, but nothing scary, I promise.
Configuration file
The new configuration will have an extension for the background process:
{
# Log level for messages to the console.
"logging": {
"console_loglevel": "INFO"
},
"contexts": {
# Testing remote instrument access.
"instr_server": {
"host": "127.0.0.1",
"tcp_server_port": 40001
},
# Testing process management.
"proc_demo": {
"host": "127.0.0.1",
"tcp_server_port": 40002,
"enabled": true,
"program_module": "task_demo "
}
}
}
The ‘”enabled”: true’ parameter makes it possible to start the context via QMI process management, and ‘”program_module”: “task_demo”’ line tells the QMI to start a module named ‘task_demo.py’ for this process. We’ll create it in a qubit, but first let’s define a task we want to make the service for.
Demo task
To demonstrate a custom task, we need to create one. Make a new
Python module inside the module path for your project. If you don’t have
a module path yet, just create a file demo_task.py
in the
current directory:
PYTHON
from dataclasses import dataclass
import logging
import qmi
from qmi.core.rpc import rpc_method
from qmi.core.task import QMI_Task, QMI_TaskRunner
# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)
@dataclass
class DemoLoopTaskSettings:
sample: float
amplitude: float
class CustomRpcControlTaskRunner(QMI_TaskRunner):
@rpc_method
def set_amplitude(self, amplitude: float):
settings = self.get_settings()
settings.amplitude = amplitude
self.set_settings(settings)
class DemoRpcControlTask(QMI_Task):
def __init__(self, task_runner, name):
super().__init__(task_runner, name)
self.settings = DemoLoopTaskSettings(amplitude=100.0, sample=None)
def run(self):
_logger.info("starting the background task")
nsg = qmi.get_instrument("proc_demo.nsg")
while not self.stop_requested():
self.update_settings()
nsg.set_amplitude(self.settings.amplitude)
self.sleep(0.01)
_logger.info("stopping the background task")
Note that we define class DemoRpcControlTask
with one
special method named run()
. This method contains the code
that makes up the background task. In this simple example, the task
simply loops once per second, reading settings from the sine generator
and adjusting its amplitude. The task uses the function qmi.core.task.QMI_Task.sleep()
to sleep instead of time.sleep()
. The advantage of this is
that it stops waiting immediately when it is necessary to stop the
task.
Task runner script
Now we still miss the program starting up and running the task. Make
the task_demo.py
file with the following contents:
PYTHON
import logging
import time
import qmi
from qmi.utils.context_managers import start_stop
from qmi.instruments.dummy.noisy_sine_generator import NoisySineGenerator
from demo_task import DemoRpcControlTask, CustomRpcControlTaskRunner
# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)
def main():
with start_stop(qmi, "proc_demo", config_file="./qmi.conf"):
ctx = qmi.context()
with qmi.make_instrument("nsg", NoisySineGenerator) as nsg:
with qmi.make_task("demo_task", DemoRpcControlTask, task_runner=CustomRpcControlTaskRunner) as task:
_logger.info("the task has been started")
while not ctx.shutdown_requested():
sample = nsg.get_sample()
amplitude = nsg.get_amplitude()
print(" " * int(40.0 + 0.25 * sample) + "*")
if abs(sample) > 10:
task.set_amplitude(amplitude * 0.9)
else:
task.set_amplitude(amplitude * 1.1)
time.sleep(0.01)
_logger.info("the task has been stopped")
if __name__ == "__main__":
main()
This program now takes care of creating the instrument
nsg
and followingly starting up the task. The script also
loops about once per second, and at each iteration prints out the latest
sample and amplitude values, and controls the
DemoRpcControlTask
’s amplitude to keep the sample value at
around 10. In practice, we are now trying to suppress the sine wave as
much as possible by continuously controlling its amplitude.
Running the task as a service
We can now start the service using qmi_proc
program.
qmi_proc
is a command-line executable created when
installing QMI, see also documentation about managing
background processes. It can be used to start, stop and inquire
status of services. To start the “proc_demo” service, type the following
(the “–config ./qmi.conf” is not necessary if the qmi.conf
is in the default path).
qmi_proc start proc_demo --config ./qmi.conf
Leave the program now to run a few seconds and then stop it:
qmi_proc stop proc_demo --config ./qmi.conf
The use of the qmi_proc
creates extra output files for
services. One was now created also for “proc_demo” service. You can find
it in the default location with name “proc_demo_
There are plenty of other things going on on this example, like the
use of a custom QMI_TaskRunner
with an RPC method added
into the runner. We also make use of file-specific loggers for logging.
For more information about QMI tasks see the tutorial,
and about logging the Design
Overview in documentation. There are also other examples of tasks in
the examples
folder of the QMI repository.
Further, the amplitude control through the task settings actually utilizes the QMI’s signalling feature. For more details on this, you can read into signalling and look at API of qmi.core.task in the documentation.
- Instruments to be accessed remotely should be defined in `qmi.conf
- Connect to another QMI context using
qmi.context().connect_to_peer("<context_name>", peer_address="<ho.st.i.p:port>")
- Obtain remote instrument control with
qmi.get_instrument("<context_name>.<instrument_name>")