Skip to content

Commit 347f0b4

Browse files
authored
Merge pull request #4043 from chuckwagoncomputing/iqair-clockinfo
New App: Air Quality Clockinfo
2 parents 9b1af79 + 9760007 commit 347f0b4

File tree

6 files changed

+245
-0
lines changed

6 files changed

+245
-0
lines changed

‎apps/airqualityci/ChangeLog‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.01: Initial clockinfo release

‎apps/airqualityci/README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Air Quality Clock Info
2+
3+
Create a list of clockinfos, each displaying air quality and/or temperature, fetched from IQAir
4+
5+
Requires Gadgetbridge internet access
6+
7+
# Display modes
8+
9+
Tapping the clockinfo changes its mode.
10+
11+
Examples of possible modes:
12+
- `45`
13+
- `45 31°`
14+
- `NYC 45`
15+
- `NYC 45 31°`
16+
- `NYC`
17+
18+
19+
Not affiliated with IQAir.

‎apps/airqualityci/app.png‎

1.63 KB
Loading

‎apps/airqualityci/clkinfo.js‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
(function() {
2+
let data = require("Storage").readJSON("airqualityci.json", 1) || {};
3+
let config = require("Storage").readJSON("airqualityci.settings.json") || {};
4+
5+
function checkAQI(cb) {
6+
let d = new Date();
7+
if (config.apiKey && config.rows) {
8+
config.rows.forEach((row) => {
9+
if (row.url) {
10+
let el = row.url.replace(/\/$/, "").split("/").reverse();
11+
let url = `https://api.airvisual.com/v2/city?city=${el[0]}&state=${el[1]}&country=${el[2]}&key=${config.apiKey}`;
12+
data[row.url] = data[row.url] || {};
13+
// If neither attempt nor time are set, then we have never tried
14+
// If attempt was set more than 1 minute ago, try again
15+
// If time was set more than 1 hour ago, refresh
16+
if ((!data[row.url].time && !data[row.url].attempt) ||
17+
(data[row.url].attempt && data[row.url].attempt + 60000 < d.getTime()) ||
18+
(data[row.url].time && data[row.url].time + 3600000 < d.getTime())) {
19+
data[row.url].attempt = d.getTime();
20+
Bangle.http(url).then((r) => {
21+
let resp = JSON.parse(r.resp);
22+
data[row.url] = {
23+
aqius: resp.data.current.pollution.aqius,
24+
temp: resp.data.current.weather.tp,
25+
time: d.getTime()
26+
}
27+
cb();
28+
require("Storage").writeJSON("airqualityci.json", data);
29+
});
30+
}
31+
}
32+
});
33+
}
34+
}
35+
36+
let locs = {
37+
name: "AirQuality",
38+
items: []
39+
};
40+
41+
if (config.rows) {
42+
config.rows.forEach((row, id) => {
43+
locs.items.push({
44+
name: "AQI " + row.name,
45+
show: () => {},
46+
hide: () => {},
47+
run: function() {
48+
if ( ! config.rows[id].mode ) {
49+
config.rows[id].mode = 2;
50+
} else if ( config.rows[id].mode === 5 ){
51+
config.rows[id].mode = 1;
52+
} else {
53+
config.rows[id].mode += 1;
54+
}
55+
this.emit("redraw");
56+
require("Storage").writeJSON("airqualityci.settings.json", config);
57+
},
58+
get: function() {
59+
checkAQI(() => this.emit("redraw"));
60+
let aqi = data[config.rows[id].url]
61+
let txt = "";
62+
let aqius = (aqi && typeof(aqi.aqius) == "number") ? aqi.aqius : "...";
63+
let temp = (aqi && typeof(aqi.temp) == "number") ? require("locale").temp(aqi.temp) : "...";
64+
switch ( config.rows[id].mode) {
65+
case 2:
66+
txt = aqius + " " + temp;
67+
break;
68+
case 3:
69+
txt = row.name + " " + aqius;
70+
break;
71+
case 4:
72+
txt = row.name + " " + aqius + " " + temp;
73+
break;
74+
case 5:
75+
txt = row.name;
76+
break;
77+
default:
78+
txt = aqius;
79+
}
80+
return {
81+
text: txt,
82+
short: aqius,
83+
img: atob("GBiBAAA4AAB8AAd+AA++QB/d8D/t8D/p+DvqcBUPsA7vgB9fwA7vgAXvgAH/AAHwgAD3wAbfwA9/wA+/gA/fAA/OAA/gAAfAAAAAAA==")
84+
};
85+
}
86+
});
87+
});
88+
}
89+
90+
return locs;
91+
})

‎apps/airqualityci/custom.html‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<html>
2+
<head>
3+
<link rel="stylesheet" href="../../css/spectre.min.css">
4+
<style>
5+
input.offset {
6+
width: 80px;
7+
}
8+
input.name {
9+
width: 150px;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<p>The AQI and temp are sourced from IQAir, which requires "Allow Internet Access" to be enabled in Gadgetbridge.</p>
15+
<p>To use AQI, you need an IQAir API key. To get one for free, create an IQAir account, then create an API key in <a target="_blank" href="https://dashboard.iqair.com/personal/api-keys">your user dashboard</a>.</p>
16+
<p>To specify the IQAir source, find your city on the IQAir website, then copy and paste the URL. It should look something like https://www.iqair.com/usa/new-york/new-york-city</p>
17+
<table id="rows" class="table table-scroll">
18+
<tr>
19+
<th>Name</th>
20+
<th>IQAir URL</th>
21+
</tr>
22+
</table>
23+
24+
<button id="addrow" class="btn">+</button>
25+
<button id="delrow" class="btn">-</button>
26+
<br>
27+
<input id="api-key" type="text" placeholder="IQAir API Key"></input>
28+
<br>
29+
<button id="upload" class="btn btn-primary">Upload</button>
30+
<template id="row-template">
31+
<tr class="row">
32+
<td>
33+
<input type="text" class="name"></input>
34+
</td>
35+
<td>
36+
<input type="text" class="url"></input>
37+
</td>
38+
</tr>
39+
</template>
40+
41+
<script src="../../core/lib/customize.js"></script>
42+
43+
<script>
44+
let storedString = localStorage.getItem('airqualityclockinfo-config');
45+
let stored = {};
46+
if (storedString) {
47+
stored = JSON.parse(storedString);
48+
}
49+
50+
if (stored.apiKey) {
51+
document.getElementById("api-key").value = stored.apiKey;
52+
}
53+
54+
if (!stored.rows) {
55+
stored.rows = [
56+
{
57+
name: "NYC",
58+
url: "https://www.iqair.com/usa/new-york/new-york",
59+
}
60+
];
61+
}
62+
63+
let tbl = document.getElementById("rows");
64+
let tmp = document.getElementById("row-template");
65+
for (let i = 0; i < stored.rows.length; i++) {
66+
let row = tmp.content.cloneNode(true);
67+
let storedRow = stored.rows[i]
68+
row.querySelector(".name").value = storedRow.name;
69+
row.querySelector(".url").value = storedRow.url;
70+
tbl.append(row);
71+
}
72+
73+
document.getElementById("addrow").addEventListener("click", function() {
74+
let tbl = document.getElementById("rows");
75+
let tmp = document.getElementById("row-template");
76+
let row = tmp.content.cloneNode(true);
77+
tbl.append(row);
78+
});
79+
80+
document.getElementById("delrow").addEventListener("click", function() {
81+
let tbl = document.getElementById("rows");
82+
tbl.deleteRow(tbl.rows.length -1);
83+
});
84+
85+
document.getElementById("upload").addEventListener("click", function() {
86+
let config = {
87+
rows: [],
88+
apiKey: document.getElementById("api-key").value
89+
};
90+
91+
document.querySelectorAll(".row").forEach((row) => {
92+
let name = row.querySelector(".name").value;
93+
let url = row.querySelector(".url").value;
94+
if ( name != "" && url != "" ) {
95+
config.rows.push({
96+
name: name,
97+
url: url,
98+
});
99+
}
100+
});
101+
102+
localStorage.setItem('airqualityclockinfo-config', JSON.stringify(config));
103+
104+
// send finished app (in addition to contents of app.json)
105+
sendCustomizedApp({
106+
storage:[
107+
{
108+
name: "airqualityci.settings.json",
109+
content: JSON.stringify(config)
110+
},
111+
]
112+
});
113+
});
114+
</script>
115+
</body>
116+
</html>

‎apps/airqualityci/metadata.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{ "id": "airqualityci",
2+
"name": "Air Quality Clockinfo",
3+
"version": "0.01",
4+
"description": "Create clockinfos for air quality and temperature",
5+
"icon": "app.png",
6+
"type": "clkinfo",
7+
"tags": "clkinfo",
8+
"supports" : ["BANGLEJS2"],
9+
"readme": "README.md",
10+
"custom": "custom.html",
11+
"storage": [
12+
{"name":"airqualityci.clkinfo.js","url":"clkinfo.js"}
13+
],
14+
"data": [
15+
{"name":"airqualityci.json"},
16+
{"name":"airqualityci.settings.json"}
17+
]
18+
}

0 commit comments

Comments
 (0)