Skip to content

Commit c2e6a64

Browse files
CFennerjcherniak
andauthored
feat: use waqi API (#25)
* Rewite to use JSON API and work based on lat/lon * Add warning if token for API is not set * revert to use location * add helper * use helper * add missing variable * add logging * remove unused code * add location * fix data access * fix header name * add section about coordinate usage --------- Co-authored-by: Justin Cherniak <justin@justin-c.com>
1 parent ff036dc commit c2e6a64

File tree

5 files changed

+131
-49
lines changed

5 files changed

+131
-49
lines changed

MMM-AirQuality.js

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,84 +7,127 @@
77
Module.register('MMM-AirQuality', {
88
// Default module config.
99
defaults: {
10+
initialDelay: 0,
1011
lang: '',
1112
location: '',
1213
showLocation: true,
1314
showIndex: true,
1415
appendLocationNameToHeader: true,
1516
updateInterval: 30, // every 30 minutes
16-
animationSpeed: 1000
17+
animationSpeed: 1000,
18+
token: '',
19+
apiBase: 'api.waqi.info/',
20+
dataEndpoint: 'feed/',
1721
},
22+
notifications: {
23+
DATA: 'AIR_QUALITY_DATA',
24+
DATA_RESPONSE: 'AIR_QUALITY_DATA_RESPONSE',
25+
},
1826
start: function(){
19-
Log.info('Starting module: ' + this.name);
20-
// load data
21-
this.load();
22-
// schedule refresh
23-
setInterval(
24-
this.load.bind(this),
25-
this.config.updateInterval * 60 * 1000);
27+
const self = this
28+
Log.info(`Starting module: ${this.name}`)
29+
self.loaded = false
30+
31+
setTimeout(function () {
32+
self.sendSocketNotification(self.notifications.DATA, self.config)
33+
}, this.config.initialDelay * 1000)
34+
35+
// set auto-update
36+
setInterval(function () {
37+
self.sendSocketNotification(self.notifications.DATA, self.config)
38+
}, this.config.updateInterval * 60 * 1000 + this.config.initialDelay * 1000)
2639
},
27-
load: function(){
28-
_aqiFeed({
29-
lang: this.config.lang,
30-
city: this.config.location,
31-
callback: this.render.bind(this)
32-
});
33-
},
34-
render: function(data){
35-
this.data.value = $(data.aqit).find("span").text();
36-
this.data.impact = data.impact;
37-
this.data.city = data.cityname;
40+
render: function(response){
41+
let data = response.data;
42+
this.data.value = data.aqi;
43+
this.data.city = data.city.name;
3844
this.loaded = true;
39-
this.updateDom(this.animationSpeed);
45+
46+
if (data.aqi < 51) {
47+
this.data.color = "#009966";
48+
this.data.impact = 'Good';
49+
} else if (data.aqi < 101) {
50+
this.data.color = "#ffde33";
51+
this.data.impact = 'Moderate';
52+
} else if (data.aqi < 151) {
53+
this.data.color = '#ff9933';
54+
this.data.impact = 'Unhealty for Sensitive Groups';
55+
} else if (data.aqi < 201) {
56+
this.data.color = '#cc0033';
57+
this.data.impact = 'Unhealthy';
58+
} else if (data.aqi < 301) {
59+
this.data.color = '#7e0023';
60+
this.data.impact = 'Hazardous';
61+
}
4062
},
4163
html: {
42-
icon: '<i class="fa fa-leaf"></i>',
64+
icon: '<i class="fa-solid fa-smog"></i>',
4365
city: '<div class="xsmall">{0}</div>',
44-
quality: '<div>{0} {1}{2}</div>'
66+
quality: '<div style="color: {0}">{1} {2}{3}</div>'
4567
},
4668
getScripts: function() {
4769
return [
48-
'aqiFeed.js',
4970
'//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.js',
5071
'String.format.js'
5172
];
5273
},
5374
getStyles: function() {
54-
return ['https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css'];
75+
return ['https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'];
5576
},
56-
// Override getHeader method.
57-
getHeader: function () {
58-
var header = ""
59-
if (this.data.header)
60-
header += this.data.header;
61-
if (this.config.appendLocationNameToHeader) {
62-
if (header != "") {
63-
header += " ";
64-
}
65-
header += this.data.city;
66-
}
67-
return header
68-
},
77+
// Override getHeader method.
78+
getHeader: function () {
79+
var header = ""
80+
if (this.data.header)
81+
header += this.data.header;
82+
if (this.config.appendLocationNameToHeader) {
83+
if (header != "") {
84+
header += " ";
85+
}
86+
header += this.data.city;
87+
}
88+
return header
89+
},
6990
// Override dom generator.
7091
getDom: function() {
7192
var wrapper = document.createElement("div");
93+
if (this.config.token === '') {
94+
wrapper.innerHTML = "Please set the AQICN token for module: " + this.name + ". You can acquire one at <a href='https://aqicn.org/data-platform/token/'>https://aqicn.org/data-platform/token/</a>.";
95+
wrapper.className = "dimmed light small";
96+
return wrapper;
97+
}
7298
if (this.config.location === '') {
7399
wrapper.innerHTML = "Please set the air quality index <i>location</i> in the config for module: " + this.name + ".";
74100
wrapper.className = "dimmed light small";
75101
return wrapper;
76102
}
103+
77104
if (!this.loaded) {
78105
wrapper.innerHTML = "Loading air quality index ...";
79106
wrapper.className = "dimmed light small";
80107
return wrapper;
81108
}
82109
wrapper.innerHTML =
83110
this.html.quality.format(
111+
this.data.color,
84112
this.html.icon,
85113
this.data.impact,
86114
(this.config.showIndex?' ('+this.data.value+')':''))+
87115
(this.config.showLocation && !this.config.appendLocationNameToHeader?this.html.city.format(this.data.city):'');
88116
return wrapper;
89-
}
117+
},
118+
socketNotificationReceived: function (notification, payload) {
119+
const self = this
120+
Log.debug('received ' + notification)
121+
switch (notification) {
122+
case self.notifications.DATA_RESPONSE:
123+
if (payload.status === 'OK') {
124+
console.log('Data %o', payload.payloadReturn)
125+
self.render(payload.payloadReturn)
126+
self.updateDom(this.animationSpeed);
127+
} else {
128+
console.log('DATA FAILED ' + payload.message)
129+
}
130+
break
131+
}
132+
},
90133
});

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Add the module configuration to your `config.js` file.
4646

4747
Determine the station you want to display. Select a station on the [map](https://aqicn.org/here/) and copy the location part from the URL. For example http://aqicn.org/city/netherland/utrecht/griftpark/ would be `netherland/utrecht/griftpark/`.
4848

49+
You may also get the data for specific longitude and latitude. Set the `location` to `geo:<lat>;<long>/` and replace `<lat>` and `<long>` with your values.
50+
4951
You may want to set the following options in the config section as well:
5052

5153
| Option | Description | Default | Required |

aqiFeed.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

helper.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* MagicMirror²
2+
* Module: MMM-AirQuality
3+
*
4+
* By Christopher Fenner https://github.com/CFenner
5+
* MIT Licensed.
6+
*/
7+
8+
module.exports = {
9+
notifications: {
10+
DATA: 'AIR_QUALITY_DATA',
11+
DATA_RESPONSE: 'AIR_QUALITY_DATA_RESPONSE',
12+
},
13+
start: function () {
14+
console.log('AirQuality helper started ...')
15+
this.token = null
16+
},
17+
loadData: async function (config) {
18+
const self = this
19+
self.config = config
20+
let url = `https://${self.config.apiBase}${self.config.dataEndpoint}${self.config.location}/?token=${this.config.token}`
21+
console.log(`AirQuality loaded: ${url}`)
22+
23+
let result = await fetch(url)
24+
.then(response => response.json())
25+
26+
self.sendSocketNotification(self.notifications.DATA_RESPONSE, {
27+
payloadReturn: result,
28+
status: 'OK',
29+
})
30+
},
31+
socketNotificationReceived: function (notification, payload) {
32+
switch (notification) {
33+
case this.notifications.DATA:
34+
this.loadData(payload)
35+
break
36+
}
37+
},
38+
}

node_helper.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* MagicMirror²
2+
* Module: MMM-AirQuality
3+
*
4+
* By Christopher Fenner https://github.com/CFenner
5+
* MIT Licensed.
6+
*/
7+
const NodeHelper = require('node_helper')
8+
const helper = require('./helper')
9+
10+
module.exports = NodeHelper.create(helper)

0 commit comments

Comments
 (0)