Hi,

I have implemented a while ago Qt based real time clock handling,
general purpose IO, and led control API for my project. This is merely
designed according to the Linux kernel API, so it is not a
cross-platform solution on its own, but more like a "backend" or
"plugin".

If you are interested in getting such things into solid, I can
solidify them at some point. See the attachment for few examples
(yeah, powermanagement is probably misnamed for rtc in general).

Best Regards,
Laszlo Papp
#ifndef FOOBAR_GENERALPURPOSEIO_H
#define FOOBAR_GENERALPURPOSEIO_H

namespace Foobar
{
    class FOOBAR_EXPORT GeneralPurposeIO
    {
        public:

            enum Direction {
                Input,
                Output
            };

            explicit GeneralPurposeIO(quint32 gpioNumber = 0);
            ~GeneralPurposeIO();

            int gpioExport();
            int gpioUnexport();
            bool isGpioExported();

            quint32 gpioNumber() const;
            void setGpioNumber(quint32 gpioNumber);

            Direction direction() const;
            int setDirection(Direction direction);

            qint32 value() const;
            int setValue(qint32 value);

        private:

            class Private;
            Private *const d;
    };
}

#endif // FOOBAR_GENERALPURPOSEIO_H

#include "generalpurposeio.h"

#include <QtCore/QDebug>
#include <QtCore/QFile>

using namespace Foobar;

class GeneralPurposeIO::Private
{
    public:
        Private()
        {
        }

        ~Private()
        {
        }

        static const QString gpioExportFilePath;
        static const QString gpioUnexportFilePath;
        static const QString gpioDirectionFilePath;
        static const QString gpioValueFilePath;
        static const QString gpioFilePath;

        quint32 gpioNumber;
};

const QString GeneralPurposeIO::Private::gpioExportFilePath = "/sys/class/gpio/export";
const QString GeneralPurposeIO::Private::gpioUnexportFilePath = "/sys/class/gpio/unexport";
const QString GeneralPurposeIO::Private::gpioDirectionFilePath = "/sys/class/gpio/gpio%1/direction";
const QString GeneralPurposeIO::Private::gpioValueFilePath = "/sys/class/gpio/gpio%1/value";
const QString GeneralPurposeIO::Private::gpioFilePath = "/sys/class/gpio/gpio%1";

GeneralPurposeIO::GeneralPurposeIO(quint32 gpioNumber)
    : d(new Private)
{
    d->gpioNumber = gpioNumber;
}

GeneralPurposeIO::~GeneralPurposeIO()
{
}

/*
 * Exports the desired gpio number.
 *
 * Note: Unfortunately, it is not possible to just call this method "export"
 * since that is a reserved keyword in C++. Systematically the unexport method
 * cannot be called "unexport" either for consistency.
 */

int GeneralPurposeIO::gpioExport()
{
    if (isGpioExported()) {
        // TODO: Proper error mutator mechanism for storing different error
        // enumeration values internally that can be requested by the API user

        qDebug() << "Cannot export the gpio pin since it is already exported:" << d->gpioNumber;
        return -1;
    }

    QFile gpioExportFile(d->gpioExportFilePath);
    if (!gpioExportFile.open(QIODevice::Append)) {
        qDebug() << "Cannot open the gpio export file:" << d->gpioExportFilePath;
        return -1;
    }

    /*
     * Seek to begining of the file
     */

    gpioExportFile.seek(0);

    /*
     * Write our value of "gpioPinNumber" to the file
     */

    if (gpioExportFile.write(QByteArray::number(d->gpioNumber)) == -1) {
        qDebug() << Q_FUNC_INFO << "Error while writing the file:" << d->gpioExportFilePath;
        gpioExportFile.close();

        return -1;
    }

    gpioExportFile.close();

    return 0;
}

int GeneralPurposeIO::gpioUnexport()
{
    if (!isGpioExported()) {
        // TODO: Proper error mutator mechanism for storing different error
        // enumeration values internally that can be requested by the API user

        qDebug() << "Cannot unexport the gpio pin since it is not exported yet:" << d->gpioNumber;
        return -1;
    }

    QFile gpioUnexportFile(d->gpioUnexportFilePath);
    if (!gpioUnexportFile.open(QIODevice::Append)) {
        qDebug() << "Cannot open the gpio export file:" << d->gpioUnexportFilePath;
        return -1;
    }

    /*
     * Seek to begining of the file
     */

    gpioUnexportFile.seek(0);

    /*
     * Write our value of "gpioPinNumber" to the file
     */

    if (gpioUnexportFile.write(QByteArray::number(d->gpioNumber)) == -1) {
        qDebug() << Q_FUNC_INFO << "Error while writing the file:" << d->gpioUnexportFilePath;
        gpioUnexportFile.close();

        return -1;
    }

    gpioUnexportFile.close();

    return 0;
}

bool GeneralPurposeIO::isGpioExported()
{
    if (!QFile(d->gpioFilePath.arg(d->gpioNumber)).exists()) {
        return false;
    }

    return true;
}

quint32 GeneralPurposeIO::gpioNumber() const
{
    return d->gpioNumber;
}

void GeneralPurposeIO::setGpioNumber(quint32 gpioNumber)
{
    d->gpioNumber = gpioNumber;
}

GeneralPurposeIO::Direction GeneralPurposeIO::direction() const
{
    // TODO: Implement me

    return GeneralPurposeIO::Output;
}

int GeneralPurposeIO::setDirection(Direction direction)
{
    if (!isGpioExported()) {
        if (gpioExport() == -1) {
            return -1;
        }
    }

    /*
     * Set the direction
     */

    QFile gpioDirectionFile(d->gpioDirectionFilePath.arg(d->gpioNumber));

    if (!gpioDirectionFile.open(QIODevice::ReadWrite)) {
        qDebug() << "Cannot open the relevant gpio direction file:" << d->gpioDirectionFilePath;
        return -1;
    }

    int retval = 0;

    /*
     * Seek to begining of the file
     */

    gpioDirectionFile.seek(0);

    switch (direction) {

    case Output:
        if (gpioDirectionFile.write("high") == -1) {
            qDebug() << Q_FUNC_INFO << "Error while writing the file:" << d->gpioDirectionFilePath;
            retval = -1;
        }

        break;

    case Input:
        if (gpioDirectionFile.write("low") == -1) {
            qDebug() << Q_FUNC_INFO << "Error while writing the file:" << d->gpioDirectionFilePath;
            retval = -1;
        }

        break;

    default:

        break;

    }

    gpioDirectionFile.close();

    return retval;
}

qint32 GeneralPurposeIO::value() const
{
    // TODO: Implement me

    return 0;
}

int GeneralPurposeIO::setValue(qint32 value)
{
    if (direction() != GeneralPurposeIO::Output) {
        qDebug() << "Cannot set the value for an input gpio pin:" << d->gpioNumber;
        return -1;
    }

    /*
     * Set the value
     */

    QFile gpioValueFile(d->gpioValueFilePath.arg(d->gpioNumber));
    if (!gpioValueFile.open(QIODevice::ReadWrite)) {
        qDebug() << "Cannot open the relevant gpio value file:" << d->gpioValueFilePath.arg(d->gpioNumber);
        gpioValueFile.close();
        return -1;
    }

    /*
     * Seek to begining of the file
     */

    gpioValueFile.seek(0);

    if (gpioValueFile.write(QByteArray::number(value)) == -1) {
        qDebug() << Q_FUNC_INFO << "Error while writing the file:" << d->gpioValueFilePath.arg(d->gpioNumber);
        gpioValueFile.close();
        return -1;
    }

    gpioValueFile.close();

    return 0;
}
#ifndef FOOBAR_POWERMANAGEMENT_H
#define FOOBAR_POWERMANAGEMENT_H

namespace Foobar
{
    const QString deviceDirectoryPath = "/dev";
    const QByteArray suspendByteArray = "mem";
    const QByteArray hibernateByteArray = "disk";

    class FOOBAR_EXPORT PowerManagement
    {
        public:

            enum ClockMode {
                CM_AUTO,
                CM_UTC,
                CM_LOCAL
            };

            PowerManagement();
            ~PowerManagement();

            bool wakeupEnabled(const QString& realTimeClockDeviceName) const;
            bool suspendAvailable() const;
            int basetimes();
            int setupAlarm(time_t *wakeup) const;
            int readClockMode();

            void suspendSystem() const;
            void setRealTimeClockDeviceName(const QString& realTimeClockDeviceName);
            void setSuspendMode(const QString& suspendMode);
            void setWakeupTime(qint64 wakeupTime);

            int setPowerSaveMode();

        private:

            class Private;
            Private *const d;
    };
}

#endif // FOOBAR_POWERMANAGEMENT_H

#include "powermanagement.h"

#include <QtCore/QFile>
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
#include <QtCore/QDir>

#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <unistd.h>

using namespace Foobar;

static qint64 maxReadLineSize = 1024;

static const QString defaultRealTimeClockDeviceName = "rtc0";
static const QString sysPowerStateFilePath = "/sys/power/state";
static const QString adjustTimeFilePath = "/etc/adjtime";
static const QString systemClassRealTimeClockDevicePowerWakeupFilePath = "/sys/class/rtc/%1/device/power/wakeup";

class PowerManagement::Private
{
    public:
        Private()
            : wakeupTime(0)
            , suspendTypeByteArray(suspendByteArray)
            , realTimeClockDeviceName(defaultRealTimeClockDeviceName)
            , clockMode(CM_AUTO)
        {
        }

        ~Private()
        {
        }

        qint64 wakeupTime;
        QByteArray suspendTypeByteArray;
        QString realTimeClockDeviceName;
        ClockMode clockMode;
        QFile file;

        /* all times should be in UTC */
        time_t systemTime;
        time_t realTimeClockTime;
};

PowerManagement::PowerManagement()
    : d(new Private)
{
}

PowerManagement::~PowerManagement()
{
}

bool PowerManagement::wakeupEnabled(const QString& realTimeClockDeviceName) const
{
    QFile file(systemClassRealTimeClockDevicePowerWakeupFilePath.arg(realTimeClockDeviceName));
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << Q_FUNC_INFO << "Failed to open the file:" << systemClassRealTimeClockDevicePowerWakeupFilePath.arg(realTimeClockDeviceName);
        return false;
    }

    QByteArray lineByteArray = file.readLine(128);

    file.close();

    /* wakeup events could be disabled or not supported */
    return lineByteArray.trimmed() == "enabled";
}

int PowerManagement::basetimes()
{
    struct rtc_time rtc;

    /* This process works in RTC time, except when working
     * with the system clock (which always uses UTC).
     */
    if (d->clockMode == CM_UTC)
        setenv("TZ", "UTC", 1);

    tzset();

    /* Read rtc and system clocks "at the same time", or as
     * precisely (+/- a second) as we can read them.
     */
    if (ioctl(d->file.handle(), RTC_RD_TIME, &rtc) < 0) {
        qDebug() << Q_FUNC_INFO << "Failed to read rtc time";
        return -1;
    }

    // Returns the number of seconds since 1970-01-01T00:00:00 Universal
    // Coordinated Time.
    d->systemTime = QDateTime::currentMSecsSinceEpoch() / 1000;

    /* convert rtc_time to normal arithmetic-friendly form,
     * updating tm.tm_wday as used by asctime().
     */
    QDateTime dateTime;
    dateTime.setDate(QDate(1900 + rtc.tm_year, 1 + rtc.tm_mon, rtc.tm_mday));
    dateTime.setTime(QTime(rtc.tm_hour, rtc.tm_min, rtc.tm_sec));

    d->realTimeClockTime = dateTime.toTime_t();

    if (d->realTimeClockTime == -1) {
        qDebug() << Q_FUNC_INFO << "Failed to convert the rtc time";
        return -1;
    }

    return 0;
}

int PowerManagement::setupAlarm(time_t *wakeup) const
{
    struct rtc_wkalrm   wake;

    /* The wakeup time is in POSIX time (more or less UTC).
     * Ideally RTCs use that same time; but PCs can't do that
     * if they need to boot MS-Windows.  Messy...
     *
     * When clockMode == CM_UTC this process's timezone is UTC,
     * so we'll pass a UTC date to the RTC.
     *
     * Else clockMode == CM_LOCAL so the time given to the RTC
     * will instead use the local time zone.
     */
    QDateTime dateTime = QDateTime::fromTime_t(*wakeup).toLocalTime();
    QDate date = dateTime.date();
    QTime time = dateTime.time();

    wake.time.tm_sec = time.second();
    wake.time.tm_min = time.minute();
    wake.time.tm_hour = time.hour();
    wake.time.tm_mday = date.day();
    wake.time.tm_mon = date.month() - 1;
    wake.time.tm_year = date.year() - 1900;
    /* wday, yday, and isdst fields are unused by Linux */
    wake.time.tm_wday = -1;
    wake.time.tm_yday = -1;
    wake.time.tm_isdst = -1;

    wake.enabled = 1;

    /* First try the preferred RTC_WKALM_SET */
    if (ioctl(d->file.handle(), RTC_WKALM_SET, &wake) < 0) {
        wake.enabled = 0;
        /* Fall back on the non-preferred way of setting wakeups; only
        * works for alarms < 24 hours from now */
        if ((d->realTimeClockTime + (24 * 60 * 60)) > *wakeup) {
            if (ioctl(d->file.handle(), RTC_ALM_SET, &wake.time) < 0) {
                qDebug() << Q_FUNC_INFO << "Set rtc alarm failed";
                return -1;
            }
            if (ioctl(d->file.handle(), RTC_AIE_ON, 0) < 0) {
                qDebug() << Q_FUNC_INFO << "enable rtc alarm failed";
                return -1;
            }
        } else {
            qDebug() << Q_FUNC_INFO << "set rtc wake alarm failed";
            return -1;
        }
    }

    return 0;
}

bool PowerManagement::suspendAvailable() const
{
    QFile file(sysPowerStateFilePath);

    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << Q_FUNC_INFO << "Failed to open the file:" << sysPowerStateFilePath;
        return false;
    }

    QByteArray lineByteArray = file.readLine(maxReadLineSize);

    file.close();

    return lineByteArray.contains(d->suspendTypeByteArray);
}

int PowerManagement::readClockMode()
{
    QFile file(adjustTimeFilePath);
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << Q_FUNC_INFO << "Failed to open the file:" << adjustTimeFilePath;
        return -1;
    }

    QByteArray lineByteArray;

    /* skip the first three lines line */
    for (int i = 0; i < 3; ++i) {
        lineByteArray = file.readLine(maxReadLineSize);

        if (lineByteArray.isEmpty()) {
            file.close();
            return -1;
        }
    }

    if (lineByteArray == "UTC") {
        d->clockMode = CM_UTC;
    } else if (lineByteArray == "LOCAL") {
        d->clockMode = CM_LOCAL;
    }

    file.close();

    return 0;
}

void PowerManagement::suspendSystem() const
{
    QFile file(sysPowerStateFilePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        qDebug() << Q_FUNC_INFO << "Failed to open the file:" << sysPowerStateFilePath;
        return;
    }

    if (file.write(d->suspendTypeByteArray) == -1) {
        qDebug() << Q_FUNC_INFO << "Error while writing the file:" << sysPowerStateFilePath << "(suspend type: " << d->suspendTypeByteArray << ")";
    }

    if (!file.flush()) {
        qDebug() << Q_FUNC_INFO << "Failed to flush the file:" << sysPowerStateFilePath;
    }

    /* This executes after wake from suspend */
    file.close();
}

int PowerManagement::setPowerSaveMode()
{
    if (d->wakeupTime <= 0) {
        qDebug() << Q_FUNC_INFO << "No proper wakeup time set:" << d->wakeupTime;
        return -1;
    }

    if (readClockMode() < 0) {
        d->clockMode = CM_UTC;
    }

    QString realTimeClockDeviceFilePath = deviceDirectoryPath + QDir::separator() + d->realTimeClockDeviceName;

    if (d->suspendTypeByteArray != "no" && !wakeupEnabled(d->realTimeClockDeviceName)) {
        qDebug() << Q_FUNC_INFO << "Wakeup events are disabled for the device:" << realTimeClockDeviceFilePath;
        return -1;
    }

    d->file.setFileName(realTimeClockDeviceFilePath);

    if (!d->file.open(QIODevice::ReadOnly)) {
        qDebug() << Q_FUNC_INFO << "Failed to open the file:" << realTimeClockDeviceFilePath;
        return -1;
    }

    /* relative or absolute alarm time, normalized to time_t */
    if (basetimes() < 0) {
        return -1;
    }

    if (d->suspendTypeByteArray != "disable") {
        if (d->suspendTypeByteArray != "no" && !suspendAvailable()) {

            qDebug() << Q_FUNC_INFO << "Suspend type unavailable:" << d->suspendTypeByteArray;
            return -1;
        }

        time_t alarm = d->realTimeClockTime + d->wakeupTime + 1;

        if (setupAlarm(&alarm) < 0) {
            return -1;
        }

        if (!d->file.flush()) {
            qDebug() << Q_FUNC_INFO << "Failed to flush the file:" << realTimeClockDeviceFilePath;
            return -1;
        }

        usleep(10 * 1000);
    }

    sync();
    suspendSystem();

    if (ioctl(d->file.handle(), RTC_AIE_OFF, 0) < 0) {
        qDebug() << Q_FUNC_INFO << "Failed to disable rtc alarm interrupt";
    }

    d->file.close();
    return 0;
}

void PowerManagement::setRealTimeClockDeviceName(const QString& realTimeClockDeviceName)
{
    d->realTimeClockDeviceName = realTimeClockDeviceName;
}

void PowerManagement::setSuspendMode(const QString& suspendMode)
{
    d->suspendTypeByteArray = suspendMode.toAscii();
}

void PowerManagement::setWakeupTime(qint64 wakeupTime)
{
    d->wakeupTime = wakeupTime;
}
_______________________________________________
Kde-hardware-devel mailing list
[email protected]
https://mail.kde.org/mailman/listinfo/kde-hardware-devel

Reply via email to