Add module subfolder support.

This commit is contained in:
Michael Teeuw
2016-04-01 17:35:29 +02:00
parent 671338425a
commit e4c54cc655
27 changed files with 56 additions and 21 deletions

View File

@@ -0,0 +1,152 @@
# Module: Calendar
The `calendar` module is one of the default modules of the MagicMirror.
This module displays events from a public .ical calendar. It can combine multiple calendars.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'calendar',
position: 'top_left', // This can be any of the regions. Best results in left or right regions.
config: {
// The config property is optional.
// If no config is set, an example calendar is shown.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>maximumEntries</code></td>
<td>The maximum number of events shown.<br>
<br><b>Possible values:</b> <code>0</code> - <code>100</code>
<br><b>Default value:</b> <code>10</code>
</td>
</tr>
<tr>
<td><code>displaySymbol</code></td>
<td>Display a symbol in front of an entry.<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>true</code>
</td>
</tr>
<tr>
<td><code>defaultSymbol</code></td>
<td>The default symbol.<br>
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
<br><b>Default value:</b> <code>calendar</code>
</td>
</tr>
<tr>
<td><code>maxTitleLength</code></td>
<td>The maximum title length.<br>
<br><b>Possible values:</b> <code>10</code> - <code>50</code>
<br><b>Default value:</b> <code>25</code>
</td>
</tr>
<tr>
<td><code>fetchInterval</code></td>
<td>How often does the content needs to be fetched? (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
<br><b>Default value:</b> <code>300000</code> (5 minutes)
</td>
</tr>
<tr>
<td><code>animationSpeed</code></td>
<td>Speed of the update animation. (Milliseconds)<br>
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
<br><b>Default value:</b> <code>2000</code> (2 seconds)
</td>
</tr>
<tr>
<td><code>fade</code></td>
<td>Fade the future events to black. (Gradient)<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>true</code>
</td>
</tr>
<tr>
<td><code>fadePoint</code></td>
<td>Where to start fade?<br>
<br><b>Possible values:</b> <code>0</code> (top of the list) - <code>1</code> (bottom of list)
<br><b>Default value:</b> <code>0.25</code>
</td>
</tr>
<tr>
<td><code>calendars</code></td>
<td>The list of calendars.<br>
<br><b>Possible values:</b> An array, see <i>calendar configuration</i> below.
<br><b>Default value:</b> <i>An example calendar.</i>
</td>
</tr>
<tr>
<td><code>titleReplace</code></td>
<td>An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title.<br>
<br><b>Example:</b> <br>
<code>
titleReplace: {'Birthday of ' : '', 'foo':'bar'}
</code>
</td>
</tr>
</tbody>
</table>
### Calendar configuration
The `calendars` property contains an array of the configured calendars.
#### Default value:
````javascript
config: {
calendars: [
{
url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics',
symbol: 'calendar',
},
],
}
````
#### Calendar configuration options:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>url</code></td>
<td>The url of the calendar .ical. This property is required.<br>
<br><b>Possible values:</b> Any public accessble .ical calendar.
</td>
</tr>
<tr>
<td><code> symbol </code></td>
<td>The symbol to show in front of an event. This property is optional.<br>
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,23 @@
.calendar .symbol {
padding-left: 0px;
padding-right: 10px;
font-size: 80%;
}
.calendar .symbol span {
display: inline-block;
-ms-transform: translate(0px,2px); /* IE 9 */
-webkit-transform: translate(0px,2px); /* Safari */
transform: translate(0px,2px);
}
.calendar .title {
padding-left: 0px;
padding-right: 0px;
}
.calendar .time {
padding-left: 30px;
text-align: right;
}

View File

@@ -0,0 +1,240 @@
/* global Module */
/* Magic Mirror
* Module: Calendar
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('calendar',{
// Define module defaults
defaults: {
maximumEntries: 10, // Total Maximum Entries
displaySymbol: true,
defaultSymbol: 'calendar', // Fontawsome Symbol see http://fontawesome.io/cheatsheet/
maxTitleLength: 25,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000,
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
calendars: [
{
symbol: 'calendar',
url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics',
},
],
titleReplace: {
'De verjaardag van ' : ''
}
},
// Define required scripts.
getStyles: function() {
return ['calendar.css', 'font-awesome.css'];
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Override start method.
start: function() {
Log.log('Starting module: ' + this.name);
// Set locale.
moment.locale(config.language);
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
calendar.url = calendar.url.replace('webcal://', 'http://');
this.addCalendar(calendar.url);
}
this.calendarData = {};
},
// Override socket notification handler.
socketNotificationReceived: function(notification, payload) {
if (notification === 'CALENDAR_EVENTS') {
if (this.hasCalendarURL(payload.url)) {
this.calendarData[payload.url] = payload.events;
}
} else if(notification === 'FETCH_ERROR') {
Log.error('Calendar Error. Could not fetch calendar: ' + payload.url);
} else if(notification === 'INCORRECT_URL') {
Log.error('Calendar Error. Incorrect url: ' + payload.url);
} else {
Log.log('Calendar received an unknown socket notification: '+notification);
}
this.updateDom(this.config.animationSpeed);
},
// Override dom generator.
getDom: function() {
var events = this.createEventList();
var wrapper = document.createElement("table");
wrapper.className = "small";
if (events.length === 0) {
wrapper.innerHTML = "Loading events ...";
wrapper.className = "small dimmed";
return wrapper;
}
for (var e in events) {
var event = events[e];
var eventWrapper = document.createElement("tr");
eventWrapper.className = "normal";
if (this.config.displaySymbol) {
var symbolWrapper = document.createElement("td");
symbolWrapper.className = "symbol";
var symbol = document.createElement("span");
symbol.className = "fa fa-" + this.symbolForUrl(event.url);
symbolWrapper.appendChild(symbol);
eventWrapper.appendChild(symbolWrapper);
}
var titleWrapper = document.createElement("td");
titleWrapper.innerHTML = this.titleTransform(event.title);
titleWrapper.className = "title bright";
eventWrapper.appendChild(titleWrapper);
var timeWrapper = document.createElement("td");
timeWrapper.innerHTML = moment(event.startDate,'x').fromNow();
timeWrapper.className = "time light";
eventWrapper.appendChild(timeWrapper);
wrapper.appendChild(eventWrapper);
// Create fade effect.
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = events.length * this.config.fadePoint;
var steps = events.length - startingPoint;
if (e >= startingPoint) {
var currentStep = e - startingPoint;
eventWrapper.style.opacity = 1 - (1 / steps * currentStep);
}
}
}
return wrapper;
},
/* hasCalendarURL(url)
* Check if this config contains the calendar url.
*
* argument url sting - Url to look for.
*
* return bool - Has calendar url
*/
hasCalendarURL: function(url) {
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
if (calendar.url === url) {
return true;
}
}
return false;
},
/* createEventList()
* Creates the sorted list of all events.
*
* return array - Array with events.
*/
createEventList: function() {
var events = [];
for (var c in this.calendarData) {
var calendar = this.calendarData[c];
for (var e in calendar) {
var event = calendar[e];
event.url = c;
events.push(event);
}
}
events.sort(function(a,b) {
return a.startDate - b.startDate;
});
return events.slice(0, this.config.maximumEntries);
},
/* createEventList(url)
* Requests node helper to add calendar url.
*
* argument url sting - Url to add.
*/
addCalendar: function(url) {
this.sendSocketNotification('ADD_CALENDAR', {
url: url,
maximumEntries: this.config.maximumEntries,
fetchInterval: this.config.fetchInterval
});
},
/* symbolForUrl(url)
* Retrieves the symbol for a specific url.
*
* argument url sting - Url to look for.
*
* return string - The Symbol
*/
symbolForUrl: function(url) {
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
if (calendar.url === url && typeof calendar.symbol === 'string') {
return calendar.symbol;
}
}
return this.config.defaultSymbol;
},
/* shorten(string, maxLength)
* Shortens a sting if it's longer than maxLenthg.
* Adds an ellipsis to the end.
*
* argument string string - The string to shorten.
* argument maxLength number - The max lenth of the string.
*
* return string - The shortened string.
*/
shorten: function(string, maxLength) {
if (string.length > maxLength) {
return string.slice(0,maxLength) + "&hellip;";
}
return string;
},
/* titleTransform(title)
* Transforms the title of an event for usage.
* Replaces parts of the text as defined in config.titleReplace.
* Shortens title based on config.maxTitleLength
*
* argument title string - The title to transform.
*
* return string - The transformed title.
*/
titleTransform: function(title) {
for (var needle in this.config.titleReplace) {
var replacement = this.config.titleReplace[needle];
title = title.replace(needle, replacement);
}
title = this.shorten(title, this.config.maxTitleLength);
return title;
}
});

View File

@@ -0,0 +1,242 @@
/* Magic Mirror
* Node Helper: Calendar
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var NodeHelper = require('node_helper');
var ical = require('ical');
var moment = require('moment');
var validUrl = require('valid-url');
var CalendarFetcher = function(url, reloadInterval, maximumEntries) {
var self = this;
var reloadTimer = null;
var events = [];
var fetchFailedCallback = function() {};
var eventsReceivedCallback = function() {};
/* fetchCalendar()
* Initiates calendar fetch.
*/
var fetchCalendar = function() {
clearTimeout(reloadTimer);
reloadTimer = null;
ical.fromURL(url, {}, function(err, data) {
if (err) {
fetchFailedCallback(self, err);
scheduleTimer();
return;
}
//console.log(data);
newEvents = [];
var limitFunction = function (date, i){return i < maximumEntries;};
for (var e in data) {
var event = data[e];
if (event.type === 'VEVENT') {
var startDate = (event.start.length === 8) ? moment(event.start, 'YYYYMMDD') : moment(new Date(event.start));
if (typeof event.rrule != 'undefined') {
var rule = event.rrule;
// Check if the timeset is set to this current time.
// If so, the RRULE line does not contain any BYHOUR, BYMINUTE, BYSECOND params.
// This causes the times of the recurring event to be incorrect.
// By adjusting the timeset property, this issue is solved.
var now = new Date();
if (rule.timeset[0].hour == now.getHours(),
rule.timeset[0].minute == now.getMinutes(),
rule.timeset[0].second == now.getSeconds()) {
rule.timeset[0].hour = startDate.format('H');
rule.timeset[0].minute = startDate.format('m');
rule.timeset[0].second = startDate.format('s');
}
var oneYear = new Date();
oneYear.setFullYear(oneYear.getFullYear() + 1);
var dates = rule.between(new Date(), oneYear, true, limitFunction);
//console.log(dates);
for (var d in dates) {
startDate = moment(new Date(dates[d]));
newEvents.push({
title: event.summary,
startDate: startDate.format('x')
});
}
} else {
// Single event.
var today = moment().startOf('day');
if (startDate > today) {
newEvents.push({
title: event.summary,
startDate: startDate.format('x')
});
}
}
}
}
newEvents.sort(function(a,b) {
return a.startDate - b.startDate;
});
events = newEvents.slice(0, maximumEntries);
self.broadcastEvents();
scheduleTimer();
});
};
/* scheduleTimer()
* Schedule the timer for the next update.
*/
var scheduleTimer = function() {
//console.log('Schedule update timer.');
clearTimeout(reloadTimer);
reloadTimer = setTimeout(function() {
fetchCalendar();
}, reloadInterval);
};
/* public methods */
/* startFetch()
* Initiate fetchCalendar();
*/
this.startFetch = function() {
fetchCalendar();
};
/* broadcastItems()
* Broadcast the exsisting events.
*/
this.broadcastEvents = function() {
if (events.length <= 0) {
//console.log('No events to broadcast yet.');
return;
}
//console.log('Broadcasting ' + events.length + ' events.');
eventsReceivedCallback(self);
};
/* onReceive(callback)
* Sets the on success callback
*
* argument callback function - The on success callback.
*/
this.onReceive = function(callback) {
eventsReceivedCallback = callback;
};
/* onError(callback)
* Sets the on error callback
*
* argument callback function - The on error callback.
*/
this.onError = function(callback) {
fetchFailedCallback = callback;
};
/* url()
* Returns the url of this fetcher.
*
* return string - The url of this fetcher.
*/
this.url = function() {
return url;
};
/* events()
* Returns current available events for this fetcher.
*
* return array - The current available events for this fetcher.
*/
this.events = function() {
return events;
};
};
module.exports = NodeHelper.create({
// Override start method.
start: function() {
var self = this;
var events = [];
this.fetchers = [];
console.log('Starting node helper for: ' + this.name);
},
// Override socketNotificationReceived method.
socketNotificationReceived: function(notification, payload) {
if (notification === 'ADD_CALENDAR') {
//console.log('ADD_CALENDAR: ');
this.createFetcher(payload.url, payload.fetchInterval, payload.maximumEntries);
}
},
/* createFetcher(url, reloadInterval)
* Creates a fetcher for a new url if it doesn't exsist yet.
* Otherwise it reuses the exsisting one.
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
*/
createFetcher: function(url, fetchInterval, maximumEntries) {
var self = this;
if (!validUrl.isUri(url)){
self.sendSocketNotification('INCORRECT_URL', {url:url});
return;
}
var fetcher;
if (typeof self.fetchers[url] === 'undefined') {
console.log('Create new calendar fetcher for url: ' + url + ' - Interval: ' + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries);
fetcher.onReceive(function(fetcher) {
//console.log('Broadcast events.');
//console.log(fetcher.events());
self.sendSocketNotification('CALENDAR_EVENTS', {
url: fetcher.url(),
events: fetcher.events()
});
});
fetcher.onError(function(fetcher, error) {
self.sendSocketNotification('FETCH_ERROR', {
url: fetcher.url(),
error: error
});
});
self.fetchers[url] = fetcher;
} else {
//console.log('Use exsisting news fetcher for url: ' + url);
fetcher = self.fetchers[url];
fetcher.broadcastEvents();
}
fetcher.startFetch();
}
});

View File

@@ -0,0 +1,49 @@
# Module: Clock
The `clock` module is one of the default modules of the MagicMirror.
This module displays the current date and time. The information will be updated realtime.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'clock',
position: 'top_left', // This can be any of the regions.
config: {
// The config property is optional.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>timeFormat</code></td>
<td>Use 12 or 24 hour format.<br>
<br><b>Possible values:</b> <code>12</code> or <code>24</code>
<br><b>Default value:</b> uses value of <i>config.timeFormat</i>
</td>
</tr>
<tr>
<td><code>displaySeconds</code></td>
<td>Display seconds.<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>true</code>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,67 @@
/* global Log, Module, moment, config */
/* Magic Mirror
* Module: Clock
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('clock',{
// Module config defaults.
defaults: {
timeFormat: config.timeFormat,
displaySeconds: true,
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
// Schedule update interval.
var self = this;
setInterval(function() {
self.updateDom();
}, 1000);
// Set locale.
moment.locale(config.language);
},
// Override dom generator.
getDom: function() {
// Create wrappers.
var wrapper = document.createElement("div");
var dateWrapper = document.createElement("div");
var timeWrapper = document.createElement("div");
var secondsWrapper = document.createElement("sup");
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
secondsWrapper.className = "dimmed";
// Set content of wrappers.
dateWrapper.innerHTML = moment().format('dddd, LL');
timeWrapper.innerHTML = moment().format((this.config.timeFormat === 24) ? 'HH:mm' : ('hh:mm'));
secondsWrapper.innerHTML = moment().format('ss');
// Combine wrappers.
wrapper.appendChild(dateWrapper);
wrapper.appendChild(timeWrapper);
if (this.config.displaySeconds) {
timeWrapper.appendChild(secondsWrapper);
}
// Return the wrapper to the dom.
return wrapper;
}
});

View File

@@ -0,0 +1,87 @@
# Module: Compliments
The `compliments` module is one of the default modules of the MagicMirror.
This module displays a random compliment.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'compliments',
position: 'lower_third', // This can be any of the regions.
// Best results in one of the middle regions like: lower_third
config: {
// The config property is optional.
// If no config is set, an example calendar is shown.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>updateInterval</code></td>
<td>How often does the compliment have to change? (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
<br><b>Default value:</b> <code>30000</code> (30 seconds)
</td>
</tr>
<tr>
<td><code>fadeSpeed</code></td>
<td>Speed of the update animation. (Milliseconds)<br>
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
<br><b>Default value:</b> <code>4000</code> (4 seconds)
</td>
</tr>
<tr>
<td><code>compliments</code></td>
<td>The list of compliments.<br>
<br><b>Possible values:</b> An object with three arrays: <code>morning</code>, <code>afternoon</code> and<code>evening</code>. See <i>compliment configuration</i> below.
<br><b>Default value:</b> See <i>compliment configuration</i> below.
</td>
</tr>
</tbody>
</table>
### Compliment configuration
The `compliments` property contains an object with three arrays: <code>morning</code>, <code>afternoon</code> and<code>evening</code>. Based on the time of the day, the compliments will be picked out of one of these arrays. The arrays contain one or multiple compliments.
#### Default value:
````javascript
config: {
compliments: {
morning: [
'Good morning, handsome!',
'Enjoy your day!',
'How was your sleep?'
],
afternoon: [
'Hello, beauty!',
'You look sexy!',
'Looking good today!'
],
evening: [
'Wow, you look hot!',
'You look nice!',
'Hi, sexy!'
]
}
}
````

View File

@@ -0,0 +1,123 @@
/* global Log, Module, moment */
/* Magic Mirror
* Module: Compliments
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('compliments',{
// Module config defaults.
defaults: {
compliments: {
morning: [
'Good morning, handsome!',
'Enjoy your day!',
'How was your sleep?'
],
afternoon: [
'Hello, beauty!',
'You look sexy!',
'Looking good today!'
],
evening: [
'Wow, you look hot!',
'You look nice!',
'Hi, sexy!'
]
},
updateInterval: 30000,
fadeSpeed: 4000
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
this.lastComplimentIndex = -1;
// Schedule update timer.
var self = this;
setInterval(function() {
self.updateDom(self.config.fadeSpeed);
}, this.config.updateInterval);
},
/* randomIndex(compliments)
* Generate a random index for a list of compliments.
*
* argument compliments Array<String> - Array with compliments.
*
* return Number - Random index.
*/
randomIndex: function(compliments) {
if (compliments.length === 1) {
return 0;
}
var generate = function() {
return Math.floor(Math.random() * compliments.length);
};
var complimentIndex = generate();
while (complimentIndex === this.lastComplimentIndex) {
complimentIndex = generate();
}
this.lastComplimentIndex = complimentIndex;
return complimentIndex;
},
/* complimentArray()
* Retrieve an array of compliments for the time of the day.
*
* return compliments Array<String> - Array with compliments for the time of the day.
*/
complimentArray: function() {
var hour = moment().hour();
if (hour >= 3 && hour < 12) {
return this.config.compliments.morning;
} else if (hour >= 12 && hour < 17) {
return this.config.compliments.afternoon;
} else {
return this.config.compliments.evening;
}
},
/* complimentArray()
* Retrieve a random compliment.
*
* return compliment string - A compliment.
*/
randomCompliment: function() {
var compliments = this.complimentArray();
var index = this.randomIndex(compliments);
return compliments[index];
},
// Override dom generator.
getDom: function() {
var complimentText = this.randomCompliment();
var compliment = document.createTextNode(complimentText);
var wrapper = document.createElement("div");
wrapper.className = 'thin xlarge bright';
wrapper.appendChild(compliment);
return wrapper;
}
});

View File

@@ -0,0 +1,145 @@
# Module: Current Weather
The `currentweather` module is one of the default modules of the MagicMirror.
This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'currentweather',
position: 'top_right', // This can be any of the regions.
// Best results in left or right regions.
config: {
// See 'Configuration options' for more information.
location: 'Amsterdam,Netherlands',
appid: 'abcde12345abcde12345abcde12345ab' //openweathermap.org API key.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>location</code></td>
<td>The location used for weather information.<br>
<br><b>Example:</b> <code>Amsterdam,Netherlands</code>
<br><b>Default value:</b> <code>New York</code>
</td>
</tr>
<tr>
<td><code>appid</code></td>
<td>The <a href="https://home.openweathermap.org" target="_blank">OpenWeatherMap</a> API key, which can be obtained by creating an OpenWeatherMap account.<br>
<br> This value is <b>REQUIRED</b>
</td>
</tr>
<tr>
<td><code>units</code></td>
<td>What units to use?<br>
<br><b>Possible values:</b> <code>default</code> = Kelvin, <code>metric</code> = Celsius, <code>imperial</code> =Fahrenheit
<br><b>Default value:</b> <code>metric</code>
</td>
</tr>
<tr>
<td><code>updateInterval</code></td>
<td>How often does the content needs to be fetched? (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
<br><b>Default value:</b> <code>300000</code> (10 minutes)
</td>
</tr>
<tr>
<td><code>animationSpeed</code></td>
<td>Speed of the update animation. (Milliseconds)<br>
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
<br><b>Default value:</b> <code>2000</code> (2 seconds)
</td>
</tr>
<tr>
<td><code>timeFormat</code></td>
<td>Use 12 or 24 hour format.<br>
<br><b>Possible values:</b> <code>12</code> or <code>24</code>
<br><b>Default value:</b> uses value of <i>config.timeFormat</i>
</td>
</tr>
<tr>
<td><code>lang</code></td>
<td>The language of the days.<br>
<br><b>Possible values:</b> <code>en</code>, <code>nl</code>, <code>ru</code>, etc ...
<br><b>Default value:</b> uses value of <i>config.language</i>
</td>
</tr>
<tr>
<td><code>initialLoadDelay</code></td>
<td>The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>5000</code>
<br><b>Default value:</b> <code>0</code>
</td>
</tr>
<tr>
<td><code>retryDelay</code></td>
<td>The delay before retrying after a request failure. (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>60000</code>
<br><b>Default value:</b> <code>2500</code>
</td>
</tr>
<tr>
<td><code>apiVersion</code></td>
<td>The OpenWeatherMap API version to use.<br>
<br><b>Default value:</b> <code>2.5</code>
</td>
</tr>
<tr>
<td><code>apiBase</code></td>
<td>The OpenWeatherMap base URL.<br>
<br><b>Default value:</b> <code>'http://api.openweathermap.org/data/'</code>
</td>
</tr>
<tr>
<td><code>weatherEndpoint</code></td>
<td>The OpenWeatherMap API endPoint.<br>
<br><b>Default value:</b> <code>'weather'</code>
</td>
</tr>
<tr>
<td><code>iconTable</code></td>
<td>The conversion table to convert the weather conditions to weather-icons.<br>
<br><b>Default value:</b> <code>iconTable: {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
}</code>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,9 @@
.currentweather .weathericon {
font-size: 75%;
line-height: 65px;
display: inline-block;
-ms-transform: translate(0px,-3px); /* IE 9 */
-webkit-transform: translate(0px,-3px); /* Safari */
transform: translate(0px,-3px);
}

View File

@@ -0,0 +1,271 @@
/* global Module */
/* Magic Mirror
* Module: CurrentWeather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('currentweather',{
// Default module config.
defaults: {
location: '',
appid: '',
units: 'metric',
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
lang: config.language,
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiVersion: '2.5',
apiBase: 'http://api.openweathermap.org/data/',
weatherEndpoint: 'weather',
iconTable: {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
},
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define required scripts.
getStyles: function() {
return ['weather-icons.css', 'currentweather.css'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
// Set locale.
moment.locale(config.language);
this.windSpeed = null;
this.sunriseSunsetTime = null;
this.sunriseSunsetIcon = null;
this.temperature = null;
this.weatherType = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
this.updateTimer = null;
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
if (this.config.appid === '') {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
}
if (this.config.location === '') {
wrapper.innerHTML = "Please set the openweather <i>location</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
}
if (!this.loaded) {
wrapper.innerHTML = "Loading weather ...";
wrapper.className = "dimmed light small";
return wrapper;
}
var small = document.createElement("div");
small.className = "normal medium";
var windIcon = document.createElement("span");
windIcon.className = "wi wi-strong-wind dimmed";
small.appendChild(windIcon);
var windSpeed = document.createElement("span");
windSpeed.innerHTML = " " + this.windSpeed;
small.appendChild(windSpeed);
var spacer = document.createElement("span");
spacer.innerHTML = "&nbsp;";
small.appendChild(spacer);
var sunriseSunsetIcon = document.createElement("span");
sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
small.appendChild(sunriseSunsetIcon);
var sunriseSunsetTime = document.createElement("span");
sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
small.appendChild(sunriseSunsetTime);
var large = document.createElement("div");
large.className = "large light";
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon " + this.weatherType;
large.appendChild(weatherIcon);
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature + '&deg;';
large.appendChild(temperature);
wrapper.appendChild(small);
wrapper.appendChild(large);
return wrapper;
},
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
*/
updateWeather: function() {
var url = this.config.apiBase + this.config.apiVersion + '/' + this.config.weatherEndpoint + this.getParams();
var self = this;
var retry = true;
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function() {
if(this.readyState === 4) {
if(this.status === 200) {
self.processWeather(JSON.parse(this.response));
} else if (this.status === 401) {
self.config.appid = '';
self.updateDom(self.config.animationSpeed);
Log.error(self.name + ": Incorrect APPID.");
retry = false;
} else {
Log.error(self.name + ": Could not load weather.");
}
if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
}
}
};
weatherRequest.send();
},
/* getParams(compliments)
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams: function() {
var params = "?";
params += 'q=' + this.config.location;
params += '&units=' + this.config.units;
params += '&lang=' + this.config.lang;
params += '&APPID=' + this.config.appid;
return params;
},
/* processWeather(data)
* Uses the received data to set the various values.
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function(data) {
this.temperature = this.roundValue(data.main.temp);
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
this.weatherType = this.config.iconTable[data.weather[0].icon];
var now = moment().format('x');
var sunrise = moment(data.sys.sunrise*1000).format('x');
var sunset = moment(data.sys.sunset*1000).format('x');
if (sunrise < now && sunset > now) {
this.sunriseSunsetTime = moment(data.sys.sunset*1000).format((this.config.timeFormat === 24) ? 'HH:mm' : 'hh:mm a');
this.sunriseSunsetIcon = 'wi-sunset';
} else {
this.sunriseSunsetTime = moment(data.sys.sunrise*1000).format((this.config.timeFormat === 24) ? 'HH:mm' : 'hh:mm a');
this.sunriseSunsetIcon = 'wi-sunrise';
}
this.loaded = true;
this.updateDom(this.config.animationSpeed);
},
/* scheduleUpdate()
* Schedule next update.
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function(delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== 'undefined' && delay >= 0) {
nextLoad = delay;
}
var self = this;
setTimeout(function() {
self.updateWeather();
}, nextLoad);
},
/* ms2Beaufort(ms)
* Converts m2 to beaufort (windspeed).
*
* argument ms number - Windspeed in m/s.
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function(ms) {
var kmh = ms * 60 * 60 / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
},
/* function(temperature)
* Rounds a temperature to 1 decimal.
*
* argument temperature number - Temperature.
*
* return number - Rounded Temperature.
*/
roundValue: function (temperature) {
return parseFloat(temperature).toFixed(1);
}
});

View File

@@ -0,0 +1,22 @@
/* Magic Mirror
* Default Modules List
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
var defaultModules = [
'calendar',
'clock',
'compliments',
'currentweather',
'helloworld',
'newsfeed',
'weatherforecast'
];
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== 'undefined') {module.exports = defaultModules;}

View File

@@ -0,0 +1,51 @@
# Module: Hello World
The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'helloworld',
position: 'bottom_bar', // This can be any of the regions.
config: {
// See 'Configuration options' for more information.
text: 'Hello world!',
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>text</code></td>
<td>The text to display.<br>
<br><b>Example:</b> <code>'Hello world!'</code>
<br><b>Default value:</b> <code>'Hello world!'</code>
</td>
</tr>
<tr>
<td><code>classes</code></td>
<td>Classes to apply to the text.<br>
<br><b>Example:</b> <code>'xsmall bold'</code>
<br><b>Default value:</b> <code>'normal medium'</code>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,27 @@
/* global Module */
/* Magic Mirror
* Module: HelloWorld
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('helloworld',{
// Default module config.
defaults: {
text: "Hello World!",
classes: "normal medium"
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
wrapper.className = this.config.classes;
wrapper.innerHTML = this.config.text;
return wrapper;
}
});

View File

@@ -0,0 +1,76 @@
# Module: News Feed
The `newsfeed ` module is one of the default modules of the MagicMirror.
This module displays news headlines based on an RSS feed.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'newsfeed',
position: 'bottom_bar', // This can be any of the regions. Best results in center regions.
config: {
// The config property is optional.
// If no config is set, an example calendar is shown.
// See 'Configuration options' for more information.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>feedUrl</code></td>
<td>The url of the feed used for the headlines.<br>
<br><b>Default value:</b> <code>'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'</code>
</td>
</tr>
<tr>
<td><code>showPublishDate</code></td>
<td>Display the publish date of an headline.<br>
<br><b>Default value:</b> <code>true</code>
<br><b>Default value:</b> <code>true</code> or <code>false</code>
</td>
</tr>
<tr>
<td><code>reloadInterval</code></td>
<td>How often does the content needs to be fetched? (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
<br><b>Default value:</b> <code>300000</code> (5 minutes)
</td>
</tr>
<tr>
<td><code>updateInterval</code></td>
<td>How often do you want to display a new headline? (Milliseconds)<br>
<br><b>Possible values:</b><code>1000</code> - <code>60000</code>
<br><b>Default value:</b> <code>7500</code> (7.5 seconds)
</td>
</tr>
<tr>
<td><code>animationSpeed</code></td>
<td>Speed of the update animation. (Milliseconds)<br>
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
<br><b>Default value:</b> <code>2000</code> (2.5 seconds)
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,111 @@
/* Magic Mirror
* Fetcher
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var NewsFetcher = require('./newsfetcher.js');
/* Fetcher
* Responsible for requesting an update on the set interval and broadcasting the data.
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
*/
var Fetcher = function(url, reloadInterval) {
var self = this;
var newsFetcher = new NewsFetcher();
if (reloadInterval < 1000) {
reloadInterval = 1000;
}
var reloadTimer = null;
var items = [];
var fetchFailedCallback = function() {};
var itemsReceivedCallback = function() {};
/* private methods */
/* fetchNews()
* Request the new items from the newsFetcher.
*/
var fetchNews = function() {
//console.log('Fetch news.');
clearTimeout(reloadTimer);
reloadTimer = null;
newsFetcher.fetchNews(url, function(fetchedItems) {
items = fetchedItems;
self.broadcastItems();
scheduleTimer();
}, function(error) {
fetchFailedCallback(self, error);
scheduleTimer();
});
};
/* scheduleTimer()
* Schedule the timer for the next update.
*/
var scheduleTimer = function() {
//console.log('Schedule update timer.');
clearTimeout(reloadTimer);
reloadTimer = setTimeout(function() {
fetchNews();
}, reloadInterval);
};
/* public methods */
/* setReloadInterval()
* Update the reload interval, but only if we need to increase the speed.
*
* attribute interval number - Interval for the update in milliseconds.
*/
this.setReloadInterval = function(interval) {
if (interval > 1000 && interval < reloadInterval) {
reloadInterval = interval;
}
};
/* startFetch()
* Initiate fetchNews();
*/
this.startFetch = function() {
fetchNews();
};
/* broadcastItems()
* Broadcast the exsisting items.
*/
this.broadcastItems = function() {
if (items.length <= 0) {
//console.log('No items to broadcast yet.');
return;
}
//console.log('Broadcasting ' + items.length + ' items.');
itemsReceivedCallback(self);
};
this.onReceive = function(callback) {
itemsReceivedCallback = callback;
};
this.onError = function(callback) {
fetchFailedCallback = callback;
};
this.url = function() {
return url;
};
this.items = function() {
return items;
};
};
module.exports = Fetcher;

View File

@@ -0,0 +1,123 @@
/* global Module */
/* Magic Mirror
* Module: NewsFeed
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('newsfeed',{
// Default module config.
defaults: {
feedUrl: 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml',
showPublishDate: true,
reloadInterval: 5 * 60 * 1000, // every 5 minutes
updateInterval: 7.5 * 1000,
animationSpeed: 2.5 * 1000,
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
// Set locale.
moment.locale(config.language);
this.newsItems = [];
this.loaded = false;
this.activeItem = 0;
this.fetchNews();
},
// Override socket notification handler.
socketNotificationReceived: function(notification, payload) {
if (notification === 'NEWS_ITEMS') {
if (payload.url === this.config.feedUrl) {
this.newsItems = payload.items;
if (!this.loaded) {
this.scheduleUpdateInterval();
}
this.loaded = true;
}
}
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0;
}
if (this.newsItems.length > 0) {
if (this.config.showPublishDate) {
var timestamp = document.createElement("div");
timestamp.className = "light small dimmed";
timestamp.innerHTML = this.capitalizeFirstLetter(moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow() + ':');
//timestamp.innerHTML = this.config.feedUrl;
wrapper.appendChild(timestamp);
}
var title = document.createElement("div");
title.className = "bright medium light";
title.innerHTML = this.newsItems[this.activeItem].title;
wrapper.appendChild(title);
} else {
wrapper.innerHTML = "Loading news ...";
wrapper.className = "small dimmed";
}
return wrapper;
},
/* fetchNews(compliments)
* Requests new data from news proxy.
*/
fetchNews: function() {
Log.log('Add news feed to fetcher: ' + this.config.feedUrl);
this.sendSocketNotification('ADD_FEED', {
url: this.config.feedUrl,
reloadInterval: this.config.reloadInterval
});
},
/* scheduleUpdateInterval()
* Schedule visual update.
*/
scheduleUpdateInterval: function() {
var self = this;
self.updateDom(self.config.animationSpeed);
setInterval(function() {
self.activeItem++;
self.updateDom(self.config.animationSpeed);
}, this.config.updateInterval);
},
/* capitalizeFirstLetter(string)
* Capitalizes the first character of a string.
*
* argument string string - Input string.
*
* return string - Capitalized output string.
*/
capitalizeFirstLetter: function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
});

View File

@@ -0,0 +1,53 @@
/* Magic Mirror
* NewsFetcher
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var FeedMe = require('feedme');
var request = require('request');
var NewsFetcher = function() {
var self = this;
self.successCallback = function(){};
self.errorCallback = function(){};
self.items = [];
var parser = new FeedMe();
parser.on('item', function(item) {
//console.log(item);
self.items.push({
title: item.title,
pubdate: item.pubdate,
});
});
parser.on('end', function(item) {
self.successCallback(self.items);
});
parser.on('error', function(error) {
self.errorCallback(error);
});
/* public methods */
/* fetchNews()
* Fetch the new news items.
*
* attribute url string - The url to fetch.
* attribute success function(items) - Callback on succes.
* attribute error function(error) - Callback on error.
*/
self.fetchNews = function(url, success, error) {
self.successCallback = success;
self.errorCallback = error;
request(url).pipe(parser);
};
};
module.exports = NewsFetcher;

View File

@@ -0,0 +1,76 @@
/* Magic Mirror
* Node Helper: Newsfeed
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var NodeHelper = require('node_helper');
var validUrl = require('valid-url');
var Fetcher = require('./fetcher.js');
module.exports = NodeHelper.create({
// Subclass start method.
start: function() {
console.log('Starting module: ' + this.name);
this.fetchers = [];
},
// Subclass socketNotificationReceived received.
socketNotificationReceived: function(notification, payload) {
if(notification === 'ADD_FEED') {
this.createFetcher(payload.url, payload.reloadInterval);
}
},
/* createFetcher(url, reloadInterval)
* Creates a fetcher for a new url if it doesn't exsist yet.
* Otherwise it reoses the exsisting one.
*
* attribute url string - URL of the news feed.
* attribute reloadInterval number - Reload interval in milliseconds.
*/
createFetcher: function(url, reloadInterval) {
var self = this;
if (!validUrl.isUri(url)){
self.sendSocketNotification('INCORRECT_URL', url);
return;
}
var fetcher;
if (typeof self.fetchers[url] === 'undefined') {
console.log('Create new news fetcher for url: ' + url + ' - Interval: ' + reloadInterval);
fetcher = new Fetcher(url, reloadInterval);
fetcher.onReceive(function(fetcher) {
self.sendSocketNotification('NEWS_ITEMS', {
url: fetcher.url(),
items: fetcher.items()
});
});
fetcher.onError(function(fetcher, error) {
self.sendSocketNotification('FETCH_ERROR', {
url: fetcher.url(),
error: error
});
});
self.fetchers[url] = fetcher;
} else {
console.log('Use exsisting news fetcher for url: ' + url);
fetcher = self.fetchers[url];
fetcher.setReloadInterval(reloadInterval);
fetcher.broadcastItems();
}
fetcher.startFetch();
}
});

View File

@@ -0,0 +1,153 @@
# Module: Weather Forecast
The `weatherforecast` module is one of the default modules of the MagicMirror.
This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature.
## Using the module
To use this module, add it to the modules array in the `config/config.js` file:
````javascript
modules: [
{
module: 'weatherforecast',
position: 'top_right', // This can be any of the regions.
// Best results in left or right regions.
config: {
// See 'Configuration options' for more information.
location: 'Amsterdam,Netherlands',
appid: 'abcde12345abcde12345abcde12345ab' //openweathermap.org API key.
}
}
]
````
## Configuration options
The following properties can be configured:
<table width="100%">
<!-- why, markdown... -->
<thead>
<tr>
<th>Option</th>
<th width="100%">Description</th>
</tr>
<thead>
<tbody>
<tr>
<td><code>location</code></td>
<td>The location used for weather information.<br>
<br><b>Example:</b> <code>Amsterdam,Netherlands</code>
<br><b>Default value:</b> <code>New York</code>
</td>
</tr>
<tr>
<td><code>appid</code></td>
<td>The <a href="https://home.openweathermap.org" target="_blank">OpenWeatherMap</a> API key, which can be obtained by creating an OpenWeatherMap account.<br>
<br> This value is <b>REQUIRED</b>
</td>
</tr>
<tr>
<td><code>units</code></td>
<td>What units to use?<br>
<br><b>Possible values:</b> <code>default</code> = Kelvin, <code>metric</code> = Celsius, <code>imperial</code> =Fahrenheit
<br><b>Default value:</b> <code>metric</code>
</td>
</tr>
<tr>
<td><code>updateInterval</code></td>
<td>How often does the content needs to be fetched? (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
<br><b>Default value:</b> <code>300000</code> (10 minutes)
</td>
</tr>
<tr>
<td><code>animationSpeed</code></td>
<td>Speed of the update animation. (Milliseconds)<br>
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
<br><b>Default value:</b> <code>2000</code> (2 seconds)
</td>
</tr>
<tr>
<td><code>lang</code></td>
<td>The language of the days.<br>
<br><b>Possible values:</b> <code>en</code>, <code>nl</code>, <code>ru</code>, etc ...
<br><b>Default value:</b> uses value of <i>config.language</i>
</td>
</tr>
<tr>
<td><code>fade</code></td>
<td>Fade the future events to black. (Gradient)<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>true</code>
</td>
</tr>
<tr>
<td><code>fadePoint</code></td>
<td>Where to start fade?<br>
<br><b>Possible values:</b> <code>0</code> (top of the list) - <code>1</code> (bottom of list)
<br><b>Default value:</b> <code>0.25</code>
</td>
</tr>
<tr>
<td><code>initialLoadDelay</code></td>
<td>The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>5000</code>
<br><b>Default value:</b> <code>0</code>
</td>
</tr>
<tr>
<td><code>retryDelay</code></td>
<td>The delay before retrying after a request failure. (Milliseconds)<br>
<br><b>Possible values:</b> <code>1000</code> - <code>60000</code>
<br><b>Default value:</b> <code>2500</code>
</td>
</tr>
<tr>
<td><code>apiVersion</code></td>
<td>The OpenWeatherMap API version to use.<br>
<br><b>Default value:</b> <code>2.5</code>
</td>
</tr>
<tr>
<td><code>apiBase</code></td>
<td>The OpenWeatherMap base URL.<br>
<br><b>Default value:</b> <code>'http://api.openweathermap.org/data/'</code>
</td>
</tr>
<tr>
<td><code>weatherEndpoint</code></td>
<td>The OpenWeatherMap API endPoint.<br>
<br><b>Default value:</b> <code>'forecast/daily'</code>
</td>
</tr>
<tr>
<td><code>iconTable</code></td>
<td>The conversion table to convert the weather conditions to weather-icons.<br>
<br><b>Default value:</b> <code>iconTable: {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
}</code>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,14 @@
.weatherforecast .day {
padding-left: 0px;
padding-right: 25px;
}
.weatherforecast .weather-icon {
padding-right: 30px;
text-align: center;
}
.weatherforecast .min-temp {
padding-left: 20px;
padding-right: 0px;
}

View File

@@ -0,0 +1,280 @@
/* global Module */
/* Magic Mirror
* Module: WeatherForecast
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.register('weatherforecast',{
// Default module config.
defaults: {
location: '',
appid: '',
units: 'metric',
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
lang: config.language,
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
initialLoadDelay: 2500, // 2.5 seconds delay. This delay is used to keep the OpenWeather API happy.
retryDelay: 2500,
apiVersion: '2.5',
apiBase: 'http://api.openweathermap.org/data/',
forecastEndpoint: 'forecast/daily',
iconTable: {
'01d':'wi-day-sunny',
'02d':'wi-day-cloudy',
'03d':'wi-cloudy',
'04d':'wi-cloudy-windy',
'09d':'wi-showers',
'10d':'wi-rain',
'11d':'wi-thunderstorm',
'13d':'wi-snow',
'50d':'wi-fog',
'01n':'wi-night-clear',
'02n':'wi-night-cloudy',
'03n':'wi-night-cloudy',
'04n':'wi-night-cloudy',
'09n':'wi-night-showers',
'10n':'wi-night-rain',
'11n':'wi-night-thunderstorm',
'13n':'wi-night-snow',
'50n':'wi-night-alt-cloudy-windy'
},
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define required scripts.
getStyles: function() {
return ['weather-icons.css', 'weatherforecast.css'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
// Set locale.
moment.locale(config.language);
this.forecast = [];
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
this.updateTimer = null;
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
if (this.config.appid === '') {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
}
if (this.config.location === '') {
wrapper.innerHTML = "Please set the openweather <i>location</i> in the config for module: " + this.name + ".";
wrapper.className = "dimmed light small";
return wrapper;
}
if (!this.loaded) {
wrapper.innerHTML = "Loading weather ...";
wrapper.className = "dimmed light small";
return wrapper;
}
var table = document.createElement("table");
table.className = "small";
for (var f in this.forecast) {
var forecast = this.forecast[f];
var row = document.createElement("tr");
table.appendChild(row);
var dayCell = document.createElement("td");
dayCell.className = 'day';
dayCell.innerHTML = forecast.day;
row.appendChild(dayCell);
var iconCell = document.createElement("td");
iconCell.className = "bright weather-icon";
row.appendChild(iconCell);
var icon = document.createElement("span");
icon.className = forecast.icon;
iconCell.appendChild(icon);
var maxTempCell = document.createElement("td");
maxTempCell.innerHTML = forecast.maxTemp;
maxTempCell.className = 'align-right bright max-temp';
row.appendChild(maxTempCell);
var minTempCell = document.createElement("td");
minTempCell.innerHTML = forecast.minTemp;
minTempCell.className = 'align-right min-temp';
row.appendChild(minTempCell);
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = this.forecast.length * this.config.fadePoint;
var steps = this.forecast.length - startingPoint;
if (f >= startingPoint) {
var currentStep = f - startingPoint;
row.style.opacity = 1 - (1 / steps * currentStep);
}
}
}
return table;
},
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
*/
updateWeather: function() {
var url = this.config.apiBase + this.config.apiVersion + '/' + this.config.forecastEndpoint + this.getParams();
var self = this;
var retry = true;
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function() {
if(this.readyState === 4) {
if(this.status === 200) {
self.processWeather(JSON.parse(this.response));
} else if (this.status === 401) {
self.config.appid = '';
self.updateDom(self.config.animationSpeed);
Log.error(self.name + ": Incorrect APPID.");
retry = false;
} else {
Log.error(self.name + ": Could not load weather.");
}
if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
}
}
};
weatherRequest.send();
},
/* getParams(compliments)
* Generates an url with api parameters based on the config.
*
* return String - URL params.
*/
getParams: function() {
var params = "?";
params += 'q=' + this.config.location;
params += '&units=' + this.config.units;
params += '&lang=' + this.config.lang;
params += '&APPID=' + this.config.appid;
return params;
},
/* processWeather(data)
* Uses the received data to set the various values.
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function(data) {
this.forecast = [];
for (var i = 0, count = data.list.length; i < count; i++) {
var forecast = data.list[i];
this.forecast.push({
day: moment(forecast.dt, 'X').format('ddd.'),
icon: this.config.iconTable[forecast.weather[0].icon],
maxTemp: this.roundValue(forecast.temp.max),
minTemp: this.roundValue(forecast.temp.min)
});
}
//Log.log(this.forecast);
this.loaded = true;
this.updateDom(this.config.animationSpeed);
},
/* scheduleUpdate()
* Schedule next update.
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function(delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== 'undefined' && delay >= 0) {
nextLoad = delay;
}
var self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function() {
self.updateWeather();
}, nextLoad);
},
/* ms2Beaufort(ms)
* Converts m2 to beaufort (windspeed).
*
* argument ms number - Windspeed in m/s.
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function(ms) {
var kmh = ms * 60 * 60 / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
},
/* function(temperature)
* Rounds a temperature to 1 decimal.
*
* argument temperature number - Temperature.
*
* return number - Rounded Temperature.
*/
roundValue: function (temperature) {
return parseFloat(temperature).toFixed(1);
}
});