-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.js
167 lines (133 loc) · 5.12 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
'use strict'
import { settings } from './user-settings.js'
import { aqi, pseudoQI, msg_format } from './app-settings.js'
import { createRequire } from "module";
const require = createRequire(import.meta.url);
var _ = require('lodash');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
async function autorizeInQingpingCloud() {
const params = new URLSearchParams()
params.append('grant_type', 'client_credentials')
params.append('scope', 'device_full_access')
// Get from https://developer.qingping.co/personal/permissionApply
const client_id = settings.APP_KEY
const client_secret = settings.APP_SECRET
const credentials = Buffer.from(client_id + ':' + client_secret).toString('base64')
const result = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'post',
headers: {
'Authorization': 'Basic ' + credentials,
},
body: params,
})
if (!result.ok) {
throw new Error(result.statusText)
}
// Useful fields:
// access_token - Access Token used to make Open API call
// expires_in - Remaining effective time in seconds. Note: nerd to check with timestamp, as this value not updated.
return await result.json()
}
async function getDeviceData(access_token) {
const result = await fetch("https://apis.cleargrass.com/v1/apis/devices?timestamp=" + Date.now().toString(), {
method: 'get',
headers: {
'Authorization': 'Bearer ' + access_token,
},
})
if (!result.ok) {
throw new Error(result.statusText)
}
const body = await result.json()
let data = body.devices[0].data
if (data.humidity.value == 99999 && data.temperature.value == 99999) {
console.log("Data is broken, retrying...")
await sleep(5000) // 5 seconds
return getDeviceData(access_token)
}
return data
}
function calculateAQI(sensor, concentration, _aqi = aqi) {
/*
* Calculate Air Quality Index.
*
* Calculations based on:
* https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
*
* Args:
* aqi: (MappingProxyType[str, dict]) Nested dictionary with values for AQI calculation.
* sensor: (str) Sensor name for which it will AQI count.
* concentration: (float) Raw data from sensor.
*
* Returns:
* int: Air Quality Index value.
*/
let aqi_value
for (let upper_bound in _aqi[sensor]) {
if (concentration < parseFloat(upper_bound)) {
let _ = _aqi[sensor][upper_bound];
aqi_value = (
(_['aqi_high'] - _['aqi_low'])
/ (_['pollutant_high'] - _['pollutant_low'])
* (concentration - _['pollutant_low'])
+ _['aqi_low']
);
break;
}
}
return Math.round(aqi_value);
}
function calculateData(raw_data) {
const getAqiCategory = (aqi_value) => aqi.aqi_category[
Object.keys(aqi.aqi_category).find(key => Number(key) > aqi_value)
]
const getPseudoQICategory = (sensor, pseudoQI_value) => pseudoQI[sensor][
Object.keys(pseudoQI[sensor]).find(key => Number(key) > pseudoQI_value)
]
let data = {
// better time
minutes_ago: Math.floor((Date.now() - raw_data.timestamp.value * 1000) / 60000),
// Pseudo Quality Index
co2: getPseudoQICategory('co2', raw_data.co2.value),
humidity: getPseudoQICategory('humidity', raw_data.humidity.value),
temperature: getPseudoQICategory('temperature', raw_data.temperature.value),
// AQI
aqi_pm25: { value: calculateAQI('pm25', raw_data.pm25.value) },
aqi_pm10: { value: calculateAQI('pm10', raw_data.pm10.value) },
aqi_worst: { value: null },
}
data.aqi_worst.value = Math.max(data.aqi_pm25.value, data.aqi_pm10.value)
data = _.merge(data, {
aqi_pm25: getAqiCategory(data.aqi_pm25.value),
aqi_pm10: getAqiCategory(data.aqi_pm10.value),
aqi_worst: getAqiCategory(data.aqi_worst.value),
})
return data
}
async function main() {
const { access_token } = await autorizeInQingpingCloud()
const raw_data = await getDeviceData(access_token)
const calculated_data = calculateData(raw_data)
const data = _.merge(calculated_data, raw_data)
let msg = ''
let stg = settings.quality_index
for (let sensor of stg.display_sensors) {
if (! stg.show_sensor_data && ! stg.show_emoji_status && ! stg.show_text_status) {
continue
}
if (data[sensor].verbosity_level < stg.verbosity_level) {
continue
}
msg += msg_format[sensor].before
+ (stg.show_emoji_status ? data[sensor].emoji : '')
+ (stg.show_text_status ? data[sensor].text : '')
+ (stg.show_sensor_data ? data[sensor].value : '')
+ msg_format[sensor].after
}
if (msg != '') {
console.log(`${msg}(>${data.minutes_ago}min ago)`)
} else if (data.minutes_ago > stg.show_msg_if_data_not_updated_minutes) {
console.log(`No data for >${data.minutes_ago}min`)
}
}
await main()