diff --git a/.github/workflows/01-make-dist.yml b/.github/workflows/01-make-dist.yml index 5dad7a7122..863420c565 100644 --- a/.github/workflows/01-make-dist.yml +++ b/.github/workflows/01-make-dist.yml @@ -141,6 +141,7 @@ jobs: libfreeipmi-dev libipmimonitoring-dev \ libavahi-common-dev libavahi-core-dev libavahi-client-dev \ libgpiod-dev \ + libglib2.0-dev \ bash dash ksh busybox \ libneon27-gnutls-dev \ build-essential git-core libi2c-dev i2c-tools lm-sensors \ diff --git a/configure.ac b/configure.ac index cae2331c24..da830e1522 100644 --- a/configure.ac +++ b/configure.ac @@ -2316,6 +2316,7 @@ AC_ARG_WITH(drivers, if test -z "${with_powerman}"; then with_powerman="yes"; fi if test -z "${with_modbus}"; then with_modbus="yes"; fi if test -z "${with_ipmi}"; then with_ipmi="yes"; fi + if test -z "${with_upower}"; then with_upower="yes"; fi dnl Platform-dependent snowflakes that are required or auto: if test -z "${with_gpio}"; then @@ -2353,6 +2354,7 @@ AC_ARG_WITH(drivers, if test -z "${with_powerman}"; then with_powerman="${withval}"; fi if test -z "${with_modbus}"; then with_modbus="${withval}"; fi if test -z "${with_ipmi}"; then with_ipmi="${withval}"; fi + if test -z "${with_upower}"; then with_upower="${withval}"; fi if test -z "${with_gpio}"; then with_gpio="${withval}"; fi if test -z "${with_linux_i2c}"; then with_linux_i2c="${withval}"; fi if test -z "${with_macosx_ups}"; then with_macosx_ups="${withval}"; fi @@ -2369,7 +2371,8 @@ AC_ARG_WITH(drivers, SERIAL_DRIVERLIST USB_LIBUSB_DRIVERLIST SERIAL_USB_DRIVERLIST SNMP_DRIVERLIST NEONXML_DRIVERLIST MACOSX_DRIVERLIST MODBUS_DRIVERLIST LINUX_I2C_DRIVERLIST - POWERMAN_DRIVERLIST IPMI_DRIVERLIST GPIO_DRIVERLIST" + POWERMAN_DRIVERLIST IPMI_DRIVERLIST GPIO_DRIVERLIST + UPOWER_DRIVERLIST" dnl Gets ONE Makefile assignment value get_drivers_makefile_value() ( @@ -2520,6 +2523,11 @@ AC_ARG_WITH(drivers, [AC_MSG_NOTICE([Requiring --with-gpio=yes for driver "$DRV"]) with_gpio=yes] )], + [UPOWER_DRIVERLIST], [ + AS_IF([test -z "${with_upower}"], + [AC_MSG_NOTICE([Requiring --with-upower=yes for driver "$DRV"]) + with_upower=yes] + )], [AC_MSG_WARN([Unhandled DRVLIST_NAME=$DRVLIST_NAME])] ) @@ -2553,7 +2561,7 @@ dnl check for --with-all (or --without-all, or --with-all=auto) flag AC_MSG_CHECKING(for --with-all) AC_ARG_WITH(all, - AS_HELP_STRING([--with-all], [enable serial, usb, snmp, neon, ipmi, powerman, modbus, gpio (currently on Linux released after ~2018), linux_i2c (on Linux), macosx-ups (on MacOS), cgi, dev, avahi, nut-scanner, nutconf, dev-libnutconf, pynut]), + AS_HELP_STRING([--with-all], [enable serial, usb, snmp, neon, ipmi, powerman, modbus, gpio (currently on Linux released after ~2018), linux_i2c (on Linux), upower, macosx-ups (on MacOS), cgi, dev, avahi, nut-scanner, nutconf, dev-libnutconf, pynut]), [ if test -n "${withval}"; then dnl Note: we allow "no" as a positive value, because @@ -2567,6 +2575,7 @@ AC_ARG_WITH(all, if test -z "${with_powerman}"; then with_powerman="${withval}"; fi if test -z "${with_modbus}"; then with_modbus="${withval}"; fi if test -z "${with_ipmi}"; then with_ipmi="${withval}"; fi + if test -z "${with_upower}"; then with_upower="${withval}"; fi dnl Platform-dependent snowflakes that are required or auto: if test -z "${with_gpio}"; then @@ -2845,6 +2854,7 @@ dnl Platform-dependent drivers, currently their detection code is directly dnl spelled out in configure.ac NUT_ARG_WITH([macosx_ups], [build and install Mac OS X Power Sources meta-driver], [auto]) NUT_ARG_WITH([linux_i2c], [build and install i2c drivers], [auto]) +NUT_ARG_WITH([upower], [build and install UPower driver (requires GLib/GIO)], [auto]) dnl A Python GUI client application for the sysadmin desktop dnl (not necessarily on the NUT server itself): @@ -5432,6 +5442,34 @@ AS_IF([test x"$have_PKG_CONFIG" = xyes], ) AC_MSG_RESULT(${have_cppunit}) +dnl ---------------------------------------------------------------------- +dnl Check for GLib / GIO (Required for UPower driver) +dnl ---------------------------------------------------------------------- + +have_glib=no + +if test "${with_upower}" != "no"; then +AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ifdef([PKG_CHECK_MODULES], + [PKG_CHECK_MODULES(GLIB, [glib-2.0 gio-2.0], [have_glib=yes], [have_glib=no])], + [have_glib=no]) + ], + [AC_MSG_WARN([pkg-config not found, cannot look properly for glib-2.0/gio-2.0])] +) +fi + +if test "${have_glib}" = "yes"; then + AC_DEFINE(HAVE_GLIB, 1, [Define to 1 if GLib and GIO are available]) +else + if test "${with_upower}" = "yes"; then + AC_MSG_ERROR([GLib/GIO not found but requested via --with-upower]) + fi + AC_MSG_WARN([GLib/GIO not found. The 'upower' driver will not be built.]) +fi + +AM_CONDITIONAL(HAVE_GLIB, test "${have_glib}" = "yes") +AC_MSG_RESULT(${have_glib}) + dnl On some systems, CppUnit inexplicably fails with trivial assertions dnl so it should not be enabled with those environments, corrupting the dnl test results with misleading errors. diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 7724634bc3..c16949759d 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -1463,6 +1463,38 @@ else !DOC_INSTALL_SELECTED_MANS_PROGS_BUILT LINKMAN_PAGES_DRIVERS += $(SRC_GPIO_PAGES) endif !DOC_INSTALL_SELECTED_MANS_PROGS_BUILT +# (--with-upower) +SRC_UPOWER_PAGES = upower_dbus.txt +INST_MAN_UPOWER_PAGES = upower_dbus.$(MAN_SECTION_CMD_SYS) +DIST_ALL_MAN_PAGES += $(INST_MAN_UPOWER_PAGES) + +if ! SOME_DRIVERS +if WITH_MANS +MAN_UPOWER_PAGES = $(INST_MAN_UPOWER_PAGES) +endif WITH_MANS + +if HAVE_GLIB +mansys_DATA += $(MAN_UPOWER_PAGES) +endif HAVE_GLIB +endif ! SOME_DRIVERS + +INST_HTML_UPOWER_MANS = \ + upower_dbus.html +DIST_ALL_HTML_PAGES += $(INST_HTML_UPOWER_MANS) + +if ! SOME_DRIVERS +HTML_UPOWER_MANS = $(INST_HTML_UPOWER_MANS) +endif ! SOME_DRIVERS + +# FIXME? Refine for HTML etc.? +if DOC_INSTALL_SELECTED_MANS_PROGS_BUILT +if HAVE_GLIB +LINKMAN_PAGES_DRIVERS += $(SRC_UPOWER_PAGES) +endif HAVE_GLIB +else !DOC_INSTALL_SELECTED_MANS_PROGS_BUILT +LINKMAN_PAGES_DRIVERS += $(SRC_UPOWER_PAGES) +endif !DOC_INSTALL_SELECTED_MANS_PROGS_BUILT + # Summarize which pages we ultimately install: MAN_MANS = if WITH_MANS @@ -1486,7 +1518,8 @@ MAN_MANS += \ $(MAN_MACOSX_PAGES) \ $(MAN_MODBUS_PAGES) \ $(MAN_LINUX_I2C_PAGES) \ - $(MAN_GPIO_PAGES) + $(MAN_GPIO_PAGES) \ + $(MAN_UPOWER_PAGES) endif WITH_MANS SRC_DRIVERS_PAGES = \ @@ -1500,7 +1533,8 @@ SRC_DRIVERS_PAGES = \ $(SRC_MACOSX_PAGES) \ $(SRC_MODBUS_PAGES) \ $(SRC_LINUX_I2C_PAGES) \ - $(SRC_GPIO_PAGES) + $(SRC_GPIO_PAGES) \ + $(SRC_UPOWER_PAGES) if SOME_DRIVERS # (Legacy note) The list above probably came up empty in this case, so make sure @@ -1576,7 +1610,8 @@ HTML_MANS = \ $(HTML_MACOSX_MANS) \ $(HTML_MODBUS_MANS) \ $(HTML_LINUX_I2C_MANS) \ - $(HTML_GPIO_MANS) + $(HTML_GPIO_MANS) \ + $(HTML_UPOWER_MANS) # htmlmandir is set by autoconf/automake htmlman_DATA = diff --git a/docs/man/upower_dbus.txt b/docs/man/upower_dbus.txt new file mode 100644 index 0000000000..80833d8a82 --- /dev/null +++ b/docs/man/upower_dbus.txt @@ -0,0 +1,71 @@ +UPOWER_DBUS(8) +============== + +NAME +---- +upower_dbus - Driver for UPower devices via D-Bus + +SYNOPSIS +-------- +*upower_dbus* -h + +*upower_dbus* -a 'UPS_NAME' ['OPTIONS'] + +NOTE: This man page only documents the specific features of the +*upower_dbus* driver. For information about the core driver, see +linkman:nutupsdrv[8]. + +DESCRIPTION +----------- +This driver allows the monitoring of power devices (UPS, batteries) that are +managed by the UPower daemon via the D-Bus system bus. + +It maps UPower properties (state, percentage, voltage, etc.) to standard NUT +variables, allowing NUT clients to monitor devices that are natively supported +by the OS desktop environment but not directly by other NUT drivers. + +EXTRA ARGUMENTS +--------------- +This driver supports the following optional settings in linkman:ups.conf[5]: + +*port*:: +The UPower Object Path of the device to monitor. + +If set to `auto` (the default), the driver will scan for the first UPower device +whose object path contains the string "ups_". + +If set to a specific path (e.g., `/org/freedesktop/UPower/devices/ups_hiddev0`), +the driver will attempt to monitor that specific device. + +*lowbatt*:: +The battery percentage threshold below which the `LB` (Low Battery) status +flag is set. The default value is 20.0. + +INSTALLATION +------------ +To use this driver, add a section to your *ups.conf*: + +---- +[myups] + driver = upower_dbus + port = auto + lowbatt = 20 + desc = "Laptop Battery or generic UPS via UPower" +---- + +REQUIREMENTS +------------ +This driver requires the `upower` daemon to be running and accessible via the +D-Bus system bus. It also requires GLib 2.0 and GIO libraries. + +AUTHOR +------ +Tim Niemueller + +SEE ALSO +-------- +linkman:ups.conf[5], linkman:nutupsdrv[8], linkman:upsc[8] + +Internet Resources: +~~~~~~~~~~~~~~~~~~~ +The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ diff --git a/docs/nut.dict b/docs/nut.dict index 1733c0a17f..3343fd1e38 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3632 utf-8 +personal_ws-1.1 en 3638 utf-8 AAC AAS ABI @@ -241,6 +241,7 @@ Cygwin DATACABLE DATAPATH DBCF +DBUS DCE DCF DCO @@ -434,7 +435,9 @@ GES GETADDRINFO GETPID GID +GIO GITREV +GLib GND GNUmakefile GObject @@ -850,6 +853,7 @@ Netman NetworkUPSTools Neus Niels +Niemueller Niklas Niro NixOS @@ -1925,6 +1929,7 @@ datasheet datasize datastale dblatex +dbus dcd dcn ddk @@ -2133,6 +2138,7 @@ formatstring fosshost fp freebsd +freedesktop freeipmi freetype frob diff --git a/drivers/Makefile.am b/drivers/Makefile.am index b9658bb51a..ebad5fb9ea 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -167,6 +167,7 @@ LINUX_I2C_DRIVERLIST = asem pijuice hwmon_ina219 POWERMAN_DRIVERLIST = powerman-pdu IPMI_DRIVERLIST = nut-ipmipsu GPIO_DRIVERLIST = generic_gpio_libgpiod +UPOWER_DRIVERLIST = upower_dbus # distribute all drivers, even ones that are not built by default EXTRA_PROGRAMS = $(SERIAL_DRIVERLIST) $(USB_DRIVERLIST) $(SERIAL_USB_DRIVERLIST) @@ -174,6 +175,7 @@ EXTRA_PROGRAMS += $(SNMP_DRIVERLIST) $(NEONXML_DRIVERLIST) $(MACOSX_DRIVERLIST) EXTRA_PROGRAMS += $(LINUX_I2C_DRIVERLIST) EXTRA_PROGRAMS += $(NUTSW_DRIVERLIST) EXTRA_PROGRAMS += $(GPIO_DRIVERLIST) +EXTRA_PROGRAMS += $(UPOWER_DRIVERLIST) # construct the list of drivers to build if SOME_DRIVERS @@ -364,6 +366,16 @@ mge_shut_SOURCES = usbhid-ups.c libshut.c libhid.c hidparser.c mge-hid.c mge_shut_CFLAGS = $(AM_CFLAGS) -DSHUT_MODE=1 mge_shut_LDADD = $(LDADD_DRIVERS_SERIAL) -lm +# UPower +if HAVE_GLIB + # Only build upower if GLib was found + driverexec_PROGRAMS += $(UPOWER_DRIVERLIST) + + upower_dbus_SOURCES = upower_dbus.c + upower_dbus_LDADD = $(LDADD_DRIVERS) $(GLIB_LIBS) + upower_dbus_CFLAGS = $(AM_CFLAGS) $(GLIB_CFLAGS) +endif + # SNMP # Please keep the MIB table below sorted roughly alphabetically (incidentally # by vendor too) to ease maintenance and codebase fork resynchronisations diff --git a/drivers/upower_dbus.c b/drivers/upower_dbus.c new file mode 100644 index 0000000000..3bdea42bd8 --- /dev/null +++ b/drivers/upower_dbus.c @@ -0,0 +1,355 @@ +/* drivers/upower_dbus.c - Driver for UPower via D-Bus + * + * Copyright (C) 2026 Tim Niemueller + * + * Links against standard NUT driver core. + * Requires: glib-2.0, gio-2.0 + */ + +#include "main.h" +#include "dstate.h" + +#include +#include +#include +#include + +#define DRIVER_NAME "UPower D-Bus Driver" +#define DRIVER_VERSION "0.1" + +/* UPower Constants */ +#define UPOWER_BUS "org.freedesktop.UPower" +#define UPOWER_PATH "/org/freedesktop/UPower" +#define UPOWER_IFACE "org.freedesktop.UPower" +#define DEVICE_IFACE "org.freedesktop.UPower.Device" +#define PROPS_IFACE "org.freedesktop.DBus.Properties" + +/* Global DBus Connection Objects */ +static GDBusConnection *connection = NULL; +static char *ups_object_path = NULL; +static double lowbatt_pct = 20.0; + +/* NUT Driver Info Structure */ +upsdrv_info_t upsdrv_info = { + DRIVER_NAME, + DRIVER_VERSION, + "Tim Niemueller ", + DRV_EXPERIMENTAL, + { NULL } +}; + +/* -------------------------------------------------------------------------- */ +/* Map UPower State to NUT Status */ +/* -------------------------------------------------------------------------- */ +static void set_nut_status(guint state, gdouble percentage) +{ + /* Clear previous status */ + status_init(); + + /* UPower States: + * 1: Charging + * 2: Discharging + * 3: Empty + * 4: Fully Charged + * 5: Pending Charge + * 6: Pending Discharge + */ + + switch (state) { + case 1: /* Charging */ + status_set("OL CHRG"); + break; + case 2: /* Discharging */ + status_set("OB DISCHRG"); + break; + case 4: /* Full */ + status_set("OL"); + break; + case 3: /* Empty */ + status_set("OB LB"); + break; + default: /* Unknown / Pending */ + status_set("OL"); /* Default to Online to prevent shutdowns */ + break; + } + + /* Override for Low Battery based on percentage */ + if (percentage < lowbatt_pct) { + status_set("LB"); + } + + status_commit(); +} + +/* -------------------------------------------------------------------------- */ +/* Find the First UPS Device (auto) or a specific one (device_path not auto) */ +/* -------------------------------------------------------------------------- */ +static int find_ups_device(void) +{ + GError *error = NULL; + GVariant *result; + GVariantIter iter; + gchar *obj_path; + int found = 0; + + /* Call EnumerateDevices on the main UPower object */ + result = g_dbus_connection_call_sync( + connection, + UPOWER_BUS, UPOWER_PATH, UPOWER_IFACE, + "EnumerateDevices", + NULL, /* No params */ + G_VARIANT_TYPE("(ao)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error + ); + + if (error) { + fatalx(EXIT_FAILURE, "DBus Error enumerating devices: %s", error->message); + } + + /* Iterate the result array of object paths */ + g_variant_iter_init(&iter, g_variant_get_child_value(result, 0)); + + while (g_variant_iter_loop(&iter, "o", &obj_path)) { + upsdebugx(2, "Enumerated UPower device: %s", obj_path); + + /* Check if we are looking for a specific path */ + if (device_path && strcmp(device_path, "auto") != 0 && *device_path != '\0') { + if (strstr(obj_path, device_path)) { + upsdebugx(1, "Match found for specific path '%s': %s", device_path, obj_path); + ups_object_path = strdup(obj_path); + found = 1; + break; + } + } else { + /* Auto-detection: Check for substring "ups_" */ + if (strstr(obj_path, "ups_")) { + upsdebugx(1, "Auto-detected UPS device: %s", obj_path); + ups_object_path = strdup(obj_path); + found = 1; + break; + } + } + } + + g_variant_unref(result); + return found; +} + +/* -------------------------------------------------------------------------- */ +/* List Available UPower Devices */ +/* -------------------------------------------------------------------------- */ +static void list_upower_devices(void) +{ + GError *error = NULL; + GDBusConnection *conn; + GVariant *result; + GVariantIter iter; + gchar *obj_path; + + /* Initialize GLib type system */ + #if !GLIB_CHECK_VERSION(2, 36, 0) + g_type_init(); + #endif + + conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!conn) { + printf("Error: Failed to connect to System Bus: %s\n", error->message); + g_error_free(error); + return; + } + + result = g_dbus_connection_call_sync( + conn, + UPOWER_BUS, UPOWER_PATH, UPOWER_IFACE, + "EnumerateDevices", + NULL, + G_VARIANT_TYPE("(ao)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error + ); + + if (error) { + printf("Error enumerating devices: %s\n", error->message); + g_error_free(error); + g_object_unref(conn); + return; + } + + printf("\nAvailable UPower devices:\n"); + g_variant_iter_init(&iter, g_variant_get_child_value(result, 0)); + + while (g_variant_iter_loop(&iter, "o", &obj_path)) { + printf(" %s\n", obj_path); + } + + g_variant_unref(result); + g_object_unref(conn); +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Help / Usage */ +/* -------------------------------------------------------------------------- */ +void upsdrv_help(void) +{ + list_upower_devices(); +} +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Initialize Command Line Args */ +/* -------------------------------------------------------------------------- */ +void upsdrv_makevartable(void) +{ + addvar(VAR_VALUE, "lowbatt", "Low battery threshold (default: 20%)"); +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Initialize UPS Connection */ +/* -------------------------------------------------------------------------- */ +void upsdrv_initups(void) +{ + GError *error = NULL; + const char *val; + + /* Initialize GLib type system */ + #if !GLIB_CHECK_VERSION(2, 36, 0) + g_type_init(); + #endif + + /* Connect to System Bus */ + connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) { + fatalx(EXIT_FAILURE, "Failed to connect to System Bus: %s", error->message); + } + + /* Find the UPS */ + if (!find_ups_device()) { + fatalx(EXIT_FAILURE, "No UPower device found containing 'ups_' in path."); + } + + upslogx(LOG_INFO, "Connected to UPower Device: %s", ups_object_path); + dstate_setinfo("driver.parameter.port", "%s", ups_object_path); + + /* Handle low battery threshold */ + if ((val = getval("lowbatt"))) { + lowbatt_pct = atof(val); + upsdebugx(1, "Low battery threshold set to: %.1f%%", lowbatt_pct); + } +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Update Info (The Main Loop) */ +/* -------------------------------------------------------------------------- */ +void upsdrv_updateinfo(void) +{ + GError *error = NULL; + GVariant *result, *props; + GVariantIter iter; + gchar *key; + GVariant *value; + + /* Fetch All Properties */ + result = g_dbus_connection_call_sync( + connection, + UPOWER_BUS, ups_object_path, PROPS_IFACE, + "GetAll", + g_variant_new("(s)", DEVICE_IFACE), + G_VARIANT_TYPE("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error + ); + + if (error) { + dstate_datastale(); + upslogx(LOG_WARNING, "Failed to get properties: %s", error->message); + g_error_free(error); + return; + } + + /* Parse Dictionary */ + props = g_variant_get_child_value(result, 0); + g_variant_iter_init(&iter, props); + + gdouble voltage = 0.0, percentage = 0.0; + gint64 time_empty = 0; + guint state = 0; + const gchar *model = "Unknown"; + const gchar *vendor = "Unknown"; + const gchar *serial = "Unknown"; + + while (g_variant_iter_loop(&iter, "{sv}", &key, &value)) { + if (g_strcmp0(key, "Voltage") == 0) { + voltage = g_variant_get_double(value); + } else if (g_strcmp0(key, "Percentage") == 0) { + percentage = g_variant_get_double(value); + } else if (g_strcmp0(key, "State") == 0) { + state = g_variant_get_uint32(value); + } else if (g_strcmp0(key, "TimeToEmpty") == 0) { + time_empty = g_variant_get_int64(value); + } else if (g_strcmp0(key, "Model") == 0) { + model = g_variant_get_string(value, NULL); + } else if (g_strcmp0(key, "Vendor") == 0) { + vendor = g_variant_get_string(value, NULL); + } else if (g_strcmp0(key, "Serial") == 0) { + serial = g_variant_get_string(value, NULL); + } + } + + /* Update NUT Data Store (dstate) */ + dstate_setinfo("battery.charge", "%.1f", percentage); + dstate_setinfo("battery.voltage", "%.1f", voltage); + + if (time_empty > 0) { + dstate_setinfo("battery.runtime", "%lld", (long long)time_empty); + } + + dstate_setinfo("device.mfr", "%s", vendor); + dstate_setinfo("device.model", "%s", model); + dstate_setinfo("device.serial", "%s", serial); + + /* Update Status Flags */ + set_nut_status(state, percentage); + + /* Commit data */ + dstate_dataok(); + + g_variant_unref(props); + g_variant_unref(result); +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Tweak Program Names */ +/* -------------------------------------------------------------------------- */ +void upsdrv_tweak_prognames(void) +{ +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Initialize Info */ +/* -------------------------------------------------------------------------- */ +void upsdrv_initinfo(void) +{ + upsdrv_updateinfo(); +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Shutdown */ +/* -------------------------------------------------------------------------- */ +void upsdrv_shutdown(void) +{ + upslogx(LOG_ERR, "shutdown not supported"); +} + +/* -------------------------------------------------------------------------- */ +/* NUT Hook: Cleanup */ +/* -------------------------------------------------------------------------- */ +void upsdrv_cleanup(void) +{ + if (ups_object_path) free(ups_object_path); + if (connection) g_object_unref(connection); +}