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 }