Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First release of this module #1

Merged
merged 1 commit into from
Nov 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 278 additions & 0 deletions MMM-NatureRemo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/* Magic Mirror
* Module: MMM-NatureRemo
*
* By Tatsuma Matsuki
* MIT Licensed.
* Some code is borrowed from
* https://github.com/roramirez/MagicMirror-Module-Template
*/

Module.register("MMM-NatureRemo", {
defaults: {
updateInterval: 10 * 60 * 1000,
retryDelay: 5000,
title: "Nature Remo",
apiBase: "https://api.nature.global/",
apiEndpoint: "1/devices",
apiToken: "",
deviceName: "",
deviceID: "",
height: "300px",
width: "500px",
showTemperature: true,
showHumidity: true,
showIllumination: true,
showMotion: true,
temperatureTitle: "Temperature: ",
humidityTitle: "Humidity: ",
illuminationTitle: "Illumination: ",
motionTitle: "Motion Detect: ",
temperatureUnit: "Celsius",
motionDateOffsetSeconds: 0,
motionDateHourFormat: "24h"
},

requiresVersion: "2.12.0",

start: function () {
var self = this;
var dataRequest = null;
var dataNotification = null;

//Flag for check if module is loaded
this.loaded = false;

// Schedule update timer.
this.getData();
setInterval(function () {
self.updateDom();
}, this.config.updateInterval);
},

/*
* getData
* function example return data and show it in the module wrapper
* get a URL request
*
*/
getData: function () {
const self = this;

if (this.config.apiToken === "") {
Log.error(self.name + ": apiToken must be specified");
return;
}

const url = `${this.config.apiBase}${this.config.apiEndpoint}`;
var retry = true;
let delay = self.config.retryDelay;

fetch(url, {
headers: { "Authorization": `Bearer ${this.config.apiToken}` }
})
.then((res) => {
if (res.status == 401) {
retry = false;
throw new Error(self.name + ": apiToken is invalid");
} else if (res.status == 429) {
delay = 60 * 1000;
throw new Error(self.name + ": too many requests and got 429 status");
} else if (!res.ok) {
throw new Error(self.name + ": failed to get api response");
}
return res.json();
})
.then((json) => {
self.processData(json);
})
.catch((msg) => {
Log.error(msg);
})
.finally(() => {
if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : delay);
}
})
},

/* 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;
}
nextLoad = nextLoad;
var self = this;
setTimeout(function () {
self.getData();
}, nextLoad);
},

getIllumiString: function (value) {
if (value <= 50) {
return `Dark (${value})`;
} else if (value > 50 && value <= 127) {
return `Dim (${value})`;
} else if (value > 127 && value <= 205) {
return `Medium (${value})`;
} else {
return `Light (${value})`;
}
},

getHourString: function (hour, minute) {
let m = ("0" + minute).slice(-2);
if (this.config.motionDateHourFormat == "12h") {
let ampm = hour < 12 ? 'a.m.' : 'p.m.';
let h = hour % 12;
h = h ? h : 12;
h = ("0" + h).slice(-2);
return `${h}:${m} ${ampm}`;
} else {
let h = ("0" + hour).slice(-2);
return `${h}:${m}`;
}
},

getDevice: function (data) {
const self = this;
let devices;
if (this.config.deviceID !== "") {
devices = data.filter(function (value) {
return value.id == self.config.deviceID;
});
} else if (this.config.deviceName !== "") {
devices = data.filter(function (value) {
return value.name == self.config.deviceName;
});
}
if (devices.length) {
return devices[0];
} else {
return NaN;
}
},

getDom: function () {
const wrapper = document.createElement("div");
const title = document.createElement("span");
title.style.fontSize = "0.5em";
title.innerHTML = this.config.title;
wrapper.appendChild(title);
wrapper.style.width = this.config.width;
wrapper.style.height = this.config.height;
wrapper.style.lineHeight = "1em";
if (this.sensorData) {
const dev = this.getDevice(this.sensorData);
if (!dev) {
const error = document.createElement("div");
error.innerHTML = "No devices are found";
wrapper.appendChild(error);
return wrapper;
}
if (this.config.showTemperature) {
const tempWrapper = document.createElement("div");
const tempTitle = document.createElement("span");
const temp = document.createElement("span");
tempTitle.style.fontSize = "0.8em";
tempTitle.innerHTML = this.config.temperatureTitle;
temp.style.color = "#fff";
if (this.config.temperatureUnit == "Fahrenheit") {
temp.innerHTML = `${dev.newest_events.te.val * 1.8 + 32}°F`;
} else {
temp.innerHTML = `${dev.newest_events.te.val}°C`;
}
tempWrapper.appendChild(tempTitle);
tempWrapper.appendChild(temp);
wrapper.appendChild(tempWrapper);
}
if (this.config.showHumidity) {
const humiWrapper = document.createElement("div");
const humiTitle = document.createElement("span");
const humi = document.createElement("span");
humiTitle.style.fontSize = "0.8em";
humiTitle.innerHTML = this.config.humidityTitle;
humi.style.color = "#fff";
humi.innerHTML = `${dev.newest_events.hu.val}%`;
humiWrapper.appendChild(humiTitle);
humiWrapper.appendChild(humi);
wrapper.appendChild(humiWrapper);
}
if (this.config.showIllumination) {
const illuWrapper = document.createElement("div");
const illuTitle = document.createElement("span");
const illu = document.createElement("span");
illuTitle.style.fontSize = "0.8em";
illuTitle.innerHTML = this.config.illuminationTitle;
illu.style.color = "#fff";
illu.innerHTML = `${this.getIllumiString(dev.newest_events.il.val)}`;
illuWrapper.appendChild(illuTitle);
illuWrapper.appendChild(illu);
wrapper.appendChild(illuWrapper);
}
if (this.config.showMotion) {
const motiWrapper = document.createElement("div");
const motiTitle = document.createElement("span");
const moti = document.createElement("span");
motiTitle.style.fontSize = "0.8em";
motiTitle.innerHTML = this.config.motionTitle;
moti.style.color = "#fff";
let unixTime = Date.parse(dev.newest_events.mo.created_at);
unixTime = unixTime + this.config.motionDateOffsetSeconds;
const date = new Date(unixTime);
const time = this.getHourString(date.getHours(), date.getMinutes());
moti.innerHTML = `${date.getMonth() + 1}/${date.getDate()} ${time}`;
motiWrapper.appendChild(motiTitle);
motiWrapper.appendChild(moti);
wrapper.appendChild(motiWrapper);
}
}

// Data from helper
if (this.dataNotification) {
var wrapperDataNotification = document.createElement("div");
// translations + datanotification
wrapperDataNotification.innerHTML = "Updated at " + this.dataNotification.date;

wrapper.appendChild(wrapperDataNotification);
}
return wrapper;
},

getScripts: function () {
return [];
},

getStyles: function () {
return [];
},

getTranslations: function () {
return false;
},

processData: function (data) {
var self = this;
this.sensorData = data;
if (this.loaded === false) { self.updateDom(self.config.animationSpeed); }
this.loaded = true;

// the data if load
// send notification to helper
this.sendSocketNotification("MMM-NatureRemo-NOTIFICATION", data);
},

// socketNotificationReceived from helper
socketNotificationReceived: function (notification, payload) {
if (notification === "MMM-NatureRemo-NOTIFICATION") {
// set dataNotification
this.dataNotification = payload;
this.updateDom();
}
},
});
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,49 @@
# MMM-NatureRemo
Magic Mirror module for displaying sensor data from Nature Remo
Magic Mirror module for displaying sensor data from [Nature Remo](https://en.nature.global/).

![image](https://user-images.githubusercontent.com/48573325/100518374-85a5de80-31d4-11eb-9604-a1e5dd07e329.png)

[Nature Remo](https://en.nature.global/) has a set of sensors and these sensor data can be fetched from [Nature Remo cloud API](https://developer.nature.global/en/overview).
The cloud API is available for anyone who got Nature Remo smart controller by creating access token on the web page.
## Installation

Clone this repository and place it on MagicMirror module directory.
```
$ cd ~/MagicMirror/modules
$ git clone https://github.com/mtatsuma/MMM-NatureRemo.git
```

## Configuration example
```
- module: MMM-NatureRemo
position: top_left
config:
apiToken: xxxx
deviceName: remo-1
```

## Configuration options

| Options | Required | Default | Description |
|:--------|:--------:|:--------|:------------|
| updateInterval | | `10 * 60 * 1000` | Weather data update interval (miliseconds). Note: the access rate for [Nature Remo cloud API](https://developer.nature.global/en/overview) is limited as 30 requests per 5 minutes. |
| retryDelay | | `5 * 1000` | Delay for retry to get weather data (miliseconds) |
| title | | `Nature Remo` | Title to display |
| apiBase | | `https://api.nature.global/` | Base URL of [Nature Remo cloud API](https://developer.nature.global/en/overview) |
| apiEndpoint | | `1/devices` | API endpoint to be used for fetching sensor data |
| apiToken | yes | | API access token to call [Nature Remo cloud API](https://developer.nature.global/en/overview). |
| deviceName | | | Device name of the target Nature Remo device. If you don't set both of `deviceName` and `deviceID`, a device on the top of the device list will be selected. |
| deviceID | | | Device ID of the target Nature Remo device. If you don't set both of `deviceName` and `deviceID`, a device on the top of the device list will be selected. |
| height | | `300px` | Height of the display box for this module. |
| width | | `500px` | Width of the display box for this module. |
| showTemperature | | `true` | Show temperature data. |
| showHumidity | | `true` | Show humidity data. |
| showIllumination | | `true` | Show illumination data (Dark, Dim, Medium, Light and sensor data value). |
| showMotion | | `true` | Show motion sensor data (motion detection time). |
| temperatureTitle | | `Temperature: ` | Title for temperature data |
| humidityTitle | | `Humidity: ` | Title for humidity data |
| illuminationTitle | | `Illumination: ` | Title for illumination data |
| motionTitle | | `Motion Detect: ` | Title for motion sensor data. |
| temperatureUnit | | `Celsius` | Unit of temperature. `Celsius` or `Fahrenheit` |
| motionDateOffsetSeconds | | `0` | Time offset (seconds) for motion detection time |
| motionDateHourFormat | | `24h` | If you set `12h`, the time format of motion detection time is changed to 12 hour format |