8 Commits

Author SHA1 Message Date
76b53423c1 fix(dbus): Add error handling for DBus publishing and main loop
Wrap DBus publishing and main loop execution in try-except blocks to
capture and log failures. This ensures better visibility into errors and
prevents silent failures during runtime.
2025-04-19 12:43:22 +02:00
114cbd8099 refactor(cli): D-Bus helpers support session and system bus types
Introduced `BusType` enum to differentiate between session and system
bus usage in D-Bus calls. Updated `simple_call` and `await_signal`
functions to include a `bus_type` parameter, improving flexibility.
Adjusted `cli_picontrol` to leverage the new `BusType` parameter for
D-Bus interactions.
2025-04-19 12:24:37 +02:00
487d5b3d46 feat(dbus): Add running property to BusProvider
This property checks if the event loop is running, enhancing code
readability and convenience. It provides an easier way to monitor the
status of the loop, which may improve debugging and control flow
handling.
2025-04-19 12:13:45 +02:00
93b328bf3f feat(dbus): Add import for BusProvider in dbus_middleware1 module
This change includes the BusProvider import in the `__init__.py` file of
dbus_middleware1. It ensures the module has access to BusProvider
functionality, improving modularity and readiness for use.
2025-04-19 12:13:44 +02:00
bde3920fc1 feat: Add session bus option for local testing and development
Introduced a `--use-session-bus` flag to optionally use the D-Bus
session bus instead of the system bus. This allows better flexibility
for local testing and development scenarios without requiring
system-level changes. Updated related classes and functions to respect
the new flag.
2025-04-19 09:33:54 +02:00
a4ccb9081f refactor(dbus): Parameterize picontrol_device and config_rsc
Replaced hardcoded paths with configurable parameters `picontrol_device`
and `config_rsc` across multiple classes. This improves flexibility,
making the components adaptable to various environments or setups.
Updated corresponding initialization and method implementations to use
these parameters.
2025-04-19 08:21:24 +02:00
4c1dc1c9b5 refactor(dbus): Move ResetDriverWatchdog to process_image_helper.py
The ResetDriverWatchdog class was relocated from dbus_helper.py to a new
helper module, process_image_helper.py, to improve code organization
and maintainability. Updated imports in relevant files to reflect this
change.
2025-04-19 08:13:02 +02:00
e756d68556 refactor(dbus): Move D-Bus helper functions to a dedicated file
Consolidated `REVPI_DBUS_*` constants and `extend_interface` function
into `dbus_helper.py` for better modularity and reusability. Updated
imports across modules to reflect this change.
2025-04-19 07:56:17 +02:00
9 changed files with 168 additions and 68 deletions

View File

@@ -4,7 +4,7 @@
from argparse import ArgumentParser
from logging import getLogger
from .dbus_helper import await_signal, simple_call
from .dbus_helper import BusType, await_signal, simple_call
from .. import proginit as pi
from ..dbus_middleware1 import extend_interface
@@ -36,12 +36,20 @@ def add_subparsers(parent_parser: ArgumentParser):
def method_reset():
log.debug("D-Bus call of method ResetDriver")
simple_call("ResetDriver", interface=extend_interface("picontrol"))
simple_call(
"ResetDriver",
interface=extend_interface("picontrol"),
bus_type=BusType.SESSION if pi.pargs.use_session_bus else BusType.SYSTEM,
)
log.info("ResetDriver called via D-Bus")
def method_await_reset(timout: int = 0):
detected_signal = await_signal("NotifyDriverReset", timout, extend_interface("picontrol"))
detected_signal = await_signal(
"NotifyDriverReset",
timout, extend_interface("picontrol"),
bus_type=BusType.SESSION if pi.pargs.use_session_bus else BusType.SYSTEM,
)
if detected_signal:
log.info("ResetDriver signal received")
else:

View File

@@ -1,46 +1,98 @@
# SPDX-FileCopyrightText: 2025 KUNBUS GmbH
# SPDX-License-Identifier: GPL-2.0-or-later
"""D-Bus helper functions for cli commands."""
from enum import Enum
from threading import Thread
from time import sleep
from gi.repository import GLib
from pydbus import SystemBus
from pydbus import SessionBus, SystemBus
from ..dbus_middleware1 import REVPI_DBUS_BASE_PATH
from ..dbus_middleware1 import REVPI_DBUS_NAME
PICONTROL_INTERFACE = "com.revolutionpi.middleware1.picontrol"
RESET_DRIVER_METHOD = "ResetDriver"
class BusType(Enum):
SESSION = "session"
SYSTEM = "system"
def simple_call(method: str, *args, interface: str, object_path=REVPI_DBUS_BASE_PATH):
def simple_call(
method: str,
*args,
interface: str,
object_path=REVPI_DBUS_BASE_PATH,
bus_type=BusType.SYSTEM,
):
"""
Executes a method on a specific D-Bus object interface within the RevPi system. This function
connects to the system bus, retrieves the desired interface and object path, and invokes
the specified method with provided arguments.
Performs a call to a D-Bus method on a specified interface and object.
This function uses the D-Bus messaging system to dynamically call a method
of a specified interface, using the given object path and bus type. It
provides a way to interact with D-Bus interfaces, using either a system or
session bus, and returns the result of executing the specified method.
Parameters:
method: str
The name of the method to be invoked on the targeted interface.
*args: tuple
Positional arguments to be passed to the method being invoked.
The name of the method to invoke on the D-Bus interface.
*args:
Additional positional arguments to pass to the specified D-Bus method.
interface: str
The name of the D-Bus interface providing the required functionality.
object_path: str, optional
The D-Bus object path of the RevPi interface. Defaults to REVPI_DBUS_BASE_PATH.
The name of the D-Bus interface containing the method.
object_path:
The path of the D-Bus object on which the interface is defined. Defaults
to REVPI_DBUS_BASE_PATH.
bus_type: BusType
Specifies whether to use the system or session bus. Defaults to BusType.SYSTEM.
Returns:
Any
The result of the method invocation on the targeted D-Bus interface.
The value returned by the D-Bus method.
Raises:
Any errors raised from the D-Bus call will propagate to the caller.
"""
bus = SystemBus()
bus = SessionBus() if bus_type is BusType.SESSION else SystemBus()
revpi = bus.get(REVPI_DBUS_NAME, object_path)
iface = revpi[interface]
return getattr(iface, method)(*args)
def await_signal(signal_name: str, timeout: int, interface: str, object_path=REVPI_DBUS_BASE_PATH):
def await_signal(
signal_name: str,
timeout: int,
interface: str,
object_path=REVPI_DBUS_BASE_PATH,
bus_type=BusType.SYSTEM,
):
"""
Waits for a specific signal and returns whether the signal was detected.
This function connects to a D-Bus interface and waits for a specific signal
to be emitted. If the signal is not received within the specified timeout
period, the function will return False. If the signal is detected within
the timeout, the function will return True. It can connect to either the
system bus or the session bus, depending on the provided `bus_type`.
Parameters:
signal_name: str
The name of the signal to be awaited.
timeout: int
The maximum time to wait for the signal, in seconds. A value of 0 or
less means that there is no timeout.
interface: str
The name of the D-Bus interface to listen on.
object_path
The D-Bus object path where the interface resides. Defaults to
REVPI_DBUS_BASE_PATH.
bus_type
The type of D-Bus to connect to. Can be either BusType.SYSTEM or
BusType.SESSION. Defaults to BusType.SYSTEM.
Returns:
bool
True if the signal was detected within the timeout period, False
otherwise.
"""
detected_signal = False
timeout = int(timeout)
loop = GLib.MainLoop()
@@ -55,7 +107,7 @@ def await_signal(signal_name: str, timeout: int, interface: str, object_path=REV
detected_signal = True
loop.quit()
bus = SystemBus()
bus = SessionBus() if bus_type is BusType.SESSION else SystemBus()
revpi = bus.get(REVPI_DBUS_NAME, object_path)
iface = revpi[interface]

View File

@@ -42,7 +42,7 @@ class MiddlewareDaemon:
if self.bus_provider and self.bus_provider.is_alive():
return
self.bus_provider = BusProvider()
self.bus_provider = BusProvider(use_system_bus=not pi.pargs.use_session_bus)
self.bus_provider.start()
log.debug("leave MiddlewareDaemon.dbus_start")

View File

@@ -2,27 +2,7 @@
# SPDX-FileCopyrightText: 2025 KUNBUS GmbH
# SPDX-License-Identifier: GPL-2.0-or-later
"""D-Bus middleware version 1 of revpi_middleware."""
from ..__about__ import __author__, __copyright__, __license__, __version__
from .dbus_helper import REVPI_DBUS_BASE_PATH, REVPI_DBUS_NAME
from .dbus_helper import extend_interface
REVPI_DBUS_NAME = "com.revolutionpi.middleware1"
REVPI_DBUS_BASE_PATH = "/com/revolutionpi/middleware1"
def extend_interface(*args) -> str:
"""
Extends an interface name by appending additional segments to a pre-defined base name.
This function takes multiple arguments, concatenates them with a predefined base
interface name, and returns the resulting string, effectively constructing an
extended interface name.
Args:
*args: str
Components to be appended to the base interface name.
Returns:
str
Fully constructed interface name by joining the base interface name with
the provided segments.
"""
return ".".join([REVPI_DBUS_NAME, *args])
from .bus_provider import BusProvider

View File

@@ -6,7 +6,7 @@ from logging import getLogger
from threading import Thread
from gi.repository import GLib
from pydbus import SystemBus
from pydbus import SessionBus, SystemBus
from . import REVPI_DBUS_NAME
from .process_image import InterfacePiControl
@@ -16,25 +16,44 @@ log = getLogger(__name__)
class BusProvider(Thread):
def __init__(self):
def __init__(
self,
picontrol_device="/dev/piControl0",
config_rsc="/etc/revpi/config.rsc",
use_system_bus=True,
):
log.debug("enter BusProvider.__init__")
super().__init__()
self._bus = SystemBus()
self._bus = SystemBus() if use_system_bus else SessionBus()
self._loop = GLib.MainLoop()
self.picontrol_device = picontrol_device
self.config_rsc = config_rsc
def run(self):
log.debug("enter BusProvider.run")
self._bus.publish(
REVPI_DBUS_NAME,
InterfacePiControl(),
)
try:
self._bus.publish(
REVPI_DBUS_NAME,
InterfacePiControl(self.picontrol_device, self.config_rsc),
)
except Exception as e:
log.error(f"can not publish dbus {REVPI_DBUS_NAME}: {e}")
try:
self._loop.run()
except Exception as e:
log.error(f"can not run dbus mainloop: {e}")
self._loop.run()
log.debug("leave BusProvider.run")
def stop(self):
log.debug("enter BusProvider.stop")
self._loop.quit()
log.debug("leave BusProvider.stop")
@property
def running(self):
return self._loop.is_running()

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: 2025 KUNBUS GmbH
# SPDX-License-Identifier: GPL-2.0-or-later
"""Helper for dbus."""
from logging import getLogger
log = getLogger(__name__)
REVPI_DBUS_NAME = "com.revolutionpi.middleware1"
REVPI_DBUS_BASE_PATH = "/com/revolutionpi/middleware1"
def extend_interface(*args) -> str:
"""
Extends an interface name by appending additional segments to a pre-defined base name.
This function takes multiple arguments, concatenates them with a predefined base
interface name, and returns the resulting string, effectively constructing an
extended interface name.
Args:
*args: str
Components to be appended to the base interface name.
Returns:
str
Fully constructed interface name by joining the base interface name with
the provided segments.
"""
return ".".join([REVPI_DBUS_NAME, *args])

View File

@@ -8,7 +8,7 @@ from logging import getLogger
from pydbus.generic import signal
from ..interface_helper import ResetDriverWatchdog
from .process_image_helper import ResetDriverWatchdog
log = getLogger(__name__)
@@ -27,10 +27,11 @@ class InterfacePiControl:
NotifyDriverReset = signal()
def __init__(self):
self.pi_control = "/dev/piControl0"
def __init__(self, picontrol_device: str, config_rsc: str):
self.picontrol_device = picontrol_device
self.config_rsc = config_rsc
self.wd_reset_driver = ResetDriverWatchdog(self.pi_control)
self.wd_reset_driver = ResetDriverWatchdog(self.picontrol_device)
self.wd_reset_driver.register_call(self.notify_reset_driver)
def notify_reset_driver(self):
@@ -40,9 +41,9 @@ class InterfacePiControl:
log.debug("enter InterfacePiControl.ResetDriver")
try:
fd = os.open(self.pi_control, os.O_WRONLY)
fd = os.open(self.picontrol_device, os.O_WRONLY)
except Exception as e:
log.warning(f"could not open ${self.pi_control} to reset driver")
log.warning(f"could not open ${self.picontrol_device} to reset driver")
raise e
execption = None

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: 2020-2023 Sven Sager
# SPDX-FileCopyrightText: 2025 KUNBUS GmbH
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Helper for the process image.
@@ -7,7 +7,6 @@ Helper for the process image.
The ResetDriverWatchdog class is a copy of revpipyload project module "watchdogs"
https://github.com/naruxde/revpipyload/blob/b51c2b617a57cc7d96fd67e1da9f090a0624eacb/src/revpipyload/watchdogs.py
"""
import os
from fcntl import ioctl
from logging import getLogger
@@ -19,9 +18,9 @@ log = getLogger(__name__)
class ResetDriverWatchdog(Thread):
"""Watchdog to catch the reset_driver action."""
def __init__(self, pi_control_device="/dev/piControl0"):
def __init__(self, picontrol_device: str):
super(ResetDriverWatchdog, self).__init__()
self.procimg = pi_control_device
self.procimg = picontrol_device
self.daemon = True
self._calls = []
self._exit = False
@@ -35,7 +34,7 @@ class ResetDriverWatchdog(Thread):
"""
Mainloop of watchdog for reset_driver.
If the thread can not open the process image or the IOCTL is not
If the thread cannot open the process image or the IOCTL is not
implemented (wheezy), the thread function will stop. The trigger
property will always return True.
"""
@@ -51,7 +50,7 @@ class ResetDriverWatchdog(Thread):
)
return
# The ioctl will return 2 byte (c-type int)
# The ioctl will return 2 bytes (c-type int)
byte_buff = bytearray(2)
while not self._exit:
try:
@@ -73,7 +72,7 @@ class ResetDriverWatchdog(Thread):
def register_call(self, function):
"""Register a function, if watchdog triggers."""
if not callable(function):
return ValueError("Function is not callable.")
raise ValueError("Function is not callable.")
if function not in self._calls:
self._calls.append(function)
@@ -89,7 +88,7 @@ class ResetDriverWatchdog(Thread):
log.debug("leave ResetDriverWatchdog.stop()")
def unregister_call(self, function=None):
"""Remove a function call on watchdog trigger."""
"""Remove a function from the watchdog trigger."""
if function is None:
self._calls.clear()
elif function in self._calls:

View File

@@ -9,7 +9,7 @@ __version__ = "1.4.0"
import logging
import sys
from argparse import ArgumentParser, Namespace
from argparse import ArgumentParser, Namespace, SUPPRESS
from configparser import ConfigParser
from enum import Enum
from os import R_OK, W_OK, access, environ, getpid, remove
@@ -262,6 +262,16 @@ parser = ArgumentParser(
prog=programname,
description="Program description",
)
# Use session bus of D-Bus for local testing and development proposes (hidden)
parser.add_argument(
"--use-session-bus",
dest="use_session_bus",
action="store_true",
default=False,
help=SUPPRESS,
)
parser.add_argument("--version", action="version", version=f"%(prog)s {program_version}")
parser.add_argument(
"-f",