Skip to content

Commit 93b3450

Browse files
feat(MMM-SenseHat): add multi-instance support and screenshots
1 parent f972e34 commit 93b3450

File tree

7 files changed

+538
-43
lines changed

7 files changed

+538
-43
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/* global Module */
2+
3+
Module.register("MMM-SenseHat", {
4+
defaults: {
5+
updateInterval: 5000,
6+
showTemperature: true,
7+
showHumidity: true,
8+
showPressure: true,
9+
showOrientation: false,
10+
temperatureUnit: "C", // "C" or "F"
11+
roundValues: 1,
12+
ledMatrixEnabled: true,
13+
ledMode: "status", // "off" | "status" | "text"
14+
ledText: "Hello from Sense HAT",
15+
ledColor: [0, 255, 0],
16+
criticalThresholds: {
17+
temperatureHigh: 30,
18+
temperatureLow: 10,
19+
humidityHigh: 80,
20+
humidityLow: 20
21+
},
22+
debug: false,
23+
// Optional: override Python executable path on some systems (e.g., "/usr/bin/python3")
24+
pythonPath: null
25+
},
26+
27+
start() {
28+
this.sensorData = null;
29+
this.ready = false;
30+
// Send configuration to node_helper and include our instance identifier for multi-instance safety
31+
const payload = Object.assign({}, this.config, { identifier: this.identifier });
32+
this.sendSocketNotification("SENSEHAT_CONFIG", payload);
33+
},
34+
35+
getDom() {
36+
const wrapper = document.createElement("div");
37+
wrapper.className = "mmm-sensehat";
38+
39+
// Loading state
40+
if (this.sensorData === null) {
41+
wrapper.innerHTML = "Loading Sense HAT data...";
42+
return wrapper;
43+
}
44+
45+
const data = this.sensorData;
46+
47+
// Error state from backend
48+
if (data && typeof data.error === "string" && data.error.trim() !== "") {
49+
wrapper.innerHTML = `Sense HAT error: ${data.error}`;
50+
return wrapper;
51+
}
52+
53+
const hasAnyValue =
54+
typeof data.temperature === "number" ||
55+
typeof data.humidity === "number" ||
56+
typeof data.pressure === "number" ||
57+
(data.orientation &&
58+
(typeof data.orientation.pitch === "number" ||
59+
typeof data.orientation.roll === "number" ||
60+
typeof data.orientation.yaw === "number"));
61+
62+
if (!hasAnyValue) {
63+
wrapper.innerHTML = "Sense HAT: no sensor data (check hardware or drivers)";
64+
return wrapper;
65+
}
66+
67+
// Temperature
68+
if (this.config.showTemperature && typeof data.temperature === "number") {
69+
const t = this.config.temperatureUnit === "F" ? (data.temperature * 9) / 5 + 32 : data.temperature;
70+
const tEl = document.createElement("div");
71+
tEl.className = "small bright";
72+
tEl.innerHTML = `${this.round(t)} °${this.config.temperatureUnit}`;
73+
wrapper.appendChild(tEl);
74+
}
75+
76+
// Humidity
77+
if (this.config.showHumidity && typeof data.humidity === "number") {
78+
const hEl = document.createElement("div");
79+
hEl.className = "xsmall light";
80+
hEl.innerHTML = `${this.round(data.humidity)} %`;
81+
wrapper.appendChild(hEl);
82+
}
83+
84+
// Pressure
85+
if (this.config.showPressure && typeof data.pressure === "number") {
86+
const pEl = document.createElement("div");
87+
pEl.className = "xsmall light";
88+
pEl.innerHTML = `${this.round(data.pressure)} hPa`;
89+
wrapper.appendChild(pEl);
90+
}
91+
92+
// Orientation
93+
if (this.config.showOrientation && data.orientation) {
94+
const o = data.orientation;
95+
const oEl = document.createElement("div");
96+
oEl.className = "xsmall dimmed";
97+
const pitch = typeof o.pitch === "number" ? this.round(o.pitch) : "-";
98+
const roll = typeof o.roll === "number" ? this.round(o.roll) : "-";
99+
const yaw = typeof o.yaw === "number" ? this.round(o.yaw) : "-";
100+
oEl.innerHTML = `P:${pitch}° R:${roll}° Y:${yaw}°`;
101+
wrapper.appendChild(oEl);
102+
}
103+
104+
return wrapper;
105+
},
106+
107+
round(value) {
108+
const places = Math.max(0, parseInt(this.config.roundValues || 0, 10));
109+
const p = Math.pow(10, places);
110+
return Math.round(value * p) / p;
111+
},
112+
113+
socketNotificationReceived(notification, payload) {
114+
if (notification === "SENSEHAT_DATA" && payload && payload.identifier === this.identifier) {
115+
this.sensorData = payload;
116+
this.updateDom();
117+
118+
// Optional LED status logic
119+
if (this.config.ledMatrixEnabled && this.config.ledMode !== "text") {
120+
// Only act when we have real numeric data and no error
121+
const hasNumeric = typeof payload.temperature === "number" || typeof payload.humidity === "number";
122+
if (!hasNumeric || (payload && typeof payload.error === "string" && payload.error.trim() !== "")) {
123+
return;
124+
}
125+
const { temperatureHigh, temperatureLow, humidityHigh, humidityLow } = this.config.criticalThresholds || {};
126+
let color = this.config.ledColor || [0, 255, 0];
127+
128+
if (
129+
(typeof payload.temperature === "number" &&
130+
(((temperatureHigh !== null && temperatureHigh !== undefined) && payload.temperature > temperatureHigh) ||
131+
((temperatureLow !== null && temperatureLow !== undefined) && payload.temperature < temperatureLow))) ||
132+
(typeof payload.humidity === "number" &&
133+
(((humidityHigh !== null && humidityHigh !== undefined) && payload.humidity > humidityHigh) ||
134+
((humidityLow !== null && humidityLow !== undefined) && payload.humidity < humidityLow)))
135+
) {
136+
color = [255, 0, 0]; // red
137+
} else {
138+
color = [0, 200, 0]; // green
139+
}
140+
141+
this.sendSocketNotification("SENSEHAT_LED_COMMAND", {
142+
identifier: this.identifier,
143+
mode: this.config.ledMode || "status",
144+
color
145+
});
146+
}
147+
}
148+
}
149+
});

modules/MMM-SenseHat/README.md

Lines changed: 92 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,51 @@
1-
MMM-SenseHat
2-
============
1+
# MMM-SenseHat
32

4-
A 3rd‑party MagicMirror² module that integrates the Raspberry Pi Sense HAT. It reads sensor data (temperature, humidity, pressure, optional orientation) and can control the Sense HAT 8×8 RGB LED matrix for simple status indication or scrolling text.
3+
## Overview
4+
MMM-SenseHat is a third-party module for MagicMirror² that integrates the official Raspberry Pi Sense HAT.
5+
It displays sensor readings (temperature, humidity, pressure, and optional orientation) and can control the
6+
Sense HAT’s 8×8 RGB LED matrix for a status color or scrolling text.
57

6-
## Screenshot
7-
![MMM-SenseHat screenshot](images/how_its_working.png)
8+
- Sense HAT product page: https://www.raspberrypi.com/products/sense-hat/
9+
- Sense HAT documentation: https://www.raspberrypi.com/documentation/accessories/sense-hat.html
810

9-
Features
10-
- Display temperature, humidity, pressure, and optionally orientation (pitch/roll/yaw)
11-
- Periodic polling interval (configurable)
12-
- Optional LED matrix control: off, status color, or scrolling text
13-
- Basic threshold‑based LED status (green when normal, red when out‑of‑range)
11+
This is **not** a core/default MagicMirror module. Install it under: `modules/MMM-SenseHat`.
1412

15-
Requirements
13+
## Features
14+
- Show temperature, humidity, and pressure
15+
- Optional orientation readout (pitch / roll / yaw)
16+
- Configurable polling interval
17+
- LED matrix control: off, status color, or scrolling text
18+
- Basic threshold-based LED status (green when normal, red when out of range)
19+
- Runs as a standard MagicMirror third-party module (folder: `modules/MMM-SenseHat`)
20+
21+
## Requirements
1622
- Raspberry Pi with a Sense HAT attached
17-
- Python 3 with the official sense HAT library
23+
- MagicMirror² installed and running
24+
- Python 3 with the official Sense HAT library
1825

19-
Install dependencies on the Raspberry Pi
26+
Install dependencies on the Raspberry Pi:
2027
```bash
2128
sudo apt update
2229
sudo apt install -y sense-hat python3-sense-hat
2330
```
2431

25-
Installation (as a 3rd‑party module)
26-
1) Clone this module into your MagicMirror installation under `modules/MMM-SenseHat`:
32+
## Installation
33+
Clone this repository into your MagicMirror installation under `modules/MMM-SenseHat`.
2734
```bash
2835
cd ~/MagicMirror/modules
2936
git clone https://github.com/YOUR_GITHUB_ACCOUNT/MMM-SenseHat.git MMM-SenseHat
3037
```
3138

32-
2) (Optional) Ensure the Python helper is executable:
39+
Optional: ensure the Python helper is executable:
3340
```bash
3441
chmod +x ~/MagicMirror/modules/MMM-SenseHat/python/reader.py
3542
```
3643

37-
Configuration
38-
Add the module to your `config/config.js`:
44+
## Configuration
45+
Add the module to your `config/config.js` following standard MagicMirror style.
46+
Paste the following object into the `modules: []` array:
3947
```js
40-
{
48+
const moduleEntry = {
4149
module: "MMM-SenseHat",
4250
position: "top_right",
4351
config: {
@@ -46,7 +54,7 @@ Add the module to your `config/config.js`:
4654
showHumidity: true,
4755
showPressure: true,
4856
showOrientation: false,
49-
temperatureUnit: "C",
57+
temperatureUnit: "C", // "C" or "F"
5058
roundValues: 1,
5159
ledMatrixEnabled: true,
5260
ledMode: "status", // "off" | "status" | "text"
@@ -59,33 +67,48 @@ Add the module to your `config/config.js`:
5967
humidityLow: 20
6068
},
6169
debug: false,
62-
// Optional: override Python executable path if needed (e.g., "/usr/bin/python3")
63-
// pythonPath: "/usr/bin/python3"
70+
// Optional: override Python executable path (e.g., "/usr/bin/python3").
71+
// For familiarity with MMM-PythonPrint, you can also use `pythonName` (alias of pythonPath).
72+
// pythonPath: "/usr/bin/python3",
73+
// pythonName: "/usr/bin/python3"
6474
}
65-
}
75+
};
76+
// Then insert `moduleEntry` into the modules array in your config.js
6677
```
6778

68-
How it works
69-
- Frontend (MMM-SenseHat.js): Displays data and optionally sends LED status commands based on thresholds.
70-
- Node Helper (node_helper.js): Spawns the Python helper to read sensors at a set interval and forwards LED commands to it. Respects `config.pythonPath` if provided.
71-
- Python Helper (python/reader.py): Uses `from sense_hat import SenseHat` to read sensors and control the LED matrix. Prints JSON to stdout in read mode.
72-
73-
Troubleshooting
79+
## How it works (architecture)
80+
- Frontend (MMM-SenseHat.js)
81+
- Renders the sensor values in the MagicMirror UI
82+
- Shows clear states: loading, error, or data
83+
- May send LED commands (status/text) back to the backend based on thresholds
84+
- Node helper (node_helper.js)
85+
- Manages one polling loop per module instance (multi-instance safe)
86+
- Spawns the Python helper on an interval to read sensors
87+
- Forwards sensor JSON to the correct frontend instance via an `identifier`
88+
- Accepts LED commands per instance and uses that instance’s config
89+
- Respects `config.pythonPath` (or `pythonName`) to select the Python executable
90+
- Python helper (python/reader.py)
91+
- Uses `from sense_hat import SenseHat`
92+
- Reads sensors and prints JSON to stdout in `--read` mode
93+
- Controls the LED matrix (status color, text, clear)
94+
95+
## Troubleshooting
7496
1) Check that the Sense HAT is detected by the kernel
7597
```bash
7698
ls -l /dev/i2c*
7799
dmesg | grep -i "sense"
78100
```
79-
Expect:
101+
Expected:
80102
- `/dev/i2c-1` present
81-
- A line similar to: `fb1: RPi-Sense FB frame buffer device`
82-
- A joystick device entry for the Sense HAT
103+
- A line like `fb1: RPi-Sense FB frame buffer device`
104+
- A Sense HAT joystick device entry
83105

84106
2) Probe I²C bus 1
85107
```bash
86108
sudo i2cdetect -y 1
87109
```
88-
On a working Sense HAT, you should see several non-"--" addresses (for example `1c`, `39`, `5c`, `5f`, `6a`). If everything shows `--`, the HAT might not be seated correctly or could be faulty.
110+
On a working Sense HAT, you should see several non-"--" addresses (e.g., `1c`, `39`, `5c`, `5f`, `6a`).
111+
If everything shows `--`, the HAT may not be seated correctly or could be faulty.
89112

90113
3) Test using the official Python library
91114
```bash
@@ -100,7 +123,7 @@ print("Pressure :", sh.get_pressure())
100123
PY
101124
```
102125
- If you get numeric values, the sensors are working.
103-
- If you see errors like `OSError: Humidity Init Failed`, there may be a contact problem on the header or a sensor issue.
126+
- If you see `OSError: Humidity Init Failed`, check the 40-pin header seating and try again.
104127

105128
4) Check the LED matrix
106129
```bash
@@ -115,15 +138,41 @@ sleep(1)
115138
sh.clear()
116139
PY
117140
```
118-
If you don’t see LEDs:
119-
- Power off the Raspberry Pi.
120-
- Firmly press the Sense HAT onto the 40pin header (common on new boards).
121-
- Boot again and rerun the test.
141+
If no LEDs appear:
142+
- Power off the Raspberry Pi
143+
- Firmly press the Sense HAT onto the 40-pin header (common on new boards)
144+
- Boot again and re-run the test
122145

123146
5) What the MagicMirror module will show
124-
- "Loading Sense HAT data…" → Python helper hasn’t delivered any data yet.
125-
- "Sense HAT: no sensor data (check hardware or drivers)" → helper runs, but all sensor fields are null.
126-
- "Sense HAT error: …" → helper reported an explicit error (library missing, init failure, etc.).
147+
- "Loading Sense HAT data…" → Python helper hasn’t delivered any data yet
148+
- "Sense HAT: no sensor data (check hardware or drivers)" → helper runs, but all sensor fields are null
149+
- "Sense HAT error: …" → helper reported an explicit error (e.g., library missing, init failure). Errors are per instance.
150+
151+
## Multiple instances
152+
You can place MMM-SenseHat in `modules:[]` multiple times, each with different options (e.g., different LED policies or intervals). The module tags all traffic with an `identifier` and the helper maintains one polling loop per instance, so configurations are isolated.
153+
154+
Example with two instances (as part of your modules array):
155+
```js
156+
[
157+
{
158+
module: "MMM-SenseHat",
159+
position: "top_right",
160+
config: { updateInterval: 4000, ledMatrixEnabled: true, ledMode: "status" }
161+
},
162+
{
163+
module: "MMM-SenseHat",
164+
position: "bottom_right",
165+
config: { updateInterval: 10000, ledMatrixEnabled: true, ledMode: "text", ledText: "Hello", ledColor: [0, 200, 255] }
166+
}
167+
]
168+
```
169+
170+
## Screenshots
171+
- images/mmm-sensehat-dashboard.png — UI screenshot of the module
172+
- images/mmm-sensehat-hardware_1.jpg — Raspberry Pi + Sense HAT setup
173+
- images/mmm-sensehat-hardware_2.jpg — Additional hardware photo
174+
175+
Styling: the module wraps its DOM in a `div.mmm-sensehat`, so you can target `.MMM-SenseHat .mmm-sensehat` from your `custom.css` if desired.
127176

128-
License
129-
MIT (follow the MagicMirror² project license)
177+
## License
178+
MIT — typical for MagicMirror third-party modules.
418 KB
Loading
16.1 MB
Loading
15.6 MB
Loading

0 commit comments

Comments
 (0)