mirror of https://github.com/espruino/BangleApps
Added Espruino control (fix #417)
parent
f40e3a2549
commit
c449a258f1
14
apps.json
14
apps.json
|
@ -1987,5 +1987,19 @@
|
|||
"storage": [
|
||||
{"name":"gpsautotime.wid.js","url":"widget.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "espruinoctrl",
|
||||
"name": "Espruino Control",
|
||||
"shortName":"Espruino Ctrl",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!",
|
||||
"tags": "",
|
||||
"readme": "README.md",
|
||||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"espruinoctrl.app.js"},
|
||||
{"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,28 @@
|
|||
# Espruino Control
|
||||
|
||||
Send commands to other Espruino devices via the Bluetooth UART interface.
|
||||
|
||||
## Customising
|
||||
|
||||
Click the customise button and you can customise your commands
|
||||
with 4 options:
|
||||
|
||||
|
||||
* **Title** - The title of the menu item that will be displayed
|
||||
* **Command** - The JS command to execute, eg. `LED.toggle()`
|
||||
* **MAC Address** - If specified, of the form `aa:bb:cc:dd:ee:ff`. The device
|
||||
with this address will be connected to directly. If not specified a menu
|
||||
showing available Espruino devices is popped up.
|
||||
* **RX** - If checked, the app will display any data received from the
|
||||
device being connected to. Use this if you want to print data - eg: `print(E.getBattery())`
|
||||
|
||||
When done, click 'Upload'. Your changes will be saved to local storage
|
||||
so they'll be remembered next time you upload from the same device.s
|
||||
|
||||
## Usage
|
||||
|
||||
Simply load the app and you'll see a menu with the menu items
|
||||
you defined. Select one and you'll be able to connect to the device
|
||||
and send the command.
|
||||
|
||||
If a command should wait for a response then
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwhH+AH4A/AH4A/AH4AFwIuuAAIllAAYIGF041IF34AKqwuuAANXF9QuCAANdGHqQgGBwvdGCIud5mjGB4udAAIwPFz3MSR61VFxQwNci4vGeh4uXGAguHGBK3WGA4AIegtXc69dGBxoBGAouWO4IwNe4gwZa4YwLFwikEFzAwLFwwwCFzQwKFw68YGB4AdF5AwmF5IwlF5QwkF5Yw/F8IwEL9WBB4IuuADwuzGxAugFAgliGBYutAH4A/AH4A/ADA="))
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,275 @@
|
|||
<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="../../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.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();
|
||||
}, BTN3);
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0,1);
|
||||
g.drawString("Back", g.getWidth()-10, g.getHeight()-50);
|
||||
}, 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", content:app},
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue