diff --git a/service/service.pro b/service/service.pro index ebb87f9..e7acf4c 100644 --- a/service/service.pro +++ b/service/service.pro @@ -13,10 +13,12 @@ DEFINES += APP_VERSION=\"\\\"$$VERSION\\\"\" DEFINES += APP_NAME=\"\\\"$$TARGET\\\"\" HEADERS += \ - src/filewatcher.h + src/battery.h \ + src/settings.h SOURCES += \ - src/filewatcher.cpp \ + src/battery.cpp \ + src/settings.cpp \ src/harbour-batterybuddy-daemon.cpp OTHER_FILES += harbour-batterybuddy-daemon.service diff --git a/service/src/battery.cpp b/service/src/battery.cpp new file mode 100644 index 0000000..5d75883 --- /dev/null +++ b/service/src/battery.cpp @@ -0,0 +1,181 @@ +/** + * Battery Buddy, a Sailfish application to prolong battery lifetime + * + * Copyright (C) 2019-2020 Matti Viljanen + * + * Battery Buddy is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Battery Buddy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. You should have received a copy of the GNU + * General Public License along with Battery Buddy. If not, see . + * + * Author: Matti Viljanen + */ +#include "battery.h" + +Battery::Battery(Settings* newSettings, QObject* parent) : QObject(parent) +{ + QString filename; + settings = newSettings; + + // Number: charge percentage, e.g. 42 + chargeFile = new QFile("/sys/class/power_supply/battery/capacity", this); + qInfo() << "Reading capacity from" << chargeFile->fileName(); + + // String: charging, discharging, full, empty, unknown (others?) + stateFile = new QFile("/sys/class/power_supply/battery/status", this); + qInfo() << "Reading charge state from" << stateFile->fileName(); + + // Number: 0 or 1 + chargerConnectedFile = new QFile("/sys/class/power_supply/usb/present", this); + qInfo() << "Reading charger status from" << chargerConnectedFile->fileName(); + + // ENABLE/DISABLE CHARGING + if(QHostInfo::localHostName().contains("SailfishEmul")) { + qInfo() << "Sailfish SDK detected"; + qInfo() << "Using dummy control file"; + filename = QStandardPaths::writableLocation(QStandardPaths::TempLocation)+"/charging_enabled_dummy"; + chargingEnabledFile = new QFile(filename, this); + enableChargingValue = 1; + disableChargingValue = 0; + } + else { + // e.g. for Sony Xperia XA2 + filename = "/sys/class/power_supply/battery/input_suspend"; + if(!chargingEnabledFile && QFile::exists(filename)) { + chargingEnabledFile = new QFile(filename, this); + enableChargingValue = 0; + disableChargingValue = 1; + } + + // e.g. for Sony Xperia Z3 Compact Tablet + filename = "/sys/class/power_supply/battery/charging_enabled"; + if(!chargingEnabledFile && QFile::exists(filename)) { + chargingEnabledFile = new QFile(filename, this); + enableChargingValue = 1; + disableChargingValue = 0; + } + + // e.g. for Jolla Phone + filename = "/sys/class/power_supply/usb/charger_disable"; + if(!chargingEnabledFile && QFile::exists(filename)) { + chargingEnabledFile = new QFile(filename, this); + enableChargingValue = 0; + disableChargingValue = 1; + } + + + if(!chargingEnabledFile) { + qWarning() << "Charger control file not found!"; + qWarning() << "Please contact the developer with your device model!"; + } + } + + // If we found a usable file, check that it is writable + if(chargingEnabledFile) { + // This should always succeed, since the service is started as root + if(chargingEnabledFile->open(QIODevice::WriteOnly)) { + qInfo() << "Controlling charging via" << chargingEnabledFile->fileName(); + chargingEnabledFile->close(); + + originalPerms = chargingEnabledFile->permissions(); + + if(originalPerms | customPerms) { + chargingEnabledFile->setPermissions(customPerms); + qDebug() << "Charger control file permissions updated."; + } + } + else { + delete chargingEnabledFile; + chargingEnabledFile = Q_NULLPTR; + qWarning() << "Charger control file" << chargingEnabledFile->fileName() << "is not writable"; + qWarning() << "Charger control feature disabled"; + } + } + + // TODO + // Implement DBus mechanism for reading battery status, or try + // QFileSystemWatcher again without /run/state/namespaces/Battery/ + // thingamabob - it is deprecated anyway. + + updateData(); +} + +Battery::~Battery() { } + +void Battery::updateData() +{ + if(chargeFile->open(QIODevice::ReadOnly)) { + nextCharge = chargeFile->readLine().trimmed().toInt(); + if(nextCharge != charge) { + charge = nextCharge; + emit chargeChanged(charge); + qDebug() << "Battery:" << charge; + } + chargeFile->close(); + } + if(chargerConnectedFile->open(QIODevice::ReadOnly)) { + nextChargerConnected = chargerConnectedFile->readLine().trimmed().toInt(); + if(nextChargerConnected != chargerConnected) { + chargerConnected = nextChargerConnected; + emit chargerConnectedChanged(chargerConnected); + qDebug() << "Charger is connected:" << chargerConnected; + } + chargerConnectedFile->close(); + } + if(stateFile->open(QIODevice::ReadOnly)) { + nextState = (QString(stateFile->readLine().trimmed().toLower())); + if(nextState != state) { + state = nextState; + emit stateChanged(state); + qDebug() << "Charging status:" << state; + } + stateFile->close(); + } + if(chargingEnabledFile && settings->getLimitEnabled()) { + if(chargingEnabled && charge >= settings->getHighLimit()) { + qDebug() << "Disabling"; + setChargingEnabled(false); + } + else if(!chargingEnabled && charge <= settings->getLowLimit()) { + qDebug() << "Enabling"; + setChargingEnabled(true); + } + } +} + +int Battery::getCharge(){ return charge; } + +QString Battery::getState() { return state; } + +bool Battery::getChargingEnabled() { return chargingEnabled; } + +void Battery::setChargingEnabled(bool isEnabled) { + if(chargingEnabledFile && chargingEnabledFile->open(QIODevice::WriteOnly)) { + if(chargingEnabledFile->write(QString("%1").arg(isEnabled ? enableChargingValue : disableChargingValue).toLatin1())) { + chargingEnabled = isEnabled; + emit chargingEnabledChanged(chargingEnabled); + + if(isEnabled) { + qInfo() << "Charging resumed"; + } + else { + qInfo() << "Charging paused"; + } + } + chargingEnabledFile->close(); + } +} + +bool Battery::getChargerConnected() { + return chargerConnected; +} + +void Battery::shutdown() { + chargingEnabledFile->setPermissions(originalPerms); + qDebug() << "Charger control file permissions updated."; +} diff --git a/service/src/battery.h b/service/src/battery.h new file mode 100644 index 0000000..bf35d52 --- /dev/null +++ b/service/src/battery.h @@ -0,0 +1,85 @@ +/** + * Battery Buddy, a Sailfish application to prolong battery lifetime + * + * Copyright (C) 2019 Matti Viljanen + * + * Battery Buddy is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Battery Buddy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. You should have received a copy of the GNU + * General Public License along with Battery Buddy. If not, see . + * + * Author: Matti Viljanen + */ +#ifndef BATTERY_H +#define BATTERY_H + +#include +#include +#include +#include +#include +#include +#include "settings.h" + +class Battery : public QObject +{ + Q_OBJECT + Q_PROPERTY(int charge READ getCharge NOTIFY chargeChanged) + Q_PROPERTY(bool chargerConnected READ getChargerConnected NOTIFY chargerConnectedChanged) + Q_PROPERTY(QString state READ getState NOTIFY stateChanged) + Q_PROPERTY(bool chargingEnabled READ getChargingEnabled WRITE setChargingEnabled NOTIFY chargingEnabledChanged) + +public: + Battery(Settings* newSettings, QObject* parent = nullptr); + ~Battery(); + + int getCharge(); + bool getCharging(); + bool getChargerConnected(); + QString getState(); + + bool getChargingEnabled(); + void setChargingEnabled(bool); + +public slots: + void updateData(); + void shutdown(); + +private: + QFile* chargeFile; + QFile* chargerConnectedFile; + QFile* stateFile; + QFile* chargingEnabledFile = Q_NULLPTR; + Settings* settings; + + // Default values: + int charge = 100; // 100% full + bool chargerConnected = false; // Charger plugged in + QString state = "idle"; // dis/charging, idle, unknown + bool chargingEnabled = true; // Only ever disabled manually + + int enableChargingValue = 1; + int disableChargingValue = 0; + bool chargerIsEnabled = true; + + int nextCharge = charge; + bool nextChargerConnected = chargerConnected; + QString nextState = state; + bool nextChargingEnabled = chargingEnabled; + + QFileDevice::Permissions originalPerms; // Updated in constructor + QFileDevice::Permissions customPerms = static_cast(0x0666); + +signals: + void chargeChanged(int); + void stateChanged(QString); + void chargingEnabledChanged(bool); + void chargerConnectedChanged(bool); +}; + +#endif // BATTERY_H diff --git a/service/src/filewatcher.cpp b/service/src/filewatcher.cpp deleted file mode 100644 index c654ec5..0000000 --- a/service/src/filewatcher.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "filewatcher.h" - -FileWatcher::FileWatcher(QObject *parent) : QObject(parent) -{ - QString filename; - - // Number: charge percentage, e.g. 42 - chargeFile = new QFile("/sys/class/power_supply/battery/capacity", this); - qInfo() << "Reading capacity from" << chargeFile->fileName(); - - // String: charging, discharging, full, empty, unknown (others?) - stateFile = new QFile("/sys/class/power_supply/battery/status", this); - qInfo() << "Reading charge state from" << stateFile->fileName(); - - // Number: 0 or 1 - chargerConnectedFile = new QFile("/sys/class/power_supply/usb/present", this); - qInfo() << "Reading charger status from" << chargerConnectedFile->fileName(); - - // Find and use the file to control the charger - - // e.g. for Sony Xperia XA2 - filename = "/sys/class/power_supply/battery/input_suspend"; - if(!chargerControlFile && QFile::exists(filename)) { - chargerControlFile = new QFile(filename, this); - enableChargingValue = 0; - disableChargingValue = 1; - } - - // e.g. for Sony Xperia Z3 Compact Tablet - filename = "/sys/class/power_supply/battery/charging_enabled"; - if(!chargerControlFile && QFile::exists(filename)) { - chargerControlFile = new QFile(filename, this); - enableChargingValue = 1; - disableChargingValue = 0; - } - - // e.g. for Jolla Phone - filename = "/sys/class/power_supply/usb/charger_disable"; - if(!chargerControlFile && QFile::exists(filename)) { - chargerControlFile = new QFile(filename, this); - enableChargingValue = 0; - disableChargingValue = 1; - } - - - if(!chargerControlFile) { - qWarning() << "Charger control file not found!"; - qWarning() << "Please contact the developer with your device model!"; - } - - // If we found a usable file, check that it is writable - if(chargerControlFile) { - if(chargerControlFile->open(QIODevice::WriteOnly)) { - qInfo() << "Controlling charging via" << chargerControlFile->fileName(); - chargerControlFile->close(); - } - else { - delete chargerControlFile; - chargerControlFile = Q_NULLPTR; - qWarning() << "Charger control file" << chargerControlFile->fileName() << "is not writable"; - qWarning() << "Charger control feature disabled"; - } - } - - chargeWatcher = new QFileSystemWatcher(QStringList(chargeFile->fileName()), this); - stateWatcher = new QFileSystemWatcher(QStringList(stateFile->fileName()), this); - chargerConnectedWatcher = new QFileSystemWatcher(QStringList(chargerConnectedFile->fileName()), this); - - connect(chargeWatcher, SIGNAL(fileChanged(QString)), this, SLOT(updateCharge())); - connect(stateWatcher, SIGNAL(fileChanged(QString)), this, SLOT(updateState())); - connect(chargerConnectedWatcher, SIGNAL(fileChanged(QString)), this, SLOT(updateChargerConnected())); - - return; -} - -FileWatcher::~FileWatcher() -{ - -} - -void FileWatcher::updateCharge() -{ - -} - -void FileWatcher::updateState() -{ - -} - -void FileWatcher::updateChargerConnected() -{ - -} diff --git a/service/src/filewatcher.h b/service/src/filewatcher.h deleted file mode 100644 index 729f5cf..0000000 --- a/service/src/filewatcher.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef FILEWATCHER_H -#define FILEWATCHER_H - -#include -#include -#include - -#include - -class FileWatcher : public QObject -{ - Q_OBJECT -public: - FileWatcher(QObject* parent = nullptr); - ~FileWatcher(); - -private: - QFileSystemWatcher *chargeWatcher, *stateWatcher, *chargerConnectedWatcher; - QFile *chargeFile, *stateFile, *chargerConnectedFile, *chargerControlFile; - int enableChargingValue = 1, disableChargingValue = 0; - -private slots: - void updateCharge(); - void updateState(); - void updateChargerConnected(); -}; - -#endif // FILEWATCHER_H diff --git a/service/src/harbour-batterybuddy-daemon.cpp b/service/src/harbour-batterybuddy-daemon.cpp index 23c2b25..6703d21 100644 --- a/service/src/harbour-batterybuddy-daemon.cpp +++ b/service/src/harbour-batterybuddy-daemon.cpp @@ -1,6 +1,50 @@ -#include "filewatcher.h" +#include +#include +#include +#include "battery.h" +#include "settings.h" +#include -int main() +int main(int argc, char** argv) { - return 0; + // Use the same config file as the GUI application. + // This is used by QSettings() + qputenv("XDG_CONFIG_HOME", "/home/nemo/.config"); + + const char* logEnvVar = "QT_LOGGING_RULES"; + for(int i = 1; i < argc; i++) { + if(!strcmp(argv[i],"-v")) { + printf("%s %s\n", APP_NAME, APP_VERSION); + return EXIT_SUCCESS; + } + else if(!strcmp(argv[i],"--verbose")) + qputenv(logEnvVar, "*.info=true;*.debug=false"); + else if(!strcmp(argv[i],"--debug")) + qputenv(logEnvVar, "*.info=true"); + else if(!strcmp(argv[i],"--help")) { + printf("%s %s\n\n", APP_NAME, APP_VERSION); + printf("This binary is meant to run as a service with root access,\n"); + printf("but it can be run manually for debugging purposes, too.\n\n"); + printf("Usage:\n"); + printf(" --verbose Enable informational messages\n"); + printf(" --debug Enable informational and debugging messages\n"); + printf(" --help Print version string and exit\n"); + return EXIT_SUCCESS; + } + } + if(!qEnvironmentVariableIsSet(logEnvVar)) + qputenv(logEnvVar, "*.info=false;*.debug=false"); + + QCoreApplication app(argc, argv); + + Settings* settings = new Settings(); + Battery* battery = new Battery(settings); + + QTimer* updater = new QTimer(); + QObject::connect(updater, SIGNAL(timeout()), battery, SLOT(updateData())); + updater->start(3000); + + int retval = app.exec(); + + return retval; } diff --git a/service/src/settings.cpp b/service/src/settings.cpp new file mode 100644 index 0000000..0ed7975 --- /dev/null +++ b/service/src/settings.cpp @@ -0,0 +1,111 @@ +/** + * Battery Buddy, a Sailfish application to prolong battery lifetime + * + * Copyright (C) 2019-2020 Matti Viljanen + * + * Battery Buddy is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Battery Buddy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. You should have received a copy of the GNU + * General Public License along with Battery Buddy. If not, see . + * + * Author: Matti Viljanen + */ +#include "settings.h" + +Settings::Settings(QObject *parent) : QObject(parent) +{ + // Use the same file location as GUI for data exchange + if(!mySettings) { + mySettings = new QSettings("harbour-batterybuddy", "harbour-batterybuddy"); + } + qDebug() << "Using" << mySettings->fileName(); + + // Migrate old settings + if(mySettings->contains("lowerLimit")) { + mySettings->setValue(sLowAlert, mySettings->value("lowerLimit")); + mySettings->remove("lowerLimit"); + qInfo() << "Migrated old lowerLimit value"; + } + + if(mySettings->contains("upperLimit")) { + mySettings->setValue(sHighAlert, mySettings->value("upperLimit")); + mySettings->remove("upperLimit"); + qInfo() << "Migrated old upperLimit value"; + } + + // Do this here, because... + watcher = new QFileSystemWatcher(QStringList(mySettings->fileName())); + connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(configChanged(QString))); + + // ...calling this deletes mySettings! + configChanged(mySettings->fileName()); + + qInfo() << "Loaded" << sLimitEnabled << limitEnabled; + + // Battery Buddy GUI application changes the settings file, + // so we must monitor it and update when it changes. + +} + +Settings::~Settings() +{ + delete mySettings; +} + +int Settings::bound(int value, int min, int max) { + return (value <= min ? min : (value >= max ? max : value)); +} + +void Settings::loadInteger(const char* key, int *value, int min, int max) { + *value = bound(mySettings->value(key, *value).toInt(), min, max); + qInfo() << "Loaded" << key << *value; +} + +void Settings::configChanged(QString path) { + + // Use the same file location as GUI for data exchange + if(!mySettings) { + mySettings = new QSettings("harbour-batterybuddy", "harbour-batterybuddy"); + } + + qDebug() << "Reading values..."; + // Read in the values + loadInteger(sLowAlert, &lowAlert, 10, 99); + loadInteger(sHighAlert, &highAlert, 11, 100); + loadInteger(sInterval, &interval, 60, 600); + loadInteger(sLimitEnabled, &limitEnabled, 0, 1); + loadInteger(sNotificationsEnabled, ¬ificationsEnabled, 0, 1); + loadInteger(sLowLimit, &lowLimit, 20, 94); + loadInteger(sHighLimit, &highLimit, 21, 95); + qDebug() << "Values read."; + + delete mySettings; + mySettings = nullptr; + + // Let the file system settle... + QThread::msleep(50); + + if(watcher->files().contains(path)) { + qDebug() << "File OK"; + } + else { + qDebug() << "File replaced, re-adding."; + watcher->addPath(path); + } +} + +// Getters condensed +int Settings::getLowAlert() { return lowAlert; } +int Settings::getHighAlert() { return highAlert; } +int Settings::getInterval() { return interval; } +int Settings::getLowLimit() { return lowLimit; } +int Settings::getHighLimit() { return highLimit; } +bool Settings::getLimitEnabled() { return limitEnabled == 1; } +bool Settings::getNotificationsEnabled() { return notificationsEnabled == 1; } +QString Settings::getLowAlertFile() { return lowAlertFile; } +QString Settings::getHighAlertFile() { return highAlertFile; } diff --git a/service/src/settings.h b/service/src/settings.h new file mode 100644 index 0000000..2d13ffc --- /dev/null +++ b/service/src/settings.h @@ -0,0 +1,82 @@ +/** + * Battery Buddy, a Sailfish application to prolong battery lifetime + * + * Copyright (C) 2019-2020 Matti Viljanen + * + * Battery Buddy is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Battery Buddy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. You should have received a copy of the GNU + * General Public License along with Battery Buddy. If not, see . + * + * Author: Matti Viljanen + */ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include +#include +#include + +class Settings : public QObject +{ + Q_OBJECT + +public: + Settings(QObject* parent = nullptr); + ~Settings(); + + int getLowAlert(); + int getHighAlert(); + int getInterval(); + int getLowLimit(); + int getHighLimit(); + bool getLimitEnabled(); + bool getNotificationsEnabled(); + QString getLowAlertFile(); + QString getHighAlertFile(); + +private: + QSettings* mySettings = nullptr; + QFileSystemWatcher *watcher = nullptr; + + // Default values + int lowAlert = 25; + int highAlert = 75; + int interval = 60; + + // Converted to boolean for QML + int limitEnabled = 0; + int notificationsEnabled = 1; + int daemonEnabled = 1; + + int lowLimit = 65; + int highLimit = 70; + QString lowAlertFile = "/usr/share/sounds/jolla-ambient/stereo/general_warning.wav"; + QString highAlertFile = "/usr/share/sounds/jolla-ambient/stereo/positive_confirmation.wav"; + + // To avoid repeating the same string over and over and over... + const char* sLowAlert = "lowAlert"; + const char* sHighAlert = "highAlert"; + const char* sInterval = "interval"; + const char* sLimitEnabled = "limitEnabled"; + const char* sNotificationsEnabled = "notificationsEnabled"; + const char* sLowLimit = "lowLimit"; + const char* sHighLimit = "highLimit"; + const char* sLowAlertFile = "lowAlertFile"; + const char* sHighAlertFile = "highAlertFile"; + + int bound(int value, int min, int max); + void loadInteger(const char *key, int *value, int min, int max); + +private slots: + void configChanged(QString path); +}; + +#endif // SETTINGS_H