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.

weather.js 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /* global WeatherProvider */
  2. /* Magic Mirror
  3. * Module: Weather
  4. *
  5. * By Michael Teeuw https://michaelteeuw.nl
  6. * MIT Licensed.
  7. */
  8. Module.register("weather", {
  9. // Default module config.
  10. defaults: {
  11. weatherProvider: "openweathermap",
  12. roundTemp: false,
  13. type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint)
  14. units: config.units,
  15. useKmh: false,
  16. tempUnits: config.units,
  17. windUnits: config.units,
  18. updateInterval: 10 * 60 * 1000, // every 10 minutes
  19. animationSpeed: 1000,
  20. timeFormat: config.timeFormat,
  21. showPeriod: true,
  22. showPeriodUpper: false,
  23. showWindDirection: true,
  24. showWindDirectionAsArrow: false,
  25. useBeaufort: true,
  26. lang: config.language,
  27. showHumidity: false,
  28. showSun: true,
  29. degreeLabel: false,
  30. decimalSymbol: ".",
  31. showIndoorTemperature: false,
  32. showIndoorHumidity: false,
  33. maxNumberOfDays: 5,
  34. maxEntries: 5,
  35. ignoreToday: false,
  36. fade: true,
  37. fadePoint: 0.25, // Start on 1/4th of the list.
  38. initialLoadDelay: 0, // 0 seconds delay
  39. appendLocationNameToHeader: true,
  40. calendarClass: "calendar",
  41. tableClass: "small",
  42. onlyTemp: false,
  43. showPrecipitationAmount: false,
  44. colored: false,
  45. showFeelsLike: true
  46. },
  47. // Module properties.
  48. weatherProvider: null,
  49. // Can be used by the provider to display location of event if nothing else is specified
  50. firstEvent: null,
  51. // Define required scripts.
  52. getStyles: function () {
  53. return ["font-awesome.css", "weather-icons.css", "weather.css"];
  54. },
  55. // Return the scripts that are necessary for the weather module.
  56. getScripts: function () {
  57. return ["moment.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")];
  58. },
  59. // Override getHeader method.
  60. getHeader: function () {
  61. if (this.config.appendLocationNameToHeader && this.weatherProvider) {
  62. if (this.data.header) return this.data.header + " " + this.weatherProvider.fetchedLocation();
  63. else return this.weatherProvider.fetchedLocation();
  64. }
  65. return this.data.header ? this.data.header : "";
  66. },
  67. // Start the weather module.
  68. start: function () {
  69. moment.locale(this.config.lang);
  70. // Initialize the weather provider.
  71. this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
  72. // Let the weather provider know we are starting.
  73. this.weatherProvider.start();
  74. // Add custom filters
  75. this.addFilters();
  76. // Schedule the first update.
  77. this.scheduleUpdate(this.config.initialLoadDelay);
  78. },
  79. // Override notification handler.
  80. notificationReceived: function (notification, payload, sender) {
  81. if (notification === "CALENDAR_EVENTS") {
  82. const senderClasses = sender.data.classes.toLowerCase().split(" ");
  83. if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
  84. this.firstEvent = null;
  85. for (let event of payload) {
  86. if (event.location || event.geo) {
  87. this.firstEvent = event;
  88. Log.debug("First upcoming event with location: ", event);
  89. break;
  90. }
  91. }
  92. }
  93. } else if (notification === "INDOOR_TEMPERATURE") {
  94. this.indoorTemperature = this.roundValue(payload);
  95. this.updateDom(300);
  96. } else if (notification === "INDOOR_HUMIDITY") {
  97. this.indoorHumidity = this.roundValue(payload);
  98. this.updateDom(300);
  99. }
  100. },
  101. // Select the template depending on the display type.
  102. getTemplate: function () {
  103. switch (this.config.type.toLowerCase()) {
  104. case "current":
  105. return "current.njk";
  106. case "hourly":
  107. return "hourly.njk";
  108. case "daily":
  109. case "forecast":
  110. return "forecast.njk";
  111. //Make the invalid values use the "Loading..." from forecast
  112. default:
  113. return "forecast.njk";
  114. }
  115. },
  116. // Add all the data to the template.
  117. getTemplateData: function () {
  118. const forecast = this.weatherProvider.weatherForecast();
  119. return {
  120. config: this.config,
  121. current: this.weatherProvider.currentWeather(),
  122. forecast: forecast,
  123. hourly: this.weatherProvider.weatherHourly(),
  124. indoor: {
  125. humidity: this.indoorHumidity,
  126. temperature: this.indoorTemperature
  127. }
  128. };
  129. },
  130. // What to do when the weather provider has new information available?
  131. updateAvailable: function () {
  132. Log.log("New weather information available.");
  133. this.updateDom(0);
  134. this.scheduleUpdate();
  135. if (this.weatherProvider.currentWeather()) {
  136. this.sendNotification("CURRENTWEATHER_TYPE", { type: this.weatherProvider.currentWeather().weatherType.replace("-", "_") });
  137. }
  138. },
  139. scheduleUpdate: function (delay = null) {
  140. let nextLoad = this.config.updateInterval;
  141. if (delay !== null && delay >= 0) {
  142. nextLoad = delay;
  143. }
  144. setTimeout(() => {
  145. switch (this.config.type.toLowerCase()) {
  146. case "current":
  147. this.weatherProvider.fetchCurrentWeather();
  148. break;
  149. case "hourly":
  150. this.weatherProvider.fetchWeatherHourly();
  151. break;
  152. case "daily":
  153. case "forecast":
  154. this.weatherProvider.fetchWeatherForecast();
  155. break;
  156. default:
  157. Log.error(`Invalid type ${this.config.type} configured (must be one of 'current', 'hourly', 'daily' or 'forecast')`);
  158. }
  159. }, nextLoad);
  160. },
  161. roundValue: function (temperature) {
  162. const decimals = this.config.roundTemp ? 0 : 1;
  163. const roundValue = parseFloat(temperature).toFixed(decimals);
  164. return roundValue === "-0" ? 0 : roundValue;
  165. },
  166. addFilters() {
  167. this.nunjucksEnvironment().addFilter(
  168. "formatTime",
  169. function (date) {
  170. date = moment(date);
  171. if (this.config.timeFormat !== 24) {
  172. if (this.config.showPeriod) {
  173. if (this.config.showPeriodUpper) {
  174. return date.format("h:mm A");
  175. } else {
  176. return date.format("h:mm a");
  177. }
  178. } else {
  179. return date.format("h:mm");
  180. }
  181. }
  182. return date.format("HH:mm");
  183. }.bind(this)
  184. );
  185. this.nunjucksEnvironment().addFilter(
  186. "unit",
  187. function (value, type) {
  188. if (type === "temperature") {
  189. if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
  190. value += "°";
  191. }
  192. if (this.config.degreeLabel) {
  193. if (this.config.tempUnits === "metric") {
  194. value += "C";
  195. } else if (this.config.tempUnits === "imperial") {
  196. value += "F";
  197. } else {
  198. value += "K";
  199. }
  200. }
  201. } else if (type === "precip") {
  202. if (value === null || isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
  203. value = "";
  204. } else {
  205. if (this.config.weatherProvider === "ukmetoffice" || this.config.weatherProvider === "ukmetofficedatahub") {
  206. value += "%";
  207. } else {
  208. value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
  209. }
  210. }
  211. } else if (type === "humidity") {
  212. value += "%";
  213. }
  214. return value;
  215. }.bind(this)
  216. );
  217. this.nunjucksEnvironment().addFilter(
  218. "roundValue",
  219. function (value) {
  220. return this.roundValue(value);
  221. }.bind(this)
  222. );
  223. this.nunjucksEnvironment().addFilter(
  224. "decimalSymbol",
  225. function (value) {
  226. return value.toString().replace(/\./g, this.config.decimalSymbol);
  227. }.bind(this)
  228. );
  229. this.nunjucksEnvironment().addFilter(
  230. "calcNumSteps",
  231. function (forecast) {
  232. return Math.min(forecast.length, this.config.maxNumberOfDays);
  233. }.bind(this)
  234. );
  235. this.nunjucksEnvironment().addFilter(
  236. "calcNumEntries",
  237. function (dataArray) {
  238. return Math.min(dataArray.length, this.config.maxEntries);
  239. }.bind(this)
  240. );
  241. this.nunjucksEnvironment().addFilter(
  242. "opacity",
  243. function (currentStep, numSteps) {
  244. if (this.config.fade && this.config.fadePoint < 1) {
  245. if (this.config.fadePoint < 0) {
  246. this.config.fadePoint = 0;
  247. }
  248. const startingPoint = numSteps * this.config.fadePoint;
  249. const numFadesteps = numSteps - startingPoint;
  250. if (currentStep >= startingPoint) {
  251. return 1 - (currentStep - startingPoint) / numFadesteps;
  252. } else {
  253. return 1;
  254. }
  255. } else {
  256. return 1;
  257. }
  258. }.bind(this)
  259. );
  260. }
  261. });