6 Commits

Author SHA1 Message Date
Sven Sager
c3417c38a3 feat(systemd): Add revpi-iosbus.service files
Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-17 09:59:44 +01:00
Sven Sager
1415055483 feat: Enhance MiddlewareDaemon with driver reset handling
Refactored `MiddlewareDaemon` to integrate `ResetDriverWatchdog` for
monitoring `procimg` changes and trigger D-Bus restarts when necessary.
Removed debus signal `NotifyDriverReset` handling from
`BusProviderIos1`.

Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-17 09:02:40 +01:00
Sven Sager
fbfda04d7c feat: Add support for configurable procimg path
Introduced a new `--procimg` argument to specify the process image path.
Updated `MiddlewareDaemon` to dynamically reset D-Bus when `procimg`
changes and pass the configured path to bus providers.

Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-17 08:37:24 +01:00
Sven Sager
9e5a9e0141 chore: Add logging for D-Bus publishing and connection closure
Added informational log messages to track when `REVPI_DBUS_NAME` is
published and its connection is closed in both `BusProviderIos1` and
`BusProviderMiddleware1`.

Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-13 09:07:46 +01:00
Sven Sager
f6f83e236e feat: Introduce configurable bus_provider for MiddlewareDaemon
Added support for a configurable `bus_provider` via command-line
arguments, enabling either "middleware" or "ios" as valid options.
Refactored `MiddlewareDaemon` to dynamically handle the selected bus
provider and streamline D-Bus management.

Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-13 09:03:59 +01:00
Sven Sager
8db3af9ca1 feat: Add name property to BusProvider classes
Introduced a `name` property returning `REVPI_DBUS_NAME` in classes
`BusProviderMiddleware1` and `BusProviderIos1`.

Signed-off-by: Sven Sager <s.sager@kunbus.com>
2026-03-13 08:55:24 +01:00
8 changed files with 130 additions and 53 deletions

View File

@@ -0,0 +1,8 @@
# Additional options that are passed to revpi-ios.
# add '-f /var/log/revpi-ios.log' to write logs to own log file
# add '-v' or '-vv' for verbose logging
DAEMON_OPTS=""
# In addition to journalctl, use your own additional log file
# DAEMON_OPTS="-f /var/log/revpi-ios.log"

View File

@@ -0,0 +1,14 @@
/var/log/revpi-ios.log
{
rotate 6
weekly
maxsize 1M
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
systemctl kill --signal=SIGUSR1 revpi-ios > /dev/null 2>&1 || true
endscript
}

View File

@@ -0,0 +1,12 @@
[Unit]
Description=D-Bus interface for Inputs/Outputs of Revolution Pi
[Service]
EnvironmentFile=-/etc/default/revpi-ios
Type=notify
NotifyAccess=all
ExecStart=/usr/sbin/revpi-middleware ios $DAEMON_OPTS
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,11 @@
[Unit]
Description=D-Bus interface for Inputs/Outputs of Revolution Pi
[Service]
EnvironmentFile=-/etc/default/revpi-ios
Type=notify-reload
NotifyAccess=all
ExecStart=/usr/sbin/revpi-middleware ios $DAEMON_OPTS
[Install]
WantedBy=multi-user.target

View File

@@ -3,35 +3,43 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
"""Main daemon of revpi-middleware.""" """Main daemon of revpi-middleware."""
from enum import Enum
from logging import getLogger from logging import getLogger
from os import getuid, environ from os import environ
from threading import Event from threading import Event
from time import perf_counter from time import perf_counter
from typing import List
from gi.repository import Gio from gi.repository import Gio
from . import proginit as pi from . import proginit as pi
from .dbus_ios1 import BusProviderIos1 from .dbus_ios1 import BusProviderIos1
from .dbus_middleware1 import BusProviderMiddleware1 from .dbus_middleware1 import BusProviderMiddleware1
from .dbus_middleware1.process_image.process_image_helper import ResetDriverWatchdog
log = getLogger(__name__) log = getLogger(__name__)
class BusProvider(Enum):
middleware = "middleware"
ios = "ios"
class MiddlewareDaemon: class MiddlewareDaemon:
"""Main program of MiddlewareDaemon class.""" """Main program of MiddlewareDaemon class."""
def __init__(self): def __init__(self, bus_provider: BusProvider):
"""Init MiddlewareDaemon class.""" """Init MiddlewareDaemon class."""
log.debug("enter MiddlewareDaemon.__init__") log.debug("enter MiddlewareDaemon.__init__")
self._cycle_time = 1.0 self._cycle_time = 1.0
self.do_cycle = Event() self.do_cycle = Event()
self._force_dbus_restart = False
self._reconfigure = False self._reconfigure = False
self._running = True self._running = True
self.bp_middleware1 = None self.bus_provider = None
self.bp_ios1 = None self.bus_provider_selected = bus_provider
self.wd_reset = ResetDriverWatchdog("")
self._configure() self._configure()
log.debug("leave MiddlewareDaemon.__init__") log.debug("leave MiddlewareDaemon.__init__")
@@ -41,6 +49,17 @@ class MiddlewareDaemon:
log.debug("enter MiddlewareDaemon._configure") log.debug("enter MiddlewareDaemon._configure")
pi.reload_conf() pi.reload_conf()
if self._force_dbus_restart:
self.dbus_stop()
self._force_dbus_restart = False
if pi.pargs.procimg != self.wd_reset.procimg:
self.dbus_stop()
self.wd_reset.stop()
self.wd_reset = ResetDriverWatchdog(pi.pargs.procimg)
self.wd_reset.register_call(self._reset_driver_callack)
log.debug("leave MiddlewareDaemon._configure") log.debug("leave MiddlewareDaemon._configure")
@staticmethod @staticmethod
@@ -54,44 +73,44 @@ class MiddlewareDaemon:
return environ.get(environ_name, bus_address) return environ.get(environ_name, bus_address)
def _reset_driver_callack(self, *args) -> None:
log.debug("enter MiddlewareDaemon._reset_driver_callack")
if self.bus_provider_selected is BusProvider.ios:
self._force_dbus_restart = True
self.reload_config()
log.debug("leave MiddlewareDaemon._reset_driver_callack")
def dbus_start(self): def dbus_start(self):
log.debug("enter MiddlewareDaemon.dbus_start") log.debug("enter MiddlewareDaemon.dbus_start")
dbus_middleware1_running = self.bp_middleware1 and self.bp_middleware1.is_alive() dbus_running = self.bus_provider and self.bus_provider.is_alive()
if not dbus_middleware1_running: if not dbus_running:
self.bp_middleware1 = BusProviderMiddleware1(self._get_bus_address()) if self.bus_provider_selected is BusProvider.middleware:
self.bp_middleware1.start() self.bus_provider = BusProviderMiddleware1(
dbus_address=self._get_bus_address(),
dbus_ios1_running = self.bp_ios1 and self.bp_ios1.is_alive() picontrol_device=pi.pargs.procimg,
if not dbus_ios1_running: )
if self.bp_middleware1.published.wait(timeout=10.0): elif self.bus_provider_selected is BusProvider.ios:
self.bp_ios1 = BusProviderIos1(self._get_bus_address()) self.bus_provider = BusProviderIos1(
self.bp_ios1.start() dbus_address=self._get_bus_address(),
picontrol_device=pi.pargs.procimg,
)
else: else:
log.error("dbus middleware1 provider thread is not alive - can not start ios1 bus") raise ValueError("Unknown bus provider")
self.bus_provider.start()
log.debug("leave MiddlewareDaemon.dbus_start") log.debug("leave MiddlewareDaemon.dbus_start")
def dbus_stop(self, bus_filter: List[object] = None): def dbus_stop(self):
log.debug("enter MiddlewareDaemon.dbus_stop") log.debug("enter MiddlewareDaemon.dbus_stop")
if bus_filter is None: if self.bus_provider:
bus_filter = [ self.bus_provider.stop()
self.bp_middleware1, self.bus_provider.join(timeout=10.0)
self.bp_ios1, if self.bus_provider.is_alive():
] log.warning(f"dbus {self.bus_provider.name} thread is still alive")
if self.bp_middleware1 and self.bp_middleware1 in bus_filter:
self.bp_middleware1.stop()
self.bp_middleware1.join(timeout=10.0)
if self.bp_middleware1.is_alive():
log.warning("dbus middleware1 provider thread is still alive")
if self.bp_ios1 and self.bp_ios1 in bus_filter:
self.bp_ios1.stop()
self.bp_ios1.join(timeout=10.0)
if self.bp_ios1.is_alive():
log.warning("dbus ios1 provider thread is still alive")
log.debug("leave MiddlewareDaemon.dbus_stop") log.debug("leave MiddlewareDaemon.dbus_stop")
@@ -122,7 +141,6 @@ class MiddlewareDaemon:
# Startup tasks # Startup tasks
self.dbus_start() self.dbus_start()
pi.startup_complete()
# Go into mainloop of daemon # Go into mainloop of daemon
while self._running: while self._running:
ot = perf_counter() ot = perf_counter()
@@ -132,19 +150,15 @@ class MiddlewareDaemon:
if self._reconfigure: if self._reconfigure:
self._configure() self._configure()
self._reconfigure = False self._reconfigure = False
pi.startup_complete()
# Monitor bus providers for errors and restart them # Monitor bus providers for errors and restart them
restart = False if not (self.bus_provider and self.bus_provider.is_alive()):
if not (self.bp_middleware1 and self.bp_middleware1.is_alive()): log.warning(f"dbus {self.bus_provider.name} thread is not alive - restarting")
log.warning("dbus middleware1 provider thread is not alive - restarting")
restart = True
if not (self.bp_ios1 and self.bp_ios1.is_alive()):
log.warning("dbus ios1 provider thread is not alive - restarting")
restart = True
if restart:
self.dbus_start() self.dbus_start()
if self.bus_provider and self.bus_provider.published.is_set():
pi.startup_complete()
# Cycle time calculation # Cycle time calculation
dm = divmod(ot - perf_counter(), self._cycle_time) dm = divmod(ot - perf_counter(), self._cycle_time)
# For float the result is (q, a % b), where q is usually math.floor(a / b) but may be 1 less than that. # For float the result is (q, a % b), where q is usually math.floor(a / b) but may be 1 less than that.

View File

@@ -92,16 +92,10 @@ class BusProviderIos1(Thread):
*lst_interfaces, *lst_interfaces,
) )
self.published.set() self.published.set()
log.info(f"published {REVPI_DBUS_NAME} on {self._bus_address}")
except Exception as e: except Exception as e:
log.error(f"can not publish dbus {REVPI_DBUS_NAME}: {e}") log.error(f"can not publish dbus {REVPI_DBUS_NAME}: {e}")
# Subscribe to NotifyDriverReset on middleware1 dbus
iface_pi_control = bus.get(
"com.revolutionpi.middleware1",
"/com/revolutionpi/middleware1",
)["com.revolutionpi.middleware1.PiControl"]
iface_pi_control.onNotifyDriverReset = self.stop
try: try:
self._loop.run() self._loop.run()
except Exception as e: except Exception as e:
@@ -109,6 +103,7 @@ class BusProviderIos1(Thread):
bus.con.close() bus.con.close()
self._modio.cleanup() self._modio.cleanup()
log.info(f"closed {REVPI_DBUS_NAME} connection to {self._bus_address}")
log.debug("leave BusProviderIos1.run") log.debug("leave BusProviderIos1.run")
@@ -117,6 +112,10 @@ class BusProviderIos1(Thread):
self._loop.quit() self._loop.quit()
log.debug("leave BusProviderIos1.stop") log.debug("leave BusProviderIos1.stop")
@property
def name(self) -> str:
return REVPI_DBUS_NAME
@property @property
def running(self): def running(self):
return self._loop.is_running() return self._loop.is_running()

View File

@@ -55,6 +55,7 @@ class BusProviderMiddleware1(Thread):
*lst_interfaces, *lst_interfaces,
) )
self.published.set() self.published.set()
log.info(f"published {REVPI_DBUS_NAME} on {self._bus_address}")
except Exception as e: except Exception as e:
log.error(f"can not publish dbus {REVPI_DBUS_NAME}: {e}") log.error(f"can not publish dbus {REVPI_DBUS_NAME}: {e}")
@@ -64,6 +65,7 @@ class BusProviderMiddleware1(Thread):
log.error(f"can not run dbus mainloop: {e}") log.error(f"can not run dbus mainloop: {e}")
bus.con.close() bus.con.close()
log.info(f"closed {REVPI_DBUS_NAME} connection to {self._bus_address}")
# Clean up all interfaces # Clean up all interfaces
for interface in lst_interfaces: for interface in lst_interfaces:
@@ -78,6 +80,10 @@ class BusProviderMiddleware1(Thread):
self._loop.quit() self._loop.quit()
log.debug("leave BusProviderMiddleware1.stop") log.debug("leave BusProviderMiddleware1.stop")
@property
def name(self) -> str:
return REVPI_DBUS_NAME
@property @property
def running(self): def running(self):
return self._loop.is_running() return self._loop.is_running()

View File

@@ -5,7 +5,7 @@
from logging import getLogger from logging import getLogger
from .daemon import MiddlewareDaemon from .daemon import MiddlewareDaemon, BusProvider
from . import proginit as pi from . import proginit as pi
log = getLogger(__name__) log = getLogger(__name__)
@@ -27,6 +27,19 @@ pi.parser.add_argument(
default="/etc/{0}/{0}.conf".format(pi.programname), default="/etc/{0}/{0}.conf".format(pi.programname),
help="application configuration file", help="application configuration file",
) )
pi.parser.add_argument(
"--procimg",
dest="procimg",
default="/dev/piControl0",
help="Path to process image",
)
pi.parser.add_argument(
"bus_provider",
default="middleware",
nargs="?",
choices=["middleware", "ios"],
help="bus provider to use",
)
def main() -> int: def main() -> int:
@@ -35,7 +48,7 @@ def main() -> int:
# Parse command line arguments # Parse command line arguments
pi.init_app() pi.init_app()
root = MiddlewareDaemon() root = MiddlewareDaemon(BusProvider(pi.pargs.bus_provider))
# Set signals # Set signals
signal.signal(signal.SIGHUP, lambda n, f: root.reload_config()) signal.signal(signal.SIGHUP, lambda n, f: root.reload_config())