Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

request weather data and send them to the wirst #29

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions asteroidsyncservice/watch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,6 @@ QString Watch::name()
return m_name;
}

QString Watch::weatherCityName()
{
return ""; // TODO
}

void Watch::setWeatherCityName(const QString &c)
{
m_iface->call("WeatherSetCityName", c);
emit weatherCityNameChanged();
}

quint8 Watch::batteryLevel() {
return m_batteryLevel;
}
Expand Down Expand Up @@ -193,3 +182,8 @@ bool Watch::weatherServiceReady()
{
return fetchProperty("StatusWeatherService").toBool();
}

void Watch::setWeatherLocation(const float lat, const float lng)
{
m_iface->call("SetWeatherLocation", QString::number(lat), QString::number(lng));
}
9 changes: 4 additions & 5 deletions asteroidsyncservice/watch.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class Watch : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString weatherCityName READ weatherCityName WRITE setWeatherCityName NOTIFY weatherCityNameChanged)
Q_PROPERTY(bool weatherServiceReady READ weatherServiceReady NOTIFY weatherServiceChanged)
Q_PROPERTY(quint8 batteryLevel READ batteryLevel NOTIFY batteryLevelChanged)
Q_PROPERTY(bool timeServiceReady READ timeServiceReady NOTIFY timeServiceChanged)
Expand All @@ -53,20 +52,20 @@ class Watch : public QObject
quint8 batteryLevel();
bool timeServiceReady();
bool screenshotServiceReady();
bool notificationServiceReady();
bool weatherServiceReady();
unsigned int screenshotProgress();

Q_INVOKABLE void setScreenshotFileInfo(const QString fileInfo);
Q_INVOKABLE void setTime(QDateTime t);
bool notificationServiceReady();
Q_INVOKABLE void setVibration(QString v);
Q_INVOKABLE void sendNotify(unsigned int id, QString appName, QString icon, QString body, QString summary);
bool weatherServiceReady();
Q_INVOKABLE void setWeatherLocation(const float lat, const float lng);

public slots:
void requestScreenshot();
void setWeatherCityName(const QString &in);

signals:
void weatherCityNameChanged();
void batteryLevelChanged();
void timeServiceChanged();
void notificationServiceChanged();
Expand Down
4 changes: 3 additions & 1 deletion asteroidsyncserviced/asteroidsyncserviced.pro
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
QT += core bluetooth dbus
QT += core bluetooth dbus network
QT -= gui

include(../version.pri)
Expand All @@ -21,6 +21,7 @@ contains(CONFIG, starfish) {
SOURCES += main.cpp \
watchesmanager.cpp \
dbusinterface.cpp \
openweathermapparser.cpp \
bluez/bluezclient.cpp \
bluez/bluez_agentmanager1.cpp \
bluez/bluez_adapter1.cpp \
Expand All @@ -30,6 +31,7 @@ SOURCES += main.cpp \

HEADERS += watchesmanager.h \
dbusinterface.h \
openweathermapparser.h \
bluez/bluezclient.h \
bluez/bluez_agentmanager1.h \
bluez/bluez_adapter1.h \
Expand Down
57 changes: 51 additions & 6 deletions asteroidsyncserviced/dbusinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@

#include "dbusinterface.h"
#include "watchesmanager.h"

#include "libasteroid/watch.h"

#include <QDBusConnection>
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>

/* Watch Interface */

Expand All @@ -44,6 +45,9 @@ DBusWatch::DBusWatch(Watch *watch, WatchesManager* wm, QObject *parent): QObject
connect(m_screenshotService, SIGNAL(screenshotReceived(QByteArray)), this, SIGNAL(ScreenshotReceived(QByteArray)));
connect(m_weatherService, SIGNAL(ready()), this, SLOT(onWeatherServiceReady()));
connect(wm, SIGNAL(disconnected()), this, SLOT(onDisconnected()));

m_nam = new QNetworkAccessManager(this);
m_wmp = new OpenWeatherMapParser(this);
}

void DBusWatch::onDisconnected()
Expand Down Expand Up @@ -86,11 +90,6 @@ void DBusWatch::RequestScreenshot()
m_screenshotService->requestScreenshot();
}

void DBusWatch::WeatherSetCityName(QString cityName)
{
m_weatherService->setCity(cityName);
}

void DBusWatch::onTimeServiceReady()
{
m_timeServiceReady = true;
Expand Down Expand Up @@ -150,6 +149,52 @@ bool DBusWatch::StatusWeatherService()
return m_weatherServiceReady;
}

void DBusWatch::SetWeatherLocation(const QString lat, const QString lng)
{
owmRequest(lat, lng);
}

void DBusWatch::owmRequest(const QString lat, const QString lng) const
{
QString owmApiKey = "b1af1d2053458fb4ab724f038ed499aa";
QUrl url;
url.setUrl("http://api.openweathermap.org/data/2.5/forecast");

QUrlQuery query;
query.addQueryItem("lat", lat);
query.addQueryItem("lon", lng);
query.addQueryItem("appid", owmApiKey);
url.setQuery(query);

QNetworkRequest request(url);
QNetworkReply* reply = m_nam->get(request);
connect(reply, &QNetworkReply::finished, this, &DBusWatch::onOwmReplyFinished);
}

void DBusWatch::onOwmReplyFinished()
{
QJsonObject rootObj;
QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
reply->deleteLater();

if (reply->error() == QNetworkReply::NoError) {
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
rootObj = document.object();
} else {
qDebug() << "Network error" << reply->errorString();
return;
}

m_wmp->prepareData(rootObj);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you have one m_wmp that you keep updating. Could you just create a new wmp here and give rootObj to its constructor?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also wouldn't have to clear fields like you do in your prepareData method

m_weatherService->setCity(m_wmp->getCity());
qDebug() << "WeatherID" << m_wmp->getWeatherId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it help to keep those qDebug in the app ?

m_weatherService->setIds(m_wmp->getWeatherId());
qDebug() << "Min Temp" << m_wmp->getTempMin();
m_weatherService->setMinTemps(m_wmp->getTempMin());
qDebug() << "Max Temp" << m_wmp->getTempMax();
m_weatherService->setMaxTemps(m_wmp->getTempMax());
}

/* Manager Interface */

DBusInterface::DBusInterface(WatchesManager *wm, QObject *parent) : QObject(parent)
Expand Down
9 changes: 7 additions & 2 deletions asteroidsyncserviced/dbusinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
#define DBUSINTERFACE_H

#include "watchesmanager.h"
#include "openweathermapparser.h"

#include <QObject>
#include <QDBusAbstractAdaptor>
#include <QDBusObjectPath>
#include <QNetworkAccessManager>

class Watch;

Expand Down Expand Up @@ -59,23 +61,26 @@ public slots:
bool StatusScreenshotService();
bool StatusWeatherService();
void RequestScreenshot();
void WeatherSetCityName(QString cityName);
void SetTime(QDateTime t);
void SetVibration(QString v);
void SendNotify(unsigned int id, QString appName, QString icon, QString body, QString summary);
void SetWeatherLocation(const QString lat, const QString lng);

private slots:
void onTimeServiceReady();
void onNotifyServiceReady();
void onScreenshotServiceReady();
void onWeatherServiceReady();
void onDisconnected();
void onOwmReplyFinished();

private:
Watch *m_watch;

WatchesManager* m_wm;

void owmRequest(const QString lat, const QString lng) const;
QNetworkAccessManager *m_nam;
OpenWeatherMapParser *m_wmp;
BatteryService *m_batteryService;
ScreenshotService *m_screenshotService;
WeatherService *m_weatherService;
Expand Down
101 changes: 101 additions & 0 deletions asteroidsyncserviced/openweathermapparser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "openweathermapparser.h"

OpenWeatherMapParser::OpenWeatherMapParser(QObject *parent) : QObject(parent)
{
}

void OpenWeatherMapParser::prepareData(QJsonObject obj)
{
m_weatherId.clear();
m_tempMin.clear();
m_tempMax.clear();
m_dateTempMin.setDate(0, 0, 0);
m_dateTempMax.setDate(0, 0, 0);

QDateTime timestamp;
QJsonArray list = obj.value("list").toArray();

for(auto element : list) {
QJsonObject dayInfo = element.toObject();
timestamp.setTime_t(dayInfo.value("dt").toInt());

setWeatherId(dayInfo.value("weather").toArray(), timestamp.time());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that every method has a different way of determining "what day it is" but ultimately it should be the same day for every method.

  • setWeatherId relies on the assumption that all previous days had a weatherId somewhere between 11 and 13. What if the measurements contain a value at 11:00:00 and 13:00:00 ? You would suddenly inject 2 values in your array and then every following day would be shifted.
  • setMaxTemp/setMinTemp both do the same calculation keeping "m_dateTempMin" and "m_dateTempMax" which are the actually the same. Here too, you're making many assumptions, for example about the order in which events are received, if two events are swapped (maybe by the parser or for some other random reason?) your logic becomes broken. You use this to access "m_tempMax.size() - 1" but this creates special cases for when the array is empty etc... By reducing your number of assumptions you can make the code much more simple and generic.

I propose that you extract here in this loop once for each element the "day index" which is going to be the index at which any value will be inserted (whether that is a weather id or a max/min temperature in their respective arrays). You could calculate this offset quite easily with https://doc.qt.io/qt-5/qdate.html#daysTo by comparing the date to the first date in the json document. If you provide this day index to all your methods, you should find that your code becomes greatly simplified. Especially your "setM**Temp" functions should have less special cases for empty arrays/new dates etc... They could just insert their value at the right index. This also provides you stronger guarantees that the "weatherId"/"maxTemp"/"minTemp" will be aligned.

setMinTemp(dayInfo.value("main").toObject(), timestamp.date());
setMaxTemp(dayInfo.value("main").toObject(), timestamp.date());
}

setCity(obj.value("city").toObject());
}

void OpenWeatherMapParser::setWeatherId(QJsonArray weather, QTime time)
{
QTime elevenoclock, thirteenoclock;
elevenoclock.setHMS(11,0,0,0);
thirteenoclock.setHMS(13,0,0,0);

if(time >= elevenoclock && time <= thirteenoclock) {
QJsonObject firstEntry = weather.first().toObject();
m_weatherId << firstEntry.value("id").toInt();
}
}

QList<short> OpenWeatherMapParser::getWeatherId()
{
return m_weatherId;
}

void OpenWeatherMapParser::setMinTemp(const QJsonObject obj, const QDate date)
{
if(m_tempMin.isEmpty()) {
m_tempMin << obj.value("temp_min").toDouble();
m_dateTempMin = date;
return;
}

if(m_dateTempMin == date && obj.value("temp_min").toDouble() < m_tempMin[m_tempMin.size() - 1]) {
m_tempMin.replace(m_tempMin.size() - 1, obj.value("temp_min").toDouble());
} else if(m_dateTempMin != date) {
if(m_tempMin.size() >= 5) return;

m_tempMin << obj.value("temp_min").toDouble();
m_dateTempMin = date;
}
}

QList<short> OpenWeatherMapParser::getTempMin()
{
return m_tempMin;
}

void OpenWeatherMapParser::setMaxTemp(const QJsonObject obj, const QDate date)
{
if(m_tempMax.isEmpty()) {
m_tempMax << obj.value("temp_max").toDouble();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You often extract this "obj.value("temp_max").toDouble()" could you make it a variable that you only extract once to make the code easier to read ?

m_dateTempMax = date;
return;
}

if(m_dateTempMax == date && obj.value("temp_max").toDouble() > m_tempMax[m_tempMax.size() - 1]) {
m_tempMax.replace(m_tempMax.size() - 1, obj.value("temp_max").toDouble());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something along the line of m_tempMax[dayIndex] = qMax(m_tempMax[dayIndex], obj.value("temp_max").toDouble()) is an option to make it easier to understand that you're keeping the max value.

} else if(m_dateTempMax != date) {
if(m_tempMax.size() >= 5) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 5? If this is a requirement of the protocol, I'd recommend that you enforce this in the "weatherservice" rather than in the parser. Ideally, the parser shouldn't have to care about limitations of the protocol that will transfer its data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, it is easy for setWeatherId to ignore this restriction.


m_tempMax << obj.value("temp_max").toDouble();
m_dateTempMax = date;
}
}

QList<short> OpenWeatherMapParser::getTempMax()
{
return m_tempMax;
}

void OpenWeatherMapParser::setCity(const QJsonObject obj)
{
m_city = obj.value("name").toString().trimmed();
}

QString OpenWeatherMapParser::getCity()
{
return m_city;
}
37 changes: 37 additions & 0 deletions asteroidsyncserviced/openweathermapparser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef OPENWEATHERMAPPARSER_H
#define OPENWEATHERMAPPARSER_H

#include <QObject>
#include <QJsonObject>
#include <QList>
#include <QJsonArray>
#include <QDateTime>

class OpenWeatherMapParser : public QObject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the methods are all stateless you don't really need a class here (an object of type OpenWeatherMapParser doesn't carry any ownership/lifetime semantic)
Or you could do the parsing once in the constructor (because all methods take the same arguments and do the same loop over and over again) and then store the results of the parsing in private members that you could return from getWeatherId(), getTempMin() etc...

{
Q_OBJECT

public:
OpenWeatherMapParser(QObject *parent = nullptr);

void prepareData(QJsonObject obj);
QList<short> getWeatherId();
QList<short> getTempMin();
QList<short> getTempMax();
QString getCity();

private:
void setWeatherId(const QJsonArray weather, const QTime time);
void setMinTemp(const QJsonObject obj, const QDate date);
void setMaxTemp(const QJsonObject obj, const QDate date);
void setCity(const QJsonObject obj);

QList<short> m_weatherId;
QList<short> m_tempMin;
QList<short> m_tempMax;
QDate m_dateTempMin;
QDate m_dateTempMax;
QString m_city;
};

#endif // OPENWEATHERMAPPARSER_H