mirror of https://github.com/espruino/BangleApps
277 lines
8.7 KiB
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>
|