BangleApps/apps/espruinoctrl/custom.html

277 lines
8.7 KiB
HTML

<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-exp.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<style>
.badgeerror[data-badge]::after { background-color: red; }
</style>
</head>
<body>
<p>Enter the menu items you'd like to see appear in the app below. <code>MAC Address</code> is the MAC address
of the device to use. If it isn't specified then a menu will be presented showing available devices.</p>
<div style="overflow-x:scroll">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Command</th>
<th>MAC Address</th>
<th>RX</th>
<th></th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
</div>
<p><button id="upload" class="btn btn-primary">Upload to Bangle.js</button></p>
<script src="../../core/lib/customize.js"></script>
<script>
var options;
try {
options = JSON.parse(localStorage.getItem("espruinoctrl"));
} catch (e) {}
if (!Array.isArray(options))
setDefaults();
function setDefaults() {
options = [
{"title":"Blink LED",cmd:"digitalPulse(LED1,1,100)",mac:"",rx:false},
{"title":"Restart",cmd:"load()",mac:"",rx:false},
{"title":"process.env",cmd:"print(process.env)",mac:"",rx:true},
{"title":"Battery",cmd:"print(`Battery ${E.getBattery()}%`)",mac:"",rx:true},
];
}
function changed() {
localStorage.setItem("espruinoctrl",JSON.stringify(options));
}
function isMACValid(mac) {
return mac.trim().match(/^[A-Fa-f0-9][A-Fa-f0-9]:[A-Fa-f0-9][A-Fa-f0-9]:[A-Fa-f0-9][A-Fa-f0-9]:[A-Fa-f0-9][A-Fa-f0-9]:[A-Fa-f0-9][A-Fa-f0-9]:[A-Fa-f0-9][A-Fa-f0-9]$/)!=null;
}
function refresh() {
var tbody = document.getElementById("tbody");
tbody.innerHTML = "";
options.forEach((option,idx)=>{
var tr = document.createElement("tr");
tr.innerHTML = `
<td><input type="text" name="title"></td>
<td><input type="text" name="command"></td>
<td><span data-badge="!"><input type="text" name="mac"></span></td>
<td><label class="form-switch"><input type="checkbox"><i class="form-icon"></i></label></td>
<td><button class="btn btn-action"><i class="icon icon-delete"></i></button></td>
`;
var titleInput = tr.children[0].firstChild;
titleInput.value = option.title;
titleInput.addEventListener("change", function(e) {
option.title = titleInput.value;
changed();
});
var cmdInput = tr.children[1].firstChild;
cmdInput.value = option.cmd;
cmdInput.addEventListener("change", function(e) {
option.cmd = cmdInput.value;
changed();
});
var warningspan = tr.children[2].firstChild;
var macInput = warningspan.firstChild;
macInput.value = option.mac;
macInput.addEventListener("change", function(e) {
if (isMACValid(macInput.value)) {
warningspan.classList.remove("badge");
warningspan.classList.remove("badgeerror");
} else {
warningspan.classList.add("badge");
warningspan.classList.add("badgeerror");
}
option.mac = macInput.value.trim();
changed();
});
var cmdRX = tr.children[3].firstChild.firstChild;
cmdRX.checked = option.rx;
cmdRX.addEventListener("change", function(e) {
option.rx = cmdRX.checked;
changed();
});
tr.children[4].firstChild.addEventListener("click", function() {
options.splice(idx,1);
changed();
refresh();
});
tbody.appendChild(tr);
});
var tr = document.createElement("tr");
tr.innerHTML = `
<td></td>
<td></td>
<td><button class="btn">Reset to Defaults</button></td>
<td></td>
<td><button class="btn btn-action"><i class="icon icon-plus"></i></button></td>
`;
tr.children[2].firstChild.addEventListener("click", function() {
setDefaults();
changed();
refresh();
});
tr.children[4].firstChild.addEventListener("click", function() {
options.push({"title":"",cmd:"",mac:""});
changed();
refresh();
});
tbody.appendChild(tr);
}
refresh();
document.getElementById("upload").addEventListener("click", function() {
var app = `
var menu = {
"" : {title:"Espruino Ctrl"},
${options.filter(o=>o.title.trim()!="").map(o=>
`${JSON.stringify(o.title)} : ()=>cmd(${JSON.stringify(o.cmd.trim())},${JSON.stringify(isMACValid(o.mac)?o.mac:undefined)},${!!o.rx})`
).join(",\n ")},
"< Back" : () => load()
};
var knownDevices = [
"Espruino","MDBT42Q","Puck.js","Bangle.js","Pixl.js"
];
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showMenu(menu);
function cmd(cmd,mac,rx) {
cmd = "\\x03\\x10"+cmd+"\\n";
var send = rx ? sendCommandRX : sendCommand;
var m;
E.showMenu();
function onDone() {
E.showMenu(menu);
}
function err(msg) {
print("Error:",msg);
E.showAlert(msg.toString(),"Error").then(function() {
E.showMenu(m || menu);
});
}
if (mac) {
E.showMessage("Connecting\\n"+mac);
if (mac.length==17) mac+=" random";
NRF.setTxPower(process.env.HWVERSION == 2 ? 8 : 4);
NRF.connect(mac).then(dev=>send(dev,cmd,onDone)).catch(err);
} else {
E.showMessage("Scanning...");
NRF.findDevices(devices => {
m = { "" : {title:"Devices"} };
devices.filter(dev=>dev.name &&
knownDevices.some(n=>dev.name.startsWith(n))
).forEach(dev=>{
m[dev.name] = ()=>{
dev.gatt.connect().then(dev=>send(dev,cmd,onDone)).catch(err);
};
});
m["< Back"] = onDone;
E.showMenu(m);
},{active:true});
}
}
// Send a command to a connected device, get response
function sendCommandRX(device, text, callback) {
var service,rx,tx;
var timeout;
E.showMessage("Connected");
return new Promise((resolve,reject) => {
function done() {
Terminal.println("\\n============\\n Disconnected");
device.disconnect();
setTimeout(function() {
setWatch(function() {
if (callback) callback();
resolve();
}, (process.env.HWVERSION==2) ? BTN1 : BTN2);
g.reset().setFont("6x8",2).setFontAlign(0,0,1);
g.drawString("Back", g.getWidth()-10, g.getHeight()/2);
}, 200);
}
device.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e").then(function(s) {
service = s;
return service.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
}).then(function(c) {
tx = c;
return service.getCharacteristic("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
}).then(function(c) {
rx = c;
rx.on('characteristicvaluechanged', function(event) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(done, 500);
Terminal.print(E.toString(event.target.value.buffer));
});
return rx.startNotifications();
}).then(function() {
E.showMessage("Sending...\\n");
function sender(resolve, reject) {
if (text.length) {
tx.writeValue(text.substr(0,20)).then(()=>{
sender(resolve, reject);
}).catch(reject);
text = text.substr(20);
} else {
resolve();
}
}
return new Promise(sender);
}).then(function() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(done, 500);
}).catch(err => {
if (timeout) clearTimeout(timeout);
reject(err);
});
});
}
// Send a command to a connected device
function sendCommand(device, text, callback) {
E.showMessage("Connected");
return device.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e").then(function(s) {
return s.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
}).then(function(c) {
E.showMessage("Sending...");
function sender(resolve, reject) {
if (text.length) {
c.writeValue(text.substr(0,20)).then(()=>{
sender(resolve, reject);
}).catch(reject);
text = text.substr(20);
} else {
resolve();
}
}
return new Promise(sender);
}).then(function() {
E.showMessage("Disconnect");
device.disconnect();
if (callback) callback();
});
}
`;
sendCustomizedApp({
storage:[
{name:"espruinoctrl.app.js", url:"app.js", content:app},
]
});
});
</script>
</body>
</html>