Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

openweathermap.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /* global WeatherProvider, WeatherObject */
  2. /* Magic Mirror
  3. * Module: Weather
  4. *
  5. * By Michael Teeuw https://michaelteeuw.nl
  6. * MIT Licensed.
  7. *
  8. * This class is the blueprint for a weather provider.
  9. */
  10. WeatherProvider.register("openweathermap", {
  11. // Set the name of the provider.
  12. // This isn't strictly necessary, since it will fallback to the provider identifier
  13. // But for debugging (and future alerts) it would be nice to have the real name.
  14. providerName: "OpenWeatherMap",
  15. // Set the default config properties that is specific to this provider
  16. defaults: {
  17. apiVersion: "2.5",
  18. apiBase: "https://api.openweathermap.org/data/",
  19. weatherEndpoint: "", // can be "onecall", "forecast" or "weather" (for current)
  20. locationID: false,
  21. location: false,
  22. lat: 0, // the onecall endpoint needs lat / lon values, it doesn'T support the locationId
  23. lon: 0,
  24. apiKey: ""
  25. },
  26. // Overwrite the fetchCurrentWeather method.
  27. fetchCurrentWeather() {
  28. this.fetchData(this.getUrl())
  29. .then((data) => {
  30. if (this.config.weatherEndpoint === "/onecall") {
  31. const weatherData = this.generateWeatherObjectsFromOnecall(data);
  32. this.setCurrentWeather(weatherData.current);
  33. this.setFetchedLocation(`${data.timezone}`);
  34. } else {
  35. const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
  36. this.setCurrentWeather(currentWeather);
  37. }
  38. })
  39. .catch(function (request) {
  40. Log.error("Could not load data ... ", request);
  41. })
  42. .finally(() => this.updateAvailable());
  43. },
  44. // Overwrite the fetchWeatherForecast method.
  45. fetchWeatherForecast() {
  46. this.fetchData(this.getUrl())
  47. .then((data) => {
  48. if (this.config.weatherEndpoint === "/onecall") {
  49. const weatherData = this.generateWeatherObjectsFromOnecall(data);
  50. this.setWeatherForecast(weatherData.days);
  51. this.setFetchedLocation(`${data.timezone}`);
  52. } else {
  53. const forecast = this.generateWeatherObjectsFromForecast(data.list);
  54. this.setWeatherForecast(forecast);
  55. this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
  56. }
  57. })
  58. .catch(function (request) {
  59. Log.error("Could not load data ... ", request);
  60. })
  61. .finally(() => this.updateAvailable());
  62. },
  63. // Overwrite the fetchWeatherHourly method.
  64. fetchWeatherHourly() {
  65. this.fetchData(this.getUrl())
  66. .then((data) => {
  67. if (!data) {
  68. // Did not receive usable new data.
  69. // Maybe this needs a better check?
  70. return;
  71. }
  72. this.setFetchedLocation(`(${data.lat},${data.lon})`);
  73. const weatherData = this.generateWeatherObjectsFromOnecall(data);
  74. this.setWeatherHourly(weatherData.hours);
  75. })
  76. .catch(function (request) {
  77. Log.error("Could not load data ... ", request);
  78. })
  79. .finally(() => this.updateAvailable());
  80. },
  81. /**
  82. * Overrides method for setting config to check if endpoint is correct for hourly
  83. *
  84. * @param {object} config The configuration object
  85. */
  86. setConfig(config) {
  87. this.config = config;
  88. if (!this.config.weatherEndpoint) {
  89. switch (this.config.type) {
  90. case "hourly":
  91. this.config.weatherEndpoint = "/onecall";
  92. break;
  93. case "daily":
  94. case "forecast":
  95. this.config.weatherEndpoint = "/forecast";
  96. break;
  97. case "current":
  98. this.config.weatherEndpoint = "/weather";
  99. break;
  100. default:
  101. Log.error("weatherEndpoint not configured and could not resolve it based on type");
  102. }
  103. }
  104. },
  105. /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
  106. /*
  107. * Gets the complete url for the request
  108. */
  109. getUrl() {
  110. return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams();
  111. },
  112. /*
  113. * Generate a WeatherObject based on currentWeatherInformation
  114. */
  115. generateWeatherObjectFromCurrentWeather(currentWeatherData) {
  116. const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  117. currentWeather.humidity = currentWeatherData.main.humidity;
  118. currentWeather.temperature = currentWeatherData.main.temp;
  119. if (this.config.windUnits === "metric") {
  120. currentWeather.windSpeed = this.config.useKmh ? currentWeatherData.wind.speed * 3.6 : currentWeatherData.wind.speed;
  121. } else {
  122. currentWeather.windSpeed = currentWeatherData.wind.speed;
  123. }
  124. currentWeather.windDirection = currentWeatherData.wind.deg;
  125. currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon);
  126. currentWeather.sunrise = moment(currentWeatherData.sys.sunrise, "X");
  127. currentWeather.sunset = moment(currentWeatherData.sys.sunset, "X");
  128. return currentWeather;
  129. },
  130. /*
  131. * Generate WeatherObjects based on forecast information
  132. */
  133. generateWeatherObjectsFromForecast(forecasts) {
  134. if (this.config.weatherEndpoint === "/forecast") {
  135. return this.fetchForecastHourly(forecasts);
  136. } else if (this.config.weatherEndpoint === "/forecast/daily") {
  137. return this.fetchForecastDaily(forecasts);
  138. }
  139. // if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
  140. const days = [new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh)];
  141. return days;
  142. },
  143. /*
  144. * Generate WeatherObjects based on One Call forecast information
  145. */
  146. generateWeatherObjectsFromOnecall(data) {
  147. if (this.config.weatherEndpoint === "/onecall") {
  148. return this.fetchOnecall(data);
  149. }
  150. // if weatherEndpoint does not match onecall, what should be returned?
  151. const weatherData = { current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh), hours: [], days: [] };
  152. return weatherData;
  153. },
  154. /*
  155. * fetch forecast information for 3-hourly forecast (available for free subscription).
  156. */
  157. fetchForecastHourly(forecasts) {
  158. // initial variable declaration
  159. const days = [];
  160. // variables for temperature range and rain
  161. let minTemp = [];
  162. let maxTemp = [];
  163. let rain = 0;
  164. let snow = 0;
  165. // variable for date
  166. let date = "";
  167. let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  168. for (const forecast of forecasts) {
  169. if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
  170. // calculate minimum/maximum temperature, specify rain amount
  171. weather.minTemperature = Math.min.apply(null, minTemp);
  172. weather.maxTemperature = Math.max.apply(null, maxTemp);
  173. weather.rain = rain;
  174. weather.snow = snow;
  175. weather.precipitation = weather.rain + weather.snow;
  176. // push weather information to days array
  177. days.push(weather);
  178. // create new weather-object
  179. weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  180. minTemp = [];
  181. maxTemp = [];
  182. rain = 0;
  183. snow = 0;
  184. // set new date
  185. date = moment(forecast.dt, "X").format("YYYY-MM-DD");
  186. // specify date
  187. weather.date = moment(forecast.dt, "X");
  188. // If the first value of today is later than 17:00, we have an icon at least!
  189. weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
  190. }
  191. if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
  192. weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
  193. }
  194. // the same day as before
  195. // add values from forecast to corresponding variables
  196. minTemp.push(forecast.main.temp_min);
  197. maxTemp.push(forecast.main.temp_max);
  198. if (forecast.hasOwnProperty("rain")) {
  199. if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) {
  200. rain += forecast.rain["3h"] / 25.4;
  201. } else if (!isNaN(forecast.rain["3h"])) {
  202. rain += forecast.rain["3h"];
  203. }
  204. }
  205. if (forecast.hasOwnProperty("snow")) {
  206. if (this.config.units === "imperial" && !isNaN(forecast.snow["3h"])) {
  207. snow += forecast.snow["3h"] / 25.4;
  208. } else if (!isNaN(forecast.snow["3h"])) {
  209. snow += forecast.snow["3h"];
  210. }
  211. }
  212. }
  213. // last day
  214. // calculate minimum/maximum temperature, specify rain amount
  215. weather.minTemperature = Math.min.apply(null, minTemp);
  216. weather.maxTemperature = Math.max.apply(null, maxTemp);
  217. weather.rain = rain;
  218. weather.snow = snow;
  219. weather.precipitation = weather.rain + weather.snow;
  220. // push weather information to days array
  221. days.push(weather);
  222. return days.slice(1);
  223. },
  224. /*
  225. * fetch forecast information for daily forecast (available for paid subscription or old apiKey).
  226. */
  227. fetchForecastDaily(forecasts) {
  228. // initial variable declaration
  229. const days = [];
  230. for (const forecast of forecasts) {
  231. const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  232. weather.date = moment(forecast.dt, "X");
  233. weather.minTemperature = forecast.temp.min;
  234. weather.maxTemperature = forecast.temp.max;
  235. weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
  236. weather.rain = 0;
  237. weather.snow = 0;
  238. // forecast.rain not available if amount is zero
  239. // The API always returns in millimeters
  240. if (forecast.hasOwnProperty("rain")) {
  241. if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
  242. weather.rain = forecast.rain / 25.4;
  243. } else if (!isNaN(forecast.rain)) {
  244. weather.rain = forecast.rain;
  245. }
  246. }
  247. // forecast.snow not available if amount is zero
  248. // The API always returns in millimeters
  249. if (forecast.hasOwnProperty("snow")) {
  250. if (this.config.units === "imperial" && !isNaN(forecast.snow)) {
  251. weather.snow = forecast.snow / 25.4;
  252. } else if (!isNaN(forecast.snow)) {
  253. weather.snow = forecast.snow;
  254. }
  255. }
  256. weather.precipitation = weather.rain + weather.snow;
  257. days.push(weather);
  258. }
  259. return days;
  260. },
  261. /*
  262. * Fetch One Call forecast information (available for free subscription).
  263. * Factors in timezone offsets.
  264. * Minutely forecasts are excluded for the moment, see getParams().
  265. */
  266. fetchOnecall(data) {
  267. let precip = false;
  268. // get current weather, if requested
  269. const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  270. if (data.hasOwnProperty("current")) {
  271. current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset / 60);
  272. current.windSpeed = data.current.wind_speed;
  273. current.windDirection = data.current.wind_deg;
  274. current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset / 60);
  275. current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset / 60);
  276. current.temperature = data.current.temp;
  277. current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
  278. current.humidity = data.current.humidity;
  279. if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
  280. if (this.config.units === "imperial") {
  281. current.rain = data.current["rain"]["1h"] / 25.4;
  282. } else {
  283. current.rain = data.current["rain"]["1h"];
  284. }
  285. precip = true;
  286. }
  287. if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) {
  288. if (this.config.units === "imperial") {
  289. current.snow = data.current["snow"]["1h"] / 25.4;
  290. } else {
  291. current.snow = data.current["snow"]["1h"];
  292. }
  293. precip = true;
  294. }
  295. if (precip) {
  296. current.precipitation = current.rain + current.snow;
  297. }
  298. current.feelsLikeTemp = data.current.feels_like;
  299. }
  300. let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  301. // get hourly weather, if requested
  302. const hours = [];
  303. if (data.hasOwnProperty("hourly")) {
  304. for (const hour of data.hourly) {
  305. weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset / 60);
  306. // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat);
  307. weather.temperature = hour.temp;
  308. weather.feelsLikeTemp = hour.feels_like;
  309. weather.humidity = hour.humidity;
  310. weather.windSpeed = hour.wind_speed;
  311. weather.windDirection = hour.wind_deg;
  312. weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
  313. precip = false;
  314. if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) {
  315. if (this.config.units === "imperial") {
  316. weather.rain = hour.rain["1h"] / 25.4;
  317. } else {
  318. weather.rain = hour.rain["1h"];
  319. }
  320. precip = true;
  321. }
  322. if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) {
  323. if (this.config.units === "imperial") {
  324. weather.snow = hour.snow["1h"] / 25.4;
  325. } else {
  326. weather.snow = hour.snow["1h"];
  327. }
  328. precip = true;
  329. }
  330. if (precip) {
  331. weather.precipitation = weather.rain + weather.snow;
  332. }
  333. hours.push(weather);
  334. weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  335. }
  336. }
  337. // get daily weather, if requested
  338. const days = [];
  339. if (data.hasOwnProperty("daily")) {
  340. for (const day of data.daily) {
  341. weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset / 60);
  342. weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset / 60);
  343. weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset / 60);
  344. weather.minTemperature = day.temp.min;
  345. weather.maxTemperature = day.temp.max;
  346. weather.humidity = day.humidity;
  347. weather.windSpeed = day.wind_speed;
  348. weather.windDirection = day.wind_deg;
  349. weather.weatherType = this.convertWeatherType(day.weather[0].icon);
  350. precip = false;
  351. if (!isNaN(day.rain)) {
  352. if (this.config.units === "imperial") {
  353. weather.rain = day.rain / 25.4;
  354. } else {
  355. weather.rain = day.rain;
  356. }
  357. precip = true;
  358. }
  359. if (!isNaN(day.snow)) {
  360. if (this.config.units === "imperial") {
  361. weather.snow = day.snow / 25.4;
  362. } else {
  363. weather.snow = day.snow;
  364. }
  365. precip = true;
  366. }
  367. if (precip) {
  368. weather.precipitation = weather.rain + weather.snow;
  369. }
  370. days.push(weather);
  371. weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  372. }
  373. }
  374. return { current: current, hours: hours, days: days };
  375. },
  376. /*
  377. * Convert the OpenWeatherMap icons to a more usable name.
  378. */
  379. convertWeatherType(weatherType) {
  380. const weatherTypes = {
  381. "01d": "day-sunny",
  382. "02d": "day-cloudy",
  383. "03d": "cloudy",
  384. "04d": "cloudy-windy",
  385. "09d": "showers",
  386. "10d": "rain",
  387. "11d": "thunderstorm",
  388. "13d": "snow",
  389. "50d": "fog",
  390. "01n": "night-clear",
  391. "02n": "night-cloudy",
  392. "03n": "night-cloudy",
  393. "04n": "night-cloudy",
  394. "09n": "night-showers",
  395. "10n": "night-rain",
  396. "11n": "night-thunderstorm",
  397. "13n": "night-snow",
  398. "50n": "night-alt-cloudy-windy"
  399. };
  400. return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
  401. },
  402. /* getParams(compliments)
  403. * Generates an url with api parameters based on the config.
  404. *
  405. * return String - URL params.
  406. */
  407. getParams() {
  408. let params = "?";
  409. if (this.config.weatherEndpoint === "/onecall") {
  410. params += "lat=" + this.config.lat;
  411. params += "&lon=" + this.config.lon;
  412. if (this.config.type === "current") {
  413. params += "&exclude=minutely,hourly,daily";
  414. } else if (this.config.type === "hourly") {
  415. params += "&exclude=current,minutely,daily";
  416. } else if (this.config.type === "daily" || this.config.type === "forecast") {
  417. params += "&exclude=current,minutely,hourly";
  418. } else {
  419. params += "&exclude=minutely";
  420. }
  421. } else if (this.config.lat && this.config.lon) {
  422. params += "lat=" + this.config.lat + "&lon=" + this.config.lon;
  423. } else if (this.config.locationID) {
  424. params += "id=" + this.config.locationID;
  425. } else if (this.config.location) {
  426. params += "q=" + this.config.location;
  427. } else if (this.firstEvent && this.firstEvent.geo) {
  428. params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
  429. } else if (this.firstEvent && this.firstEvent.location) {
  430. params += "q=" + this.firstEvent.location;
  431. } else {
  432. this.hide(this.config.animationSpeed, { lockString: this.identifier });
  433. return;
  434. }
  435. params += "&units=" + this.config.units;
  436. params += "&lang=" + this.config.lang;
  437. params += "&APPID=" + this.config.apiKey;
  438. return params;
  439. }
  440. });