0286dd5bc337e6d18c458d7b3a39ed34d463a2f1
[apps/thermostat-demo.git] / webdataengine / openweathermapdataengine.cpp
1 #include "openweathermapdataengine.h"
3 #include <QNetworkAccessManager>
4 #include <QNetworkProxy>
5 #include <QNetworkReply>
6 #include <QNetworkRequest>
7 #include <QScriptEngine>
8 #include <QtScript>
9 #include <QtDebug>
11 #include "globalsettings.h"
12 #include "forecastdata.h"
13 #include "weatherdata.h"
15 OpenWeatherMapDataEngine::OpenWeatherMapDataEngine(QNetworkAccessManager *manager, QObject *parent) :
16     WebDataEngine(manager, parent)
17 {
18     generateJSONWeatherLookupTables();
19     connect(&m_networkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
20     connect(&m_forecastNetworkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
21 }
23 void OpenWeatherMapDataEngine::setCity(QString city)
24 {
25     //save the full city name for use later
26     m_fullCity = city;
27     //openweathermap searches for a city name alone, so chop anything attached by commas
28     m_preparedCity = city.left(city.lastIndexOf(","));
29 }
31 void OpenWeatherMapDataEngine::dispatchRequest()
32 {
33     m_weatherData = new WeatherData;
34     m_forecastReceived = false;
35     m_weatherReceived = false;
37     //for openweather map we first must find the city
38     QString cityUrl = "http://openweathermap.org/data/2.1/find/name?q="+m_preparedCity;
40     // receive document and parse it
41     QNetworkRequest request;
42     request.setUrl(QUrl(cityUrl));
44     //set up timer to check for network timeout
45     connect(&m_networkTimer, SIGNAL(timeout()), this, SLOT(handleNetworkTimeout()));
46     m_networkTimer.start(15000);
48     //make actual network request
49     m_reply = m_manager->get(request);
51     connect(m_reply, SIGNAL(finished()),this,SLOT(responseReceived()));
52 }
54 void OpenWeatherMapDataEngine::handleNetworkTimeout()
55 {
56     m_networkTimer.stop();
57     m_forecastNetworkTimer.stop();
58     emit(networkTimeout());
59 }
61 void OpenWeatherMapDataEngine::responseReceived()
62 {
63     m_networkTimer.stop();
65     if(m_reply->error() == QNetworkReply::NoError)
66     {
67         QByteArray data = m_reply->readAll();
68         //qDebug() << data;
70         m_cityId = parseCityInformation(QString::fromAscii(data));
71         //docs say do not delete in the slot so well pass it off to the event loop
72         m_reply->deleteLater();
73         if(m_cityId == -1)
74             emit networkTimeout();
75         else
76             //now we need to grab current weather information and forecast data
77             dispatchWeatherDataRequests();
78     }
79     else
80     {
81         qDebug() << "Network Error: " << m_reply->errorString();
82         loadLocalData();
83         emit networkTimeout();
84     }
85 }
87 void OpenWeatherMapDataEngine::dispatchWeatherDataRequests()
88 {
90     m_weatherData->setCurrentCity(m_fullCity);
92     //first send request for current weather
93     QString currentWeatherURL = "http://openweathermap.org/data/2.1/weather/city/"+QString::number(m_cityId);
95     QNetworkRequest request;
96     request.setUrl(QUrl(currentWeatherURL));
98     //set up timer to check for network timeout
100     m_networkTimer.start(15000);
102     //make actual network request
103     m_reply = m_manager->get(request);
104     connect(m_reply,SIGNAL(finished()),this,SLOT(currentWeatherResponseReceived()));
106     //next send request for current weather
107     QString forecastWeatherURL = "http://openweathermap.org/data/2.1/forecast/city/"+QString::number(m_cityId);
109     request.setUrl(QUrl(forecastWeatherURL));
111     //set up timer to check for network timeout
112     m_forecastNetworkTimer.start(15000);
114     //make actual network request
115     m_forecastReply = m_manager->get(request);
116     connect(m_forecastReply,SIGNAL(finished()),this, SLOT(forecastResponseReceived()));
119 void OpenWeatherMapDataEngine::currentWeatherResponseReceived()
121     m_networkTimer.stop();
123     if(m_reply->error() != QNetworkReply::NoError)
124     {
125         qDebug() << "Network Error: " << m_reply->errorString();
126         emit networkTimeout();
127         m_reply->deleteLater();
128         return;
129     }
130     m_rawJSONWeatherString = m_reply->readAll();
131     bool result = parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData);
132     if(!result)
133         emit networkTimeout();
134     else
135     {
136         m_weatherReceived = true;
137         checkIfDone();
138     }
141 void OpenWeatherMapDataEngine::forecastResponseReceived()
143     if(m_forecastReply->error() != QNetworkReply::NoError)
144     {
145         qDebug() << "Network Error: " << m_reply->errorString();
146         emit networkTimeout();
147         m_forecastReply->deleteLater();
148         return;
149     }
150     m_forecastNetworkTimer.stop();
151     m_rawJSONForecastString = m_forecastReply->readAll();
152     bool result = parseJSONForecastData(&m_rawJSONForecastString, m_weatherData);
153     if(!result)
154         emit networkTimeout();
155     else
156     {
157         m_forecastReceived = true;
158         checkIfDone();
159     }
162 void OpenWeatherMapDataEngine::checkIfDone()
164     if(m_forecastReceived && m_weatherReceived)
165     {
166         m_weatherData->setLastUpdated(QDateTime::currentDateTime());
167         writeToCache();
168         emit(dataAvailable(m_weatherData));
169     }
172 bool OpenWeatherMapDataEngine::parseJSONWeatherData(QString *jsonData, WeatherData *weatherData)
174     QScriptEngine engine;
175     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
177     if(result.isError())return false;
179     int temp = kelvinToFahrenheit(result.property("main").property("temp").toNumber());
180     QDateTime localTime = QDateTime::fromTime_t(result.property("dt").toNumber());
182     int iconIndex = convertImageNameToIndex(result.property("weather").property(0).property("icon").toString());
184     QString icon = m_iconNameToWeatherHash[iconIndex];
186     weatherData->setCurrentTemp(temp);
187     weatherData->setLocalTime(localTime);
188     weatherData->setIcon(icon);
189     return true;
192 bool OpenWeatherMapDataEngine::parseJSONForecastData(QString *jsonData, WeatherData* weatherData)
194     QScriptEngine engine;
195     QScriptValue result = engine.evaluate("weatherObject="+*jsonData);
198     if(result.isError())return false;
200     QScriptValueIterator it(result.property("list"));
202     int high = 0;
203     int low = 500;
204     QString icon = "";
206     QDateTime workingDateTime;
207     while (it.hasNext()) {
208         it.next();
209         //qDebug() << it.name() << it.value().toString() << it.value().property("img").toNumber();
211         QDateTime dateTime = QDateTime::fromTime_t(it.value().property("dt").toNumber());
213         //on the first iteration initialize the workingDateTime to parsedDateTime so we dont end up with a blank first day
214         if(workingDateTime.isNull())
215             workingDateTime = dateTime;
217         if(workingDateTime.date().day() == dateTime.date().day())
218         {
219             if(high < it.value().property("main").property("temp_max").toNumber()) high = it.value().property("main").property("temp_max").toInteger();
220             if(low > it.value().property("main").property("temp_min").toNumber()) low = it.value().property("main").property("temp_min").toInteger();
222             int iconIndex = convertImageNameToIndex(it.value().property("weather").property(0).property("icon").toString());
224             if(m_weatherPriorityHash[icon]< m_weatherPriorityHash[m_iconNameToWeatherHash[iconIndex]]) icon = m_iconNameToWeatherHash[iconIndex];
226         }
227         else
228         {
229             ForecastData *forecastDay = new ForecastData;
230             forecastDay->setDay(workingDateTime.date().toString("ddd"));
231             forecastDay->setHighTemp(kelvinToFahrenheit(high));
232             forecastDay->setLowTemp(kelvinToFahrenheit(low));
233             forecastDay->setIcon(icon);
234             weatherData->addForecastDay(forecastDay);
236             //initialize for next day of parsing;
237             workingDateTime = dateTime;
238             high = 0;
239             low=500;
240             icon = "";
241         }
242     }
243     return true;
247 void OpenWeatherMapDataEngine::generateJSONWeatherLookupTables()
249     m_iconNameToWeatherHash.insert(1, "sunny");
250     m_iconNameToWeatherHash.insert(2, "partlysunny");
251     m_iconNameToWeatherHash.insert(3, "cloudy");
252     m_iconNameToWeatherHash.insert(4, "cloudy");
253     m_iconNameToWeatherHash.insert(5, "rain");
254     m_iconNameToWeatherHash.insert(6, "tstorms");
255     m_iconNameToWeatherHash.insert(7, "sleet");
256     m_iconNameToWeatherHash.insert(8, "snow");
257     m_iconNameToWeatherHash.insert(9, "rain");
258     m_iconNameToWeatherHash.insert(10, "rain");
259     m_iconNameToWeatherHash.insert(11, "tstorms");
260     m_iconNameToWeatherHash.insert(12, "sleet");
261     m_iconNameToWeatherHash.insert(13, "snow");
262     m_iconNameToWeatherHash.insert(14, "snow");
263     m_iconNameToWeatherHash.insert(15, "cloudy");
264     m_iconNameToWeatherHash.insert(16, "sunny");
265     m_iconNameToWeatherHash.insert(17, "partlysunny");
266     m_iconNameToWeatherHash.insert(18, "rain");
267     m_iconNameToWeatherHash.insert(19, "snow");
270     m_weatherPriorityHash.insert("", 0);
271     m_weatherPriorityHash.insert("sunny", 1);
272     m_weatherPriorityHash.insert("partlysunny", 2);
273     m_weatherPriorityHash.insert("cloudy", 3);
274     m_weatherPriorityHash.insert("rain", 4);
275     m_weatherPriorityHash.insert("tstorms", 5);
276     m_weatherPriorityHash.insert("sleet", 6);
277     m_weatherPriorityHash.insert("snow", 7);
281 qlonglong OpenWeatherMapDataEngine::parseCityInformation(QString jsonData)
283     QScriptEngine engine;
285     //must have an object set equal to the class data received from the web or qt throws parse error
286     QScriptValue result = engine.evaluate("weatherObject="+jsonData);
288     if(result.property("message").toString() != "")
289         return -1;
290     else
291         return result.property("list").property("0").property("id").toInteger();
294 int OpenWeatherMapDataEngine::kelvinToFahrenheit(double k)
296     return qRound((k-273.15)*1.8+32);
299 int OpenWeatherMapDataEngine::convertImageNameToIndex(QString img)
301     QString iconName = img;
303     int lastSlash = iconName.lastIndexOf("/");
304     int lastDot = iconName.lastIndexOf(".");
305     int endShift=1;
307     if(lastSlash == -1 && lastDot == -1 && iconName[iconName.size()-1].isLetter())
308         return iconName.mid(0,iconName.size()-1).toInt();
310     if(iconName[lastDot-1].isLetter())
311         endShift = 2;
313    return  iconName.mid(lastSlash+1, lastDot-lastSlash-endShift).toInt();
316 void OpenWeatherMapDataEngine::loadLocalData()
318     m_weatherData = new WeatherData;
319     m_weatherData->setCachedDataFlag();
321     bool result = readFromCache();
322     //if we can't read from the cache file, read from the one included in the qrc!
323     if(!result)
324         readFromCache(":/data/cache.dat");
326     //qDebug() << m_rawJSONWeatherString << m_rawJSONForecastString;
328     if(!parseJSONWeatherData(&m_rawJSONWeatherString, m_weatherData))
329     {
330         emit networkTimeout();
331     }
332     else if(!parseJSONForecastData(&m_rawJSONForecastString, m_weatherData))
333     {
334         emit networkTimeout();
335     }
336     else
337     {
338         emit(dataAvailable(m_weatherData));
339     }
342 //FUNCTION writeToCache
343 //
344 //  *xmlData : QByteArray containing the XML byte stream retrieved through the API
345 //             call
346 //
347 //  Writes the data to a local cache file called cache.xml found in the same location
348 //  as the configuration file.
349 //
351 bool OpenWeatherMapDataEngine::writeToCache()
353     QFile cacheFile(m_globalSettings->dataPath() +"/cache.dat");
355     bool result = cacheFile.open(QFile::WriteOnly);
357     if(!result)
358     {
359         //qDebug() << "Cannot open cache file for writing!";
360         return false;
361     }
363     QDataStream stream(&cacheFile);
364     stream.setVersion(QDataStream::Qt_4_6);
365     stream << m_weatherData->currentCity();
366     stream << m_weatherData->lastUpdated();
367     stream << m_rawJSONWeatherString;
368     stream << m_rawJSONForecastString;
370     if(cacheFile.error() != QFile::NoError)
371     {
372         //qDebug() << "Cannot write to cache file!";
373         return false;
374     }
376     cacheFile.close();
377     //if we made it this far things are looking good, return true
379     return true;
382 //FUNCTION readFromCache
383 //
384 //  *xmlData : QByteArray to read the local json stream to.
385 //  alternateCacheFile: QString that can contain a different cache to read from,
386 //                      by default it will read from the same as the write function
387 //
388 //  Reads the xml data from a local cache or if desired will read from somewhere else.
389 //  like a resource file.
391 bool OpenWeatherMapDataEngine::readFromCache(QString alternateCacheFile)
393     QFile cacheFile;
395     if(alternateCacheFile.length() > 0)
396         cacheFile.setFileName(alternateCacheFile);
397     else
398         cacheFile.setFileName(m_globalSettings->dataPath() +"/cache.dat");
400     bool result = cacheFile.open(QFile::ReadOnly);
402     if(!result)
403     {
404         //qDebug() << "XMLCACHE: Cannot open cache file for reading!";
405         return false;
406     }
408     QDataStream stream(&cacheFile);
409     stream.setVersion(QDataStream::Qt_4_6);
410     QString cityString;
411     stream >> cityString;
412     m_weatherData->setCurrentCity(cityString);
413     QDateTime lastUpdated;
414     stream >> lastUpdated;
415     m_weatherData->setLastUpdated(lastUpdated);
416     stream >> m_rawJSONWeatherString;
417     stream >> m_rawJSONForecastString;
419     //we can at least assume if the size is zero, something is not right.
420     if(cacheFile.error() != QFile::NoError)
421     {
422         //qDebug() << "XMLCACHE: No data loaded...";
423         return false;
424     }
426     cacheFile.close();
427     //if we made it this far things are looking good, return true
429     return true;
432 QString OpenWeatherMapDataEngine::licenseString()
434     return QString("Weather data from  openweathermap.org under CC-BY-SA");