File indexing completed on 2024-05-12 09:41:25

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
0003     SPDX-FileCopyrightText: 2008-2010 Dario Freddi <drf@kde.org>
0004     SPDX-FileCopyrightText: 2010 Alejandro Fiestas <alex@eyeos.org>
0005     SPDX-FileCopyrightText: 2010-2013 Lukáš Tinkl <ltinkl@redhat.com>
0006     SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 
0010 */
0011 
0012 #include "batterycontroller.h"
0013 
0014 #include <QDBusConnection>
0015 
0016 #define UPOWER_SERVICE "org.freedesktop.UPower"
0017 #define UPOWER_PATH "/org/freedesktop/UPower"
0018 #define UPOWER_IFACE "org.freedesktop.UPower"
0019 #define UPOWER_IFACE_DEVICE "org.freedesktop.UPower.Device"
0020 
0021 BatteryController::BatteryController()
0022     : QObject()
0023 {
0024     QDBusConnection::systemBus().connect(UPOWER_SERVICE,
0025                                          UPOWER_PATH,
0026                                          "org.freedesktop.DBus.Properties",
0027                                          "PropertiesChanged",
0028                                          this,
0029                                          SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)));
0030 
0031     QDBusConnection::systemBus().connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_IFACE, "DeviceAdded", this, SLOT(slotDeviceAdded(QDBusObjectPath)));
0032     QDBusConnection::systemBus().connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_IFACE, "DeviceRemoved", this, SLOT(slotDeviceRemoved(QDBusObjectPath)));
0033 
0034     m_upowerInterface = new OrgFreedesktopUPowerInterface(UPOWER_SERVICE, "/org/freedesktop/UPower", QDBusConnection::systemBus(), this);
0035 
0036     m_onBattery = m_upowerInterface->onBattery();
0037 
0038     QDBusReply<QDBusObjectPath> reply = m_upowerInterface->call("GetDisplayDevice");
0039     if (reply.isValid()) {
0040         const QString path = reply.value().path();
0041         if (!path.isEmpty() && path != QStringLiteral("/")) {
0042             m_displayDevice = std::make_unique<UPowerDevice>(path);
0043             connect(m_displayDevice.get(), &UPowerDevice::propertiesChanged, this, &BatteryController::updateDeviceProps);
0044         }
0045     }
0046 
0047     if (!m_displayDevice) {
0048         const QList<QDBusObjectPath> deviceList = m_upowerInterface->EnumerateDevices();
0049         for (const QDBusObjectPath &device : deviceList) {
0050             if (m_devices.count(device.path())) {
0051                 continue;
0052             }
0053             addDevice(device.path());
0054         }
0055     }
0056 
0057     updateDeviceProps();
0058 
0059     if (m_onBattery) {
0060         m_acAdapterState = Unplugged;
0061     } else {
0062         m_acAdapterState = Plugged;
0063     }
0064     Q_EMIT acAdapterStateChanged(m_acAdapterState);
0065 }
0066 
0067 void BatteryController::addDevice(const QString &device)
0068 {
0069     if (m_displayDevice) {
0070         return;
0071     }
0072 
0073     auto upowerDevice = std::make_unique<UPowerDevice>(device);
0074     connect(upowerDevice.get(), &UPowerDevice::propertiesChanged, this, &BatteryController::updateDeviceProps);
0075 
0076     m_devices[device] = std::move(upowerDevice);
0077 }
0078 
0079 void BatteryController::slotDeviceAdded(const QDBusObjectPath &path)
0080 {
0081     addDevice(path.path());
0082     updateDeviceProps();
0083 }
0084 
0085 void BatteryController::slotDeviceRemoved(const QDBusObjectPath &path)
0086 {
0087     m_devices.erase(path.path());
0088     updateDeviceProps();
0089 }
0090 
0091 void BatteryController::updateDeviceProps()
0092 {
0093     double energyTotal = 0.0;
0094     double energyRateTotal = 0.0; // +: charging, -: discharging (contrary to EnergyRate)
0095     double energyFullTotal = 0.0;
0096     qulonglong timestamp = 0;
0097 
0098     if (m_displayDevice) {
0099         if (!m_displayDevice->isPresent()) {
0100             // No Battery/Ups, nothing to report
0101             return;
0102         }
0103         const auto state = m_displayDevice->state();
0104         energyTotal = m_displayDevice->energy();
0105         energyFullTotal = m_displayDevice->energyFull();
0106         timestamp = m_displayDevice->updateTime();
0107         // Workaround for https://gitlab.freedesktop.org/upower/upower/-/issues/252
0108         if (state == UPowerDevice::State::Charging) {
0109             energyRateTotal = std::abs(m_displayDevice->energyRate());
0110         } else if (state == UPowerDevice::State::Discharging) {
0111             energyRateTotal = -std::abs(m_displayDevice->energyRate());
0112         }
0113     } else {
0114         for (const auto &[key, upowerDevice] : m_devices) {
0115             if (!upowerDevice->isPowerSupply() || !upowerDevice->isPresent()) {
0116                 continue;
0117             }
0118             const auto type = upowerDevice->type();
0119             if (type == UPowerDevice::Type::Battery || type == UPowerDevice::Type::Ups) {
0120                 const auto state = upowerDevice->state();
0121                 energyFullTotal += upowerDevice->energyFull();
0122                 energyTotal += upowerDevice->energy();
0123 
0124                 if (state == UPowerDevice::State::FullyCharged) {
0125                     continue;
0126                 }
0127 
0128                 timestamp = std::max(timestamp, upowerDevice->updateTime());
0129                 // Workaround for https://gitlab.freedesktop.org/upower/upower/-/issues/252
0130                 if (state == UPowerDevice::State::Charging) {
0131                     energyRateTotal += std::abs(upowerDevice->energyRate());
0132                 } else if (state == UPowerDevice::State::Discharging) {
0133                     energyRateTotal -= std::abs(upowerDevice->energyRate());
0134                 }
0135             }
0136         }
0137     }
0138 
0139     m_batteryEnergy = energyTotal;
0140     m_batteryEnergyFull = energyFullTotal;
0141     setBatteryRate(energyRateTotal, timestamp);
0142 }
0143 
0144 void BatteryController::onPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps)
0145 {
0146     if (ifaceName != UPOWER_IFACE) {
0147         return;
0148     }
0149 
0150     bool onBattery = m_onBattery;
0151     if (changedProps.contains(QStringLiteral("OnBattery"))) {
0152         onBattery = changedProps[QStringLiteral("OnBattery")].toBool();
0153     } else if (invalidatedProps.contains(QStringLiteral("OnBattery"))) {
0154         onBattery = m_upowerInterface->onBattery();
0155     }
0156     if (onBattery != m_onBattery) {
0157         m_acAdapterState = onBattery ? Unplugged : Plugged;
0158         Q_EMIT acAdapterStateChanged(m_acAdapterState);
0159         m_onBattery = onBattery;
0160     }
0161 }
0162 
0163 /**
0164  * Filter data along one dimension using exponential moving average.
0165  */
0166 double emafilter(const double last, const double update, double weight)
0167 {
0168     double current = last * (1 - weight) + update * weight;
0169 
0170     return current;
0171 }
0172 
0173 void BatteryController::setBatteryRate(const double rate, qulonglong timestamp)
0174 {
0175     // remaining time in milliseconds
0176     qulonglong time = 0;
0177 
0178     if (rate > 0) {
0179         // Energy and rate are in Watt*hours resp. Watt
0180         time = 3600 * 1000 * (m_batteryEnergyFull - m_batteryEnergy) / rate;
0181     } else if (rate < 0) {
0182         time = 3600 * 1000 * (0.0 - m_batteryEnergy) / rate;
0183     }
0184 
0185     if (m_batteryRemainingTime != time) {
0186         m_batteryRemainingTime = time;
0187         Q_EMIT batteryRemainingTimeChanged(time);
0188     }
0189 
0190     // Charging or full
0191     if ((rate > 0) || (time == 0)) {
0192         if (m_smoothedBatteryRemainingTime != time) {
0193             m_smoothedBatteryRemainingTime = time;
0194             Q_EMIT smoothedBatteryRemainingTimeChanged(time);
0195         }
0196         return;
0197     }
0198 
0199     double oldRate = m_smoothedBatteryDischargeRate;
0200     if (oldRate == 0) {
0201         m_smoothedBatteryDischargeRate = rate;
0202     } else {
0203         // To have a time constant independent from the update frequency
0204         // the weight must be scaled
0205         double weight = 0.005 * std::min<qulonglong>(60, timestamp - m_lastRateTimestamp);
0206         m_lastRateTimestamp = timestamp;
0207         m_smoothedBatteryDischargeRate = emafilter(oldRate, rate, weight);
0208     }
0209 
0210     time = 3600 * 1000 * (0.0 - m_batteryEnergy) / m_smoothedBatteryDischargeRate;
0211 
0212     if (m_smoothedBatteryRemainingTime != time) {
0213         m_smoothedBatteryRemainingTime = time;
0214         Q_EMIT smoothedBatteryRemainingTimeChanged(m_smoothedBatteryRemainingTime);
0215     }
0216 }
0217 
0218 BatteryController::AcAdapterState BatteryController::acAdapterState() const
0219 {
0220     return m_acAdapterState;
0221 }
0222 
0223 qulonglong BatteryController::batteryRemainingTime() const
0224 {
0225     return m_batteryRemainingTime;
0226 }
0227 
0228 qulonglong BatteryController::smoothedBatteryRemainingTime() const
0229 {
0230     return m_smoothedBatteryRemainingTime;
0231 }