2018-06-25 13:42:48 -04:00
|
|
|
const ArgumentType = require('../../extension-support/argument-type');
|
|
|
|
const BlockType = require('../../extension-support/block-type');
|
|
|
|
const Cast = require('../../util/cast');
|
|
|
|
const log = require('../../util/log');
|
|
|
|
const Base64Util = require('../../util/base64-util');
|
|
|
|
const BTSession = require('../../io/BTSession');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* High-level primitives / constants used by the extension.
|
|
|
|
* @type {object}
|
|
|
|
*/
|
|
|
|
const BTCommand = {
|
|
|
|
LAYER: 0x00,
|
|
|
|
NUM8: 0x81,
|
|
|
|
NUM16: 0x82,
|
|
|
|
NUM32: 0x83,
|
|
|
|
COAST: 0x0,
|
|
|
|
BRAKE: 0x1,
|
|
|
|
LONGRAMP: 50,
|
|
|
|
STEPSPEED: 0xAE,
|
|
|
|
TIMESPEED: 0xAF,
|
|
|
|
OUTPUTSTOP: 0xA3,
|
|
|
|
OUTPUTRESET: 0xA2,
|
|
|
|
STEPSPEEDSYNC: 0xB0,
|
|
|
|
TIMESPEEDSYNC: 0xB1
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of accepted motor ports.
|
|
|
|
* @note These should not be translated as they correspond to labels on
|
|
|
|
* the EV3 hub.
|
|
|
|
* @type {array}
|
|
|
|
*/
|
|
|
|
const MOTOR_PORTS = [
|
|
|
|
{
|
|
|
|
name: 'A',
|
|
|
|
value: 1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'B',
|
|
|
|
value: 2
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'C',
|
|
|
|
value: 4
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'D',
|
|
|
|
value: 8
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of accepted sensor ports.
|
|
|
|
* @note These should not be translated as they correspond to labels on
|
|
|
|
* the EV3 hub.
|
|
|
|
* @type {array}
|
|
|
|
*/
|
2018-06-25 14:11:26 -04:00
|
|
|
const SENSOR_PORTS = [
|
|
|
|
{
|
|
|
|
name: '1',
|
|
|
|
value: 1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: '2',
|
|
|
|
value: 2
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: '3',
|
|
|
|
value: 3
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: '4',
|
|
|
|
value: 4
|
|
|
|
}
|
|
|
|
];
|
2018-06-25 13:42:48 -04:00
|
|
|
|
|
|
|
class EV3 {
|
|
|
|
|
|
|
|
constructor (runtime, extensionId) {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Scratch 3.0 runtime used to trigger the green flag button.
|
|
|
|
* @type {Runtime}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._runtime = runtime;
|
|
|
|
|
2018-06-26 15:07:08 -04:00
|
|
|
/**
|
|
|
|
* State
|
|
|
|
*/
|
2018-06-25 13:42:48 -04:00
|
|
|
this.connected = false;
|
|
|
|
this.speed = 50;
|
2018-06-26 15:07:08 -04:00
|
|
|
this._sensors = {
|
|
|
|
distance: 0
|
|
|
|
};
|
2018-06-25 13:42:48 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Bluetooth connection session for reading/writing device data.
|
|
|
|
* @type {BTSession}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._bt = null;
|
|
|
|
this._runtime.registerExtensionDevice(extensionId, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: keep here?
|
|
|
|
/**
|
|
|
|
* Called by the runtime when user wants to scan for a device.
|
|
|
|
*/
|
|
|
|
startDeviceScan () {
|
|
|
|
log.info('making a new BT session');
|
|
|
|
this._bt = new BTSession(this._runtime, {
|
|
|
|
majorDeviceClass: 8,
|
|
|
|
minorDeviceClass: 1
|
2018-06-26 15:07:08 -04:00
|
|
|
}, this._onSessionConnect.bind(this), this._onSessionMessage.bind(this));
|
2018-06-25 13:42:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: keep here?
|
|
|
|
/**
|
|
|
|
* Called by the runtime when user wants to connect to a certain device.
|
|
|
|
* @param {number} id - the id of the device to connect to.
|
|
|
|
*/
|
|
|
|
connectDevice (id) {
|
|
|
|
this._bt.connectDevice(id);
|
|
|
|
}
|
|
|
|
|
2018-06-25 14:39:38 -04:00
|
|
|
get distance () {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
2018-06-26 15:07:08 -04:00
|
|
|
return this._sensors.distance;
|
2018-06-25 14:39:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
get brightness () {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: read brightness sensor data
|
|
|
|
log.info(`return brightness`);
|
|
|
|
}
|
|
|
|
|
|
|
|
getMotorPosition (args) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: read motor position data
|
|
|
|
log.info(`return motor ${args} position`);
|
|
|
|
}
|
|
|
|
|
|
|
|
isButtonPressed (args) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: read button pressed data
|
|
|
|
log.info(`return button ${args} pressed`);
|
|
|
|
}
|
|
|
|
|
|
|
|
isBrightnessLessThan (args) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: read brightness sensor data
|
|
|
|
log.info(`return brightness less than ${args}`);
|
|
|
|
}
|
|
|
|
|
2018-06-25 13:42:48 -04:00
|
|
|
beep () {
|
|
|
|
if (!this.connected) return;
|
|
|
|
this._bt.sendMessage({
|
|
|
|
message: 'DwAAAIAAAJQBgQKC6AOC6AM=',
|
|
|
|
encoding: 'base64'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
motorTurnClockwise (port, time) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// Build up motor command
|
|
|
|
const cmd = this._applyPrefix(0, this._motorCommand(
|
|
|
|
BTCommand.TIMESPEED,
|
|
|
|
port,
|
|
|
|
time,
|
|
|
|
this.speed,
|
|
|
|
BTCommand.LONGRAMP
|
|
|
|
));
|
|
|
|
|
|
|
|
// Send message
|
|
|
|
this._bt.sendMessage({
|
|
|
|
message: Base64Util.arrayBufferToBase64(cmd),
|
|
|
|
encoding: 'base64'
|
|
|
|
});
|
|
|
|
|
|
|
|
// Yield for time
|
|
|
|
return new Promise(resolve => {
|
|
|
|
setTimeout(() => {
|
|
|
|
resolve();
|
|
|
|
}, time);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
motorTurnCounterClockwise (port, time) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// Build up motor command
|
|
|
|
const cmd = this._applyPrefix(0, this._motorCommand(
|
|
|
|
BTCommand.TIMESPEED,
|
|
|
|
port,
|
|
|
|
time,
|
|
|
|
this.speed * -1,
|
|
|
|
BTCommand.LONGRAMP
|
|
|
|
));
|
|
|
|
|
|
|
|
// Send message
|
|
|
|
this._bt.sendMessage({
|
|
|
|
message: Base64Util.arrayBufferToBase64(cmd),
|
|
|
|
encoding: 'base64'
|
|
|
|
});
|
|
|
|
|
|
|
|
// Yield for time
|
|
|
|
return new Promise(resolve => {
|
|
|
|
setTimeout(() => {
|
|
|
|
resolve();
|
|
|
|
}, time);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-06-25 14:39:38 -04:00
|
|
|
motorRotate (port, degrees) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: Build up motor command
|
|
|
|
log.info(`motor rotate port: ${port} and degrees: ${degrees}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
motorSetPosition (port, degrees) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: Build up motor command
|
|
|
|
log.info(`motor set position port: ${port} and degrees: ${degrees}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
motorSetPower (port, power) {
|
|
|
|
if (!this.connected) return;
|
|
|
|
|
|
|
|
// TODO: Build up motor command
|
|
|
|
log.info(`motor set power port: ${port} and degrees: ${power}`);
|
|
|
|
}
|
|
|
|
|
2018-06-25 13:42:48 -04:00
|
|
|
_applyPrefix (n, cmd) {
|
|
|
|
const len = cmd.length + 5;
|
|
|
|
return [].concat(
|
|
|
|
len & 0xFF,
|
|
|
|
(len >> 8) & 0xFF,
|
|
|
|
0x1,
|
|
|
|
0x0,
|
|
|
|
0x0,
|
|
|
|
n,
|
|
|
|
0x0,
|
|
|
|
cmd
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a motor command in EV3 byte array format (CMD, LAYER, PORT,
|
|
|
|
* SPEED, RAMP UP, RUN, RAMP DOWN, BREAKING TYPE)
|
|
|
|
* @param {string} command Motor command primitive (i.e. "prefix")
|
|
|
|
* @param {string} port Port to address
|
|
|
|
* @param {number} n Value to be passed to motor command
|
|
|
|
* @param {number} speed Speed value
|
|
|
|
* @param {number} ramp Ramp value
|
|
|
|
* @return {array} Byte array
|
|
|
|
*/
|
|
|
|
_motorCommand (command, port, n, speed, ramp) {
|
|
|
|
/**
|
|
|
|
* Generate run values for a given input.
|
|
|
|
* @param {number} run Run input
|
|
|
|
* @return {array} Run values (byte array)
|
|
|
|
*/
|
|
|
|
const getRunValues = function (run) {
|
|
|
|
// If run duration is less than max 16-bit integer
|
|
|
|
if (run < 0x7fff) {
|
|
|
|
return [
|
|
|
|
BTCommand.NUM16,
|
|
|
|
run & 0xff,
|
|
|
|
(run >> 8) & 0xff
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run forever
|
|
|
|
return [
|
|
|
|
BTCommand.NUM32,
|
|
|
|
run & 0xff,
|
|
|
|
(run >> 8) & 0xff,
|
|
|
|
(run >> 16) & 0xff,
|
|
|
|
(run >> 24) & 0xff
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
// If speed is less than zero, make it positive and multiply the input
|
|
|
|
// value by -1
|
|
|
|
if (speed < 0) {
|
|
|
|
speed = -1 * speed;
|
|
|
|
n = -1 * n;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the input value is less than 0
|
|
|
|
const dir = (n < 0) ? 0x100 - speed : speed; // step negative or possitive
|
|
|
|
n = Math.abs(n);
|
|
|
|
|
|
|
|
// Setup motor run duration and ramping behavior
|
|
|
|
let rampup = ramp;
|
|
|
|
let rampdown = ramp;
|
|
|
|
let run = n - ramp * 2;
|
|
|
|
if (run < 0) {
|
|
|
|
rampup = Math.floor(n / 2);
|
|
|
|
run = 0;
|
|
|
|
rampdown = n - rampup;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate motor command
|
|
|
|
const runcmd = getRunValues(run);
|
|
|
|
return [
|
|
|
|
command,
|
|
|
|
BTCommand.LAYER,
|
|
|
|
port,
|
|
|
|
BTCommand.NUM8,
|
|
|
|
dir & 0xff,
|
|
|
|
BTCommand.NUM8,
|
|
|
|
rampup
|
|
|
|
].concat(runcmd.concat([
|
|
|
|
BTCommand.NUM8,
|
|
|
|
rampdown,
|
|
|
|
BTCommand.BRAKE
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
|
|
|
|
_onSessionConnect () {
|
|
|
|
log.info('bt device connected!');
|
|
|
|
this.connected = true;
|
2018-06-26 15:07:08 -04:00
|
|
|
|
|
|
|
// Start reading data
|
|
|
|
const intervalID = window.setInterval(this._getSessionData.bind(this), 300);
|
|
|
|
// TODO: clear intervalID later on disconnect, etc.
|
|
|
|
}
|
|
|
|
|
|
|
|
_getSessionData () {
|
|
|
|
// GET EV3 DISTANCE PORT 0
|
|
|
|
/*
|
|
|
|
99 [153] input device
|
|
|
|
1D [ 29] ready si
|
|
|
|
00 [ 0] layer (this brick)
|
|
|
|
00 [ 0] sensor port 0
|
|
|
|
00 [ 0] do not change type
|
|
|
|
01 [ 1] mode 1 = EV3-Ultrasonic-Inch
|
|
|
|
01 [ 1] one data set
|
|
|
|
60 [ 96] global var index
|
|
|
|
*/
|
|
|
|
this._bt.sendMessage({
|
|
|
|
message: 'DQAAAAAEAJkdAAAAAQFg', // [13, 0, 0, 0, 0, 4, 0, 153, 29, 0, 0, 0, 1, 1, 96]
|
|
|
|
encoding: 'base64'
|
|
|
|
});
|
|
|
|
|
|
|
|
// GET EV3 BRIGHTNESS PORT 1
|
|
|
|
/*
|
|
|
|
99 [153] input device
|
|
|
|
1D [ 29] ready si
|
|
|
|
00 [ 0] layer (this brick)
|
|
|
|
01 [ 1] sensor port 1
|
|
|
|
00 [ 0] do not change type
|
|
|
|
01 [ 1] mode 1 = EV3-Color-Ambient
|
|
|
|
01 [ 1] one data set
|
|
|
|
60 [ 96] global var index
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
this._bt.sendMessage({
|
|
|
|
message: 'DQAAAAAEAJkdAAEAAQFg', // [13, 0, 0, 0, 0, 4, 0, 153, 29, 0, 1, 0, 1, 1, 96]
|
|
|
|
encoding: 'base64'
|
|
|
|
});
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
_onSessionMessage (params) {
|
|
|
|
const message = params.message;
|
|
|
|
const array = Base64Util.base64ToUint8Array(message);
|
|
|
|
|
|
|
|
// TODO: this only gets distance so far
|
|
|
|
const distance = this._array2float([array[5], array[6], array[7], array[8]]);
|
|
|
|
this._sensors.distance = distance;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: put elsewhere, in a util
|
|
|
|
_array2float (list) {
|
|
|
|
const buffer = new Uint8Array(list).buffer;
|
|
|
|
const view = new DataView(buffer);
|
|
|
|
return view.getFloat32(0, true);
|
2018-06-25 13:42:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class Scratch3Ev3Blocks {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The ID of the extension.
|
|
|
|
* @return {string} the id
|
|
|
|
*/
|
|
|
|
static get EXTENSION_ID () {
|
|
|
|
return 'ev3';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new instance of the EV3 extension.
|
|
|
|
* @param {object} runtime VM runtime
|
|
|
|
* @constructor
|
|
|
|
*/
|
|
|
|
constructor (runtime) {
|
|
|
|
/**
|
|
|
|
* The Scratch 3.0 runtime.
|
|
|
|
* @type {Runtime}
|
|
|
|
*/
|
|
|
|
this.runtime = runtime;
|
|
|
|
|
2018-06-25 18:24:23 -04:00
|
|
|
// Create a new EV3 device instance
|
2018-06-25 13:42:48 -04:00
|
|
|
this._device = new EV3(this.runtime, Scratch3Ev3Blocks.EXTENSION_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Define the EV3 extension.
|
|
|
|
* @return {object} Extension description.
|
|
|
|
*/
|
|
|
|
getInfo () {
|
|
|
|
return {
|
|
|
|
id: Scratch3Ev3Blocks.EXTENSION_ID,
|
|
|
|
name: 'LEGO MINDSTORMS EV3',
|
|
|
|
iconURI: null,
|
2018-06-25 18:24:23 -04:00
|
|
|
showStatusButton: true,
|
2018-06-25 13:42:48 -04:00
|
|
|
blocks: [
|
|
|
|
{
|
|
|
|
opcode: 'motorTurnClockwise',
|
2018-06-25 14:11:26 -04:00
|
|
|
text: 'motor [PORT] turn clockwise for [TIME] seconds',
|
2018-06-25 13:42:48 -04:00
|
|
|
blockType: BlockType.COMMAND,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
},
|
|
|
|
TIME: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'motorTurnCounterClockwise',
|
2018-06-25 14:11:26 -04:00
|
|
|
text: 'motor [PORT] turn counter for [TIME] seconds',
|
2018-06-25 13:42:48 -04:00
|
|
|
blockType: BlockType.COMMAND,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
},
|
|
|
|
TIME: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2018-06-25 14:11:26 -04:00
|
|
|
{
|
|
|
|
opcode: 'motorRotate',
|
|
|
|
text: 'motor [PORT] rotate [DEGREES] degrees',
|
|
|
|
blockType: BlockType.COMMAND,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
},
|
|
|
|
DEGREES: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 90
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'motorSetPosition',
|
|
|
|
text: 'motor [PORT] set position [DEGREES] degrees',
|
|
|
|
blockType: BlockType.COMMAND,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
},
|
|
|
|
DEGREES: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 90
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'motorSetPower',
|
|
|
|
text: 'motor [PORT] set power [POWER] %',
|
|
|
|
blockType: BlockType.COMMAND,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
},
|
|
|
|
POWER: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 50
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'getMotorPosition',
|
|
|
|
text: 'motor [PORT] position',
|
|
|
|
blockType: BlockType.REPORTER,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'motorPorts',
|
|
|
|
defaultValue: MOTOR_PORTS[0].value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'whenButtonPressed',
|
|
|
|
text: 'when button [PORT] pressed',
|
|
|
|
blockType: BlockType.HAT,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'sensorPorts',
|
|
|
|
defaultValue: SENSOR_PORTS[0].value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'whenDistanceLessThan',
|
|
|
|
text: 'when distance < [DISTANCE]',
|
|
|
|
blockType: BlockType.HAT,
|
|
|
|
arguments: {
|
|
|
|
DISTANCE: {
|
|
|
|
type: ArgumentType.NUMBER,
|
2018-06-26 15:07:08 -04:00
|
|
|
defaultValue: 5
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'whenBrightnessLessThan',
|
|
|
|
text: 'when brightness < [DISTANCE]',
|
|
|
|
blockType: BlockType.HAT,
|
|
|
|
arguments: {
|
|
|
|
DISTANCE: {
|
|
|
|
type: ArgumentType.NUMBER,
|
|
|
|
defaultValue: 50
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'buttonPressed',
|
|
|
|
text: 'button [PORT] pressed?',
|
|
|
|
blockType: BlockType.BOOLEAN,
|
|
|
|
arguments: {
|
|
|
|
PORT: {
|
|
|
|
type: ArgumentType.STRING,
|
|
|
|
menu: 'sensorPorts',
|
|
|
|
defaultValue: SENSOR_PORTS[0].value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'getDistance',
|
|
|
|
text: 'distance',
|
|
|
|
blockType: BlockType.REPORTER
|
|
|
|
},
|
|
|
|
{
|
|
|
|
opcode: 'getBrightness',
|
|
|
|
text: 'brightness',
|
|
|
|
blockType: BlockType.REPORTER
|
|
|
|
},
|
2018-06-25 13:42:48 -04:00
|
|
|
{
|
|
|
|
opcode: 'beep',
|
|
|
|
text: 'beep',
|
|
|
|
blockType: BlockType.COMMAND
|
|
|
|
}
|
|
|
|
],
|
|
|
|
menus: {
|
2018-06-25 14:11:26 -04:00
|
|
|
motorPorts: this._buildMenu(MOTOR_PORTS),
|
|
|
|
sensorPorts: this._buildMenu(SENSOR_PORTS)
|
2018-06-25 13:42:48 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create data for a menu in scratch-blocks format, consisting of an array of objects with text and
|
|
|
|
* value properties. The text is a translated string, and the value is one-indexed.
|
|
|
|
* @param {object[]} info - An array of info objects each having a name property.
|
|
|
|
* @return {array} - An array of objects with text and value properties.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_buildMenu (info) {
|
|
|
|
return info.map((entry, index) => {
|
|
|
|
const obj = {};
|
|
|
|
obj.text = entry.name;
|
|
|
|
obj.value = String(index + 1);
|
|
|
|
return obj;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
motorTurnClockwise (args) {
|
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
const time = Cast.toNumber(args.TIME) * 1000;
|
|
|
|
|
|
|
|
this._device.motorTurnClockwise(port, time);
|
2018-06-25 14:39:38 -04:00
|
|
|
return;
|
2018-06-25 13:42:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
motorTurnCounterClockwise (args) {
|
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
const time = Cast.toNumber(args.TIME) * 1000;
|
|
|
|
|
|
|
|
this._device.motorTurnCounterClockwise(port, time);
|
2018-06-25 14:39:38 -04:00
|
|
|
return;
|
2018-06-25 13:42:48 -04:00
|
|
|
}
|
|
|
|
|
2018-06-25 14:11:26 -04:00
|
|
|
motorRotate (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
const degrees = Cast.toNumber(args.DEGREES);
|
|
|
|
|
|
|
|
this._device.motorRotate(port, degrees);
|
|
|
|
return;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
motorSetPosition (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
const degrees = Cast.toNumber(args.DEGREES);
|
|
|
|
|
|
|
|
this._device.motorSetPosition(port, degrees);
|
|
|
|
return;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
motorSetPower (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
const power = Cast.toNumber(args.POWER);
|
|
|
|
|
|
|
|
this._device.motorSetPower(port, power);
|
|
|
|
return;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
getMotorPosition (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
|
|
|
|
return this._device.getMotorPosition(port);
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
whenButtonPressed (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
|
|
|
|
return this._device.isButtonPressed(port);
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
whenDistanceLessThan (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const distance = Cast.toNumber(args.DISTANCE);
|
|
|
|
|
2018-06-26 15:07:08 -04:00
|
|
|
return this._device.distance < distance;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
whenBrightnessLessThan (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const brightness = Cast.toNumber(args.DISTANCE);
|
|
|
|
|
|
|
|
return this._device.isBrightnessLessThan(brightness);
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
buttonPressed (args) {
|
2018-06-25 14:39:38 -04:00
|
|
|
const port = Cast.toNumber(args.PORT);
|
|
|
|
|
|
|
|
return this._device.isButtonPressed(port);
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
getDistance () {
|
2018-06-25 14:39:38 -04:00
|
|
|
return this._device.distance;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
getBrightness () {
|
2018-06-25 14:39:38 -04:00
|
|
|
return this._device.brightness;
|
2018-06-25 14:11:26 -04:00
|
|
|
}
|
|
|
|
|
2018-06-25 13:42:48 -04:00
|
|
|
beep () {
|
|
|
|
return this._device.beep();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Scratch3Ev3Blocks;
|