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.

ukmetofficedatahub.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /* global WeatherProvider, WeatherObject */
  2. /* Magic Mirror
  3. * Module: Weather
  4. *
  5. * By Malcolm Oakes https://github.com/maloakes
  6. * Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus
  7. * MIT Licensed.
  8. *
  9. * This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
  10. * For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
  11. * Data available:
  12. * Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
  13. * 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
  14. * Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
  15. *
  16. * NOTES
  17. * This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
  18. * Provide the following in your config.js file:
  19. * weatherProvider: "ukmetofficedatahub",
  20. * apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
  21. * apiKey: "[YOUR API KEY]",
  22. * apiSecret: "[YOUR API SECRET]]",
  23. * lat: [LATITUDE (DECIMAL)],
  24. * lon: [LONGITUDE (DECIMAL)],
  25. * windUnits: "mps" | "kph" | "mph" (default)
  26. * tempUnits: "imperial" | "metric" (default)
  27. *
  28. * At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
  29. * setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
  30. *
  31. * Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable:
  32. * - Temperatures are in degrees Celsius (°C)
  33. * - Wind speeds are in metres per second (m/s)
  34. * - Wind direction given in degrees (°)
  35. * - Pressures are in Pascals (Pa)
  36. * - Distances are in metres (m)
  37. * - Probabilities and humidity are given as percentages (%)
  38. * - Precipitation is measured in millimetres (mm) with rates per hour (mm/h)
  39. *
  40. * See the PDFs linked above for more information on the data their corresponding units.
  41. */
  42. WeatherProvider.register("ukmetofficedatahub", {
  43. // Set the name of the provider.
  44. providerName: "UK Met Office (DataHub)",
  45. // Set the default config properties that is specific to this provider
  46. defaults: {
  47. apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
  48. apiKey: "",
  49. apiSecret: "",
  50. lat: 0,
  51. lon: 0,
  52. windUnits: "mph"
  53. },
  54. // Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
  55. getUrl(forecastType) {
  56. let queryStrings = "?";
  57. queryStrings += "latitude=" + this.config.lat;
  58. queryStrings += "&longitude=" + this.config.lon;
  59. queryStrings += "&includeLocationName=" + true;
  60. // Return URL, making sure there is a trailing "/" in the base URL.
  61. return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
  62. },
  63. // Build the list of headers for the request
  64. // For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
  65. // Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
  66. getHeaders() {
  67. return {
  68. accept: "application/json",
  69. "x-ibm-client-id": this.config.apiKey,
  70. "x-ibm-client-secret": this.config.apiSecret
  71. };
  72. },
  73. // Fetch data using supplied URL and request headers
  74. async fetchWeather(url, headers) {
  75. const response = await fetch(url, { headers: headers });
  76. // Return JSON data
  77. return response.json();
  78. },
  79. // Fetch hourly forecast data (to use for current weather)
  80. fetchCurrentWeather() {
  81. this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
  82. .then((data) => {
  83. // Check data is useable
  84. if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
  85. // Did not receive usable new data.
  86. // Maybe this needs a better check?
  87. Log.error("Possibly bad current/hourly data?");
  88. Log.error(data);
  89. return;
  90. }
  91. // Set location name
  92. this.setFetchedLocation(`${data.features[0].properties.location.name}`);
  93. // Generate current weather data
  94. const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
  95. this.setCurrentWeather(currentWeather);
  96. })
  97. // Catch any error(s)
  98. .catch((error) => Log.error("Could not load data: " + error.message))
  99. // Let the module know there're new data available
  100. .finally(() => this.updateAvailable());
  101. },
  102. // Create a WeatherObject using current weather data (data for the current hour)
  103. generateWeatherObjectFromCurrentWeather(currentWeatherData) {
  104. const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  105. // Extract the actual forecasts
  106. let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;
  107. // Define now
  108. let nowUtc = moment.utc();
  109. // Find hour that contains the current time
  110. for (let hour in forecastDataHours) {
  111. let forecastTime = moment.utc(forecastDataHours[hour].time);
  112. if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
  113. currentWeather.date = forecastTime;
  114. currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m);
  115. currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
  116. currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature);
  117. currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp);
  118. currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp);
  119. currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
  120. currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
  121. currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
  122. currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
  123. currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
  124. currentWeather.feelsLikeTemp = this.convertTemp(forecastDataHours[hour].feelsLikeTemperature);
  125. // Pass on full details so they can be used in custom templates
  126. // Note the units of the supplied data when using this (see top of file)
  127. currentWeather.rawData = forecastDataHours[hour];
  128. }
  129. }
  130. // Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
  131. // Passes {longitude, latitude} to SunCalc, could pass height to, but
  132. // SunCalc.getTimes doesnt take that into account
  133. currentWeather.updateSunTime(this.config.lat, this.config.lon);
  134. return currentWeather;
  135. },
  136. // Fetch daily forecast data
  137. fetchWeatherForecast() {
  138. this.fetchWeather(this.getUrl("daily"), this.getHeaders())
  139. .then((data) => {
  140. // Check data is useable
  141. if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
  142. // Did not receive usable new data.
  143. // Maybe this needs a better check?
  144. Log.error("Possibly bad forecast data?");
  145. Log.error(data);
  146. return;
  147. }
  148. // Set location name
  149. this.setFetchedLocation(`${data.features[0].properties.location.name}`);
  150. // Generate the forecast data
  151. const forecast = this.generateWeatherObjectsFromForecast(data);
  152. this.setWeatherForecast(forecast);
  153. })
  154. // Catch any error(s)
  155. .catch((error) => Log.error("Could not load data: " + error.message))
  156. // Let the module know there're new data available
  157. .finally(() => this.updateAvailable());
  158. },
  159. // Create a WeatherObject for each day using daily forecast data
  160. generateWeatherObjectsFromForecast(forecasts) {
  161. const dailyForecasts = [];
  162. // Extract the actual forecasts
  163. let forecastDataDays = forecasts.features[0].properties.timeSeries;
  164. // Define today
  165. let today = moment.utc().startOf("date");
  166. // Go through each day in the forecasts
  167. for (let day in forecastDataDays) {
  168. const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
  169. // Get date of forecast
  170. let forecastDate = moment.utc(forecastDataDays[day].time);
  171. // Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
  172. if (forecastDate.isSameOrAfter(today)) {
  173. forecastWeather.date = forecastDate;
  174. forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature);
  175. forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature);
  176. // Using daytime forecast values
  177. forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed);
  178. forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
  179. forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
  180. forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
  181. forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
  182. forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
  183. forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
  184. forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
  185. forecastWeather.feelsLikeTemp = this.convertTemp(forecastDataDays[day].dayMaxFeelsLikeTemp);
  186. // Pass on full details so they can be used in custom templates
  187. // Note the units of the supplied data when using this (see top of file)
  188. forecastWeather.rawData = forecastDataDays[day];
  189. dailyForecasts.push(forecastWeather);
  190. }
  191. }
  192. return dailyForecasts;
  193. },
  194. // Set the fetched location name.
  195. setFetchedLocation: function (name) {
  196. this.fetchedLocationName = name;
  197. },
  198. // Convert temperatures to Fahrenheit (from degrees C), if required
  199. convertTemp(tempInC) {
  200. return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
  201. },
  202. // Convert wind speed from metres per second
  203. // To keep the supplied metres per second units, use "mps"
  204. // To use kilometres per hour, use "kph"
  205. // Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
  206. convertWindSpeed(windInMpS) {
  207. if (this.config.windUnits === "mps") {
  208. return windInMpS;
  209. }
  210. if (this.config.windUnits === "kph" || this.config.windUnits === "metric" || this.config.useKmh) {
  211. return windInMpS * 3.6;
  212. }
  213. return windInMpS * 2.23694;
  214. },
  215. // Match the Met Office "significant weather code" to a weathericons.css icon
  216. // Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
  217. // and: https://erikflowers.github.io/weather-icons/
  218. convertWeatherType(weatherType) {
  219. const weatherTypes = {
  220. 0: "night-clear",
  221. 1: "day-sunny",
  222. 2: "night-alt-cloudy",
  223. 3: "day-cloudy",
  224. 5: "fog",
  225. 6: "fog",
  226. 7: "cloudy",
  227. 8: "cloud",
  228. 9: "night-sprinkle",
  229. 10: "day-sprinkle",
  230. 11: "raindrops",
  231. 12: "sprinkle",
  232. 13: "night-alt-showers",
  233. 14: "day-showers",
  234. 15: "rain",
  235. 16: "night-alt-sleet",
  236. 17: "day-sleet",
  237. 18: "sleet",
  238. 19: "night-alt-hail",
  239. 20: "day-hail",
  240. 21: "hail",
  241. 22: "night-alt-snow",
  242. 23: "day-snow",
  243. 24: "snow",
  244. 25: "night-alt-snow",
  245. 26: "day-snow",
  246. 27: "snow",
  247. 28: "night-alt-thunderstorm",
  248. 29: "day-thunderstorm",
  249. 30: "thunderstorm"
  250. };
  251. return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
  252. }
  253. });