diff --git a/.github/workflows/sailfishos.yml b/.github/workflows/sailfishos.yml new file mode 100644 index 00000000..76be8f9f --- /dev/null +++ b/.github/workflows/sailfishos.yml @@ -0,0 +1,38 @@ +name: SailfishOS Build + +on: [push, pull_request] + +env: + OS_VERSION: 4.5.0.16 + +jobs: + build: + runs-on: ubuntu-latest + name: Build App + strategy: + matrix: + arch: ['armv7hl', 'aarch64', 'i486'] + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Prepare + run: docker pull coderus/sailfishos-platform-sdk:$OS_VERSION && mkdir output + + - name: Build ${{ matrix.arch }} + run: docker run --rm --privileged -v $PWD:/share coderus/sailfishos-platform-sdk:$OS_VERSION /bin/bash -c " + mkdir -p build ; + cd build ; + cp -r /share/* . ; + sb2 -t SailfishOS-$OS_VERSION-${{ matrix.arch }} -R zypper --non-interactive ar --no-gpgcheck http://repo.merproject.org/obs/home:/piggz:/kf5/sailfish_latest_${{ matrix.arch }}/ piggz ; + sb2 -t SailfishOS-$OS_VERSION-${{ matrix.arch }} -R zypper --non-interactive refresh ; + sb2 -t SailfishOS-$OS_VERSION-${{ matrix.arch }} -R zypper --non-interactive in -y mpris-qt5-devel libkf5archive-devel kcoreaddons-devel kdb-devel libKDb3-3 mkcal-qt5-devel libicu-devel; + mb2 -t SailfishOS-$OS_VERSION-${{ matrix.arch }} build ; + sudo cp -r RPMS/*.rpm /share/output" + + - name: Upload RPM (${{ matrix.arch }}) + uses: actions/upload-artifact@v3 + with: + name: rpm-${{ matrix.arch }} + path: output diff --git a/clickable.yaml b/clickable.yaml index 021679b1..4308e62c 100644 --- a/clickable.yaml +++ b/clickable.yaml @@ -22,6 +22,7 @@ dependencies_target: - libkf5archive-dev - libkf5coreaddons-dev - libdbus-1-dev +- libtelepathy-qt5-dev libraries: qtmpris: diff --git a/daemon/daemon.pro b/daemon/daemon.pro index 5a0c59ca..1e2699aa 100644 --- a/daemon/daemon.pro +++ b/daemon/daemon.pro @@ -41,6 +41,12 @@ flavor_silica { voicecall \ notificationmonitor \ calendar +} else:flavor_uuitk { + DEFINES += UUITK_EDITION + WATCHFISH_FEATURES += music \ + voicecall \ + notificationmonitor \ + calendar } else { WATCHFISH_FEATURES += music \ notificationmonitor \ @@ -125,11 +131,13 @@ SOURCES += \ src/services/infinitimemotionservice.cpp \ src/services/infinitimenavservice.cpp \ src/services/infinitimeweatherservice.cpp \ + src/services/pinetimesimpleweatherservice.cpp \ src/services/pinetimemusicservice.cpp \ src/services/uartservice.cpp \ src/typeconversion.cpp \ src/bipbatteryinfo.cpp \ src/devicefactory.cpp \ + src/realtimeactivitysample.cpp \ src/services/mibandservice.cpp \ src/services/miband2service.cpp \ src/services/alertnotificationservice.cpp \ @@ -198,6 +206,7 @@ HEADERS += \ src/services/infinitimemotionservice.h \ src/services/infinitimenavservice.h \ src/services/infinitimeweatherservice.h \ + src/services/pinetimesimpleweatherservice.h \ src/services/pinetimemusicservice.h \ src/services/uartservice.h \ src/services/immediatealertservice.h \ @@ -210,6 +219,7 @@ HEADERS += \ src/activitysummary.h \ src/activitysample.h \ src/devicefactory.h \ + src/realtimeactivitysample.h \ src/services/mibandservice.h \ src/services/miband2service.h \ src/services/alertnotificationservice.h \ diff --git a/daemon/libwatchfish b/daemon/libwatchfish index 772bbb33..b8e2e8c0 160000 --- a/daemon/libwatchfish +++ b/daemon/libwatchfish @@ -1 +1 @@ -Subproject commit 772bbb3375bd9ac4f38b2bb52dcf82f953f76db7 +Subproject commit b8e2e8c0d2e2b17153823ef7ca18d5b73b0dabe3 diff --git a/daemon/src/deviceinterface.cpp b/daemon/src/deviceinterface.cpp index 8481867a..9aa1f11b 100644 --- a/daemon/src/deviceinterface.cpp +++ b/daemon/src/deviceinterface.cpp @@ -15,6 +15,7 @@ #include #include +#include static const char *SERVICE = SERVICE_NAME_AMAZFISH; static const char *PATH = "/application"; @@ -47,7 +48,7 @@ DeviceInterface::DeviceInterface() connect(&m_notificationMonitor, &watchfish::NotificationMonitor::notification, this, &DeviceInterface::onNotification); // Calls -#ifdef MER_EDITION_SAILFISH +#if defined(MER_EDITION_SAILFISH) || defined(UUITK_EDITION) connect(&m_voiceCallController, &watchfish::VoiceCallController::ringingChanged, this, &DeviceInterface::onRingingChanged); #endif //Weather @@ -178,7 +179,7 @@ void DeviceInterface::onNotification(watchfish::Notification *notification) void DeviceInterface::onRingingChanged() { -#ifdef MER_EDITION_SAILFISH +#if defined(MER_EDITION_SAILFISH) || defined(UUITK_EDITION) qDebug() << Q_FUNC_INFO << m_voiceCallController.ringing(); if (!m_device) { @@ -245,6 +246,39 @@ void DeviceInterface::createTables() } + if (!m_conn->containsTable("info_log")) { + KDbTableSchema *t_info = new KDbTableSchema("info_log"); + t_info->setCaption("Info log"); + t_info->addField(f = new KDbField("id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); + f->setCaption("ID"); + t_info->addField(f = new KDbField("timestamp", KDbField::Integer, nullptr)); + f->setCaption("Timestamp"); + t_info->addField(f = new KDbField("timestamp_dt", KDbField::DateTime)); + f->setCaption("Timestamp in Date/Time format"); + t_info->addField(f = new KDbField("key", KDbField::Integer, nullptr, KDbField::Unsigned)); + f->setCaption("Key based on AbstractDevice::Info"); + t_info->addField(f = new KDbField("value", KDbField::Integer, nullptr, KDbField::Unsigned)); + f->setCaption("Value"); + + if (!m_conn->createTable(t_info)) { + qDebug() << m_conn->result(); + return; + } + qDebug() << "-- info_log created --"; + qDebug() << *t_info; + } + + int batteryLevel = 0; + + if (m_conn->querySingleNumber( + KDbEscapedString("SELECT value FROM info_log WHERE key = %1 ORDER BY id DESC").arg(AbstractDevice::INFO_BATTERY), // automatically adds LIMIT 1 into query + &batteryLevel) == true) { // comparision of tristate type (true, false, canceled) + m_lastBatteryLevel = batteryLevel; + qDebug() << "Last Battery Level: " << m_lastBatteryLevel; + } else { + qWarning() << "Cannot get battery level"; + } + if (!m_conn->containsTable("sports_data")) { KDbTableSchema *t_summary = new KDbTableSchema("sports_data"); t_summary->setCaption("Sports Data"); @@ -395,6 +429,7 @@ void DeviceInterface::onConnectionStateChanged() qDebug() << "DeviceInterface::onConnectionStateChanged" << connectionState(); if (connectionState() == "authenticated") { + m_device->setDatabase(dbConnection()); if (miBandService()) { miBandService()->setDatabase(dbConnection()); m_dbusHRM->setMiBandService(miBandService()); @@ -415,6 +450,42 @@ void DeviceInterface::onConnectionStateChanged() emit connectionStateChanged(); } +void DeviceInterface::log_battery_level(int level) { + + if (!m_conn || !(m_conn->isDatabaseUsed())) { + qDebug() << "Database not connected"; + return; + } + + QDateTime m_sampleTime = QDateTime::currentDateTime(); + qDebug() << "Start time" << m_sampleTime; + + KDbTransaction transaction = m_conn->beginTransaction(); + KDbTransactionGuard tg(transaction); + + KDbFieldList fields; + auto s_battery = m_conn->tableSchema("info_log"); + + fields.addField(s_battery->field("timestamp")); + fields.addField(s_battery->field("timestamp_dt")); + fields.addField(s_battery->field("key")); + fields.addField(s_battery->field("value")); + + QList values; + + values << m_sampleTime.toMSecsSinceEpoch() / 1000; + values << m_sampleTime; + values << AbstractDevice::INFO_BATTERY; + values << level; + + if (!m_conn->insertRecord(&fields, values)) { + qDebug() << "error inserting record"; + return; + } + tg.commit(); + +} + void DeviceInterface::slot_informationChanged(AbstractDevice::Info key, const QString &val) { qDebug() << Q_FUNC_INFO << key << val; @@ -426,11 +497,15 @@ void DeviceInterface::slot_informationChanged(AbstractDevice::Info key, const QS //Handle notification of low battery if (key == AbstractDevice::INFO_BATTERY) { - if (val.toInt() != m_lastBatteryLevel) { - if (val.toInt() <= 10 && val.toInt() < m_lastBatteryLevel && AmazfishConfig::instance()->appNotifyLowBattery()) { + int battery_level = val.toInt(); + if (battery_level != m_lastBatteryLevel) { + + log_battery_level(battery_level); + + if (battery_level <= 10 && battery_level < m_lastBatteryLevel && AmazfishConfig::instance()->appNotifyLowBattery()) { sendAlert("Amazfish", tr("Low Battery"), tr("Battery level now ") + QString::number(m_lastBatteryLevel) + "%"); } - m_lastBatteryLevel = val.toInt(); + m_lastBatteryLevel = battery_level; } } @@ -474,13 +549,16 @@ void DeviceInterface::deviceEvent(AbstractDevice::Events event) case AbstractDevice::EVENT_APP_MUSIC: musicChanged(); break; -#ifdef MER_EDITION_SAILFISH +#if defined(MER_EDITION_SAILFISH) || defined(UUITK_EDITION) case AbstractDevice::EVENT_IGNORE_CALL: m_voiceCallController.silence(); break; case AbstractDevice::EVENT_DECLINE_CALL: m_voiceCallController.hangup(); break; + case AbstractDevice::EVENT_ANSWER_CALL: + m_voiceCallController.answer(); + break; #endif } } @@ -858,6 +936,13 @@ void DeviceInterface::enableFeature(int feature) } } +void DeviceInterface::fetchLogs() +{ + if (miBandService()) { + miBandService()->fetchLogs(); + } +} + QStringList DeviceInterface::supportedDisplayItems() { qDebug() << Q_FUNC_INFO; diff --git a/daemon/src/deviceinterface.h b/daemon/src/deviceinterface.h index 02562b96..303901ab 100644 --- a/daemon/src/deviceinterface.h +++ b/daemon/src/deviceinterface.h @@ -76,6 +76,7 @@ class DeviceInterface : public QObject Q_INVOKABLE void updateCalendar(); Q_INVOKABLE void reloadCities(); Q_INVOKABLE void enableFeature(int feature); + Q_INVOKABLE void fetchLogs(); Q_INVOKABLE QStringList supportedDisplayItems(); Q_INVOKABLE void immediateAlert(int level); @@ -105,6 +106,8 @@ class DeviceInterface : public QObject void createSettings(); void updateServiceController(); + void log_battery_level(int level); + //TODO Minimise use of these funcitons MiBandService *miBandService() const; HRMService *hrmService() const; @@ -124,7 +127,7 @@ class DeviceInterface : public QObject //Watchfish watchfish::MusicController m_musicController; -#ifdef MER_EDITION_SAILFISH +#if defined(MER_EDITION_SAILFISH) || defined(UUITK_EDITION) watchfish::VoiceCallController m_voiceCallController; #endif watchfish::NotificationMonitor m_notificationMonitor; diff --git a/daemon/src/devices/abstractdevice.cpp b/daemon/src/devices/abstractdevice.cpp index 95f77da8..232429f3 100644 --- a/daemon/src/devices/abstractdevice.cpp +++ b/daemon/src/devices/abstractdevice.cpp @@ -86,6 +86,11 @@ bool AbstractDevice::supportsFeature(AbstractDevice::Feature f) const return (supportedFeatures() & f) == f; } +void AbstractDevice::setDatabase(KDbConnection *conn) +{ + m_conn = conn; +} + QString AbstractDevice::deviceName() const { return m_pairedName; diff --git a/daemon/src/devices/abstractdevice.h b/daemon/src/devices/abstractdevice.h index 7acdc869..7d0637e4 100644 --- a/daemon/src/devices/abstractdevice.h +++ b/daemon/src/devices/abstractdevice.h @@ -6,6 +6,11 @@ #include "weather/currentweather.h" #include "abstractfirmwareinfo.h" +#include +#include +#include +#include + class AbstractDevice : public QBLEDevice { Q_OBJECT @@ -84,6 +89,8 @@ class AbstractDevice : public QBLEDevice bool supportsFeature(Feature f) const; virtual int supportedFeatures() const = 0; + void setDatabase(KDbConnection *conn); + virtual QString deviceType() const = 0; QString deviceName() const; virtual void abortOperations(); @@ -127,11 +134,13 @@ class AbstractDevice : public QBLEDevice QTimer *m_reconnectTimer; void setConnectionState(const QString &state); + KDbConnection *m_conn = nullptr; private: void reconnectionTimer(); void devicePairFinished(const QString& status); QString m_pairedName; + }; #endif diff --git a/daemon/src/devices/banglejsdevice.cpp b/daemon/src/devices/banglejsdevice.cpp index 89d0ae80..46186a24 100644 --- a/daemon/src/devices/banglejsdevice.cpp +++ b/daemon/src/devices/banglejsdevice.cpp @@ -195,10 +195,10 @@ void BangleJSDevice::sendWeather(CurrentWeather *weather) QJsonObject o; o.insert("t", "weather"); o.insert("temp", weather->temperature()); - o.insert("hum", 0); //TODO we dont have this + o.insert("hum", weather->humidity()); o.insert("txt", weather->description()); - o.insert("wind", 0); //TODO we dont have this - o.insert("wdir", ""); // TODO we dont have this + o.insert("wind", weather->windSpeed()); + o.insert("wdir", weather->windDeg()); o.insert("loc", weather->city()->name()); uart->txJson(o); diff --git a/daemon/src/devices/infinitimefirmwareinfo.cpp b/daemon/src/devices/infinitimefirmwareinfo.cpp index 9e6ba070..3ebd1256 100644 --- a/daemon/src/devices/infinitimefirmwareinfo.cpp +++ b/daemon/src/devices/infinitimefirmwareinfo.cpp @@ -52,7 +52,27 @@ void InfinitimeFirmwareInfo::determineFirmwareVersion() { switch (m_type) { case Firmware: - m_version = "FW ()"; + { + m_version = "FW ()"; + + QRegularExpression binNameVersionPattern(".*-((\\d+\\.){2}\\d+)\\.bin$"); + if (m_bytes.startsWith(UCHARARR_TO_BYTEARRAY(ZIP_HEADER))) { + QDataStream in(&m_bytes, QIODevice::ReadOnly); + KCompressionDevice dev(in.device(), false, KCompressionDevice::CompressionType::None); + KZip zip(&dev); + + if(zip.open(QIODevice::ReadOnly)) { + auto* root = zip.directory(); + foreach (const QString &entryName, root->entries()) { + QRegularExpressionMatch match = binNameVersionPattern.match(entryName); + if (match.hasMatch()) { + m_version = QString("FW (%1)").arg(match.captured(1)); + break; + } + } + } + } + } break; case Res_Compressed: m_version = "Ressource ()"; diff --git a/daemon/src/devices/pinetimejfdevice.cpp b/daemon/src/devices/pinetimejfdevice.cpp index 41a028e5..6601d778 100644 --- a/daemon/src/devices/pinetimejfdevice.cpp +++ b/daemon/src/devices/pinetimejfdevice.cpp @@ -11,8 +11,11 @@ #include "hrmservice.h" #include "infinitimemotionservice.h" #include "infinitimeweatherservice.h" +#include "pinetimesimpleweatherservice.h" #include "adafruitblefsservice.h" #include "batteryservice.h" +#include "amazfishconfig.h" +#include "realtimeactivitysample.h" #include namespace { @@ -152,6 +155,8 @@ void PinetimeJFDevice::parseServices() addService(HRMService::UUID_SERVICE_HRM, new HRMService(path, this)); } else if (uuid == InfiniTimeMotionService::UUID_SERVICE_MOTION && !service(InfiniTimeMotionService::UUID_SERVICE_MOTION)) { addService(InfiniTimeMotionService::UUID_SERVICE_MOTION, new InfiniTimeMotionService(path, this)); + } else if (uuid == PineTimeSimpleWeatherService::UUID_SERVICE_SIMPLE_WEATHER && !service(PineTimeSimpleWeatherService::UUID_SERVICE_SIMPLE_WEATHER)) { + addService(PineTimeSimpleWeatherService::UUID_SERVICE_SIMPLE_WEATHER, new PineTimeSimpleWeatherService(path, this)); } else if (uuid == InfiniTimeWeatherService::UUID_SERVICE_WEATHER && !service(InfiniTimeWeatherService::UUID_SERVICE_WEATHER)) { addService(InfiniTimeWeatherService::UUID_SERVICE_WEATHER, new InfiniTimeWeatherService(path, this)); } else if (uuid == AdafruitBleFsService::UUID_SERVICE_FS && !service(AdafruitBleFsService::UUID_SERVICE_FS)) { @@ -218,6 +223,7 @@ void PinetimeJFDevice::initialise() HRMService *hrm = qobject_cast(service(HRMService::UUID_SERVICE_HRM)); if (hrm) { connect(hrm, &HRMService::informationChanged, this, &AbstractDevice::informationChanged, Qt::UniqueConnection); + connect(hrm, &HRMService::informationChanged, &realtimeActivitySample, &RealtimeActivitySample::slot_informationChanged, Qt::UniqueConnection); } InfiniTimeMotionService *motion = qobject_cast(service(InfiniTimeMotionService::UUID_SERVICE_MOTION)); @@ -225,14 +231,66 @@ void PinetimeJFDevice::initialise() motion->enableNotification(InfiniTimeMotionService::UUID_CHARACTERISTIC_MOTION_STEPS); //motion->enableNotification(InfiniTimeMotionService::UUID_CHARACTERISTIC_MOTION_MOTION); connect(motion, &InfiniTimeMotionService::informationChanged, this, &AbstractDevice::informationChanged, Qt::UniqueConnection); + connect(motion, &InfiniTimeMotionService::informationChanged, &realtimeActivitySample, &RealtimeActivitySample::slot_informationChanged, Qt::UniqueConnection); } AdafruitBleFsService *bleFs = qobject_cast(service(AdafruitBleFsService::UUID_SERVICE_FS)); if (bleFs) { connect(bleFs, &AdafruitBleFsService::downloadProgress, this, &PinetimeJFDevice::downloadProgress, Qt::UniqueConnection); } + + connect(&realtimeActivitySample, &RealtimeActivitySample::samplesReady, this, &PinetimeJFDevice::sampledActivity, Qt::UniqueConnection); +} + +void PinetimeJFDevice::sampledActivity(QDateTime dt, int kind, int intensity, int steps, int heartrate) { + qDebug() << Q_FUNC_INFO << dt << kind << intensity << steps << heartrate; + + + if (!m_conn || !(m_conn->isDatabaseUsed())) { + qDebug() << "Database not connected"; + return; + } + + auto config = AmazfishConfig::instance(); + uint id = qHash(config->profileName()); + uint devid = qHash(config->pairedAddress()); + + KDbTransaction transaction = m_conn->beginTransaction(); + KDbTransactionGuard tg(transaction); + + KDbFieldList fields; + auto mibandActivity = m_conn->tableSchema("mi_band_activity"); + + fields.addField(mibandActivity->field("timestamp")); + fields.addField(mibandActivity->field("timestamp_dt")); + fields.addField(mibandActivity->field("device_id")); + fields.addField(mibandActivity->field("user_id")); + fields.addField(mibandActivity->field("raw_intensity")); + fields.addField(mibandActivity->field("steps")); + fields.addField(mibandActivity->field("raw_kind")); + fields.addField(mibandActivity->field("heartrate")); + + QList values; + + values << dt.toMSecsSinceEpoch() / 1000; + values << dt; + values << devid; + values << id; + values << intensity; + values << steps; + values << kind; + values << heartrate; + + if (!m_conn->insertRecord(&fields, values)) { + qWarning() << "error inserting record"; + tg.rollback(); + return; + } + tg.commit(); + } + void PinetimeJFDevice::onPropertiesChanged(QString interface, QVariantMap map, QStringList list) { qDebug() << "PinetimeJFDevice::onPropertiesChanged:" << interface << map << list; @@ -345,23 +403,30 @@ void PinetimeJFDevice::refreshInformation() info->refreshInformation(); } - BatteryService *bat = qobject_cast(service(BatteryService::UUID_SERVICE_BATTERY)); if (bat) { bat->refreshInformation(); } + InfiniTimeMotionService *motion = qobject_cast(service(InfiniTimeMotionService::UUID_SERVICE_MOTION)); + if (motion) { + motion->refreshSteps(); +// motion->refreshMotion(); + } + } QString PinetimeJFDevice::information(Info i) const { DeviceInfoService *info = qobject_cast(service(DeviceInfoService::UUID_SERVICE_DEVICEINFO)); if (!info) { + qWarning() << "Device info service doesn't exists"; return QString(); } - InfiniTimeMotionService *motion = qobject_cast(service(InfiniTimeMotionService::UUID_CHARACTERISTIC_MOTION_STEPS)); + InfiniTimeMotionService *motion = qobject_cast(service(InfiniTimeMotionService::UUID_SERVICE_MOTION)); if (!motion) { + qWarning() << "Motion service doesn't exists"; return QString(); } @@ -440,6 +505,12 @@ void PinetimeJFDevice::sendWeather(CurrentWeather *weather) if (w){ w->sendWeather(weather); } + + PineTimeSimpleWeatherService *sw = qobject_cast(service(PineTimeSimpleWeatherService::UUID_SERVICE_SIMPLE_WEATHER)); + + if (sw){ + sw->sendWeather(weather); + } } void PinetimeJFDevice::immediateAlert(int level) diff --git a/daemon/src/devices/pinetimejfdevice.h b/daemon/src/devices/pinetimejfdevice.h index f6f747b2..a1802063 100644 --- a/daemon/src/devices/pinetimejfdevice.h +++ b/daemon/src/devices/pinetimejfdevice.h @@ -3,6 +3,7 @@ #include #include "abstractdevice.h" +#include "realtimeactivitysample.h" class PinetimeJFDevice : public AbstractDevice { @@ -46,6 +47,11 @@ class PinetimeJFDevice : public AbstractDevice Q_SLOT void serviceEvent(const QString &characteristic, uint8_t event); AbstractFirmwareInfo::Type firmwareType; + RealtimeActivitySample realtimeActivitySample; + + Q_SLOT void sampledActivity(QDateTime dt, int kind, int intensity, int steps, int heartrate); + + }; #endif // PINETIMEJFDEVICE_H diff --git a/daemon/src/realtimeactivitysample.cpp b/daemon/src/realtimeactivitysample.cpp new file mode 100644 index 00000000..cba82cde --- /dev/null +++ b/daemon/src/realtimeactivitysample.cpp @@ -0,0 +1,98 @@ +#include "realtimeactivitysample.h" + +#include + +RealtimeActivitySample::RealtimeActivitySample(QObject* parent) : QObject(parent) { + m_lastSync = QDateTime::currentDateTime(); +} + +void RealtimeActivitySample::setKind(int _kind) { + if (_kind == 0) { + return; + } + sampleUpdated(); + m_kind = _kind; +} + +void RealtimeActivitySample::setIntensity(int _intensity) { + if (_intensity == 0) { + return; + } + sampleUpdated(); + m_intensity = _intensity; +} + +void RealtimeActivitySample::setSteps(int _steps) { + if (_steps == 0) { + return; + } + sampleUpdated(); + m_steps = _steps; +} + +void RealtimeActivitySample::setHeartrate(int _heartrate) { + if (_heartrate == 0) { + return; + } + sampleUpdated(); + m_heartrate_samples.push_back(_heartrate); +} + +void RealtimeActivitySample::sampleUpdated() { + QDateTime now = QDateTime::currentDateTime(); + + + int steps_diff = 0; + int avg_heartrate = 0; + + if (m_steps_previous == 0) { + m_steps_previous = m_steps; + } + + + + // send old data + if (m_lastSync.secsTo(now) > m_interval) { + + if ((m_steps_previous != 0) && (m_steps > m_steps_previous)) { + steps_diff = m_steps - m_steps_previous; + } + + if (!m_heartrate_samples.isEmpty()) { + for (int value : m_heartrate_samples) { + avg_heartrate += value; + } + avg_heartrate = avg_heartrate / m_heartrate_samples.size(); + } + if ((m_intensity != 0) || (steps_diff != 0) || (avg_heartrate != 0)) { + emit samplesReady(m_lastSync, m_kind, m_intensity, steps_diff, avg_heartrate); + + // prepare for new data + m_kind = 0; + m_intensity = 0; + m_steps_previous = m_steps; + m_steps = 0; + m_heartrate_samples.clear(); + m_lastSync = now; + + } + } + + +} + +void RealtimeActivitySample::slot_informationChanged(AbstractDevice::Info infokey, const QString &infovalue) { + + switch (infokey) { + case AbstractDevice::INFO_STEPS: + setSteps(infovalue.toInt()); + break; + case AbstractDevice::INFO_HEARTRATE: + setHeartrate(infovalue.toInt()); + break; + default: + qWarning() << Q_FUNC_INFO << "unexpeted key " << infokey; + break; + } + +} \ No newline at end of file diff --git a/daemon/src/realtimeactivitysample.h b/daemon/src/realtimeactivitysample.h new file mode 100644 index 00000000..a22f3ddc --- /dev/null +++ b/daemon/src/realtimeactivitysample.h @@ -0,0 +1,39 @@ +#ifndef REALTIME_ACTIVITY_SAMPLE__H +#define REALTIME_ACTIVITY_SAMPLE__H + +#include +#include +#include "abstractdevice.h" + +class RealtimeActivitySample : public QObject { + Q_OBJECT + + public: + RealtimeActivitySample(QObject* parent = NULL); + + void setKind(int _kind); + void setIntensity(int _intensity); + void setSteps(int _steps); + void setHeartrate(int _heartrate); + + signals: + void samplesReady(QDateTime dt, int kind, int intensity, int steps, int heart); + + public slots: + Q_SLOT void slot_informationChanged(AbstractDevice::Info infokey, const QString &infovalue); + + + private: + int m_kind = 0; + int m_intensity = 0; + int m_steps = 0; + int m_steps_previous = 0; + QList m_heartrate_samples; + + QDateTime m_lastSync; + qint64 m_interval = 60; + + void sampleUpdated(); +}; + +#endif // REALTIME_ACTIVITY_SAMPLE__H diff --git a/daemon/src/services/alertnotificationservice.cpp b/daemon/src/services/alertnotificationservice.cpp index bf6e671f..510c7143 100644 --- a/daemon/src/services/alertnotificationservice.cpp +++ b/daemon/src/services/alertnotificationservice.cpp @@ -17,10 +17,6 @@ void AlertNotificationService::sendAlert(const QString &sender, const QString &s { qDebug() << "Alert:" << sender << subject << message; - if (message.isEmpty()) { - return; - } - int category = 0xfa; //Custom Huami icon int icon = mapSenderToIcon(sender); diff --git a/daemon/src/services/alertnotificationservice.h b/daemon/src/services/alertnotificationservice.h index 82c94821..a4df312a 100644 --- a/daemon/src/services/alertnotificationservice.h +++ b/daemon/src/services/alertnotificationservice.h @@ -97,6 +97,7 @@ class AlertNotificationService : public QBLEService /* Instagram */ {"instagram", HuamiIcon::INSTAGRAM}, /* Telegram clients */ + {"teleports.ubports_teleports", HuamiIcon::TELEGRAM}, {"depecher", HuamiIcon::TELEGRAM}, {"fernschreiber", HuamiIcon::TELEGRAM}, {"sailorgram", HuamiIcon::TELEGRAM}, diff --git a/daemon/src/services/batteryservice.cpp b/daemon/src/services/batteryservice.cpp index 0d3e51b0..307b70cc 100644 --- a/daemon/src/services/batteryservice.cpp +++ b/daemon/src/services/batteryservice.cpp @@ -24,9 +24,8 @@ void BatteryService::characteristicRead(const QString &characteristic, const QBy if (characteristic == UUID_CHARACTERISTIC_BATTERY_LEVEL) { m_batteryLevel = value[0]; emit informationChanged(AbstractDevice::INFO_BATTERY, QString::number(m_batteryLevel)); - qDebug() << "Batery" << m_batteryLevel; } else { - qDebug() << "Unknown value"; + qWarning() << "Unknown value"; } } diff --git a/daemon/src/services/infinitimemotionservice.cpp b/daemon/src/services/infinitimemotionservice.cpp index f51e947c..be2baca4 100644 --- a/daemon/src/services/infinitimemotionservice.cpp +++ b/daemon/src/services/infinitimemotionservice.cpp @@ -11,7 +11,16 @@ InfiniTimeMotionService::InfiniTimeMotionService(const QString &path, QObject *p { qDebug() << Q_FUNC_INFO; connect(this, &QBLEService::characteristicChanged, this, &InfiniTimeMotionService::characteristicChanged); +} + +void InfiniTimeMotionService::refreshSteps() +{ + readAsync(UUID_CHARACTERISTIC_MOTION_STEPS); +} +void InfiniTimeMotionService::refreshMotion() +{ + readAsync(UUID_CHARACTERISTIC_MOTION_MOTION); } int InfiniTimeMotionService::steps() const @@ -21,7 +30,7 @@ int InfiniTimeMotionService::steps() const void InfiniTimeMotionService::characteristicChanged(const QString &characteristic, const QByteArray &value) { - qDebug() << "MiBand Changed:" << characteristic << value.toHex(); + qDebug() << Q_FUNC_INFO << characteristic << value.toHex(); if (characteristic == UUID_CHARACTERISTIC_MOTION_STEPS) { qDebug() << "...Got realtime steps:" << value.length(); @@ -29,5 +38,13 @@ void InfiniTimeMotionService::characteristicChanged(const QString &characteristi m_steps = TypeConversion::toUint32(value[0], value[1], value[2], value[3]); emit informationChanged(AbstractDevice::INFO_STEPS, QString::number(m_steps)); } + } else if (characteristic == UUID_CHARACTERISTIC_MOTION_MOTION) { + if (value.length() == 6) { + double x = (double) TypeConversion::toInt16(value[0], value[1]) * 0.01; + double y = (double) TypeConversion::toInt16(value[2], value[3]) * 0.01; + double z = (double) TypeConversion::toInt16(value[4], value[5]) * 0.01; + emit motionChanged(x, y, z); + qDebug() << "...Got realtime motion: x = " << x << " y = " << y << " z = " << z; + } } } diff --git a/daemon/src/services/infinitimemotionservice.h b/daemon/src/services/infinitimemotionservice.h index ccccffd1..0a511101 100644 --- a/daemon/src/services/infinitimemotionservice.h +++ b/daemon/src/services/infinitimemotionservice.h @@ -19,7 +19,11 @@ class InfiniTimeMotionService : public QBLEService static const char *UUID_CHARACTERISTIC_MOTION_STEPS; static const char *UUID_CHARACTERISTIC_MOTION_MOTION; + Q_INVOKABLE void refreshMotion(); + Q_INVOKABLE void refreshSteps(); + Q_SIGNAL void informationChanged(AbstractDevice::Info key, const QString &val); + Q_SIGNAL void motionChanged(double x, double y, double z); int steps() const; diff --git a/daemon/src/services/mibandservice.cpp b/daemon/src/services/mibandservice.cpp index d8ca7456..a39ce800 100644 --- a/daemon/src/services/mibandservice.cpp +++ b/daemon/src/services/mibandservice.cpp @@ -781,7 +781,7 @@ void MiBandService::sendWeather(const CurrentWeather *weather, bool supportsCond QBuffer buffer(&buf); buffer.open(QIODevice::WriteOnly); - char temp = weather->temperature() - 273; + char temp = weather->temperature() - 273.15; qint32 dt = qToLittleEndian(weather->dateTime()); qDebug() << dt << temp << condition; @@ -830,8 +830,8 @@ void MiBandService::sendWeather(const CurrentWeather *weather, bool supportsCond buffer.putChar(NR_DAYS); buffer.putChar(condition); buffer.putChar(condition); - buffer.putChar((char) (weather->maxTemperature() - 273)); - buffer.putChar((char) (weather->minTemperature() - 273)); + buffer.putChar((char) (weather->maxTemperature() - 273.15)); + buffer.putChar((char) (weather->minTemperature() - 273.15)); if (supportsConditionString) { buffer.write(weather->description().toLatin1()); buffer.putChar((char)0x00); @@ -845,8 +845,8 @@ void MiBandService::sendWeather(const CurrentWeather *weather, bool supportsCond buffer.putChar(condition); buffer.putChar(condition); - buffer.putChar((char) (fc.maxTemperature() - 273)); - buffer.putChar((char) (fc.minTemperature() - 273)); + buffer.putChar((char) (fc.maxTemperature() - 273.15)); + buffer.putChar((char) (fc.minTemperature() - 273.15)); if (supportsConditionString) { buffer.write(fc.description().toLatin1()); diff --git a/daemon/src/services/pinetimesimpleweatherservice.cpp b/daemon/src/services/pinetimesimpleweatherservice.cpp new file mode 100644 index 00000000..8613061b --- /dev/null +++ b/daemon/src/services/pinetimesimpleweatherservice.cpp @@ -0,0 +1,141 @@ +#include "pinetimesimpleweatherservice.h" +#include "typeconversion.h" +#include "codec.h" + +#include +#include +#include + +const char* PineTimeSimpleWeatherService::UUID_SERVICE_SIMPLE_WEATHER = "00050000-78fc-48fe-8e23-433b3a1942d0"; +const char* PineTimeSimpleWeatherService::UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA = "00050001-78fc-48fe-8e23-433b3a1942d0"; + +PineTimeSimpleWeatherService::PineTimeSimpleWeatherService(const QString &path, QObject *parent) : QBLEService(UUID_SERVICE_SIMPLE_WEATHER, path, parent) +{ + qDebug() << Q_FUNC_INFO; +} + + +void PineTimeSimpleWeatherService::sendWeather(CurrentWeather *weather) +{ + + qDebug() << "Current weather data" +#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) + << QDateTime::fromSecsSinceEpoch(weather->dateTime()) +#else + << QDateTime::fromTime_t(weather->dateTime()) +#endif + << weather->dateTime() + << weather->temperature() - 273.15 + << weather->minTemperature() - 273.15 + << weather->maxTemperature() - 273.15 + << weather->city()->name() + << weather->weatherIcon() + << QMetaEnum::fromType().valueToKey( + (int)iconToEnum(weather->weatherIcon()) + ) + << (int)iconToEnum(weather->weatherIcon()) +// << weather->clouds() +// << weather->humidity() +// << weather->windDeg() +// << weather->windSpeed() +// << weather->windGusts() + ; + + QByteArray cityNameBytes = weather->city()->name().toLocal8Bit().left(32); + if(cityNameBytes.size() < 32) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) + cityNameBytes.append(32-cityNameBytes.size(), 0x00); +#else + for (int i = 0; i < (32 - cityNameBytes.size()); i++) { + cityNameBytes.append( '\0' ); + } +#endif + } + + QByteArray weatherBytes; + + weatherBytes += TypeConversion::fromInt8(0); // message type + weatherBytes += TypeConversion::fromInt8(0); // version information + weatherBytes += TypeConversion::fromInt64(weather->dateTime()); + weatherBytes += TypeConversion::fromInt16( round((weather->temperature() - 273.15) * 100) ); + weatherBytes += TypeConversion::fromInt16( round((weather->minTemperature() - 273.15) * 100) ); + weatherBytes += TypeConversion::fromInt16( round((weather->maxTemperature() - 273.15) * 100) ); + weatherBytes += cityNameBytes; + weatherBytes += TypeConversion::fromInt8( (int)iconToEnum(weather->weatherIcon()) ); + +// weatherBytes += TypeConversion::fromInt8( weather->clouds() ); +// weatherBytes += TypeConversion::fromInt8( weather->humidity() ); + +// weatherBytes += TypeConversion::fromInt16( (int)(100 * weather->windDeg())); +// weatherBytes += TypeConversion::fromInt16((int)(100 * weather->windSpeed())); +// weatherBytes += TypeConversion::fromInt16((int)(100 * weather->windGusts())); + + qDebug() << "Weather bytes" << weatherBytes.toHex(); + + writeValue(UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA, weatherBytes); + + + int fcDays = std::min(weather->forecastCount(), 5); + + QByteArray forecastBytes; + forecastBytes += TypeConversion::fromInt8(1); // message type + forecastBytes += TypeConversion::fromInt8(0); // version information + forecastBytes += TypeConversion::fromInt64(weather->dateTime()); + forecastBytes += TypeConversion::fromInt8(fcDays); + + + + for (int f = 0; (f < fcDays); f++) { + CurrentWeather::Forecast fc = weather->forecast(f); +// qDebug() << "Forecast:" << f << fc.dateTime()<< fc.weatherCode() << (fc.maxTemperature() - 273) << (fc.minTemperature() - 273) << fc.humidity() << fc.pressure() << fc.windMaxSpeed() << fc.clouds(); + + + qDebug() << "Forecast Day" << f +#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) + << QDateTime::fromSecsSinceEpoch(fc.dateTime()) +#else + << QDateTime::fromTime_t(fc.dateTime()) +#endif + << fc.dateTime() + << fc.minTemperature() - 273.15 + << fc.maxTemperature() - 273.15 + << fc.weatherIcon() + << QMetaEnum::fromType().valueToKey( + (int)iconToEnum(fc.weatherIcon()) + ) + << (int)iconToEnum(fc.weatherIcon()) + ; + + forecastBytes += TypeConversion::fromInt16( round((fc.minTemperature() - 273.15) * 100) ); + forecastBytes += TypeConversion::fromInt16( round((fc.maxTemperature() - 273.15) * 100) ); + forecastBytes += TypeConversion::fromInt8( (int)iconToEnum(fc.weatherIcon()) ); + + } + + qDebug() << "Forecast bytes" << forecastBytes.toHex(); + + writeValue(UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA, forecastBytes); + +} + +PineTimeSimpleWeatherService::WeatherIcons PineTimeSimpleWeatherService::iconToEnum(const QString& iconName) { + if (iconName == "01d") return WeatherIcons::ClearSky; + if (iconName == "01n") return WeatherIcons::ClearSky; + if (iconName == "02d") return WeatherIcons::FewClouds; + if (iconName == "02n") return WeatherIcons::FewClouds; + if (iconName == "03d") return WeatherIcons::ScatteredClouds; + if (iconName == "03n") return WeatherIcons::ScatteredClouds; + if (iconName == "04d") return WeatherIcons::BrokenClouds; + if (iconName == "04n") return WeatherIcons::BrokenClouds; + if (iconName == "09d") return WeatherIcons::ShowerRain; + if (iconName == "09n") return WeatherIcons::ShowerRain; + if (iconName == "10d") return WeatherIcons::Rain; + if (iconName == "10n") return WeatherIcons::Rain; + if (iconName == "11d") return WeatherIcons::Thunderstorm; + if (iconName == "11n") return WeatherIcons::Thunderstorm; + if (iconName == "13d") return WeatherIcons::Snow; + if (iconName == "13n") return WeatherIcons::Snow; + if (iconName == "50d") return WeatherIcons::Mist; + if (iconName == "50n") return WeatherIcons::Mist; + return WeatherIcons::Unknown; +} diff --git a/daemon/src/services/pinetimesimpleweatherservice.h b/daemon/src/services/pinetimesimpleweatherservice.h new file mode 100644 index 00000000..b5e77478 --- /dev/null +++ b/daemon/src/services/pinetimesimpleweatherservice.h @@ -0,0 +1,39 @@ +#ifndef PINETIMESIMPLEWEATHERSERVICE_H +#define PINETIMESIMPLEWEATHERSERVICE_H + +#include +#include "qble/qbleservice.h" +#include "devices/abstractdevice.h" + +class PineTimeSimpleWeatherService : public QBLEService +{ + Q_OBJECT +public: + static const char *UUID_SERVICE_SIMPLE_WEATHER; + static const char *UUID_CHARACTERISTIC_SIMPLE_WEATHER_DATA; + + explicit PineTimeSimpleWeatherService(const QString &path, QObject *parent); + + + void sendWeather(CurrentWeather *weather); + + enum class WeatherIcons { + ClearSky = 0, + FewClouds = 1, + ScatteredClouds = 2, + BrokenClouds = 3, + ShowerRain = 4, + Rain = 5, + Thunderstorm = 6, + Snow = 7, + Mist = 8, + Unknown = 255 // for any icon not listed above + }; + + Q_ENUM(WeatherIcons) + + WeatherIcons iconToEnum(const QString& iconName); + +}; + +#endif // #define PINETIMESIMPLEWEATHERSERVICE_H diff --git a/daemon/src/transliterator.cpp b/daemon/src/transliterator.cpp index 95914833..30374e07 100644 --- a/daemon/src/transliterator.cpp +++ b/daemon/src/transliterator.cpp @@ -26,7 +26,85 @@ QString Transliterator::convert(const QString& input) { std::string str_result; uInput.toUTF8String(str_result); - return QString::fromStdString(str_result); + return replaceEmojis(QString::fromStdString(str_result)); +} + + +QString Transliterator::replaceEmojis(const QString &input) { + const QMap emojiTransliterator = { + {"\xE2\x9D\xA4", "<3"}, // ❤ heart + {"\xE2\x98\xB9", ":-("}, // ☹ frowning_face + {"\xF0\x9F\x91\x8D", ":thumbs-up:"}, // 👍 thumbs-up + {"\xF0\x9F\x91\x8E", ":thumbs-down:"}, // 👎 thumbs-down + {"\xF0\x9F\x91\x8F", ":clap:"}, // 👏 clap + {"\xF0\x9F\x94\xA5", ":fire:"}, // 🔥 fire + {"\xF0\x9F\x99\x8F", ":praying:"}, // 🙏 praying + {"\xF0\x9F\x98\x94", ":think:"}, // 😔 pensive + {"\xF0\x9F\x91\x89", ":point:"}, // 👉 point + {"\xF0\x9F\x91\x8B", ":wave:"}, // 👋 wave + {"\xF0\x9F\x8E\x89", ":party:"}, // 🎉 party + {"\xF0\x9F\x98\x8D", ":heart_eyes:"}, // 😍 heart_eyes + {"\xF0\x9F\xA4\x94", ":thinking:"}, // 🤔 thinking + {"\xF0\x9F\x98\x80", ":-D"}, // 😀 grinning + {"\xF0\x9F\x98\x81", ":-D"}, // 😁 grinning_face_with_smiling_eyes + {"\xF0\x9F\x98\x82", ":'D"}, // 😂 face_with_tears_of_joy + {"\xF0\x9F\x98\x83", ":-D"}, // 😃 smiling_face_with_open_mouth + {"\xF0\x9F\x98\x84", ":-D"}, // 😄 smiling_face_with_open_mouth_and_smiling_eyes + {"\xF0\x9F\x98\x85", ":'D"}, // 😅 smiling_face_with_open_mouth_and_cold_sweat + {"\xF0\x9F\x98\x86", "X-D"}, // 😆 smiling_face_with_open_mouth_and_tightly-closed_eyes + {"\xF0\x9F\x98\x87", "O:-)"}, // 😇 innocent + {"\xF0\x9F\x98\x89", ";-)"}, // 😉 wink + {"\xF0\x9F\x98\x8A", ":-)"}, // 😊 blush + {"\xF0\x9F\x98\x8B", ":-p"}, // 😋 yum + {"\xF0\x9F\x98\x8E", "B-)"}, // 😎 sunglasses + {"\xF0\x9F\x98\x95", ":-/"}, // 😕 confused + {"\xF0\x9F\x98\x96", ":-S"}, // 😖 confounded_face + {"\xF0\x9F\x98\x97", ":*"}, // 😗 kissing_face + {"\xF0\x9F\x98\x98", ";-*"}, // 😘 face_throwing_a_kiss + {"\xF0\x9F\x98\x99", ":-*"}, // 😙 kissing_face_with_smiling_eyes + {"\xF0\x9F\x98\x9A", ":-*"}, // 😚 kissing_closed_eyes + {"\xF0\x9F\x98\x9B", ":-P"}, // 😛 stuck_out_tongue + {"\xF0\x9F\x98\x9C", ";-P"}, // 😜 stuck_out_tongue_winking_eye + {"\xF0\x9F\x98\x9D", "X-P"}, // 😝 stuck_out_tongue_and_tightly-closed_eyes + {"\xF0\x9F\x98\x9E", ":-S"}, // 😞 disappointed + {"\xF0\x9F\x98\xA0", ":-@"}, // 😠 angry_face + {"\xF0\x9F\x98\xA1", ":-@"}, // 😡 pouting_face + {"\xF0\x9F\x98\xA2", ":'("}, // 😢 cry + {"\xF0\x9F\x98\xA3", ":'("}, // 😣 persevering_face + {"\xF0\x9F\x98\xA4", ":-O"}, // 😤 face_with_steam_from_nose + {"\xF0\x9F\x98\xA5", ":'O"}, // 😥 face_with_cold_sweat + {"\xF0\x9F\x98\xA8", ":-O"}, // 😨 fearful + {"\xF0\x9F\x98\xA9", ":-O"}, // 😩 weary_face + {"\xF0\x9F\x98\xAA", ":'("}, // 😪 sleepy_face + {"\xF0\x9F\x98\xAB", ":-|"}, // 😫 tired_face + {"\xF0\x9F\x98\xAC", ":-|"}, // 😬 grimacing_face + {"\xF0\x9F\x98\xAD", ":'("}, // 😭 loudly_crying_face + {"\xF0\x9F\x98\xAE", ":-O"}, // 😮 face_with_open_mouth + {"\xF0\x9F\x98\xAF", ":-/"}, // 😯 hushed_face + {"\xF0\x9F\x98\xB0", ":-/"}, // 😰 face_with_open_mouth_and_cold_sweat + {"\xF0\x9F\x98\xB1", ":-O"}, // 😱 face_screaming_in_fear + {"\xF0\x9F\x98\xB2", ":-/"}, // 😲 astonished_face + {"\xF0\x9F\x98\xB3", ":-S"}, // 😳 flushed_face + {"\xF0\x9F\x98\xB4", ":-S"}, // 😴 sleeping_face + {"\xF0\x9F\x98\xB5", ":-P"}, // 😵 dizzy_face + {"\xF0\x9F\x98\xB7", ":-x"}, // 😷 face_with_medical_mask + {"\xF0\x9F\x98\xB8", "O.o"}, // 😸 grinning_cat_face_with_smiling_eyes + {"\xF0\x9F\x98\xB9", ":-)"}, // 😹 cat_face_with_tears_of_joy + {"\xF0\x9F\x98\xBA", ":-("}, // 😺 grinning_cat_face + {"\xF0\x9F\x98\xBB", ":-D"}, // 😻 heart_eyes_cat + {"\xF0\x9F\x98\xBC", ":'-("}, // 😼 cat_face_with_wry_smile + {"\xF0\x9F\x98\xBD", ":'("}, // 😽 kissing_cat_face + {"\xF0\x9F\x98\xBE", ":-@"}, // 😾 pouting_cat_face + {"\xF0\x9F\x98\xBF", ":-@"}, // 😿 crying_cat_face + {"\xF0\x9F\x99\x80", "O.O"}, // 🙀 screaming_cat_face + {"\xF0\x9F\x99\x83", "(-:"}, // 🙃 upside-down_face + {"\xF0\x9F\x91\x8C", ":ok:"}, // 👌 OK_hand + }; + QString output(input); + for (auto it = emojiTransliterator.begin(); it != emojiTransliterator.end(); ++it) { + output.replace(it.key(), it.value()); + } + return output; } void Transliterator::test() { @@ -39,6 +117,7 @@ void Transliterator::test() { "Frida var den skönaste kvinnan på Gotland.", // Swedish "Příliš žluťoučký kůň úpěl ďábelské ódy", // Czech "Звонко чепуху щеголя прямо фьордом с экземплярами живых бегемотов.", // Russian + "❤🙃😂😍👍🙏👏🔥👉🤔😁👌", // emojis }; // Iterate through the input strings and transliterate each one @@ -48,4 +127,4 @@ void Transliterator::test() { << "\n >>>" << transliterated; } -} \ No newline at end of file +} diff --git a/daemon/src/transliterator.h b/daemon/src/transliterator.h index 90888b9b..f453fd41 100644 --- a/daemon/src/transliterator.h +++ b/daemon/src/transliterator.h @@ -6,6 +6,7 @@ class Transliterator { public: static QString convert(const QString& input); + static QString replaceEmojis(const QString& input); static void test(); }; diff --git a/daemon/src/typeconversion.cpp b/daemon/src/typeconversion.cpp index 766eecbc..60a85801 100644 --- a/daemon/src/typeconversion.cpp +++ b/daemon/src/typeconversion.cpp @@ -25,6 +25,23 @@ QByteArray fromInt32(int val) return QByteArray(1, val & 0xff) + QByteArray(1, ((val >> 8) & 0xff)) + QByteArray(1, ((val >> 16) & 0xff)) + QByteArray(1, ((val >> 24) & 0xff)); } +QByteArray fromInt64(long long int val) +{ + QByteArray ret = QByteArray(1, val & 0xff) + + QByteArray(1, ((val >> 8) & 0xff)) + + QByteArray(1, ((val >> 16) & 0xff)) + + QByteArray(1, ((val >> 24) & 0xff)) + + QByteArray(1, ((val >> 32) & 0xff)) + + QByteArray(1, ((val >> 40) & 0xff)) + + QByteArray(1, ((val >> 48) & 0xff)) + + QByteArray(1, ((val >> 56) & 0xff)); + + qDebug() << "Converting int64 to char" << val << " " << ret.toHex(); + + return ret; +} + + QByteArray dateTimeToBytes(const QDateTime &dt, int format, bool adjustForTZ) { QByteArray ret; @@ -129,7 +146,6 @@ int toUint16(char val1, char val2) { return (val1 & 0xff) | ((val2 & 0xff) << 8); } - int toUint32(char val1, char val2, char val3, char val4) { return (val1 & 0xff) | ((val2 & 0xff) << 8) | ((val3 & 0xff) << 16) | ((val4 & 0xff) << 24); } diff --git a/daemon/src/typeconversion.h b/daemon/src/typeconversion.h index b16bff81..140e70ea 100644 --- a/daemon/src/typeconversion.h +++ b/daemon/src/typeconversion.h @@ -9,6 +9,7 @@ QByteArray fromInt8(int val); QByteArray fromInt16(int val); QByteArray fromInt24(int val); QByteArray fromInt32(int val); +QByteArray fromInt64(long long int val); QDateTime rawBytesToDateTime(const QByteArray &value, bool honorDeviceTimeOffset); QByteArray dateTimeToBytes(const QDateTime &dt, int format, bool adjustForTZ = true); int toUint16(char val1, char val2); diff --git a/documentation/build-instructions.md b/documentation/build-instructions.md index e0d44eb1..ddc27e71 100644 --- a/documentation/build-instructions.md +++ b/documentation/build-instructions.md @@ -13,7 +13,7 @@ To get into the build machine (if started by Sailfish IDE) Use the `sb2-config` command to make your ARM target the default Add this repo which provides KF5 packages -`sb2 -R zypper ar http://repo.merproject.org/obs/home:/piggz:/kf5/sailfishos_3.4.0.24/ piggz` +`sb2 -R zypper ar http://repo.merproject.org/obs/home:/piggz:/kf5/sailfish_latest_aarch64/ piggz` and `sb2 -R zypper refresh` @@ -22,10 +22,7 @@ In the .pro file, you can set `FLAVOR = silica` (or `FLAVOR = $$FLAVOR` and in t To install missing packages, as pointed out by build errors, use: ``` -sb2 -R zypper in kcoreaddons-devel -sb2 -R zypper in kdb-devel -sb2 -R zypper in libKDb3-3 -sb2 -R zypper in mkcal-qt5-devel +sb2 -R zypper in kcoreaddons-devel kdb-devel libKDb3-3 mkcal-qt5-devel libicu-devel ``` etc as pointed out by build errors. @@ -148,8 +145,7 @@ clickable desktop To build the application for ARM architecture and install it on your device, follow these steps: ``` -clickable build --libs --arch detect -clickable build --arch detect +clickable build --libs --app --arch detect --skip-review clickable install ``` diff --git a/lib/src/weather/currentweather.cpp b/lib/src/weather/currentweather.cpp index 13a3d17b..6b833f74 100644 --- a/lib/src/weather/currentweather.cpp +++ b/lib/src/weather/currentweather.cpp @@ -61,7 +61,7 @@ void CurrentWeather::setCity(City *city) } } -int CurrentWeather::temperature() const +qreal CurrentWeather::temperature() const { return m_temperature; } @@ -109,11 +109,21 @@ void CurrentWeather::handleCurrent(const QByteArray &reply) QJsonObject weather = object.value("weather").toArray().first().toObject(); m_weatherCode = weather.value("id").toVariant().toInt(); m_description = weather.value("description").toVariant().toString(); + m_weatherIcon = weather.value("icon").toVariant().toString(); + + QJsonObject wind = object.value("wind").toArray().first().toObject(); + m_windDeg = wind.value("wind_deg").toVariant().toDouble(); + m_windSpeed = wind.value("wind_speed").toVariant().toDouble(); + m_windGusts = wind.value("wind_gusts").toVariant().toDouble(); + + QJsonObject clouds = object.value("clouds").toArray().first().toObject(); + m_clouds = clouds.value("all").toVariant().toInt(); QJsonObject main = object.value("main").toObject(); - m_temperature = int(main.value("temp").toDouble()); - m_minTemperature = int(main.value("temp_min").toDouble()); - m_maxTemperature = int(main.value("temp_max").toDouble()); + m_temperature = main.value("temp").toDouble(); + m_minTemperature = main.value("temp_min").toDouble(); + m_maxTemperature = main.value("temp_max").toDouble(); + m_humidity = main.value("humidity").toDouble(); } void CurrentWeather::handleForecast(const QByteArray &reply) @@ -152,27 +162,29 @@ void CurrentWeather::handleForecast(const QByteArray &reply) QJsonObject weather = obj.value("weather").toArray().first().toObject(); + QString weatherIcon = weather.value("icon").toVariant().toString(); + int code = weather.value("id").toVariant().toInt(); QString desc = weather.value("description").toVariant().toString(); QJsonObject main = obj.value("main").toObject(); - int min_temp = int(main.value("temp_min").toDouble()); - int max_temp = int(main.value("temp_max").toDouble()); + int min_temp = main.value("temp_min").toDouble(); + int max_temp = main.value("temp_max").toDouble(); int wind_speed = 0; f.setWindMaxSpeed(0); f.setWindMinSpeed(255); if (obj.contains("rain")) { QJsonObject rain = obj.value("rain").toObject(); - total_rain += int(rain.value("3h").toDouble()); + total_rain += rain.value("3h").toDouble(); } if (obj.contains("snow")) { QJsonObject snow = obj.value("snow").toObject(); - total_snow += int(snow.value("3h").toDouble()); + total_snow += snow.value("3h").toDouble(); } if (obj.contains("wind")) { QJsonObject wind = obj.value("wind").toObject(); - wind_speed += int(wind.value("speed").toDouble()); + wind_speed += wind.value("speed").toDouble(); } qDebug() << "Forecast on " << dt << d << t << min_temp << max_temp << desc << code << "Hour:" << t.hour(); @@ -197,8 +209,9 @@ void CurrentWeather::handleForecast(const QByteArray &reply) f.setDateTime(dt); f.setDescription(desc); f.setWeatherCode(code); - f.setPressure(int(main.value("pressure").toDouble())); - f.setHumidity(int(main.value("humidity").toDouble())); + f.setWeatherIcon(weatherIcon); + f.setPressure(main.value("pressure").toDouble()); + f.setHumidity(main.value("humidity").toDouble()); } if (max_temp > f.maxTemperature()){ f.setMaxTemperature(max_temp); @@ -308,12 +321,42 @@ int CurrentWeather::weatherCode() const return m_weatherCode; } -int CurrentWeather::minTemperature() const +QString CurrentWeather::weatherIcon() const +{ + return m_weatherIcon; +} + +qreal CurrentWeather::windDeg() const +{ + return m_windDeg; +} + +qreal CurrentWeather::windSpeed() const +{ + return m_windSpeed; +} + +qreal CurrentWeather::windGusts() const +{ + return m_windGusts; +} + +int CurrentWeather::humidity() const +{ + return m_humidity; +} + +int CurrentWeather::clouds() const +{ + return m_clouds; +} + +qreal CurrentWeather::minTemperature() const { return m_minTemperature; } -int CurrentWeather::maxTemperature() const +qreal CurrentWeather::maxTemperature() const { return m_maxTemperature; } @@ -328,22 +371,33 @@ QString CurrentWeather::description() const return m_description; } -int CurrentWeather::Forecast::minTemperature() const +qreal CurrentWeather::Forecast::minTemperature() const { return m_minTemperature; } -void CurrentWeather::Forecast::setMinTemperature(int minTemperature) +void CurrentWeather::Forecast::setMinTemperature(qreal minTemperature) { m_minTemperature = minTemperature; } -int CurrentWeather::Forecast::maxTemperature() const +qreal CurrentWeather::Forecast::maxTemperature() const { return m_maxTemperature; } -void CurrentWeather::Forecast::setMaxTemperature(int maxTemperature) +void CurrentWeather::Forecast::setWeatherIcon(QString _weatherIcon) +{ + m_weatherIcon = _weatherIcon; +} + +QString CurrentWeather::Forecast::weatherIcon() const +{ + return m_weatherIcon; +} + + +void CurrentWeather::Forecast::setMaxTemperature(qreal maxTemperature) { m_maxTemperature = maxTemperature; } diff --git a/lib/src/weather/currentweather.h b/lib/src/weather/currentweather.h index 9bccf1b5..b8b33428 100644 --- a/lib/src/weather/currentweather.h +++ b/lib/src/weather/currentweather.h @@ -46,15 +46,18 @@ class CurrentWeather : public QObject class Forecast { public: - int minTemperature() const; - void setMinTemperature(int minTemperature); + qreal minTemperature() const; + void setMinTemperature(qreal minTemperature); - int maxTemperature() const; - void setMaxTemperature(int maxTemperature); + qreal maxTemperature() const; + void setMaxTemperature(qreal maxTemperature); int weatherCode() const; void setWeatherCode(int weatherCode); + QString weatherIcon() const; + void setWeatherIcon(QString weatherIcon); + void setDescription(const QString &description); QString description() const; @@ -86,9 +89,9 @@ class CurrentWeather : public QObject void setWindDirection(uint8_t newWindDirection); private: - int m_temperature = 0; - int m_minTemperature = 0; - int m_maxTemperature = 0; + qreal m_temperature = 0; + qreal m_minTemperature = 0; + qreal m_maxTemperature = 0; int m_weatherCode = 0; QString m_description; qlonglong m_dateTime = 0; @@ -100,6 +103,7 @@ class CurrentWeather : public QObject uint8_t m_snowMMDay = 0; uint8_t m_humidity = 0; uint8_t m_pressure = 0; + QString m_weatherIcon; }; @@ -109,12 +113,29 @@ class CurrentWeather : public QObject QString language() const; void setLanguage(const QString &language); - Q_PROPERTY(int temperature READ temperature NOTIFY ready) - - int temperature() const; - int minTemperature() const; - int maxTemperature() const; + Q_PROPERTY(qreal temperature READ temperature NOTIFY ready) + Q_PROPERTY(qreal minTemperature READ minTemperature NOTIFY ready) + Q_PROPERTY(qreal maxTemperature READ maxTemperature NOTIFY ready) + Q_PROPERTY(int weatherCode READ weatherCode NOTIFY ready) + Q_PROPERTY(QString weatherIcon READ weatherIcon NOTIFY ready) + Q_PROPERTY(qreal windDeg READ windDeg NOTIFY ready) + Q_PROPERTY(qreal windSpeed READ windSpeed NOTIFY ready) + Q_PROPERTY(qreal windGusts READ windGusts NOTIFY ready) + Q_PROPERTY(int humidity READ humidity NOTIFY ready) + Q_PROPERTY(int clouds READ clouds NOTIFY ready) + + + qreal temperature() const; + qreal minTemperature() const; + qreal maxTemperature() const; int weatherCode() const; + QString weatherIcon() const; + qreal windDeg() const; + qreal windSpeed() const; + qreal windGusts() const; + int humidity() const; + int clouds() const; + QString description() const; qlonglong dateTime() const; @@ -141,10 +162,18 @@ class CurrentWeather : public QObject City *m_city = nullptr; QString m_language; - int m_temperature = 0; - int m_minTemperature = 0; - int m_maxTemperature = 0; + qreal m_temperature = 0; + qreal m_minTemperature = 0; + qreal m_maxTemperature = 0; int m_weatherCode = 0; + QString m_weatherIcon; + + qreal m_windDeg = 0; + qreal m_windSpeed = 0; + qreal m_windGusts = 0; + int m_humidity = 0; + int m_clouds = 0; + QString m_description; qlonglong m_dateTime = 0; diff --git a/resources/icon-m-heartrate2.svg b/resources/icon-m-heartrate2.svg new file mode 100644 index 00000000..8a4d069e --- /dev/null +++ b/resources/icon-m-heartrate2.svg @@ -0,0 +1,73 @@ + + + + diff --git a/resources/icon-m-steps2.svg b/resources/icon-m-steps2.svg new file mode 100644 index 00000000..70bf23dd --- /dev/null +++ b/resources/icon-m-steps2.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/qml/components/DownloadDataMenuItem.qml b/ui/qml/components/DownloadDataMenuItem.qml index 5040553f..ed7ea925 100644 --- a/ui/qml/components/DownloadDataMenuItem.qml +++ b/ui/qml/components/DownloadDataMenuItem.qml @@ -2,6 +2,7 @@ import QtQuick 2.0 import "platform" PageMenuItemPL { + iconSource: styler.iconDownloadData !== undefined ? styler.iconDownloadData : "" text: qsTr("Download Data") onClicked: DaemonInterfaceInstance.downloadActivityData() enabled: DaemonInterfaceInstance.connectionState === "authenticated" diff --git a/ui/qml/components/GraphData.qml b/ui/qml/components/GraphData.qml index 0ffb9a8a..07abf852 100644 --- a/ui/qml/components/GraphData.qml +++ b/ui/qml/components/GraphData.qml @@ -47,7 +47,7 @@ Item { property bool scale: false property color lineColor: styler.themeHighlightColor - property real lineWidth: 1 + property real lineWidth: 3 property real minY: 0 //Always 0 property real maxY: 0 @@ -233,7 +233,11 @@ Item { //ctx.globalAlpha = 0.8; //PGZ - lineWidth = width / end; + if (graphType == bar) { + lineWidth = width / end; + } else { + stepX = width / end; + } ctx.lineWidth = lineWidth; ctx.beginPath(); diff --git a/ui/qml/components/platform.silica/PageMenuItemPL.qml b/ui/qml/components/platform.silica/PageMenuItemPL.qml index fa45a2c6..7f05ef0c 100644 --- a/ui/qml/components/platform.silica/PageMenuItemPL.qml +++ b/ui/qml/components/platform.silica/PageMenuItemPL.qml @@ -21,4 +21,5 @@ import Sailfish.Silica 1.0 MenuItem { property string iconName // compatibility with other platforms + property string iconSource // compatibility with other platforms } diff --git a/ui/qml/components/platform.uuitk/PageMenuItemPL.qml b/ui/qml/components/platform.uuitk/PageMenuItemPL.qml index c26cfcd8..83e3a21b 100644 --- a/ui/qml/components/platform.uuitk/PageMenuItemPL.qml +++ b/ui/qml/components/platform.uuitk/PageMenuItemPL.qml @@ -21,7 +21,7 @@ import QtQuick.Controls 2.2 import Lomiri.Components 1.3 as UC UC.Action { - iconSource: iconName + iconName: iconSource signal clicked onTriggered: clicked() } diff --git a/ui/qml/components/platform.uuitk/StylerPL.qml b/ui/qml/components/platform.uuitk/StylerPL.qml index 0e0d7eb1..f4730fa8 100644 --- a/ui/qml/components/platform.uuitk/StylerPL.qml +++ b/ui/qml/components/platform.uuitk/StylerPL.qml @@ -60,10 +60,14 @@ QtObject { // used icons property string iconBluetooth: "image://theme/bluetooth-active" property string iconBattery: "image://theme/battery-good-symbolic" - property string iconSteps: "../../pics/custom-icons/icon-m-steps.png" - property string iconHeartrate: "../../pics/custom-icons/icon-m-heartrate.png" + property string iconSteps: "../../pics/custom-icons/icon-m-steps2.png" + property string iconHeartrate: "../../pics/custom-icons/icon-m-heartrate2.png" + property string iconDeviceScan: "image://theme/toolkit_input-search" + property string iconStravaLogin: "image://theme/user-admin" + property string iconUploadToStrava: "image://theme/share" + property string iconDownloadData: "image://theme/transfer-progress-download" property string iconAbout: "image://theme/info" //Qt.resolvedUrl("../../icons/help-about-symbolic.svg") property string iconBack: "image://theme/back" //Qt.resolvedUrl("../../icons/go-previous-symbolic.svg") property string iconBackward: "image://theme/back" @@ -111,7 +115,7 @@ QtObject { property string iconDiagnostic: "image://theme/info" // "Debug Info" property string iconFavoriteSelected: "image://theme/bookmark" // "Donate" - + property string customIconPrefix: "../../pics/custom-icons/" // item sizes property real themeItemSizeLarge: themeFontSizeLarge * 3 diff --git a/ui/qml/custom-icons/icon-m-heartrate2.png b/ui/qml/custom-icons/icon-m-heartrate2.png new file mode 100644 index 00000000..1d433c04 Binary files /dev/null and b/ui/qml/custom-icons/icon-m-heartrate2.png differ diff --git a/ui/qml/custom-icons/icon-m-steps2.png b/ui/qml/custom-icons/icon-m-steps2.png new file mode 100644 index 00000000..e62cd563 Binary files /dev/null and b/ui/qml/custom-icons/icon-m-steps2.png differ diff --git a/ui/qml/pages/AddCityPage.qml b/ui/qml/pages/AddCityPage.qml index 936e0855..9bac38f6 100644 --- a/ui/qml/pages/AddCityPage.qml +++ b/ui/qml/pages/AddCityPage.qml @@ -16,6 +16,30 @@ PagePL { id: weather } + function owmIconToLocal(omw) { + const weatherIcons = { + '01d': 'weather-clear.png', + '01n': 'weather-clear-night.png', + '02d': 'weather-few-clouds.png', + '02n': 'weather-few-clouds-night.png', + '03d': 'weather-clouds.png', + '03n': 'weather-clouds-night.png', + '04d': 'weather-many-clouds.png', + '04n': 'weather-many-clouds.png', // Assuming night uses the same icon for "many clouds" + '09d': 'weather-showers.png', + '09n': 'weather-showers-night.png', + '10d': 'weather-showers-scattered-day.png', + '10n': 'weather-showers-scattered-night.png', + '11d': 'weather-storm-day.png', + '11n': 'weather-storm-night.png', + '13d': 'weather-snow.png', + '13n': 'weather-snow-scattered-night.png', // Assuming scattered snow at night for 13n + '50d': 'weather-mist.png', + '50n': 'weather-mist.png' // Assuming night uses the same icon for "mist" + }; + return "../pics/" + weatherIcons[omw]; + } + onCurrentCityChanged: { if ((cityManager == null) || (cityManager.cities.length <= 0)) { return @@ -34,11 +58,26 @@ PagePL { id: citymodel } - LabelPL { - text: page.currentCity + ( - (weather.temperature !== 0) ? " " + (weather.temperature-273) + "˚C" : "" - ) - visible: page.currentCity !== "" + + Row { + width: parent.width - (2 * anchors.margins) + LabelPL { + text: page.currentCity + width: parent.width * 0.75 + } + + LabelPL { + id: temperatureLabel + text: (weather.weatherIcon !== "") ? (Math.round(weather.temperature-273.15) + "˚C") : "" + width: parent.width * 0.15 + + } + + IconPL { + source: (weather.weatherIcon !== "") ? owmIconToLocal(weather.weatherIcon) : "" + height: temperatureLabel.height + } + } SearchFieldPL { diff --git a/ui/qml/pages/AnalysisPage.qml b/ui/qml/pages/AnalysisPage.qml index 6f9e420b..8b05c766 100644 --- a/ui/qml/pages/AnalysisPage.qml +++ b/ui/qml/pages/AnalysisPage.qml @@ -86,12 +86,31 @@ PagePL { updateGraph(day); } } + + Graph { + id: graphBat + graphTitle: qsTr("Battery") + graphHeight: 300 + + axisY.units: "%" + type: DataSource.BatteryLog + + minY: 0 + maxY: 100 + valueConverter: function(value) { + return value.toFixed(0); + } + onClicked: { + updateGraph(day); + } + } } function updateGraphs() { graphHeartrate.updateGraph(day); graphSteps.updateGraph(day); graphIntensity.updateGraph(day); + graphBat.updateGraph(day); } Component.onCompleted: { diff --git a/ui/qml/pages/BatteryPage.qml b/ui/qml/pages/BatteryPage.qml new file mode 100644 index 00000000..a78212b7 --- /dev/null +++ b/ui/qml/pages/BatteryPage.qml @@ -0,0 +1,74 @@ +import QtQuick 2.0 +import uk.co.piggz.amazfish 1.0 +import QtQuick.Layouts 1.1 +import "../components/" +import "../components/platform" + +PagePL { + id: page + title: qsTr("Battery") + + property var day: new Date() + + Column { + id: column + width: parent.width + anchors.top: parent.top + anchors.margins: styler.themePaddingMedium + spacing: styler.themePaddingLarge + + LabelPL { + id: lblCurrentHeartrate + font.pixelSize: styler.themeFontSizeExtraLarge * 3 + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + text: qsTr("%1 %").arg(_InfoBatteryPercent) + horizontalAlignment: Text.AlignHCenter + } + + DateNavigation { + text: day.toDateString(); + onBackward: { + day.setDate(day.getDate() - 1); + text = day.toDateString(); + updateGraphs(); + } + onForward: { + day.setDate(day.getDate() + 1); + text = day.toDateString(); + updateGraphs(); + } + } + + Graph { + id: graphBat + graphTitle: qsTr("Battery") + graphHeight: 500 + + axisY.units: "%" + type: DataSource.BatteryLog + graphType: 1 + + minY: 0 + maxY: 100 + valueConverter: function(value) { + return value.toFixed(0); + } + onClicked: { + updateGraph(day); + } + } + + } + + + function updateGraphs() { + graphBat.updateGraph(day); + } + + Component.onCompleted: { + updateGraphs(); +// DaemonInterfaceInstance.requestManualHeartrate(); + } + +} diff --git a/ui/qml/pages/DebugInfo.qml b/ui/qml/pages/DebugInfo.qml index 9c03daab..47a2455e 100644 --- a/ui/qml/pages/DebugInfo.qml +++ b/ui/qml/pages/DebugInfo.qml @@ -12,6 +12,7 @@ PagePL { pageMenu: PageMenuPL { PageMenuItemPL { + iconSource: styler.iconRefresh !== undefined ? styler.iconRefresh : "" text: qsTr("Refresh") onClicked: { DaemonInterfaceInstance.refreshInformation(); @@ -175,7 +176,7 @@ PagePL { anchors.horizontalCenter: parent.horizontalCenter width: parent.width * 0.8 onClicked: { - DaemonInterfaceInstance.miBandService().fetchLogs(); + DaemonInterfaceInstance.fetchLogs(); } } ButtonPL { diff --git a/ui/qml/pages/FirstPage.qml b/ui/qml/pages/FirstPage.qml index 4f9655d3..e2964638 100644 --- a/ui/qml/pages/FirstPage.qml +++ b/ui/qml/pages/FirstPage.qml @@ -11,7 +11,7 @@ PagePL { function unpairAccepted() { DaemonInterfaceInstance.disconnect(); - app.pages.replace(Qt.resolvedUrl("./PairSelectDeviceType.qml")); + app.pages.push(Qt.resolvedUrl("./PairSelectDeviceType.qml")); } pageMenu: PageMenuPL { diff --git a/ui/qml/pages/PairPage.qml b/ui/qml/pages/PairPage.qml index ca612750..d2ee781d 100644 --- a/ui/qml/pages/PairPage.qml +++ b/ui/qml/pages/PairPage.qml @@ -21,7 +21,11 @@ PageListPL { property QtObject _bluetoothManager : BluezQt.Manager function startDiscovery() { - if (!adapter || adapter.discovering) { + if (!adapter) { + showMessage(qsTr("Bluetooth adapter is not available")) + return + } + if (adapter.discovering) { return } adapter.startDiscovery() @@ -150,6 +154,7 @@ PageListPL { PageMenuItemPL { enabled: !DaemonInterfaceInstance.pairing + iconSource: adapter && adapter.discovering ? "" : (styler.iconDeviceScan !== undefined ? styler.iconDeviceScan : "") text: adapter && adapter.discovering ? qsTr("Stop scanning") : qsTr("Scan for devices") @@ -159,13 +164,13 @@ PageListPL { stopDiscovery(); } else { startDiscovery(); - console.log(devicesModel.rowCount()); + console.log("devicesModel.rowCount:" + devicesModel.rowCount() ); } } } PageMenuItemPL { - visible: text + visible: text != "" text: adapter && adapter.discovering ? qsTr("Scanning for devices…") : DaemonInterfaceInstance.pairing @@ -176,6 +181,7 @@ PageListPL { BusyIndicatorPL { id: busyIndicator + anchors.centerIn: parent running: (adapter && adapter.discovering && !page.count) || DaemonInterfaceInstance.connectionState == "pairing" } } diff --git a/ui/qml/pages/Settings-profile.qml b/ui/qml/pages/Settings-profile.qml index 53ff3b58..7eb5d2b8 100644 --- a/ui/qml/pages/Settings-profile.qml +++ b/ui/qml/pages/Settings-profile.qml @@ -40,8 +40,9 @@ PagePL { text: new Date(page.dob).toLocaleDateString(); onClicked: { + var profile_DOB_date = new Date(AmazfishConfig.profileDOB); var dialog = app.pages.push(pickerComponent, { - date: new Date(AmazfishConfig.profileDOB) + date: !isNaN(profile_DOB_date) ? profile_DOB_date : new Date() }) dialog.accepted.connect(function() { page.dob = dialog.date; @@ -105,7 +106,7 @@ PagePL { SliderPL { id: sldFitnessGoal width: parent.width - minimumValue: 5000 + minimumValue: 1000 maximumValue: 30000 stepSize: 100 label: qsTr("Goal (steps): ") + value diff --git a/ui/qml/pages/SportPage.qml b/ui/qml/pages/SportPage.qml index 8b9c7347..3f68e98b 100644 --- a/ui/qml/pages/SportPage.qml +++ b/ui/qml/pages/SportPage.qml @@ -26,6 +26,7 @@ PagePL { pageMenu: PageMenuPL { PageMenuItemPL { + iconSource: styler.iconUploadToStrava !== undefined ? styler.iconUploadToStrava : "" text: qsTr("Send to Strava") visible: o2strava.linked onClicked: { diff --git a/ui/qml/pages/SportsSummaryPage.qml b/ui/qml/pages/SportsSummaryPage.qml index c3630a98..315e04b8 100644 --- a/ui/qml/pages/SportsSummaryPage.qml +++ b/ui/qml/pages/SportsSummaryPage.qml @@ -10,6 +10,7 @@ PageListPL { pageMenu: PageMenuPL { PageMenuItemPL { + iconSource: styler.iconDownloadData !== undefined ? styler.iconDownloadData : "" text: qsTr("Download Next Activity") onClicked: DaemonInterfaceInstance.downloadSportsData(); enabled: DaemonInterfaceInstance.connectionState === "authenticated" @@ -153,4 +154,9 @@ PageListPL { SportsModel.update(); } } + + onPageStatusActive: { + pushAttached(Qt.resolvedUrl("BatteryPage.qml")) + } + } diff --git a/ui/qml/pages/StepsPage.qml b/ui/qml/pages/StepsPage.qml index afd530b8..443158eb 100644 --- a/ui/qml/pages/StepsPage.qml +++ b/ui/qml/pages/StepsPage.qml @@ -55,7 +55,7 @@ PagePL { graphType: 2 minY: 0 - maxY: 20000 + maxY: 2 * AmazfishConfig.profileFitnessGoal valueConverter: function(value) { return value.toFixed(0); } diff --git a/ui/qml/pages/StravaSettingsPage.qml b/ui/qml/pages/StravaSettingsPage.qml index 367c7a30..0d37bb4a 100644 --- a/ui/qml/pages/StravaSettingsPage.qml +++ b/ui/qml/pages/StravaSettingsPage.qml @@ -32,6 +32,7 @@ PagePL { PageMenuItemPL { id: btnAuth + iconSource: styler.iconStravaLogin !== undefined ? styler.iconStravaLogin : "" text: o2strava.linked ? qsTr("Logout") : qsTr("Login") onClicked: { if (o2strava.linked) { @@ -52,7 +53,7 @@ PagePL { username = (athlete["username"] !== undefined) ? athlete["username"] : athlete["firstname"] + " " + athlete["lastname"]; country = athlete["country"]; } else { - username = "not logged in"; + username = qsTr("not logged in"); country = ""; } } @@ -66,7 +67,7 @@ PagePL { username = (athlete["username"] !== undefined) ? athlete["username"] : athlete["firstname"] + " " + athlete["lastname"]; country = athlete["country"]; } else { - username = "not logged in"; + username = qsTr("not logged in"); country = ""; } } diff --git a/ui/src/daemoninterface.cpp b/ui/src/daemoninterface.cpp index 2cc32324..af129d41 100644 --- a/ui/src/daemoninterface.cpp +++ b/ui/src/daemoninterface.cpp @@ -323,6 +323,13 @@ void DaemonInterface::enableFeature(Amazfish::Feature feature) iface->call(QStringLiteral("enableFeature"), (int)feature); } +void DaemonInterface::fetchLogs() { + if (!iface || !iface->isValid()) { + return; + } + iface->call(QStringLiteral("fetchLogs")); +} + QStringList DaemonInterface::supportedDisplayItems() { if (!iface || !iface->isValid()) { diff --git a/ui/src/daemoninterface.h b/ui/src/daemoninterface.h index c8cf76da..cf6ec49a 100644 --- a/ui/src/daemoninterface.h +++ b/ui/src/daemoninterface.h @@ -51,6 +51,7 @@ class DaemonInterface : public QObject Q_INVOKABLE void updateCalendar(); Q_INVOKABLE void reloadCities(); Q_INVOKABLE void enableFeature(Amazfish::Feature feature); + Q_INVOKABLE void fetchLogs(); Q_INVOKABLE QStringList supportedDisplayItems(); Q_INVOKABLE void immediateAlert(int level); diff --git a/ui/src/datasource.cpp b/ui/src/datasource.cpp index 049de0b8..b950ae98 100644 --- a/ui/src/datasource.cpp +++ b/ui/src/datasource.cpp @@ -170,6 +170,10 @@ QVariant DataSource::data(Type type, const QDate &day) qry = "SELECT date(timestamp_dt), sum(steps) FROM mi_band_activity WHERE date(timestamp_dt) >= date('" + day.toString("yyyy-MM-ddT00:00:00") + "','-10 day') AND timestamp_dt <= '" + day.toString("yyyy-MM-ddT23:59:59") + "' GROUP BY date(timestamp_dt) ORDER BY timestamp_dt ASC"; + } else if (type == DataSource::BatteryLog) { + qry = "SELECT timestamp_dt, value FROM info_log WHERE date(timestamp_dt) >= date('" + + day.toString("yyyy-MM-ddT00:00:00") + "','-10 day') AND timestamp_dt <= '" + + day.toString("yyyy-MM-ddT23:59:59") + "' AND key = '"+ QString::number((int)Amazfish::Info::INFO_BATTERY) +"' ORDER BY timestamp_dt ASC"; // 7 = AbstractDevice::INFO_BATTERY } qDebug() << qry; diff --git a/ui/src/datasource.h b/ui/src/datasource.h index 0847545c..e43a8865 100644 --- a/ui/src/datasource.h +++ b/ui/src/datasource.h @@ -18,7 +18,8 @@ class DataSource: public QObject Sleep = 3, Intensity = 4, StepSummary = 5, - SleepSummary = 6 + SleepSummary = 6, + BatteryLog = 7, }; Q_ENUM(Type) diff --git a/ui/translations/harbour-amazfish-ui-cs.ts b/ui/translations/harbour-amazfish-ui-cs.ts index 2f6a49a2..777d57b2 100644 --- a/ui/translations/harbour-amazfish-ui-cs.ts +++ b/ui/translations/harbour-amazfish-ui-cs.ts @@ -65,6 +65,10 @@ Download Data Stažení dat + + Battery + Baterie + AuthKeyDialog @@ -73,6 +77,17 @@ Vložte autentizační klíč + + BatteryPage + + Battery + Baterie + + + %1 % + %1 % + + BipDevice @@ -428,6 +443,10 @@ Pairing… Párování... + + Bluetooth adapter is not available + Adaptér bluetooth není k dispozici + PairSelectDeviceType @@ -588,6 +607,10 @@ Refresh calendar every (%1) minutes Obnovit kalendář každých (%1) minut + + Transliterate notifications + Přepsat diakritiku oznámení + Settings-bip-shortcuts @@ -1250,6 +1273,10 @@ Country: Země: + + not logged in + nepříhlášen + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-de.ts b/ui/translations/harbour-amazfish-ui-de.ts index e99ec026..4832f9c3 100644 --- a/ui/translations/harbour-amazfish-ui-de.ts +++ b/ui/translations/harbour-amazfish-ui-de.ts @@ -61,6 +61,10 @@ Intensity Intensität + + Battery + + AuthKeyDialog @@ -69,6 +73,17 @@ Auth-Key Eingeben + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -367,6 +382,10 @@ No devices found Kein Gerät gefunden + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -507,6 +526,10 @@ Refresh calendar every (%1) minutes + + Transliterate notifications + + Settings-button-action @@ -1126,6 +1149,10 @@ Country: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-es.ts b/ui/translations/harbour-amazfish-ui-es.ts index 6cb1dd81..f38a0710 100644 --- a/ui/translations/harbour-amazfish-ui-es.ts +++ b/ui/translations/harbour-amazfish-ui-es.ts @@ -61,6 +61,10 @@ Intensity Intensidad + + Battery + + AuthKeyDialog @@ -69,6 +73,17 @@ Introduce clave + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -367,6 +382,10 @@ No devices found Ningún dispositivo encontrado + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -507,6 +526,10 @@ Refresh calendar every (%1) minutes + + Transliterate notifications + + Settings-button-action @@ -1126,6 +1149,10 @@ Country: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-fr.ts b/ui/translations/harbour-amazfish-ui-fr.ts index f2523b21..8a8385c1 100644 --- a/ui/translations/harbour-amazfish-ui-fr.ts +++ b/ui/translations/harbour-amazfish-ui-fr.ts @@ -72,6 +72,10 @@ Intensity Intensité + + Battery + + AuthKeyDialog @@ -80,6 +84,17 @@ + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -410,6 +425,10 @@ Pairing… + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -562,6 +581,10 @@ Button Actions + + Transliterate notifications + + Settings-bip-shortcuts @@ -1268,6 +1291,10 @@ Country: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-nl.ts b/ui/translations/harbour-amazfish-ui-nl.ts index 6ee0059f..6b8438fb 100644 --- a/ui/translations/harbour-amazfish-ui-nl.ts +++ b/ui/translations/harbour-amazfish-ui-nl.ts @@ -61,6 +61,10 @@ Intensity Intensiteit + + Battery + + AuthKeyDialog @@ -69,6 +73,17 @@ Auth-sleutel invoeren + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -367,6 +382,10 @@ Pairing… Koppelen... + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -507,6 +526,10 @@ Refresh calendar every (%1) minutes + + Transliterate notifications + + Settings-button-action @@ -1126,6 +1149,10 @@ Country: Land: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-pl.ts b/ui/translations/harbour-amazfish-ui-pl.ts index 8cca6134..a4916cea 100644 --- a/ui/translations/harbour-amazfish-ui-pl.ts +++ b/ui/translations/harbour-amazfish-ui-pl.ts @@ -79,6 +79,10 @@ Intensity Intensywność + + Battery + + AuthKeyDialog @@ -87,6 +91,17 @@ + + BatteryPage + + Battery + + + + %1 % + + + BipDevice @@ -457,6 +472,10 @@ Pairing… + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -625,6 +644,10 @@ Button Actions + + Transliterate notifications + + Settings-bip-shortcuts @@ -1319,6 +1342,10 @@ Country: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui-sv.ts b/ui/translations/harbour-amazfish-ui-sv.ts index cef3902e..4b83984d 100644 --- a/ui/translations/harbour-amazfish-ui-sv.ts +++ b/ui/translations/harbour-amazfish-ui-sv.ts @@ -61,6 +61,10 @@ Intensity Intensitet + + Battery + + AuthKeyDialog @@ -69,6 +73,17 @@ Ange autentiseringsnyckel + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -367,6 +382,10 @@ No devices found Inga enheter hittades + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -507,6 +526,10 @@ Refresh calendar every (%1) minutes + + Transliterate notifications + + Settings-button-action @@ -1126,6 +1149,10 @@ Country: Land: + + not logged in + + StravaUploadPage diff --git a/ui/translations/harbour-amazfish-ui.ts b/ui/translations/harbour-amazfish-ui.ts index af821efa..69ea1896 100644 --- a/ui/translations/harbour-amazfish-ui.ts +++ b/ui/translations/harbour-amazfish-ui.ts @@ -61,6 +61,10 @@ Intensity + + Battery + + AuthKeyDialog @@ -69,6 +73,17 @@ + + BatteryPage + + Battery + + + + %1 % + + + BipFirmwarePage @@ -367,6 +382,10 @@ Pairing… + + Bluetooth adapter is not available + + PairSelectDeviceType @@ -495,6 +514,10 @@ Button Actions + + Transliterate notifications + + Settings-button-action @@ -1114,6 +1137,10 @@ Country: + + not logged in + + StravaUploadPage