mirror of https://github.com/espruino/BangleApps
264 lines
7.9 KiB
JavaScript
264 lines
7.9 KiB
JavaScript
(() => {
|
|
|
|
var s = require("Storage").readJSON("widancs.json",1)||{settings:{enabled:false, category:[1,2,4]}};
|
|
var ENABLED = s.settings.enabled;
|
|
var CATEGORY = s.settings.category;
|
|
|
|
function advert(){
|
|
NRF.setAdvertising([
|
|
0x02, //length
|
|
0x01, //flags
|
|
0x06, //
|
|
0x11, //length
|
|
0x15, //solicited Service UUID
|
|
0xD0,0x00,0x2D,0x12,0x1E,0x4B,
|
|
0x0F,0xA4,
|
|
0x99,0x4E,
|
|
0xCE,0xB5,
|
|
0x31,0xF4,0x05,0x79],{connectable:true,discoverable:true,interval:375});
|
|
}
|
|
|
|
var state = {
|
|
gatt:null,
|
|
ancs:null,
|
|
current:{cat:0,uid:0},
|
|
notqueue:[],
|
|
msgTO:undefined,
|
|
com:new Uint8Array([0,0,0,0,0,1,20,0,3,100,0]),
|
|
buf:new Uint8Array(132),
|
|
inp:0,
|
|
store:function(b){
|
|
var i = this.inp;
|
|
if (i+b.length<=132){
|
|
this.buf.set(b,i);
|
|
this.inp+=b.length;
|
|
}
|
|
},
|
|
gotmsg:function(){
|
|
var n = this.inp;
|
|
var vw = DataView(this.buf.buffer);
|
|
if (n<8) return null;
|
|
var tn=vw.getUint16(6,true);
|
|
if (n<(tn+8)) return null;
|
|
var mn=vw.getUint16(9+tn,true);
|
|
if (n<(mn+tn+11)) return null;
|
|
return {tlen:tn, mlen:mn};
|
|
}
|
|
};
|
|
|
|
//stop advertising when peripheral link disconnected
|
|
if (!NRF.getGattforCentralServer && ENABLED && typeof SCREENACCESS!='undefined')
|
|
NRF.on('disconnect',function(reason){
|
|
NRF.sleep();
|
|
});
|
|
|
|
if (ENABLED && typeof SCREENACCESS!='undefined')
|
|
NRF.on('connect',function(addr){
|
|
if(NRF.getGattforCentralServer)
|
|
do_bond(NRF.getGattforCentralServer(addr));
|
|
else
|
|
NRF.connect(addr).then(do_bond);
|
|
});
|
|
|
|
function do_bond(g) {
|
|
var tval, ival;
|
|
state.gatt = g;
|
|
function cleanup(){
|
|
drawIcon(0); //disconnect from iPhone
|
|
delete state.gatt;
|
|
delete state.ancs;
|
|
if(!NRF.getGattforCentralServer) NRF.disconnect();
|
|
setTimeout(()=>{NRF.wake();},500);
|
|
}
|
|
drawIcon(1); //connect from iPhone
|
|
state.gatt.device.on('gattserverdisconnected', function(reason) {
|
|
if (ival) clearInterval(ival);
|
|
if (tval) clearInterval(tval);
|
|
cleanup();
|
|
});
|
|
E.on("kill",function(){
|
|
state.gatt.disconnect().then(function(){NRF.sleep();});
|
|
});
|
|
NRF.setSecurity({passkey:"123456",mitm:1,display:1});
|
|
tval = setTimeout(function(){
|
|
if (ival) clearInterval(ival);
|
|
state.gatt.disconnect().then(cleanup);
|
|
},10000);
|
|
state.gatt.startBonding().then(function(){
|
|
ival = setInterval(function(){
|
|
var sec = state.gatt.getSecurityStatus();
|
|
if (!sec.connected) {clearInterval(ival); clearTimeout(tval); return;}
|
|
if (sec.connected && sec.encrypted){
|
|
clearInterval(ival);
|
|
clearTimeout(tval);
|
|
drawIcon(2); //bonded to iPhone
|
|
do_ancs();
|
|
return;
|
|
}
|
|
},1000);
|
|
}).catch(function(e){
|
|
Terminal.println("ERROR "+e);
|
|
});
|
|
}
|
|
|
|
function do_ancs() {
|
|
state.ancs = {primary:null, notify:null, control:null, data:null};
|
|
state.gatt.getPrimaryService("7905F431-B5CE-4E99-A40F-4B1E122D00D0").then(function(s) {
|
|
state.ancs.primary=s;
|
|
return s.getCharacteristic("9FBF120D-6301-42D9-8C58-25E699A21DBD");
|
|
}).then(function(c) {
|
|
state.ancs.notify=c;
|
|
return state.ancs.primary.getCharacteristic("69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9");
|
|
}).then(function(c) {
|
|
state.ancs.control=c;
|
|
return state.ancs.primary.getCharacteristic("22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB");
|
|
}).then(function(c) {
|
|
state.ancs.data =c;
|
|
drawIcon(3);//got remote services
|
|
state.ancs.notify.on('characteristicvaluechanged', function(ev) {
|
|
getnotify(ev.target.value);
|
|
});
|
|
state.ancs.data.on('characteristicvaluechanged', function(e) {
|
|
state.store(e.target.value.buffer);
|
|
var inds = state.gotmsg();
|
|
if (inds) printmsg(state.buf,inds);
|
|
});
|
|
state.ancs.notify.startNotifications().then(function(){
|
|
state.ancs.data.startNotifications().then(function(){
|
|
drawIcon(4); //ready for messages
|
|
});
|
|
});
|
|
}).catch(function(e){
|
|
Terminal.println("ERROR "+e);
|
|
});
|
|
}
|
|
|
|
function wordwrap(s){
|
|
var txt = s.split("\n");
|
|
var MAXCHARS = 18;
|
|
for (var i = 0; i < txt.length; i++) {
|
|
txt[i] = txt[i].trim();
|
|
var l = txt[i];
|
|
if (l.length > MAXCHARS) {
|
|
var p = MAXCHARS;
|
|
while (p > MAXCHARS - 8 && !" \t-_".includes(l[p]))
|
|
p--;
|
|
if (p == MAXCHARS - 8) p = MAXCHARS;
|
|
txt[i] = l.substr(0, p);
|
|
txt.splice(i + 1, 0, l.substr(p));
|
|
}
|
|
}
|
|
return txt.join("\n");
|
|
}
|
|
|
|
|
|
var buzzing =false;
|
|
var screentimeout = undefined;
|
|
var inalert = false;
|
|
|
|
function release_screen(){
|
|
screentimeout= setTimeout(() => {
|
|
SCREENACCESS.release();
|
|
screentimeout = undefined;
|
|
inalert=false;
|
|
next_notify();
|
|
}, 500);
|
|
}
|
|
|
|
function printmsg(buf,inds){
|
|
|
|
function send_action(tf){
|
|
var bb = new Uint8Array(6);
|
|
var v = DataView(bb.buffer);
|
|
v.setUint8(0,2);
|
|
v.setUint32(1,state.current.uid,true);
|
|
v.setUint8(5,tf?0:1 );
|
|
state.ancs.control.writeValue(bb).then(release_screen);
|
|
}
|
|
|
|
if (state.msgTO) clearTimeout(state.msgTO);
|
|
var title="";
|
|
for (var i=8;i<8+inds.tlen; ++i) title+=String.fromCharCode(buf[i]);
|
|
var message = "";
|
|
for (var j=11+inds.tlen;j<11+inds.tlen+inds.mlen;++j) {
|
|
message+=String.fromCharCode(buf[j]);
|
|
}
|
|
message = wordwrap(message);
|
|
//we may already be displaying a prompt, so clear it
|
|
E.showPrompt();
|
|
if (screentimeout) clearTimeout(screentimeout);
|
|
Bangle.setLCDPower(true);
|
|
SCREENACCESS.request();
|
|
if (!buzzing){
|
|
buzzing=true;
|
|
Bangle.buzz(500).then(()=>{buzzing=false;});
|
|
}
|
|
if (state.current.cat!=1){
|
|
E.showAlert(message,title).then(send_action.bind(null,false));
|
|
} else {
|
|
E.showPrompt(message,{title:title,buttons:{"Accept":true,"Cancel":false}}).then(send_action);
|
|
}
|
|
}
|
|
|
|
var notifyTO;
|
|
function getnotify(d){
|
|
var eid = d.getUint8(0);
|
|
var ct = d.getUint8(2);
|
|
var id = d.getUint32(4,true);
|
|
if (eid>1) return;
|
|
if (notifyTO) clearTimeout(notifyTO);
|
|
if(!CATEGORY.includes(ct)) return;
|
|
var len = state.notqueue.length;
|
|
if (ct == 1) { // it's a call so pre-empt
|
|
if (inalert) {state.notqueue.push(state.current); inalert=false;}
|
|
state.notqueue.push({cat:ct, uid:id});
|
|
} else if (len<16)
|
|
state.notqueue[len] = {cat:ct, uid:id};
|
|
notifyTO = setTimeout(next_notify,1000);
|
|
}
|
|
|
|
function next_notify(){
|
|
if(state.notqueue.length==0 || inalert) return;
|
|
inalert=true;
|
|
state.current = state.notqueue.pop();
|
|
var v = DataView(state.com.buffer);
|
|
if (state.current.cat==6) v.setUint8(8,2); else v.setUint8(8,3);//get email title
|
|
v.setUint32(1,state.current.uid,true);
|
|
state.inp=0;
|
|
state.ancs.control.writeValue(state.com).then(function(){
|
|
state.msgTO=setTimeout(()=>{
|
|
inalert=false;
|
|
state.msgTO=undefined;
|
|
next_notify();
|
|
},1000);
|
|
});
|
|
}
|
|
|
|
var stage = 5;
|
|
//grey, pink, lightblue, yellow, green
|
|
function draw(){
|
|
var colors = new Uint16Array([0xc618,0xf818,0x3ff,0xffe0,0x07e0,0x0000]);
|
|
var img = E.toArrayBuffer(atob("GBgBAAAABAAADgAAHwAAPwAAf4AAP4AAP4AAP4AAHwAAH4AAD8AAB+AAA/AAAfgAAf3gAH/4AD/8AB/+AA/8AAf4AAHwAAAgAAAA"));
|
|
g.setColor(colors[stage]);
|
|
g.drawImage(img,this.x,this.y);
|
|
}
|
|
|
|
WIDGETS["ancs"] ={area:"tl", width:24,draw:draw};
|
|
|
|
function drawIcon(id){
|
|
stage = id;
|
|
WIDGETS["ancs"].draw();
|
|
}
|
|
|
|
if (ENABLED && typeof SCREENACCESS!='undefined') {
|
|
stage = 0;
|
|
NRF.setServices(undefined,{uart:false});
|
|
NRF.sleep();
|
|
NRF.wake();
|
|
advert();
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|