Support naming, Farenheit, and alerts.

Add ability to rename tags.
Add Sauna, Fridge & Freezer alert. Support °F based on locale.

Add possibility to name tag "Porch".

How could I forget that? I need it myself!
pull/1447/head
Marc Englund 2022-02-13 22:21:19 +02:00
parent 405de6d6c0
commit 8350108d88
5 changed files with 274 additions and 27 deletions

View File

@ -1,3 +1,4 @@
0.01: Hello Ruuvi Watch!
0.02: Clear gfx on startup.
0.03: Improve design and code, reduce flicker.
0.04: Ability to rename tags. Sauna, Fridge & Freezer alert. Support °F based on locale.

View File

@ -2,6 +2,8 @@
Watch the status of [RuuviTags](https://ruuvi.com) in range.
By Marc Englund [GitHub](https://github.com/emarc) | [Twitter](https://twitter.com/marcenglund)
![Ruuvi Watch in action](/BangleApps/apps/ruuviwatch/ruuviwatch-in-action.jpg)
- Id
@ -9,18 +11,23 @@ Watch the status of [RuuviTags](https://ruuvi.com) in range.
- Humidity (%)
- Pressure (hPa)
- Battery voltage
Also shows how "fresh" the data is (age of reading).
- Reading "freshness" (age)
- Ability to name tags
- Alerts for Sauna, Fridge, Freezer
## Usage
- Scans for devices when launched and every N seconds.
- Page trough devices with BTN1/BTN3.
- Trigger scan with BTN2.
- Page trough devices with left/right swipe or BTN1/BTN3.
- Page past last/first to trigger scan.
- BTN2 = Menu; name tag & trigger scan
- Change locale (via App Loader) to get Farenheit.
## Todo / ideas
- Settings for scan frequency, units
- Allow to "name" known devices
- Include more data
- Bangle 2 support (I don't have one, let me know if you want to help with testing!)
- Settings for scan frequency
- Settings for alert limits
- Alert for "Wine cellar"
- Alert for Washer & Dryer (stops shaking = ready)
- Support older Ruuvi protocols

View File

@ -2,7 +2,7 @@
"name": "Ruuvi Watch",
"shortName":"Ruuvi Watch",
"icon": "ruuviwatch.png",
"version":"0.03",
"version":"0.04",
"description": "Keep an eye on RuuviTag devices (https://ruuvi.com). For RuuviTags using the v5 format.",
"readme":"README.md",
"tags": "bluetooth",

View File

@ -7,11 +7,21 @@ require("Storage").write("ruuviwatch.info", {
const lookup = {};
const ruuvis = [];
const names = require("Storage").readJSON("RuuviNames") || {};
let current = 0;
let scanning = false;
let paused = false;
const SCAN_FREQ = 1000 * 30;
// ALERT LIMITS
LIMIT_SAUNA = 60;
LIMIT_FRIDGE = 4;
LIMIT_FREEZER = -18;
// TODO add wine cellar limits
// TODO configurable limits
// Fonts
const FONT_L = "Vector:60";
const FONT_M = "Vector:20";
@ -80,8 +90,8 @@ function p(data) {
int2Hex(data[OFFSET + 23]),
].join(":");
robject.name =
"Ruuvi " + int2Hex(data[OFFSET + 22]) + int2Hex(data[OFFSET + 23]);
robject.id = int2Hex(data[OFFSET + 22]) + int2Hex(data[OFFSET + 23]);
return robject;
}
@ -114,6 +124,7 @@ function drawAge() {
}
function redrawAge() {
if (paused) return;
const originalColor = g.getColor();
g.clearRect(0, SCANNING_Y - 10, g.getWidth(), SCANNING_Y + 10);
g.setFont(FONT_S);
@ -128,9 +139,15 @@ function redrawAge() {
g.setColor(originalColor);
}
function getName(id) {
let name = names[id] || "Ruuvi";
return name + " (" + id + ")";
}
function redraw() {
g.clear();
g.setColor("#ffffff");
g.setFontAlign(0, 0);
if (ruuvis.length > 0 && ruuvis[current]) {
const ruuvi = ruuvis[current];
@ -145,14 +162,22 @@ function redraw() {
// name
g.setFont(FONT_M);
g.drawString(ruuvi.name, CENTER, NAME_Y);
g.drawString(getName(ruuvi.id), CENTER, NAME_Y);
// age
redrawAge();
// temp
g.setFont(FONT_L);
g.drawString(ruuvi.temperature.toFixed(2) + "°c", CENTER, TEMP_Y);
if (
(ruuvi.name.startsWith("Sauna") && ruuvi.temperature > LIMIT_SAUNA) ||
(ruuvi.name.startsWith("Fridge") && ruuvi.temperature > LIMIT_FRIDGE) ||
(ruuvi.name.startsWith("Freezer") && ruuvi.temperature > LIMIT_FREEZER)
) {
g.setColor("#ffe800");
}
g.drawString(getTempString(ruuvi.temperature), CENTER, TEMP_Y);
g.setColor("#ffffff");
// humid & pressure
g.setFont(FONT_M);
@ -175,8 +200,28 @@ function redraw() {
}
}
function getTempString(temp) {
// workaround: built-in 'locale' looses precision :-(
let unit = "°C";
const isF = require("locale").temp(1).endsWith("F");
if (isF) {
unit = "°F";
temp = (temp + 40) * 1.8 - 40;
}
return temp.toFixed(2) + unit;
}
function attention(message) {
// message ignored for now
Bangle.beep();
Bangle.beep();
Bangle.beep();
Bangle.buzz();
}
function scan() {
if (scanning) return;
if (paused) return;
scanning = true;
NRF.findDevices(
function (devices) {
@ -184,11 +229,36 @@ function scan() {
devices.forEach((device) => {
const data = p(device.data);
data.time = new Date().getTime();
const idx = lookup[data.name];
data.name = names[data.id] || "Ruuvi";
const idx = lookup[data.id];
if (idx !== undefined) {
const old = ruuvis[idx];
if (
data.name.startsWith("Sauna") &&
old.temperature < LIMIT_SAUNA &&
data.temperature > LIMIT_SAUNA
) {
current = idx;
attention(data.name + " ready!");
} else if (
data.name.startsWith("Fridge") &&
old.temperature < LIMIT_FRIDGE &&
data.temperature > LIMIT_FRIDGE
) {
current = idx;
attention(data.name + " warning!");
} else if (
data.name.startsWith("Freezer") &&
old.temperature < LIMIT_FREEZER &&
data.temperature > LIMIT_FREEZER
) {
current = idx;
attention(data.name + " warning!");
}
ruuvis[idx] = data;
} else {
lookup[data.name] = ruuvis.push(data) - 1;
lookup[data.id] = ruuvis.push(data) - 1;
foundNew = true;
}
});
@ -202,23 +272,195 @@ function scan() {
);
}
function setName(newName) {
const ruuvi = ruuvis[current];
ruuvi.name = newName;
names[ruuvi.id] = ruuvi.name;
require("Storage").writeJSON("RuuviNames", names);
}
function closeMenu() {
E.showMenu();
paused = false;
redraw();
}
function showMenu() {
// TODO make this DRY + indicate current in menu
if (!ruuvis.length) {
scan();
return;
}
paused = true;
const ruuvi = ruuvis[current];
const id = ruuvi.id;
const name = getName(id);
var mainmenu = {
"": { title: name },
"Scan now": function () {
closeMenu();
scan();
},
"Rename tag": function () {
E.showMenu(namemenu);
},
"< Back": function () {
closeMenu();
}, // remove the menu
};
// Submenu
var namemenu = {
"": { title: "Rename " + name },
Ruuvi: function () {
setName("Ruuvi");
closeMenu();
},
Indoors: function () {
setName("Indoors");
closeMenu();
},
Downstairs: function () {
setName("Downstairs");
closeMenu();
},
Upstairs: function () {
setName("Upstairs");
closeMenu();
},
Attic: function () {
setName("Attic");
closeMenu();
},
Basement: function () {
setName("Basement");
closeMenu();
},
Kitchen: function () {
setName("Kitchen");
closeMenu();
},
Pantry: function () {
setName("Pantry");
closeMenu();
},
"Living room": function () {
setName("Living room");
closeMenu();
},
"Dining room": function () {
setName("Dining room");
closeMenu();
},
Office: function () {
setName("Office");
closeMenu();
},
Bedroom: function () {
setName("Bedroom");
closeMenu();
},
Bathroom: function () {
setName("Bathroom");
closeMenu();
},
Sauna: function () {
setName("Sauna");
closeMenu();
},
"Wine cellar": function () {
setName("Wine cellar");
closeMenu();
},
Outdoors: function () {
setName("Outdoors");
closeMenu();
},
Porch: function () {
setName("Porch");
closeMenu();
},
Backyard: function () {
setName("Backyard");
closeMenu();
},
Garage: function () {
setName("Garage");
closeMenu();
},
Greenhouse: function () {
setName("Greenhouse");
closeMenu();
},
Shed: function () {
setName("Shed");
closeMenu();
},
Fridge: function () {
setName("Fridge");
closeMenu();
},
Freezer: function () {
setName("Freezer");
closeMenu();
},
Dryer: function () {
setName("Dryer");
closeMenu();
},
Washer: function () {
setName("Washer");
closeMenu();
},
"< Back": function () {
E.showMenu(mainmenu);
},
};
// Actually display the menu
E.showMenu(mainmenu);
}
function nextPage() {
current++;
if (current >= ruuvis.length) {
current = 0;
scan();
}
redraw();
}
function prevPage() {
current--;
if (current < 0) {
current = ruuvis.length - 1;
scan();
}
redraw();
}
// START
Bangle.on("swipe", function (dir) {
if (paused) return;
if (dir > 0) {
prevPage();
} else {
nextPage();
}
});
// Button 1 pages up
setWatch(
() => {
current--;
if (current < 0) {
current = ruuvis.length - 1;
}
redraw();
if (paused) return;
prevPage();
},
BTN1,
{ repeat: true }
);
// button triggers scan
// button triggers menu
setWatch(
() => {
scan();
if (paused) return;
showMenu();
},
BTN2,
{ repeat: true }
@ -226,11 +468,8 @@ setWatch(
// button 3 pages down
setWatch(
() => {
current++;
if (current >= ruuvis.length) {
current = 0;
}
redraw();
if (paused) return;
nextPage();
},
BTN3,
{ repeat: true }

2
core

@ -1 +1 @@
Subproject commit 187af1527e0b830c804049aae834ed658ffeed08
Subproject commit 3093d78a5d752cbf03ea8f9a1a7c0b50b9c8123b