espruino-web-ide
Version:
A Terminal and Graphical code Editor for Espruino JavaScript Microcontrollers
386 lines (362 loc) • 13.9 kB
JavaScript
/**
Copyright 2016 Gordon Williams (gw@pur3.co.uk)
This Source Code is subject to the terms of the Mozilla Public
License, v2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
------------------------------------------------------------------
Blockly blocks for Espruino BLE
------------------------------------------------------------------
**/
var BLE_COL = 210;
function bleStatement(blk, comment) {
blk.setPreviousStatement(true);
blk.setNextStatement(true);
blk.setColour(BLE_COL);
//blk.setInputsInline(true);
blk.setTooltip(comment);
}
function bleInput(blk, comment, type) {
blk.setOutput(true, type||'Number');
blk.setColour(BLE_COL);
//blk.setInputsInline(true);
blk.setTooltip(comment);
}
var BLE_CHARACTERISTICS = [
['Digital', '0001,2A56'],
['Analog', '0001,2A58'],
['Temperature', '0001,2A6E'],
['UART TX', '6e400001b5a3f393e0a9e50e24dcca9e,6e400002b5a3f393e0a9e50e24dcca9e'],
['UART RX', '6e400001b5a3f393e0a9e50e24dcca9e,6e400003b5a3f393e0a9e50e24dcca9e']
];
function bleUpdateServices(service) {
var currService = Blockly.JavaScript.definitions_["NRF.setServices"];
if (currService===undefined) {
currService = {};
} else {
currService = currService.substring("NRF.setServices(".length, currService.length-2)
currService = currService.replace(/"onWrite":function\(v\){ble_value=v.data.toString\(\);([a-zA-Z0-9]*)\(\);}/g,'"onWrite":"$1"');
currService = JSON.parse(currService);
}
for (var n in service)
currService[n] = service[n];
var str = JSON.stringify(currService);
str = str.replace(/"onWrite":"([a-zA-Z0-9]*)"/g,'"onWrite":function(v){ble_value=v.data.toString();$1();}');
Blockly.JavaScript.definitions_["NRF.setServices"] =
"NRF.setServices("+str+");";
}
function formatCharacteristic(c) {
// Sadly we need this because devices shipped with 1v88 not 1v89 :(
if ("string"==typeof c &&
c.length==4)
return "0x"+c;
return JSON.stringify(c);
}
function splitCharacteristic(char) {
var c = char.split(",");
return [formatCharacteristic(c[0]), formatCharacteristic(c[1])];
}
// ----------------------------------------------------------
Blockly.Blocks.ble_connected = {
category: 'BLE',
init: function() {
this.appendDummyInput().appendField('[BLE] When Connected');
this.appendStatementInput('DO').appendField('do');
bleStatement(this, 'Run code on BLE event');
}
};
Blockly.JavaScript.ble_connected = function() {
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
return "NRF.on('connect', function() {\n"+code+"});\n";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_disconnected = {
category: 'BLE',
init: function() {
this.appendDummyInput().appendField('[BLE] When Disconnected');
this.appendStatementInput('DO').appendField('do');
bleStatement(this, 'Run code on BLE event');
}
};
Blockly.JavaScript.ble_disconnected = function() {
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
return "NRF.on('disconnect', function() {\n"+code+"});\n";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_advertise = {
category: 'BLE',
init: function() {
this.appendValueInput('VAL')
.setCheck(['String','Number','Boolean']).appendField('[BLE] Advertise');;
bleStatement(this, "Advertise on Espruino's manufacturer data");
}
};
Blockly.JavaScript.ble_advertise = function() {
var data = Blockly.JavaScript.valueToCode(this, 'VAL', Blockly.JavaScript.ORDER_ASSIGNMENT) || undefined;
return "NRF.setAdvertising({},{manufacturer: 0x0590, manufacturerData:"+data+"});\n";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_on_advertise = {
category: 'BLE',
init: function() {
this.appendDummyInput().appendField('[BLE] On Advertising');
this.appendStatementInput('DO').appendField('do');
bleStatement(this, "When an advertisement with Espruino's manufacturer data is detected");
}
};
Blockly.JavaScript.ble_on_advertise = function() {
var data = Blockly.JavaScript.valueToCode(this, 'DATA', Blockly.JavaScript.ORDER_ASSIGNMENT) || undefined;
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
return "NRF.setScan(function(dev) {var ble_value=dev.manufacturerData;"+code+"}, { filters: [{ manufacturerData:{0x0590:{}} }] });\n";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_uart_write = {
category: 'BLE',
init: function() {
this.appendValueInput('VAL').setCheck(['Number','Boolean','String']).appendField('[BLE] Send');
this.appendValueInput('DEV').setCheck(['BLEDevice']).appendField('to UART on');
this.appendStatementInput('DO').appendField('then');
bleStatement(this, 'Sends data to the UART on a Bluetooth device - adds a newline');
}
};
Blockly.JavaScript.ble_uart_write = function() {
var dev = Blockly.JavaScript.valueToCode(this, 'DEV', Blockly.JavaScript.ORDER_ASSIGNMENT) || "Promise.reject()";
var val = Blockly.JavaScript.valueToCode(this, 'VAL', Blockly.JavaScript.ORDER_ASSIGNMENT)+'+"\\n"';
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
if (!dev) return "";
return `(function(){
var gatt;
var text = ${val};
${dev}.then(function(g) {
gatt = g;
return g.getPrimaryService("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
}).then(function(s) {
return s.getCharacteristic("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
}).then(function(c) {
function sender(resolve, reject) {
if (text.length) {
var d = text.substr(0,20);
text = text.substr(20);
c.writeValue(d).then(function() {
sender(resolve, reject);
},reject);
} else {
resolve();
}
}
return new Promise(sender);
}).then(function() {
return gatt.disconnect();
}).then(function() {
${code}
});
})();`;
};
// ============================================================================
Blockly.Blocks.ble_dev_name = {
category: 'BLE',
init: function() {
this.appendDummyInput().
appendField('Device Named').
appendField(new Blockly.FieldTextInput("Puck.js ABCD"), 'NAME');
bleInput(this, '', 'BLEDevice');
}
};
Blockly.JavaScript.ble_dev_name = function() {
var name = this.getFieldValue('NAME');
return ["NRF.requestDevice({ filters: [{ name: "+JSON.stringify(name)+" }] }).then(function(device) {\n"+
" return device.gatt.connect();\n"+
"})", Blockly.JavaScript.ORDER_ATOMIC];
};
// ----------------------------------------------------------
Blockly.Blocks.ble_dev_prefix = {
category: 'BLE',
init: function() {
this.appendDummyInput().
appendField('Device Starting with').
appendField(new Blockly.FieldTextInput("Puck.js"), 'NAME');
bleInput(this, '', 'BLEDevice');
}
};
Blockly.JavaScript.ble_dev_prefix = function() {
var name = this.getFieldValue('NAME');
return ["NRF.requestDevice({ filters: [{ namePrefix: "+JSON.stringify(name)+" }] }).then(function(device) {\n"+
" return device.gatt.connect();\n"+
"})", Blockly.JavaScript.ORDER_ATOMIC];
};
// ----------------------------------------------------------
Blockly.Blocks.ble_dev_address = {
category: 'BLE',
init: function() {
this.appendDummyInput().
appendField('Device Address').
appendField(new Blockly.FieldTextInput("Puck.js"), 'ADDR');
bleInput(this, '', 'BLEDevice');
}
};
Blockly.JavaScript.ble_dev_address = function() {
var addr = this.getFieldValue('ADDR');
return ["NRF.connect("+JSON.stringify(addr)+")", Blockly.JavaScript.ORDER_ATOMIC];
};
// ============================================================================
Blockly.Blocks.ble_characteristic = {
category: 'BLE',
init: function() {
this.appendDummyInput()
.appendField('Characteristic ')
.appendField(new Blockly.FieldTextInput("0001"), 'SERV')
.appendField(':')
.appendField(new Blockly.FieldTextInput("2A56"), 'CHAR');
bleInput(this, '', 'BLECharacteristic');
}
};
Blockly.JavaScript.ble_characteristic = function() {
return [this.getFieldValue('SERV')+","+this.getFieldValue('CHAR'), Blockly.JavaScript.ORDER_ATOMIC];
};
// ----------------------------------------------------------
Blockly.Blocks.ble_characteristic_dropdown = {
category: 'BLE',
init: function() {
this.appendDummyInput()
.appendField('Characteristic ')
.appendField(new Blockly.FieldDropdown(BLE_CHARACTERISTICS), 'CHAR')
bleInput(this, '', 'BLECharacteristic');
}
};
Blockly.JavaScript.ble_characteristic_dropdown = function() {
return [this.getFieldValue('CHAR'), Blockly.JavaScript.ORDER_ATOMIC];
};
// ============================================================================
Blockly.Blocks.ble_setchar = {
category: 'BLE',
init: function() {
this.appendValueInput('CHAR').setCheck(['BLECharacteristic']).appendField('[BLE] Set');
this.appendValueInput('DEV').setCheck(['BLEDevice']).appendField('on');
this.appendValueInput('VAL').setCheck(['Number','Boolean','String']).appendField('to');
this.appendStatementInput('DO').appendField('then');
bleStatement(this, 'Sets a value on a BLE device');
}
};
Blockly.JavaScript.ble_setchar = function() {
var char = Blockly.JavaScript.valueToCode(this, 'CHAR', Blockly.JavaScript.ORDER_ASSIGNMENT);
var dev = Blockly.JavaScript.valueToCode(this, 'DEV', Blockly.JavaScript.ORDER_ASSIGNMENT) || "Promise.reject()";
var val = Blockly.JavaScript.valueToCode(this, 'VAL', Blockly.JavaScript.ORDER_ASSIGNMENT) || '0';
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
if (!dev || !char) return "";
char = splitCharacteristic(char);
return `(function() {
var gatt;
${dev}.then(function(g) {
gatt = g;
return gatt.getPrimaryService(${char[0]});
}).then(function(service) {
return service.getCharacteristic((${char[1]});
}).then(function(characteristic) {
characteristic.writeValue((${val});
}).then(function() {
return gatt.disconnect();
}).then(function() {
${code}
});
})();`;
};
// ----------------------------------------------------------
Blockly.Blocks.ble_getchar = {
category: 'BLE',
init: function() {
this.appendValueInput('CHAR').setCheck(['BLECharacteristic']).appendField('[BLE] Get');
this.appendValueInput('DEV').setCheck(['BLEDevice']).appendField('from');
this.appendStatementInput('DO').appendField('then');
bleStatement(this, 'Gets a value from a BLE device');
}
};
Blockly.JavaScript.ble_getchar = function() {
var char = Blockly.JavaScript.valueToCode(this, 'CHAR', Blockly.JavaScript.ORDER_ASSIGNMENT);
var dev = Blockly.JavaScript.valueToCode(this, 'DEV', Blockly.JavaScript.ORDER_ASSIGNMENT) || "Promise.reject()";
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
if (!dev || !char) return "";
char = splitCharacteristic(char);
return `(function() {
var gatt;
${dev}.then(function(g) {
gatt = g;
return gatt.getPrimaryService(${char[0]});
}).then(function(service) {
return service.getCharacteristic((${char[1]});
}).then(function(characteristic) {
return characteristic.readValue();\n"+
}).then(function(value) {
ble_value = value;
return gatt.disconnect();
}).then(function() {
${code}
});
})();`;
};
// ----------------------------------------------------------
Blockly.Blocks.ble_write = {
category: 'BLE',
init: function() {
this.appendValueInput('CHAR').setCheck(['BLECharacteristic']).appendField('[BLE] Set');
this.appendValueInput('VAL').setCheck(['Number','Boolean']).appendField('to');
bleStatement(this, "Set Espruino's BLE characteristic to the given value");
}
};
Blockly.JavaScript.ble_write = function() {
var char = Blockly.JavaScript.valueToCode(this, 'CHAR', Blockly.JavaScript.ORDER_ASSIGNMENT);
var value = Blockly.JavaScript.valueToCode(this, 'VAL', Blockly.JavaScript.ORDER_ASSIGNMENT) || '0';
if (!char) return "";
char = splitCharacteristic(char);
var service = {};
service[char[0]] = {};
service[char[0]][char[1]] = {
readable : true,
notify : true
};
bleUpdateServices(service);
return "NRF.updateServices({\n"+
" "+char[0]+" : {\n"+
" "+char[1]+" : {\n"+
" value : "+value+",\n"+
" notify: true\n"+
" }\n"+
" }\n"+
"});\n";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_onwritten = {
category: 'BLE',
init: function() {
this.appendValueInput('CHAR').setCheck(['BLECharacteristic']).appendField('[BLE] When');
this.appendStatementInput('DO').appendField('changed, do');
bleStatement(this, "Called when a BLE characteristic is written on Espruino");
}
};
Blockly.JavaScript.ble_onwritten = function() {
var char = Blockly.JavaScript.valueToCode(this, 'CHAR', Blockly.JavaScript.ORDER_ASSIGNMENT);
var code = Blockly.JavaScript.statementToCode(this, 'DO') || "";
if (!char) return "";
char = splitCharacteristic(char);
var callbackVar = Blockly.JavaScript.variableDB_.getDistinctName(
'onWriteCallback', Blockly.Variables.NAME_TYPE);
var service = {};
service[char[0]] = {};
service[char[0]][char[1]] = {
readable : true,
writable : true,
notify : true,
onWrite : callbackVar // this gets replaced by bleUpdateServices
};
bleUpdateServices(service);
return "function "+callbackVar+"() {\n "+code+"\n}";
};
// ----------------------------------------------------------
Blockly.Blocks.ble_value = {
category: 'BLE',
init: function() {
this.appendDummyInput().appendField('[BLE] Value');
bleInput(this, 'The value from the last BLE operation');
}
};
Blockly.JavaScript.ble_value = function() {
return ["ble_value", Blockly.JavaScript.ORDER_ATOMIC];
};