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.

clock.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /* global SunCalc */
  2. /* Magic Mirror
  3. * Module: Clock
  4. *
  5. * By Michael Teeuw https://michaelteeuw.nl
  6. * MIT Licensed.
  7. */
  8. Module.register("clock", {
  9. // Module config defaults.
  10. defaults: {
  11. displayType: "digital", // options: digital, analog, both
  12. timeFormat: config.timeFormat,
  13. timezone: null,
  14. displaySeconds: true,
  15. showPeriod: true,
  16. showPeriodUpper: false,
  17. clockBold: false,
  18. showDate: true,
  19. showTime: true,
  20. showWeek: false,
  21. dateFormat: "dddd, LL",
  22. /* specific to the analog clock */
  23. analogSize: "200px",
  24. analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive)
  25. analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right'
  26. analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
  27. secondsColor: "#888888",
  28. showSunTimes: false,
  29. showMoonTimes: false,
  30. lat: 47.630539,
  31. lon: -122.344147
  32. },
  33. // Define required scripts.
  34. getScripts: function () {
  35. return ["moment.js", "moment-timezone.js", "suncalc.js"];
  36. },
  37. // Define styles.
  38. getStyles: function () {
  39. return ["clock_styles.css"];
  40. },
  41. // Define start sequence.
  42. start: function () {
  43. Log.info("Starting module: " + this.name);
  44. // Schedule update interval.
  45. this.second = moment().second();
  46. this.minute = moment().minute();
  47. // Calculate how many ms should pass until next update depending on if seconds is displayed or not
  48. const delayCalculator = (reducedSeconds) => {
  49. const EXTRA_DELAY = 50; // Deliberate imperceptible delay to prevent off-by-one timekeeping errors
  50. if (this.config.displaySeconds) {
  51. return 1000 - moment().milliseconds() + EXTRA_DELAY;
  52. } else {
  53. return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY;
  54. }
  55. };
  56. // A recursive timeout function instead of interval to avoid drifting
  57. const notificationTimer = () => {
  58. this.updateDom();
  59. // If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
  60. if (this.config.displaySeconds) {
  61. this.second = moment().second();
  62. if (this.second !== 0) {
  63. this.sendNotification("CLOCK_SECOND", this.second);
  64. setTimeout(notificationTimer, delayCalculator(0));
  65. return;
  66. }
  67. }
  68. // If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
  69. this.minute = moment().minute();
  70. this.sendNotification("CLOCK_MINUTE", this.minute);
  71. setTimeout(notificationTimer, delayCalculator(0));
  72. };
  73. // Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
  74. setTimeout(notificationTimer, delayCalculator(this.second));
  75. // Set locale.
  76. moment.locale(config.language);
  77. },
  78. // Override dom generator.
  79. getDom: function () {
  80. const wrapper = document.createElement("div");
  81. wrapper.classList.add("clockGrid");
  82. /************************************
  83. * Create wrappers for analog and digital clock
  84. */
  85. const analogWrapper = document.createElement("div");
  86. analogWrapper.className = "clockCircle";
  87. const digitalWrapper = document.createElement("div");
  88. digitalWrapper.className = "digital";
  89. digitalWrapper.style.gridArea = "center";
  90. /************************************
  91. * Create wrappers for DIGITAL clock
  92. */
  93. const dateWrapper = document.createElement("div");
  94. const timeWrapper = document.createElement("div");
  95. const secondsWrapper = document.createElement("sup");
  96. const periodWrapper = document.createElement("span");
  97. const sunWrapper = document.createElement("div");
  98. const moonWrapper = document.createElement("div");
  99. const weekWrapper = document.createElement("div");
  100. // Style Wrappers
  101. dateWrapper.className = "date normal medium";
  102. timeWrapper.className = "time bright large light";
  103. secondsWrapper.className = "seconds dimmed";
  104. sunWrapper.className = "sun dimmed small";
  105. moonWrapper.className = "moon dimmed small";
  106. weekWrapper.className = "week dimmed medium";
  107. // Set content of wrappers.
  108. // The moment().format("h") method has a bug on the Raspberry Pi.
  109. // So we need to generate the timestring manually.
  110. // See issue: https://github.com/MichMich/MagicMirror/issues/181
  111. let timeString;
  112. const now = moment();
  113. if (this.config.timezone) {
  114. now.tz(this.config.timezone);
  115. }
  116. let hourSymbol = "HH";
  117. if (this.config.timeFormat !== 24) {
  118. hourSymbol = "h";
  119. }
  120. if (this.config.clockBold) {
  121. timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
  122. } else {
  123. timeString = now.format(hourSymbol + ":mm");
  124. }
  125. if (this.config.showDate) {
  126. dateWrapper.innerHTML = now.format(this.config.dateFormat);
  127. digitalWrapper.appendChild(dateWrapper);
  128. }
  129. if (this.config.displayType !== "analog" && this.config.showTime) {
  130. timeWrapper.innerHTML = timeString;
  131. secondsWrapper.innerHTML = now.format("ss");
  132. if (this.config.showPeriodUpper) {
  133. periodWrapper.innerHTML = now.format("A");
  134. } else {
  135. periodWrapper.innerHTML = now.format("a");
  136. }
  137. if (this.config.displaySeconds) {
  138. timeWrapper.appendChild(secondsWrapper);
  139. }
  140. if (this.config.showPeriod && this.config.timeFormat !== 24) {
  141. timeWrapper.appendChild(periodWrapper);
  142. }
  143. digitalWrapper.appendChild(timeWrapper);
  144. }
  145. /**
  146. * Format the time according to the config
  147. *
  148. * @param {object} config The config of the module
  149. * @param {object} time time to format
  150. * @returns {string} The formatted time string
  151. */
  152. function formatTime(config, time) {
  153. let formatString = hourSymbol + ":mm";
  154. if (config.showPeriod && config.timeFormat !== 24) {
  155. formatString += config.showPeriodUpper ? "A" : "a";
  156. }
  157. return moment(time).format(formatString);
  158. }
  159. /****************************************************************
  160. * Create wrappers for Sun Times, only if specified in config
  161. */
  162. if (this.config.showSunTimes) {
  163. const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
  164. const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
  165. let nextEvent;
  166. if (now.isBefore(sunTimes.sunrise)) {
  167. nextEvent = sunTimes.sunrise;
  168. } else if (now.isBefore(sunTimes.sunset)) {
  169. nextEvent = sunTimes.sunset;
  170. } else {
  171. const tomorrowSunTimes = SunCalc.getTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
  172. nextEvent = tomorrowSunTimes.sunrise;
  173. }
  174. const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
  175. const untilNextEventString = untilNextEvent.hours() + "h " + untilNextEvent.minutes() + "m";
  176. sunWrapper.innerHTML =
  177. '<span class="' +
  178. (isVisible ? "bright" : "") +
  179. '"><i class="fa fa-sun-o" aria-hidden="true"></i> ' +
  180. untilNextEventString +
  181. "</span>" +
  182. '<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
  183. formatTime(this.config, sunTimes.sunrise) +
  184. "</span>" +
  185. '<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
  186. formatTime(this.config, sunTimes.sunset) +
  187. "</span>";
  188. digitalWrapper.appendChild(sunWrapper);
  189. }
  190. /****************************************************************
  191. * Create wrappers for Moon Times, only if specified in config
  192. */
  193. if (this.config.showMoonTimes) {
  194. const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
  195. const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
  196. const moonRise = moonTimes.rise;
  197. let moonSet;
  198. if (moment(moonTimes.set).isAfter(moonTimes.rise)) {
  199. moonSet = moonTimes.set;
  200. } else {
  201. const nextMoonTimes = SunCalc.getMoonTimes(now.clone().add(1, "day"), this.config.lat, this.config.lon);
  202. moonSet = nextMoonTimes.set;
  203. }
  204. const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true;
  205. const illuminatedFractionString = Math.round(moonIllumination.fraction * 100) + "%";
  206. moonWrapper.innerHTML =
  207. '<span class="' +
  208. (isVisible ? "bright" : "") +
  209. '"><i class="fa fa-moon-o" aria-hidden="true"></i> ' +
  210. illuminatedFractionString +
  211. "</span>" +
  212. '<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
  213. (moonRise ? formatTime(this.config, moonRise) : "...") +
  214. "</span>" +
  215. '<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
  216. (moonSet ? formatTime(this.config, moonSet) : "...") +
  217. "</span>";
  218. digitalWrapper.appendChild(moonWrapper);
  219. }
  220. if (this.config.showWeek) {
  221. weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
  222. digitalWrapper.appendChild(weekWrapper);
  223. }
  224. /****************************************************************
  225. * Create wrappers for ANALOG clock, only if specified in config
  226. */
  227. if (this.config.displayType !== "digital") {
  228. // If it isn't 'digital', then an 'analog' clock was also requested
  229. // Calculate the degree offset for each hand of the clock
  230. if (this.config.timezone) {
  231. now.tz(this.config.timezone);
  232. }
  233. const second = now.seconds() * 6,
  234. minute = now.minute() * 6 + second / 60,
  235. hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
  236. // Create wrappers
  237. analogWrapper.style.width = this.config.analogSize;
  238. analogWrapper.style.height = this.config.analogSize;
  239. if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
  240. analogWrapper.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
  241. analogWrapper.style.backgroundSize = "100%";
  242. // The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
  243. // analogWrapper.style.border = "1px solid black";
  244. analogWrapper.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
  245. } else if (this.config.analogFace !== "none") {
  246. analogWrapper.style.border = "2px solid white";
  247. }
  248. const clockFace = document.createElement("div");
  249. clockFace.className = "clockFace";
  250. const clockHour = document.createElement("div");
  251. clockHour.id = "clockHour";
  252. clockHour.style.transform = "rotate(" + hour + "deg)";
  253. clockHour.className = "clockHour";
  254. const clockMinute = document.createElement("div");
  255. clockMinute.id = "clockMinute";
  256. clockMinute.style.transform = "rotate(" + minute + "deg)";
  257. clockMinute.className = "clockMinute";
  258. // Combine analog wrappers
  259. clockFace.appendChild(clockHour);
  260. clockFace.appendChild(clockMinute);
  261. if (this.config.displaySeconds) {
  262. const clockSecond = document.createElement("div");
  263. clockSecond.id = "clockSecond";
  264. clockSecond.style.transform = "rotate(" + second + "deg)";
  265. clockSecond.className = "clockSecond";
  266. clockSecond.style.backgroundColor = this.config.secondsColor;
  267. clockFace.appendChild(clockSecond);
  268. }
  269. analogWrapper.appendChild(clockFace);
  270. }
  271. /*******************************************
  272. * Update placement, respect old analogShowDate even if its not needed anymore
  273. */
  274. if (this.config.displayType === "analog") {
  275. // Display only an analog clock
  276. if (this.config.analogShowDate === "top") {
  277. wrapper.classList.add("clockGrid--bottom");
  278. } else if (this.config.analogShowDate === "bottom") {
  279. wrapper.classList.add("clockGrid--top");
  280. } else {
  281. //analogWrapper.style.gridArea = "center";
  282. }
  283. } else if (this.config.displayType === "both") {
  284. wrapper.classList.add("clockGrid--" + this.config.analogPlacement);
  285. }
  286. wrapper.appendChild(analogWrapper);
  287. wrapper.appendChild(digitalWrapper);
  288. // Return the wrapper to the dom.
  289. return wrapper;
  290. }
  291. });