vm---Scratch3MicroBitBlocks
2019-10-29 本文已影响0人
hanxianshe_9530
/**
* Enum for tilt sensor direction.
* @readonly
* @enum {string}
*/
const MicroBitTiltDirection = {
FRONT: 'front',
BACK: 'back',
LEFT: 'left',
RIGHT: 'right',
ANY: 'any'
};
/**
* Enum for micro:bit gestures.
* @readonly
* @enum {string}
*/
const MicroBitGestures = {
MOVED: 'moved',
SHAKEN: 'shaken',
JUMPED: 'jumped'
};
/**
* Enum for micro:bit buttons.
* @readonly
* @enum {string}
*/
const MicroBitButtons = {
A: 'A',
B: 'B',
ANY: 'any'
};
/**
* Enum for micro:bit pin states.
* @readonly
* @enum {string}
*/
const MicroBitPinState = {
ON: 'on',
OFF: 'off'
};
/**
* Scratch 3.0 blocks to interact with a MicroBit peripheral.
*/
class Scratch3MicroBitBlocks {
/**
* @return {string} - the name of this extension.
*/
static get EXTENSION_NAME () {
return 'micro:bit';
}
/**
* @return {string} - the ID of this extension.
*/
static get EXTENSION_ID () {
return 'microbit';
}
/**
* @return {number} - the tilt sensor counts as "tilted" if its tilt angle meets or exceeds this threshold.
*/
static get TILT_THRESHOLD () {
return 15;
}
/**
* @return {array} - text and values for each buttons menu element
*/
get BUTTONS_MENU () {
return [
{
text: 'A',
value: MicroBitButtons.A
},
{
text: 'B',
value: MicroBitButtons.B
},
{
text: formatMessage({
id: 'microbit.buttonsMenu.any',
default: 'any',
description: 'label for "any" element in button picker for micro:bit extension'
}),
value: MicroBitButtons.ANY
}
];
}
/**
* @return {array} - text and values for each gestures menu element
*/
get GESTURES_MENU () {
return [
{
text: formatMessage({
id: 'microbit.gesturesMenu.moved',
default: 'moved',
description: 'label for moved gesture in gesture picker for micro:bit extension'
}),
value: MicroBitGestures.MOVED
},
{
text: formatMessage({
id: 'microbit.gesturesMenu.shaken',
default: 'shaken',
description: 'label for shaken gesture in gesture picker for micro:bit extension'
}),
value: MicroBitGestures.SHAKEN
},
{
text: formatMessage({
id: 'microbit.gesturesMenu.jumped',
default: 'jumped',
description: 'label for jumped gesture in gesture picker for micro:bit extension'
}),
value: MicroBitGestures.JUMPED
}
];
}
/**
* @return {array} - text and values for each pin state menu element
*/
get PIN_STATE_MENU () {
return [
{
text: formatMessage({
id: 'microbit.pinStateMenu.on',
default: 'on',
description: 'label for on element in pin state picker for micro:bit extension'
}),
value: MicroBitPinState.ON
},
{
text: formatMessage({
id: 'microbit.pinStateMenu.off',
default: 'off',
description: 'label for off element in pin state picker for micro:bit extension'
}),
value: MicroBitPinState.OFF
}
];
}
/**
* @return {array} - text and values for each tilt direction menu element
*/
get TILT_DIRECTION_MENU () {
return [
{
text: formatMessage({
id: 'microbit.tiltDirectionMenu.front',
default: 'front',
description: 'label for front element in tilt direction picker for micro:bit extension'
}),
value: MicroBitTiltDirection.FRONT
},
{
text: formatMessage({
id: 'microbit.tiltDirectionMenu.back',
default: 'back',
description: 'label for back element in tilt direction picker for micro:bit extension'
}),
value: MicroBitTiltDirection.BACK
},
{
text: formatMessage({
id: 'microbit.tiltDirectionMenu.left',
default: 'left',
description: 'label for left element in tilt direction picker for micro:bit extension'
}),
value: MicroBitTiltDirection.LEFT
},
{
text: formatMessage({
id: 'microbit.tiltDirectionMenu.right',
default: 'right',
description: 'label for right element in tilt direction picker for micro:bit extension'
}),
value: MicroBitTiltDirection.RIGHT
}
];
}
/**
* @return {array} - text and values for each tilt direction (plus "any") menu element
*/
get TILT_DIRECTION_ANY_MENU () {
return [
...this.TILT_DIRECTION_MENU,
{
text: formatMessage({
id: 'microbit.tiltDirectionMenu.any',
default: 'any',
description: 'label for any direction element in tilt direction picker for micro:bit extension'
}),
value: MicroBitTiltDirection.ANY
}
];
}
/**
* Construct a set of MicroBit blocks.
* @param {Runtime} runtime - the Scratch 3.0 runtime.
*/
constructor (runtime) {
/**
* The Scratch 3.0 runtime.
* @type {Runtime}
*/
this.runtime = runtime;
// Create a new MicroBit peripheral instance
this._peripheral = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);
}
/**
* @returns {object} metadata for this extension and its blocks.
*/
getInfo () {
return {
id: Scratch3MicroBitBlocks.EXTENSION_ID,
name: Scratch3MicroBitBlocks.EXTENSION_NAME,
blockIconURI: blockIconURI,
showStatusButton: true,
blocks: [
{
opcode: 'whenButtonPressed',
text: formatMessage({
id: 'microbit.whenButtonPressed',
default: 'when [BTN] button pressed',
description: 'when the selected button on the micro:bit is pressed'
}),
blockType: BlockType.HAT,
arguments: {
BTN: {
type: ArgumentType.STRING,
menu: 'buttons',
defaultValue: MicroBitButtons.A
}
}
},
{
opcode: 'isButtonPressed',
text: formatMessage({
id: 'microbit.isButtonPressed',
default: '[BTN] button pressed?',
description: 'is the selected button on the micro:bit pressed?'
}),
blockType: BlockType.BOOLEAN,
arguments: {
BTN: {
type: ArgumentType.STRING,
menu: 'buttons',
defaultValue: MicroBitButtons.A
}
}
},
'---',
{
opcode: 'whenGesture',
text: formatMessage({
id: 'microbit.whenGesture',
default: 'when [GESTURE]',
description: 'when the selected gesture is detected by the micro:bit'
}),
blockType: BlockType.HAT,
arguments: {
GESTURE: {
type: ArgumentType.STRING,
menu: 'gestures',
defaultValue: MicroBitGestures.MOVED
}
}
},
'---',
{
opcode: 'displaySymbol',
text: formatMessage({
id: 'microbit.displaySymbol',
default: 'display [MATRIX]',
description: 'display a pattern on the micro:bit display'
}),
blockType: BlockType.COMMAND,
arguments: {
MATRIX: {
type: ArgumentType.MATRIX,
defaultValue: '0101010101100010101000100'
}
}
},
{
opcode: 'displayText',
text: formatMessage({
id: 'microbit.displayText',
default: 'display text [TEXT]',
description: 'display text on the micro:bit display'
}),
blockType: BlockType.COMMAND,
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: formatMessage({
id: 'microbit.defaultTextToDisplay',
default: 'Hello!',
description: `default text to display.
IMPORTANT - the micro:bit only supports letters a-z, A-Z.
Please substitute a default word in your language
that can be written with those characters,
substitute non-accented characters or leave it as "Hello!".
Check the micro:bit site documentation for details`
})
}
}
},
{
opcode: 'displayClear',
text: formatMessage({
id: 'microbit.clearDisplay',
default: 'clear display',
description: 'display nothing on the micro:bit display'
}),
blockType: BlockType.COMMAND
},
'---',
{
opcode: 'whenTilted',
text: formatMessage({
id: 'microbit.whenTilted',
default: 'when tilted [DIRECTION]',
description: 'when the micro:bit is tilted in a direction'
}),
blockType: BlockType.HAT,
arguments: {
DIRECTION: {
type: ArgumentType.STRING,
menu: 'tiltDirectionAny',
defaultValue: MicroBitTiltDirection.ANY
}
}
},
{
opcode: 'isTilted',
text: formatMessage({
id: 'microbit.isTilted',
default: 'tilted [DIRECTION]?',
description: 'is the micro:bit is tilted in a direction?'
}),
blockType: BlockType.BOOLEAN,
arguments: {
DIRECTION: {
type: ArgumentType.STRING,
menu: 'tiltDirectionAny',
defaultValue: MicroBitTiltDirection.ANY
}
}
},
{
opcode: 'getTiltAngle',
text: formatMessage({
id: 'microbit.tiltAngle',
default: 'tilt angle [DIRECTION]',
description: 'how much the micro:bit is tilted in a direction'
}),
blockType: BlockType.REPORTER,
arguments: {
DIRECTION: {
type: ArgumentType.STRING,
menu: 'tiltDirection',
defaultValue: MicroBitTiltDirection.FRONT
}
}
},
'---',
{
opcode: 'whenPinConnected',
text: formatMessage({
id: 'microbit.whenPinConnected',
default: 'when pin [PIN] connected',
description: 'when the pin detects a connection to Earth/Ground'
}),
blockType: BlockType.HAT,
arguments: {
PIN: {
type: ArgumentType.STRING,
menu: 'touchPins',
defaultValue: '0'
}
}
}
],
menus: {
buttons: {
acceptReporters: true,
items: this.BUTTONS_MENU
},
gestures: {
acceptReporters: true,
items: this.GESTURES_MENU
},
pinState: {
acceptReporters: true,
items: this.PIN_STATE_MENU
},
tiltDirection: {
acceptReporters: true,
items: this.TILT_DIRECTION_MENU
},
tiltDirectionAny: {
acceptReporters: true,
items: this.TILT_DIRECTION_ANY_MENU
},
touchPins: {
acceptReporters: true,
items: ['0', '1', '2']
}
}
};
}
/**
* Test whether the A or B button is pressed
* @param {object} args - the block's arguments.
* @return {boolean} - true if the button is pressed.
*/
whenButtonPressed (args) {
if (args.BTN === 'any') {
return this._peripheral.buttonA | this._peripheral.buttonB;
} else if (args.BTN === 'A') {
return this._peripheral.buttonA;
} else if (args.BTN === 'B') {
return this._peripheral.buttonB;
}
return false;
}
/**
* Test whether the A or B button is pressed
* @param {object} args - the block's arguments.
* @return {boolean} - true if the button is pressed.
*/
isButtonPressed (args) {
if (args.BTN === 'any') {
return (this._peripheral.buttonA | this._peripheral.buttonB) !== 0;
} else if (args.BTN === 'A') {
return this._peripheral.buttonA !== 0;
} else if (args.BTN === 'B') {
return this._peripheral.buttonB !== 0;
}
return false;
}
/**
* Test whether the micro:bit is moving
* @param {object} args - the block's arguments.
* @return {boolean} - true if the micro:bit is moving.
*/
whenGesture (args) {
const gesture = cast.toString(args.GESTURE);
if (gesture === 'moved') {
return (this._peripheral.gestureState >> 2) & 1;
} else if (gesture === 'shaken') {
return this._peripheral.gestureState & 1;
} else if (gesture === 'jumped') {
return (this._peripheral.gestureState >> 1) & 1;
}
return false;
}
/**
* Display a predefined symbol on the 5x5 LED matrix.
* @param {object} args - the block's arguments.
* @return {Promise} - a Promise that resolves after a tick.
*/
displaySymbol (args) {
const symbol = cast.toString(args.MATRIX).replace(/\s/g, '');
const reducer = (accumulator, c, index) => {
const value = (c === '0') ? accumulator : accumulator + Math.pow(2, index);
return value;
};
const hex = symbol.split('').reduce(reducer, 0);
if (hex !== null) {
this._peripheral.ledMatrixState[0] = hex & 0x1F;
this._peripheral.ledMatrixState[1] = (hex >> 5) & 0x1F;
this._peripheral.ledMatrixState[2] = (hex >> 10) & 0x1F;
this._peripheral.ledMatrixState[3] = (hex >> 15) & 0x1F;
this._peripheral.ledMatrixState[4] = (hex >> 20) & 0x1F;
this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
}
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, BLESendInterval);
});
}
/**
* Display text on the 5x5 LED matrix.
* @param {object} args - the block's arguments.
* @return {Promise} - a Promise that resolves after the text is done printing.
* Note the limit is 19 characters
* The print time is calculated by multiplying the number of horizontal pixels
* by the default scroll delay of 120ms.
* The number of horizontal pixels = 6px for each character in the string,
* 1px before the string, and 5px after the string.
*/
displayText (args) {
const text = String(args.TEXT).substring(0, 19);
if (text.length > 0) this._peripheral.displayText(text);
const yieldDelay = 120 * ((6 * text.length) + 6);
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, yieldDelay);
});
}
/**
* Turn all 5x5 matrix LEDs off.
* @return {Promise} - a Promise that resolves after a tick.
*/
displayClear () {
for (let i = 0; i < 5; i++) {
this._peripheral.ledMatrixState[i] = 0;
}
this._peripheral.displayMatrix(this._peripheral.ledMatrixState);
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, BLESendInterval);
});
}
/**
* Test whether the tilt sensor is currently tilted.
* @param {object} args - the block's arguments.
* @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
*/
whenTilted (args) {
return this._isTilted(args.DIRECTION);
}
/**
* Test whether the tilt sensor is currently tilted.
* @param {object} args - the block's arguments.
* @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
*/
isTilted (args) {
return this._isTilted(args.DIRECTION);
}
/**
* @param {object} args - the block's arguments.
* @property {TiltDirection} DIRECTION - the direction (front, back, left, right) to check.
* @return {number} - the tilt sensor's angle in the specified direction.
* Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).
*/
getTiltAngle (args) {
return this._getTiltAngle(args.DIRECTION);
}
/**
* Test whether the tilt sensor is currently tilted.
* @param {TiltDirection} direction - the tilt direction to test (front, back, left, right, or any).
* @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.
* @private
*/
_isTilted (direction) {
switch (direction) {
case MicroBitTiltDirection.ANY:
return (Math.abs(this._peripheral.tiltX / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD) ||
(Math.abs(this._peripheral.tiltY / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD);
default:
return this._getTiltAngle(direction) >= Scratch3MicroBitBlocks.TILT_THRESHOLD;
}
}
/**
* @param {TiltDirection} direction - the direction (front, back, left, right) to check.
* @return {number} - the tilt sensor's angle in the specified direction.
* Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).
* @private
*/
_getTiltAngle (direction) {
switch (direction) {
case MicroBitTiltDirection.FRONT:
return Math.round(this._peripheral.tiltY / -10);
case MicroBitTiltDirection.BACK:
return Math.round(this._peripheral.tiltY / 10);
case MicroBitTiltDirection.LEFT:
return Math.round(this._peripheral.tiltX / -10);
case MicroBitTiltDirection.RIGHT:
return Math.round(this._peripheral.tiltX / 10);
default:
log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);
}
}
/**
* @param {object} args - the block's arguments.
* @return {boolean} - the touch pin state.
* @private
*/
whenPinConnected (args) {
const pin = parseInt(args.PIN, 10);
if (isNaN(pin)) return;
if (pin < 0 || pin > 2) return false;
return this._peripheral._checkPinState(pin);
}
}
MicroBit