BangleApps/apps/widancs/ancs.js

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();
}
})();