1
0
Fork 0

add new bluetoothdock app

master
Gordon Williams 2020-11-17 12:31:56 +00:00
parent 24c84f9ba6
commit 5fc5b83102
8 changed files with 249 additions and 0 deletions

View File

@ -460,6 +460,20 @@
{"name":"chargeanim.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "bluetoothdock",
"name": "Bluetooth Dock",
"shortName":"Dock",
"icon": "app.png",
"version":"0.01",
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
"tags": "bluetooth",
"readme": "README.md",
"storage": [
{"name":"bluetoothdock.app.js","url":"app.js"},
{"name":"bluetoothdock.boot.js","url":"boot.js"},
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "widbat",
"name": "Battery Level Widget",
"icon": "widget.png",

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -0,0 +1,35 @@
# Charging Dock
When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen.
Rotates by 90 degrees if it detects it is sideways, allowing for use
in a Charging Dock.
When devices are out of range (eg low water level in a plant) they are
highlighted red.
Currently supported devices:
* Mi Flora/other Xiaomi
* Bluetooth 0x1809 (eg. [Espruino Apps](https://espruino.github.io/EspruinoApps/#bletemp))
* Espruino Manufacturer Data (0x0590)
In the future it'd be nice to support more types of device in the future!
## Espruino Devices
To use your own Espruino device, use code like the following:
```
var data = {a:1,t:E.getTemperature()};
NRF.setAdvertising({},{
showName:false,
manufacturer:0x0590,
manufacturerData:JSON.stringify(data)
});
```
Currently:
* `t` is the temperature (if defined)
* `t` is the alert status (1 or 0)

View File

@ -0,0 +1,15 @@
// Create an entry in apps.json as follows:
{ "id": "bluetoothdock",
"name": "Bluetooth Dock",
"shortName":"Dock",
"icon": "app.png",
"version":"0.01",
"description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen",
"tags": "bluetooth",
"readme": "README.md",
"storage": [
{"name":"bluetoothdock.app.js","url":"app.js"},
{"name":"bluetoothdock.boot.js","url":"boot.js"},
{"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwggNK93gEikO93uC6gWBF6ECkQuVkUikAuVAAIuVAAIuGGZgXDlwuDhWkpWqAARHLkQpChWql3kC4YYHmQXDSQWq0Xu8QXE0AWEgYWESQIuC90qlQwJFwoABFwnyGBBdEC4guC1X/GBAXIVYJdC/4wFUw4XFFYX/GApIDC5BJBC4YwEC6QwEC5pHD+YwE0IXMGAX//U/GAgXNU4X60YwEU5YABnQXC0RhEFxkv+YXCl5iBF4gXKLQM6IgIuBGoIXCIxOqlRaBRoIABFwYXBUheqGAIACFwYXKBoYwBFwwXGVoQuDGAguEC4MzCwUQC4UKBwmvFw2qgczmUikAWCC4OikUzAAQvH+YXCCwcAmQVDC4YwFBIIVEgA3BAALADR48zmAWEh4VBPAS/DAIQXKJwIlDd4f6AgQXIIoSPCFwWqC4IFDL4YAFmAXCFIYXB0RhBKQRvDAAa/Dl4oCC4Mv//ya4gWFC4eiLAQUBFwgXBAA8Bc4qnCFwehC5EAC5AuD0AXRFwYXTFweqwAXJPAQXDFwh2JC5AuE0QXKJAouFLxQwGFwhGLPJAuPMI4uQDBAKD"))

182
apps/bluetoothdock/app.js Normal file
View File

@ -0,0 +1,182 @@
var deviceInfo = {};
if (Bangle.getAccel().x < -0.7)
g.setRotation(3); // assume watch in charge cradle
// Tile sizes
var TILESIZE = 60;
// Tiles along width of screen
var TILEX = 4;
// Map devices to nice names...
var deviceNames = {
"eb:44:c1:71:2e:89 random" : "Office",
"c4:7c:8d:6a:ac:79 public" : "Peacelily"
};
var scanHandlers = [
{ filter : {serviceData:{"fe95":{}}}, // Xiaomi
handler : function(device) {
if (!device.serviceData["fe95"]) return;
var d = new DataView(device.serviceData["fe95"]);
var frame = d.getUint16(0,true);
var offset = 5;
if (frame&16) offset+=6; // mac address
if (frame&32) offset+=1; // capabilitities
if (frame&64) { // event
var l = d.getUint8(offset+2);
var code = d.getUint16(offset,true);
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id};
event = deviceInfo[device.id];
switch (code) {
case 0x1004: event.temperature = d.getInt16(offset+3,true)/10; break;
case 0x1006: event.humidity = d.getInt16(offset+3)/10; break;
case 0x100D:
event.temperature = d.getInt16(offset+3,true)/10;
event.humidity = d.getInt16(offset+5)/10; break;
case 0x1008: event.moisture = d.getUint8(offset+3); break;
case 0x1009: event.fertility = d.getUint16(offset+3,true)/10; break;
// case 0x1007: break; // 3 bytes? got 84,0,0 or 68,0,0
default: event.code = code;
event.raw = new Uint8Array(d.buffer, offset+3, l);
break;
}
}}}, {
filter : {serviceData:{"1809":{}}}, // Standard Bluetooth
handler : function(device) {
if (!device.serviceData["1809"]) return;
var d = new DataView(device.serviceData["1809"]);
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name};
event = deviceInfo[device.id];
event.temperature = d.getInt16(0,1)/100;
}}, {
filter : { manufacturerData:{0x0590:{}} }, // Espruino
handler : function(device) {
if (!device.manufacturerData) return;
var j;
try { j = JSON.parse(E.toString(device.manufacturerData)); }
catch (e) { return; } // not JSON
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id,name:device.name};
event = deviceInfo[device.id];
if (j.t) event.temperature = j.t;
if (j.a) event.alert = j.a;
}}
];
function getImgHum() {
return require("heatshrink").decompress(atob("jUoxH+AEtlsoYYDS4ZYDAYaVDLAYFDSQYHDSIZYDBIaPDLAYLDRoZYDBoaLDLAYPDRIZYDCIaHDLAYTDQoZYDCoaDDOQYXAA+JxIYX1utDSwYBAAIzYGiwZUTgpODQpzPGGgY3OdI4aRDIIaMDJIYCDIztDGRwaJP5oaWDAwaRDBAbOC5YcKB5I="));
}
function getImgTemp() {
return require("heatshrink").decompress(atob("iUqxH+AA2sAAQLHCBASMCAoSLCPOBAAQRfI/5Hn3YACy4ACCL4ADCL5H/I/AQHCRAQJCQwQLCQgQNCQYRQCB4A/ADaPjYqTpSCRYQGCZALFA"));
}
function drawAlert(tile,x,y) {
g.setFont("Vector",56).setFontAlign(0,0);
g.drawString("!",x+TILESIZE/2,y+10+TILESIZE/2);
}
function drawMoisture(tile,x,y) {
g.drawImage(getImgHum(),x+2,y+18);
g.setFont("Vector",28);
g.drawString(tile.device.moisture,x+26,y+12);
}
function drawTemperature(tile,x,y) {
g.drawImage(getImgTemp(),x+3,y+16);
g.setFont("Vector",30);
var t = Math.round(tile.device.temperature);
g.drawString(t,x+25,y+13);
}
function getTiles() {
var tiles = [];
Object.keys(deviceInfo).forEach(id=>{
var dev = deviceInfo[id];
if (dev.alert) {
tiles.push({
alert: true, device: dev,
draw: drawAlert
});
}
if (dev.moisture && dev.moisture<40) {
tiles.push({
alert: true, device: dev,
draw: drawMoisture
});
}
if (dev.temperature) {
tiles.push({
device: dev,
draw: drawTemperature
});
}
});
tiles.sort((a,b)=>(b.alert|0)-(a.alert|0))
return tiles;
}
g.clear();
require("Font7x11Numeric7Seg").add(Graphics);
function drawClock() {
var d = new Date();
var size = 3;
var x = (g.getWidth()/2) - size*6,
y = size;
g.reset();
g.setFont("7x11Numeric7Seg",size).setFontAlign(1,-1);
g.drawString(d.getHours(), x, y, true);
g.setFontAlign(-1,-1);
if (d.getSeconds()&1) g.drawString(":", x,y);
g.drawString(("0"+d.getMinutes()).substr(-2),x+size*4,y, true);
// draw seconds
g.setFont("7x11Numeric7Seg",size/2);
g.drawString(("0"+d.getSeconds()).substr(-2),x+size*18,y + size*7, true);
// date
var s = d.toString().split(" ").slice(0,4).join(" ");
g.reset().setFontAlign(0,-1);
g.drawString(s,g.getWidth()/2, y + size*12, true);
// keep screen on
g.flip();
}
function drawTiles() {
// draw tiles
var tiles = getTiles();
for (var i=0;i<6;i++) {
var x = (i%TILEX)*TILESIZE;
var y = TILESIZE + TILESIZE*((i/TILEX)|0);
g.reset();
var tile = tiles[i];
if (tile && tile.alert) {
g.setBgColor(0.5,0,0);
}
g.clearRect(x,y,x+TILESIZE-1,y+TILESIZE-1);
if (tile) {
g.reset().setFont("6x8");
var t = deviceNames[tile.device.id];
if (!t) t = tile.device.name || tile.device.id.substr(0,17);
g.drawString(t,x+2,y+2);
tile.draw(tile, x, y);
if (tile.alert) {
g.setColor(1,1,0);
g.drawRect(x,y,x+TILESIZE-1,y+TILESIZE-1);
}
}
}
g.flip(); // keep forcing display on
}
setInterval(drawClock, 1000);
setInterval(drawTiles, 10000);
drawClock();
drawTiles();
function parseDevice(dev) {
if (!dev.serviceData) dev.serviceData={};
scanHandlers.forEach(s=>s.handler(dev));
}
NRF.setScan(parseDevice, { filters: scanHandlers.map(s=>s.filter), timeout: 2000 });
if (Bangle.isCharging()) {
Bangle.on("charging", isCharging => {
if (!isCharging) load();
});
}

BIN
apps/bluetoothdock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1 @@
Bangle.on("charging", isCharging => { if (isCharging) load("bluetoothdock.app.js"); });