-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculator.js
156 lines (137 loc) · 5.06 KB
/
calculator.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
import { config } from "dotenv";
config()
/** Time step for numerical integration, in seconds. */
const DELTA_TIME = 1.0;
/** Maximum time for calculateTime to return, in seconds. */
const MAX_COMPUTED_TIME = 86400.0;
/** Heat capacity of air, in J/kg*K */
const HEAT_CAPACITY = 1005.0;
/** Heat transfer of air, in W/m^2*K */
const HEAT_TRANSFER_COEFF = 10.0;
/**
* Calculates the time to leave windows open for to achieve a target temperature.
*
* @param currentTemp the current temperature inside the room, in degrees celsius.
* @param targetTemp the target temperature of the room, in degrees celsius
* @param latitude the latitude of the room.
* @param longitude the longitude of the room.
* @param roomVolume the volume of the room, in cubic meters.
* @param windowArea the area of the open window, in square meters.
* @param percentError the acceptable percentage error from the target temperature, default 10%.
*
* @return a structure containing the time to leave the window open (`time`) and the final
* temperature reached after that time (`finalTemp`).
*/
export async function calculateTime(currentTemp,
targetTemp,
latitude,
longitude,
roomVolume,
windowArea,
percentError = 0.01) {
let now = new Date();
let weatherData = await getWeatherData(latitude, longitude);
for (let t = 0.0; t < MAX_COMPUTED_TIME; t += DELTA_TIME) {
let outsideTemp = getDataValue(now, weatherData, "temp");
let airPressure = getDataValue(now, weatherData, "pressure");
let airDensity = getDensity(airPressure, outsideTemp);
let heatTransferRequired = HEAT_TRANSFER_COEFF * windowArea * (outsideTemp - currentTemp);
let heatTransfer = roomVolume * airDensity * HEAT_CAPACITY;
currentTemp += (heatTransferRequired * DELTA_TIME) / heatTransfer;
if (isAcceptableTemp(currentTemp, targetTemp, percentError)) {
return {
time: t,
finalTemp: currentTemp
}
}
now.setSeconds(now.getSeconds() + DELTA_TIME);
}
return undefined;
}
/**
* Determines whether the current temperature is acceptably close to the target temperature.
*
* @param currentTemp the current temperature, in degrees celsius.
* @param targetTemp the target temperature, in degrees celsius
* @param percentError the acceptable percentage error from the target temperature
*
* @return whether the current temperature is acceptably close to the target temperature.
*/
function isAcceptableTemp(currentTemp, targetTemp, percentError) {
let deviation = percentError * targetTemp;
let lowerBound = targetTemp - deviation;
let upperBound = targetTemp + deviation;
return currentTemp >= lowerBound && currentTemp <= upperBound;
}
/**
* Gets the density of the air in kg/m^3.
*
* @param airPressure the pressure of the air in hPa.
* @param airTemp the temperature of the air in C.
*
* @return the density of the air in kg/m^3.
*/
function getDensity(airPressure, airTemp) {
let airPressurePa = 100 * airPressure;
let airTempK = airTemp + 273.15;
let rSpecific = 287.052874;
return airPressurePa / (rSpecific * airTempK);
}
/**
* Gets a specific type of weather data for the measurement closest to a specific time.
*
* @param time the time to get the value for.
* @param weatherData the weather data to choose from.
* @param valueName the name of the value to get.
*
* @return the value for the measurement taken closest to the specified time.
*/
function getDataValue(time, weatherData, valueName) {
let currentTime = Math.floor(time.getTime() / 1000);
let closestTime = Infinity;
let closestValue = NaN;
for (const dataEntry of weatherData) {
let timeDifference = Math.abs(currentTime - dataEntry.dt);
if (timeDifference < closestTime) {
closestTime = timeDifference;
closestValue = dataEntry.main[valueName];
}
}
return closestValue;
}
/**
* Gets weather forecast data at a specified location.
*
* @param latitude the latitude of the location.
* @param longitude the longitude of the location.
*
* @return the weather data.
*/
async function getWeatherData(latitude, longitude) {
let endpoint = getWeatherEndpoint(latitude, longitude);
let weatherData;
await fetch(endpoint)
.then((response) => response.json())
.then((data) => {
weatherData = data.list;
})
.catch((error) => {
console.error(error);
});
return weatherData;
}
/**
* Gets the API endpoint for OpenWeatherMap for a specific location.
*
* @param latitude the latitude of the location.
* @param longitude the longitude of the location.
*
* @return a string representing the API endpoint to use.
*/
function getWeatherEndpoint(latitude, longitude) {
return "https://api.openweathermap.org/data/2.5/forecast?" +
"lat=" + latitude + "&" +
"lon=" + longitude + "&" +
"units=metric&" +
"appid=" + process.env.OPEN_WEATHER_MAP_KEY; // MARK: no hard coded key
}