diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js index 7cb556c5d0d..989f3fcb20b 100644 --- a/src/extension-support/extension-manager.js +++ b/src/extension-support/extension-manager.js @@ -17,6 +17,9 @@ const builtinExtensions = { wedo2: () => require('../extensions/scratch3_wedo2'), music: () => require('../extensions/scratch3_music'), microbit: () => require('../extensions/scratch3_microbit'), + jdcode: () => require('../extensions/scratch3_jdcode'), + jcboard: () => require('../extensions/scratch3_jcboard'), + uglybot: () => require('../extensions/scratch3_uglybot'), text2speech: () => require('../extensions/scratch3_text2speech'), translate: () => require('../extensions/scratch3_translate'), videoSensing: () => require('../extensions/scratch3_video_sensing'), diff --git a/src/extensions/scratch3_jcboard/index.js b/src/extensions/scratch3_jcboard/index.js new file mode 100644 index 00000000000..a8ee24e0232 --- /dev/null +++ b/src/extensions/scratch3_jcboard/index.js @@ -0,0 +1,470 @@ +const ArgumentType = require('../../extension-support/argument-type'); +const BlockType = require('../../extension-support/block-type'); +const log = require('../../util/log'); +const cast = require('../../util/cast'); +const formatMessage = require('format-message'); +const BLE = require('../../io/ble'); +const Base64Util = require('../../util/base64-util'); + + +// eslint-disable-next-line max-len +const blockIconURI = 'data:image/png;base64,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'; + + + +const BLETimeout = 4500; +const BLESendInterval = 20; +const BLEDataStoppedError = 'JCBoard extension stopped receiving data'; + + +const BLEUUID = { + service: 0x2262, + rxChar: '00000227-0000-1000-8000-00805f9b34fb', + txChar: '00000227-0000-1000-8000-00805f9b34fb' +}; + + +class JCBoard { + constructor (runtime, extensionId) { + this._runtime = runtime; + this._ble = null; + this._runtime.registerPeripheralExtension(extensionId, this); + this._extensionId = extensionId; + this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this)); + this._timeoutID = null; + this._busy = false; + this._busyTimeoutID = null; + this.reset = this.reset.bind(this); + this._onConnect = this._onConnect.bind(this); + this._onMessage = this._onMessage.bind(this); + this.rxData = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.txData = new Uint8Array(20); + this._sensors = { + button: 0, + ir: [0, 0, 0], + ultrasonic: 0, + joystick: [0, 0], + tilt: [0, 0], + sound: 0, + illum:0 + }; + } + + stopAll () { + for (let i = 0; i < 20; i++) + this.txData[i] = 0; + this.send(this.txData); + } + + scan () { + if (this._ble) { + this._ble.disconnect(); + } + this._ble = new BLE(this._runtime, this._extensionId, { + filters: [ + {services: [BLEUUID.service]} + ] + }, this._onConnect, this.reset); + } + + connect (id) { + if (this._ble) { + this._ble.connectPeripheral(id); + } + } + + disconnect () { + if (this._ble) { + this._ble.disconnect(); + } + this.reset(); + } + + reset () { + if (this._timeoutID) { + window.clearTimeout(this._timeoutID); + this._timeoutID = null; + } + } + + isConnected () { + + let connected = false; + if (this._ble) { + connected = this._ble.isConnected(); + } + return connected; + } + + send (message) { + if (!this.isConnected()) return; + + console.log(message); + this._ble.write(BLEUUID.service, BLEUUID.txChar, message, 'ASCII', true).then( + () => { + this._busy = false; + window.clearTimeout(this._busyTimeoutID); + } + ); + } + + _onConnect () { + this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + _onMessage (strData) { + this.rxData = strData.split(','); + window.clearTimeout(this._timeoutID); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + get getRxData() { + return this.rxData; + } + + get getTxData() { + return this.rxData; + } +} + +class Scratch3JCBoardBlocks { + static get EXTENSION_NAME () { + return 'JCBoard'; + } + static get EXTENSION_ID () { + return 'jcboard'; + } + + constructor (runtime) { + + this.runtime = runtime; + this._peripheral = new JCBoard(this.runtime, Scratch3JCBoardBlocks.EXTENSION_ID); + this.message = this._peripheral.txData; + this.tuneID = 0; + this.moveID = 0; + this.rotID = 0; + } + + + getInfo () { + return { + id: Scratch3JCBoardBlocks.EXTENSION_ID, + name: Scratch3JCBoardBlocks.EXTENSION_NAME, + blockIconURI: blockIconURI, + showStatusButton: true, + blocks: [ + { + opcode: 'ultrasonic', + text: formatMessage({ + id: 'ultrasonic', + default: '[ONEFIVE] \uD3EC\uD2B8\uB97C \uCD08\uC74C\uD30C\uC13C\uC11C\uB85C \uC0AC\uC6A9', + }), + blockType: BlockType.COMMAND, + arguments: { + ONEFIVE: { + type: ArgumentType.STRING, + menu: 'onefive', + defaultValue: '1\uBC88' + } + } + }, + { + opcode: 'led', + text: formatMessage({ + id: 'led', + default: '[ONETWO] LED [ONOFF]', + }), + blockType: BlockType.COMMAND, + arguments: { + ONETWO: { + type: ArgumentType.STRING, + menu: 'onetwo', + defaultValue: '1\uBC88' + }, + ONOFF: { + type: ArgumentType.STRING, + menu: 'onoff', + defaultValue: '\uCF1C\uAE30' + } + } + }, + { + opcode: 'buzzer', + text: formatMessage({ + id: 'buzzer', + default: '[TUNE] \uC74C\uC744 [DELAY] \uCD08\uB3D9\uC548 \uC18C\uB9AC\uB0B4\uAE30', + }), + blockType: BlockType.COMMAND, + arguments: { + TUNE: { + type: ArgumentType.STRING, + menu: 'tune', + defaultValue: '\uB3C4' + }, + DELAY: { + type: ArgumentType.STRING, + menu: 'delay', + defaultValue: '1' + } + } + }, + { + opcode: 'motor', + text: formatMessage({ + id: 'motor', + default: '[ONETWO] DC\uBAA8\uD130\uB97C [TEXT] \uC138\uAE30\uB85C \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + ONETWO: { + type: ArgumentType.STRING, + menu: 'onetwo', + defaultValue: '1\uBC88' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + { + opcode: 'servo', + text: formatMessage({ + id: 'servo', + default: '[ONEFOUR] \uC11C\uBCF4\uBAA8\uD130\uB97C [TEXT] \uB3C4 \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + ONEFOUR: { + type: ArgumentType.STRING, + menu: 'onefour', + defaultValue: '1\uBC88' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + { + opcode: 'digitalpin', + text: formatMessage({ + id: 'digitalpin', + default: '[ONEFIVE] \uB514\uC9C0\uD138\uD540\uC744 [HIGHLOW] \uB85C \uC124\uC815', + }), + blockType: BlockType.COMMAND, + arguments: { + ONEFIVE: { + type: ArgumentType.STRING, + menu: 'onefive', + defaultValue: '1\uBC88' + }, + HIGHLOW: { + type: ArgumentType.STRING, + menu: 'highlow', + defaultValue: 'HIGH' + }, + } + }, + + '---', + + { + opcode: 'getbutton', + text: formatMessage({ + id: 'getbutton', + default: '[ONETWO] \uBC84\uD2BC \uAC12', + }), + blockType: BlockType.REPORTER, + arguments: { + ONETWO: { + type: ArgumentType.STRING, + menu: 'onetwo', + defaultValue: '1\uBC88' + }, + } + }, + { + opcode: 'getanalog', + text: formatMessage({ + id: 'getanalog', + default: '[ONEFIVE] \uC544\uB0A0\uB85C\uADF8 \uAC12', + }), + blockType: BlockType.REPORTER, + arguments: { + ONEFIVE: { + type: ArgumentType.STRING, + menu: 'onefive', + defaultValue: '1\uBC88' + }, + } + }, + ], + menus: { + onoff: { + acceptReporters: true, + items: ['\uCF1C\uAE30', '\uB044\uAE30'] //Äѱ⠲ô±â + }, + tune: { + acceptReporters: true, + items: ['\uB3C4', '\uB808', '\uBBF8', '\uD30C', '\uC194', '\uB77C', '\uC2DC'] //µµ·¹¹ÌÆļֶó½Ã + }, + delay: { + acceptReporters: true, + items: ['0.5', '0.8', '1', '2', '3', '4', '5'] + }, + onetwo: { + acceptReporters: true, + items: ['1\uBC88', '2\uBC88'] + }, + onefour: { + acceptReporters: true, + items: ['1\uBC88', '2\uBC88', '3\uBC88', '4\uBC88'] + }, + onefive: { + acceptReporters: true, + items: ['1\uBC88', '2\uBC88', '3\uBC88', '4\uBC88', '5\uBC88'] + }, + highlow: { + acceptReporters: true, + items: ['HIGH', 'LOW'] + }, + } + }; + } + led (args) { + if(args.ONOFF == '\uCF1C\uAE30'){ + if(args.ONETWO == '1\uBC88') + this.message[0] |= 0x11; + else + this.message[0] |= 0x12; + } + else{ + if(args.ONETWO == '1\uBC88') + this.message[0] &= ~0x01; + else + this.message[0] &= ~0x02; + } + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + digitalpin(args){ + if(args.HIGHLOW == 'HIGH'){ + if(args.ONEFIVE == '1\uBC88') this.message[1] |= 0x01; + if(args.ONEFIVE == '2\uBC88') this.message[1] |= 0x02; + if(args.ONEFIVE == '3\uBC88') this.message[1] |= 0x04; + if(args.ONEFIVE == '4\uBC88') this.message[1] |= 0x08; + if(args.ONEFIVE == '5\uBC88') this.message[1] |= 0x10; + } + else{ + if(args.ONEFIVE == '1\uBC88') this.message[1] &= ~0x01; + if(args.ONEFIVE == '2\uBC88') this.message[1] &= ~0x02; + if(args.ONEFIVE == '3\uBC88') this.message[1] &= ~0x04; + if(args.ONEFIVE == '4\uBC88') this.message[1] &= ~0x08; + if(args.ONEFIVE == '5\uBC88') this.message[1] &= ~0x10; + } + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + buzzer(args){ + this.tuneID = (this.tuneID+1)%16; + if(args.TUNE == '\uB3C4') this.message[2] = 1; + if(args.TUNE == '\uB808') this.message[2] = 2; + if(args.TUNE == '\uBBF8') this.message[2] = 3; + if(args.TUNE == '\uD30C') this.message[2] = 4; + if(args.TUNE == '\uC194') this.message[2] = 5; + if(args.TUNE == '\uB77C') this.message[2] = 6; + if(args.TUNE == '\uC2DC') this.message[2] = 7; + this.message[2] |= (this.tuneID<<4); + + if(args.DELAY == '0.5') this.message[3] = 5; + if(args.DELAY == '0.8') this.message[3] = 8; + if(args.DELAY == '1') this.message[3] = 10; + if(args.DELAY == '2') this.message[3] = 20; + if(args.DELAY == '3') this.message[3] = 30; + if(args.DELAY == '4') this.message[3] = 40; + if(args.DELAY == '5') this.message[3] = 50; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + motor (args){ + if(args.ONETWO == '1\uBC88') + this.message[4] = args.TEXT; + else + this.message[5] = args.TEXT; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + servo (args){ + if(args.ONEFOUR == '1\uBC88') this.message[6] = args.TEXT; + if(args.ONEFOUR == '2\uBC88') this.message[7] = args.TEXT; + if(args.ONEFOUR == '3\uBC88') this.message[8] = args.TEXT; + if(args.ONEFOUR == '4\uBC88') this.message[9] = args.TEXT; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + ultrasonic (args) { + if(args.ONEFIVE == '1\uBC88') this.message[10] = 0x01; + if(args.ONEFIVE == '2\uBC88') this.message[10] = 0x02; + if(args.ONEFIVE == '3\uBC88') this.message[10] = 0x04; + if(args.ONEFIVE == '4\uBC88') this.message[10] = 0x08; + if(args.ONEFIVE == '5\uBC88') this.message[10] = 0x10; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + getbutton(args){ + var rxData = this._peripheral.rxData; + if(args.ONETWO == '1\uBC88') return rxData[0]&0x01; + if(args.ONETWO == '2\uBC88') return (rxData[0]>>1)&0x01; + } + + getanalog(args){ + var rxData = this._peripheral.rxData; + if(args.ONEFIVE == '1\uBC88') return rxData[1]; + if(args.ONEFIVE == '2\uBC88') return rxData[2]; + if(args.ONEFIVE == '3\uBC88') return rxData[3]; + if(args.ONEFIVE == '4\uBC88') return rxData[4]; + if(args.ONEFIVE == '5\uBC88') return rxData[5]; + } +} + +module.exports = Scratch3JCBoardBlocks; diff --git a/src/extensions/scratch3_jdcode/index.js b/src/extensions/scratch3_jdcode/index.js new file mode 100644 index 00000000000..100b5855285 --- /dev/null +++ b/src/extensions/scratch3_jdcode/index.js @@ -0,0 +1,560 @@ +const ArgumentType = require('../../extension-support/argument-type'); +const BlockType = require('../../extension-support/block-type'); +const log = require('../../util/log'); +const cast = require('../../util/cast'); +const formatMessage = require('format-message'); +const BLE = require('../../io/ble'); +const Base64Util = require('../../util/base64-util'); + + +// eslint-disable-next-line max-len +const blockIconURI = 'data:image/png;base64,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'; + + + +const BLETimeout = 4500; +const BLESendInterval = 20; +const BLEDataStoppedError = 'JDCode extension stopped receiving data'; + + +const BLEUUID = { + service: 0x2261, + rxChar: '00000227-0000-1000-8000-00805f9b34fb', + txChar: '00000227-0000-1000-8000-00805f9b34fb' +}; + + +class JDCode { + constructor (runtime, extensionId) { + this._runtime = runtime; + this._ble = null; + this._runtime.registerPeripheralExtension(extensionId, this); + this._extensionId = extensionId; + this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this)); + this._timeoutID = null; + this._busy = false; + this._busyTimeoutID = null; + this.reset = this.reset.bind(this); + this._onConnect = this._onConnect.bind(this); + this._onMessage = this._onMessage.bind(this); + this.rxData = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.txData = new Int16Array(7); + this.txArray = new Uint16Array(14); + this.moveX = 0; + this.moveY = 0; + this.rotation = 0; + this._sensors = { + button: 0, + ir: [0, 0, 0], + ultrasonic: 0, + joystick: [0, 0], + tilt: [0, 0], + sound: 0, + illum:0 + }; + this.txData[5] = 100; + this.txData[6] = 100; + } + + stopAll () { + for (let i = 0; i < 7; i++) + this.txData[i] = 0; + this.txData[5] = 100; + this.txData[6] = 100; + this.send(this.txData); + this.moveX = 0; + this.moveY = 0; + this.rotation = 0; + } + + scan () { + if (this._ble) { + this._ble.disconnect(); + } + this._ble = new BLE(this._runtime, this._extensionId, { + filters: [ + {services: [BLEUUID.service]} + ] + }, this._onConnect, this.reset); + } + + connect (id) { + if (this._ble) { + this._ble.connectPeripheral(id); + } + } + + disconnect () { + if (this._ble) { + this._ble.disconnect(); + } + this.reset(); + } + + reset () { + if (this._timeoutID) { + window.clearTimeout(this._timeoutID); + this._timeoutID = null; + } + } + + isConnected () { + + let connected = false; + if (this._ble) { + connected = this._ble.isConnected(); + } + return connected; + } + + send (message) { + if (!this.isConnected()) return; + for(n=0;n<7;n++){ + this.txArray[n*2] = message[n]&0xFF; + this.txArray[n*2+1] = (message[n]>>8)&0xFF; + } + console.log(this.txArray); + this._ble.write(BLEUUID.service, BLEUUID.txChar, this.txArray, 'ASCII', true).then( + () => { + this._busy = false; + window.clearTimeout(this._busyTimeoutID); + } + ); + } + + _onConnect () { + this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + _onMessage (strData) { + this.rxData = strData.split(','); + window.clearTimeout(this._timeoutID); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + get getRxData() { + return this.rxData; + } + + get getTxData() { + return this.rxData; + } +} + +class Scratch3JDCodeBlocks { + static get EXTENSION_NAME () { + return 'JDCode'; + } + static get EXTENSION_ID () { + return 'jdcode'; + } + + constructor (runtime) { + + this.runtime = runtime; + this._peripheral = new JDCode(this.runtime, Scratch3JDCodeBlocks.EXTENSION_ID); + this.message = this._peripheral.txData; + this.tuneID = 0; + this.moveID = 0; + this.rotID = 0; + } + + + getInfo () { + return { + id: Scratch3JDCodeBlocks.EXTENSION_ID, + name: Scratch3JDCodeBlocks.EXTENSION_NAME, + blockIconURI: blockIconURI, + showStatusButton: true, + blocks: [ + { + opcode: 'takeoff', + text: formatMessage({ + id: 'takeoff', + default: '\uB4DC\uB860 \uC774\uB959\uD558\uAE30', + }), + }, + { + opcode: 'landing', + text: formatMessage({ + id: 'landing', + default: '\uB4DC\uB860 \uCC29\uB959\uD558\uAE30', + }), + }, + { + opcode: 'alt', + text: formatMessage({ + id: 'alt', + default: '[TEXT] cm \uB192\uC774\uB85C \uBE44\uD589', + }), + blockType: BlockType.COMMAND, + arguments: { + TEXT: { + type: ArgumentType.STRING, + defaultValue: '70' + } + } + }, + + { + opcode: 'velocity', + text: formatMessage({ + id: 'velocity', + default: '[FBRL] (\uC73C)\uB85C [TEXT] \uC18D\uB3C4(cm/s)\uB85C \uBE44\uD589', + }), + blockType: BlockType.COMMAND, + arguments: { + FBRL: { + type: ArgumentType.STRING, + menu: 'fbrl', + defaultValue: '\uC55E' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '70' + } + } + }, + + { + opcode: 'move', + text: formatMessage({ + id: 'move', + default: '[FBRL](\uC73C)\uB85C [TEXT1]cm \uAC70\uB9AC\uB97C [TEXT2] \uC18D\uB3C4(cm/s)\uB85C \uBE44\uD589', + }), + blockType: BlockType.COMMAND, + arguments: { + FBRL: { + type: ArgumentType.STRING, + menu: 'fbrl', + defaultValue: '\uC55E' + }, + TEXT1: { + type: ArgumentType.STRING, + defaultValue: '100' + }, + TEXT2: { + type: ArgumentType.STRING, + defaultValue: '70' + } + } + }, + + { + opcode: 'rotation', + text: formatMessage({ + id: 'rotation', + default: '[ROTDIR]\uC73C\uB85C [TEXT1] \uB3C4\uB97C [TEXT2]\uAC01\uC18D\uB3C4(deg/s)\uB85C \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + ROTDIR: { + type: ArgumentType.STRING, + menu: 'rotdir', + defaultValue: '\uC2DC\uACC4\uBC29\uD5A5' + }, + TEXT1: { + type: ArgumentType.STRING, + defaultValue: '90' + }, + TEXT2: { + type: ArgumentType.STRING, + defaultValue: '70' + } + } + }, + { + opcode: 'proprot', + text: formatMessage({ + id: 'proprot', + default: '\uD504\uB85C\uD3A0\uB7EC\uB97C [TEXT]\uC138\uAE30\uB85C \uB3CC\uB9AC\uAE30', + }), + blockType: BlockType.COMMAND, + arguments: { + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + { + opcode: 'motorot', + text: formatMessage({ + id: 'motorot', + default: '[LTRB]\uBAA8\uD130\uB97C [TEXT]\uC138\uAE30\uB85C \uB3CC\uB9AC\uAE30', + }), + blockType: BlockType.COMMAND, + arguments: { + LTRB: { + type: ArgumentType.STRING, + menu: 'ltrb', + defaultValue: '\uC67C\uCABD\uC544\uB798' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + { + opcode: 'emergency', + text: formatMessage({ + id: 'emergency', + default: '\uB4DC\uB860\uBE44\uD589\uC744 \uC989\uC2DC \uBA48\uCDA4', + }), + }, + '---', + { + opcode: 'getready', + text: formatMessage({ + id: 'getready', + default: '\uB4DC\uB860 \uBE44\uD589 \uC900\uBE44 \uC0C1\uD0DC', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'getbattery', + text: formatMessage({ + id: 'getbattery', + default: '\uBC30\uD130\uB9AC(%)', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'getalt', + text: formatMessage({ + id: 'getalt', + default: '\uB4DC\uB860 \uB192\uC774', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'gettilt', + text: formatMessage({ + id: 'gettilt', + default: '\uB4DC\uB860[FBLR] \uAE30\uC6B8\uAE30', + }), + blockType: BlockType.REPORTER, + arguments: { + FBLR: { + type: ArgumentType.STRING, + menu: 'fblr', + defaultValue: '\uC55E\uB4A4' + }, + } + }, + { + opcode: 'getmove', + text: formatMessage({ + id: 'getmove', + default: '\uB4DC\uB860[FBLR] \uC774\uB3D9', + }), + blockType: BlockType.REPORTER, + arguments: { + FBLR: { + type: ArgumentType.STRING, + menu: 'fblr', + defaultValue: '\uC55E\uB4A4' + }, + } + }, + ], + menus: { + fbrl: { + acceptReporters: true, + items: ['\uC55E', '\uB4A4','\uC624\uB978\uCABD', '\uC67C\uCABD'] //¾Õ µÚ ¿À¸¥ÂÊ ¿ÞÂÊ + }, + rotdir: { + acceptReporters: true, + items: ['\uC2DC\uACC4\uBC29\uD5A5', '\uBC18\uC2DC\uACC4\uBC29\uD5A5'] //½Ã°è¹æÇ⠹ݽðè¹æÇâ + }, + fblr: { + acceptReporters: true, + items: ['\uC55E\uB4A4', '\uC88C\uC6B0'] // ¾ÕµÚ Á¿ì + }, + ltrb: { + acceptReporters: true, + items: ['\uC67C\uCABD\uC544\uB798', '\uC67C\uCABD\uC704', '\uC624\uB978\uCABD\uC544\uB798', '\uC624\uB978\uCABD\uC704'] + //¿ÞÂʾƷ¡, ¿ÞÂÊ À§, ¿À¸¥ÂʾƷ¡, ¿À¸¥ÂÊÀ§ + }, + } + }; + } + takeoff () { + this.message[3] = 70; + this.message[4] = 0x2F; + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + landing () { + this.message[3] = 0x0; + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + alt(args){ + var value = args.TEXT>150? 150 : args.Text<0? 0 : args.TEXT; + this.message[3] = value; + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + velocity(args){ + var vel = args.TEXT>200? 200 : args.TEXT<0? 0 : args.TEXT; + if(args.FBRL == '\uC55E') this.message[1] = vel; + if(args.FBRL == '\uB4A4') this.message[1] = vel*-1; + if(args.FBRL == '\uC624\uB978\uCABD') this.message[0] = vel; + if(args.FBRL == '\uC67C\uCABD') this.message[0] = vel*-1; + this.message[4] = 0x0F; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + move (args){ + var dist = args.TEXT1>2000? 2000 : args.TEXT1<0? 0 : args.TEXT1; + var vel = args.TEXT2>200? 200 : args.TEXT2<0? 0 : args.TEXT2; + if(args.FBRL == '\uC55E') this._peripheral.moveY += dist; //this.message[1] = dist; + if(args.FBRL == '\uB4A4') this._peripheral.moveY += (-1*dist); //this.message[1] = dist*-1; + if(args.FBRL == '\uC624\uB978\uCABD') this._peripheral.moveX += dist; //this.message[0] = dist; + if(args.FBRL == '\uC67C\uCABD') this._peripheral.moveX += (-1*dist); //this.message[0] = dist*-1; + this.message[0] = this._peripheral.moveX; + this.message[1] = this._peripheral.moveY; + this.message[4] = 0x2F; + this.message[5] = vel; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + rotation (args){ + var degree = args.TEXT1>179? 179 : args.TEXT1<0? 0 : args.TEXT1; + var vel = args.TEXT2>200? 200 : args.TEXT2<0? 0 : args.TEXT2; + if(args.ROTDIR == '\uC2DC\uACC4\uBC29\uD5A5') this._peripheral.rotation += degree; //this.message[2] = degree; + else this.message[2] = this._peripheral.rotation += (-1*degree); //degree*-1; + this.message[2] = this._peripheral.rotation; + this.message[6] = vel; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + proprot (args){ + var speed = args.TEXT>1000? 1000 : args.TEXT<0? 0 : args.TEXT; + this.message[3] = speed; + this.message[4] = 0x01; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + motorot (args){ + var speed = args.TEXT>100? 100 : args.TEXT<0? 0 : args.TEXT; + if(args.LTRB == '\uC67C\uCABD\uC544\uB798') this.message[2] = speed; + if(args.LTRB == '\uC67C\uCABD\uC704') this.message[1] = speed; + if(args.LTRB == '\uC624\uB978\uCABD\uC544\uB798') this.message[3] = speed; + if(args.LTRB == '\uC624\uB978\uCABD\uC704') this.message[0] = speed; + this.message[4] = 0x8000; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + emergency (args){ + this._peripheral.moveX = 0; + this._peripheral.moveY = 0; + this._peripheral.rotation = 0; + for (let i = 0; i < 7; i++) + this.message[i] = 0; + this.message[5] = 100; + this.message[6] = 100; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + + getready(){ + var rxData = this._peripheral.rxData; + if((rxData[0]&0x03) == 0) + return 1; + else + return 0; + } + + getbattery(){ + var rxData = this._peripheral.rxData; + return rxData[1]; + } + + getalt(){ + var rxData = this._peripheral.rxData; + return rxData[4]; + } + + gettilt(args){ + var rxData = this._peripheral.rxData; + if(args.FBLR == '\uC88C\uC6B0') + return rxData[2]>127? rxData[2]-256 : rxData[2]; + else + return rxData[3]>127? rxData[3]-256 : rxData[3]; + } + + getmove(args){ + var rxData = this._peripheral.rxData; + if(args.FBLR == '\uC88C\uC6B0'){ + var dist = (rxData[6]<<8) | rxData[5]; + return dist>0x7FFF? dist-0x10000 : dist; + + } + else{ + var dist = (rxData[8]<<8) | rxData[7]; + return dist>0x7FFF? dist-0x10000 : dist; + } + } +} + +module.exports = Scratch3JDCodeBlocks; diff --git a/src/extensions/scratch3_uglybot/index.js b/src/extensions/scratch3_uglybot/index.js new file mode 100644 index 00000000000..cd3494b003e --- /dev/null +++ b/src/extensions/scratch3_uglybot/index.js @@ -0,0 +1,614 @@ +const ArgumentType = require('../../extension-support/argument-type'); +const BlockType = require('../../extension-support/block-type'); +const log = require('../../util/log'); +const cast = require('../../util/cast'); +const formatMessage = require('format-message'); +const BLE = require('../../io/ble'); +const Base64Util = require('../../util/base64-util'); + + +// eslint-disable-next-line max-len +const blockIconURI = 'data:image/png;base64,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'; + + + +const BLETimeout = 4500; +const BLESendInterval = 20; +const BLEDataStoppedError = 'UglyBot extension stopped receiving data'; + + +const BLEUUID = { + service: 0x2263, + rxChar: '00000227-0000-1000-8000-00805f9b34fb', + txChar: '00000227-0000-1000-8000-00805f9b34fb' +}; + + +class UglyBot { + constructor (runtime, extensionId) { + this._runtime = runtime; + this._ble = null; + this._runtime.registerPeripheralExtension(extensionId, this); + this._extensionId = extensionId; + this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this)); + this._timeoutID = null; + this._busy = false; + this._busyTimeoutID = null; + this.reset = this.reset.bind(this); + this._onConnect = this._onConnect.bind(this); + this._onMessage = this._onMessage.bind(this); + this.rxData = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.txData = new Uint8Array(12); + this._sensors = { + button: 0, + ir: [0, 0, 0], + ultrasonic: 0, + joystick: [0, 0], + tilt: [0, 0], + sound: 0, + illum:0 + }; + } + + stopAll () { + for (let i = 0; i < 12; i++) + this.txData[i] = 0; + this.send(this.txData); + } + + scan () { + if (this._ble) { + this._ble.disconnect(); + } + this._ble = new BLE(this._runtime, this._extensionId, { + filters: [ + {services: [BLEUUID.service]} + ] + }, this._onConnect, this.reset); + } + + connect (id) { + if (this._ble) { + this._ble.connectPeripheral(id); + } + } + + disconnect () { + if (this._ble) { + this._ble.disconnect(); + } + this.reset(); + } + + reset () { + if (this._timeoutID) { + window.clearTimeout(this._timeoutID); + this._timeoutID = null; + } + } + + isConnected () { + + let connected = false; + if (this._ble) { + connected = this._ble.isConnected(); + } + return connected; + } + + send (message) { + if (!this.isConnected()) return; + + //console.log(message); + this._ble.write(BLEUUID.service, BLEUUID.txChar, message, 'ASCII', true).then( + () => { + this._busy = false; + window.clearTimeout(this._busyTimeoutID); + } + ); + } + + _onConnect () { + this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + _onMessage (strData) { + this.rxData = strData.split(','); + window.clearTimeout(this._timeoutID); + this._timeoutID = window.setTimeout( + () => this._ble.handleDisconnectError(BLEDataStoppedError), + BLETimeout + ); + } + + get getRxData() { + return this.rxData; + } + + get getTxData() { + return this.rxData; + } +} + +class Scratch3UglyBotBlocks { + static get EXTENSION_NAME () { + return 'UglyBot'; + } + static get EXTENSION_ID () { + return 'uglybot'; + } + + constructor (runtime) { + + this.runtime = runtime; + this._peripheral = new UglyBot(this.runtime, Scratch3UglyBotBlocks.EXTENSION_ID); + this.message = this._peripheral.txData; + this.tuneID = 0; + this.moveID = 0; + this.rotID = 0; + } + + + getInfo () { + return { + id: Scratch3UglyBotBlocks.EXTENSION_ID, + name: Scratch3UglyBotBlocks.EXTENSION_NAME, + blockIconURI: blockIconURI, + showStatusButton: true, + blocks: [ + { + opcode: 'led', + text: formatMessage({ + id: 'led', + default: '[RIGHTLEFT] LED [ONOFF]', + }), + blockType: BlockType.COMMAND, + arguments: { + RIGHTLEFT: { + type: ArgumentType.STRING, + menu: 'rightleft', + defaultValue: '\uC624\uB978\uCABD' + }, + ONOFF: { + type: ArgumentType.STRING, + menu: 'onoff', + defaultValue: '\uCF1C\uAE30' + } + } + }, + { + opcode: 'buzzer', + text: formatMessage({ + id: 'buzzer', + default: '[TUNE] \uC74C\uC744 [DELAY] \uCD08\uB3D9\uC548 \uC18C\uB9AC\uB0B4\uAE30', + }), + blockType: BlockType.COMMAND, + arguments: { + TUNE: { + type: ArgumentType.STRING, + menu: 'tune', + defaultValue: '\uB3C4' + }, + DELAY: { + type: ArgumentType.STRING, + menu: 'delay', + defaultValue: '1' + } + } + }, + { + opcode: 'motor', + text: formatMessage({ + id: 'motor', + default: '[RIGHTLEFT] \uBAA8\uD130\uB97C [TEXT] \uC138\uAE30\uB85C \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + RIGHTLEFT: { + type: ArgumentType.STRING, + menu: 'rightleft', + defaultValue: '\uC624\uB978\uCABD' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + + { + opcode: 'move', + text: formatMessage({ + id: 'move', + default: '[FRONTBACK] (\uC73C)\uB85C [TEXT] cm \uC774\uB3D9', + }), + blockType: BlockType.COMMAND, + arguments: { + FRONTBACK: { + type: ArgumentType.STRING, + menu: 'frontback', + defaultValue: '\uC55E' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + + { + opcode: 'rotate', + text: formatMessage({ + id: 'rotate', + default: '[ROTDIR] \uC73C\uB85C [TEXT] \uB3C4 \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + ROTDIR: { + type: ArgumentType.STRING, + menu: 'rotdir', + defaultValue: '\uC2DC\uACC4\uBC29\uD5A5' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + + { + opcode: 'servo', + text: formatMessage({ + id: 'servo', + default: '[ONETWO] \uC11C\uBCF4\uBAA8\uD130\uB97C [TEXT] \uB3C4 \uD68C\uC804', + }), + blockType: BlockType.COMMAND, + arguments: { + ONETWO: { + type: ArgumentType.STRING, + menu: 'onetwo', + defaultValue: '1\uBC88' + }, + TEXT: { + type: ArgumentType.STRING, + defaultValue: '0' + } + } + }, + { + opcode: 'irsensor', + text: formatMessage({ + id: 'irsensor', + default: '[LMR] \uC801\uC678\uC120 \uC13C\uC11C\uB97C [ONOFF]', + }), + blockType: BlockType.COMMAND, + arguments: { + LMR: { + type: ArgumentType.STRING, + menu: 'lmr', + defaultValue: '\uC67C\uCABD' + }, + ONOFF: { + type: ArgumentType.STRING, + menu: 'onoff', + defaultValue: '\uCF1C\uAE30' + } + } + }, + + '---', + + { + opcode: 'getbutton', + text: formatMessage({ + id: 'getbutton', + default: '\uBC84\uD2BC \uAC12', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'getirsensor', + text: formatMessage({ + id: 'getirsensor', + default: '[LMR] \uC801\uC678\uC120\uC13C\uC11C \uAC12', + }), + blockType: BlockType.REPORTER, + arguments: { + LMR: { + type: ArgumentType.STRING, + menu: 'lmr', + defaultValue: '\uC67C\uCABD' + }, + } + }, + { + opcode: 'getultrasonic', + text: formatMessage({ + id: 'getultrasonic', + default: '\uCD08\uC74C\uD30C\uC13C\uC11C \uAC12', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'getjoystic', + text: formatMessage({ + id: 'getjoystic', + default: '[FBLR] \uC870\uC774\uC2A4\uD2F1 \uAC12', + }), + blockType: BlockType.REPORTER, + arguments: { + FBLR: { + type: ArgumentType.STRING, + menu: 'fblr', + defaultValue: '\uC55E\uB4A4' + }, + } + }, + { + opcode: 'gettilt', + text: formatMessage({ + id: 'gettilt', + default: '[FBLR] \uAE30\uC6B8\uAE30 \uAC12', + }), + blockType: BlockType.REPORTER, + arguments: { + FBLR: { + type: ArgumentType.STRING, + menu: 'fblr', + defaultValue: '\uC55E\uB4A4' + }, + } + }, + { + opcode: 'getsound', + text: formatMessage({ + id: 'getsound', + default: '\uC18C\uB9AC\uC13C\uC11C \uAC12', + }), + blockType: BlockType.REPORTER, + }, + { + opcode: 'getillum', + text: formatMessage({ + id: 'getillum', + default: '\uC870\uB3C4\uC13C\uC11C \uAC12', + }), + blockType: BlockType.REPORTER, + } + ], + menus: { + rightleft: { + acceptReporters: true, + items: ['\uC624\uB978\uCABD', '\uC67C\uCABD'] //¿À¸¥ÂÊ ¿ÞÂÊ + }, + lmr: { + acceptReporters: true, + items: ['\uC67C\uCABD', '\uC911\uAC04', '\uC624\uB978\uCABD'] //¿ÞÂÊ Áß°£ ¿À¸¥ÂÊ + }, + frontback: { + acceptReporters: true, + items: ['\uC55E', '\uB4A4'] //¾Õ µÚ + }, + onoff: { + acceptReporters: true, + items: ['\uCF1C\uAE30', '\uB044\uAE30'] //Äѱ⠲ô±â + }, + tune: { + acceptReporters: true, + items: ['\uB3C4', '\uB808', '\uBBF8', '\uD30C', '\uC194', '\uB77C', '\uC2DC'] //µµ·¹¹ÌÆļֶó½Ã + }, + delay: { + acceptReporters: true, + items: ['0.5', '0.8', '1', '2', '3', '4', '5'] + }, + rotdir: { + acceptReporters: true, + items: ['\uC2DC\uACC4\uBC29\uD5A5', '\uBC18\uC2DC\uACC4\uBC29\uD5A5'] //½Ã°è¹æÇ⠹ݽðè¹æÇâ + }, + onetwo: { + acceptReporters: true, + items: ['1\uBC88', '2\uBC88'] // 1¹ø 2 + }, + fblr: { + acceptReporters: true, + items: ['\uC55E\uB4A4', '\uC88C\uC6B0'] // ¾ÕµÚ Á¿ì + }, + } + }; + } + led (args) { + if(args.ONOFF == '\uCF1C\uAE30'){ + if(args.RIGHTLEFT == '\uC624\uB978\uCABD') + this.message[0] |= 0x11; + else + this.message[0] |= 0x12; + } + else{ + if(args.RIGHTLEFT == '\uC624\uB978\uCABD') + this.message[0] &= ~0x01; + else + this.message[0] &= ~0x02; + } + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + buzzer(args){ + this.tuneID = (this.tuneID+1)%16; + if(args.TUNE == '\uB3C4') this.message[1] = 1; + if(args.TUNE == '\uB808') this.message[1] = 2; + if(args.TUNE == '\uBBF8') this.message[1] = 3; + if(args.TUNE == '\uD30C') this.message[1] = 4; + if(args.TUNE == '\uC194') this.message[1] = 5; + if(args.TUNE == '\uB77C') this.message[1] = 6; + if(args.TUNE == '\uC2DC') this.message[1] = 7; + this.message[1] |= (this.tuneID<<4); + + if(args.DELAY == '0.5') this.message[2] = 5; + if(args.DELAY == '0.8') this.message[2] = 8; + if(args.DELAY == '1') this.message[2] = 10; + if(args.DELAY == '2') this.message[2] = 20; + if(args.DELAY == '3') this.message[2] = 30; + if(args.DELAY == '4') this.message[2] = 40; + if(args.DELAY == '5') this.message[2] = 50; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + motor (args){ + if(args.RIGHTLEFT == '\uC624\uB978\uCABD') + this.message[3] = args.TEXT; + else + this.message[4] = args.TEXT; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + move (args){ + var value = args.TEXT; + if(args.FRONTBACK == '\uB4A4') + value *= -1; + + this.moveID = (this.moveID+1)%16; + value = value&0xFFF; + value |= (this.moveID<<12); + + this.message[6] = (value>>8); + this.message[5] = value&0xFF; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + rotate (args){ + var value = args.TEXT; + if(args.ROTDIR == '\uBC18\uC2DC\uACC4\uBC29\uD5A5') + value *= -1; + + this.rotID = (this.rotID+1)%16; + value = value&0xFFF; + value |= (this.rotID<<12); + + this.message[8] = (value>>8); + this.message[7] = value&0xFF; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + servo (args){ + if(args.ONETWO == '1\uBC88') + this.message[9] = args.TEXT; + else + this.message[10] = args.TEXT; + + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + irsensor (args){ + if(args.ONOFF == '\uCF1C\uAE30'){ + if(args.LMR == '\uC67C\uCABD') + this.message[11] |= 0x01; + else if(args.LMR == '\uC911\uAC04') + this.message[11] |= 0x02; + else + this.message[11] |= 0x04; + } + else{ + if(args.LMR == '\uC67C\uCABD') + this.message[11] &= ~0x01; + else if(args.LMR == '\uC911\uAC04') + this.message[11] &= ~0x02; + else + this.message[11] &= ~0x04; + } + this._peripheral.send(this.message); + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, BLESendInterval); + }); + } + + + + + getbutton(){ + var rxData = this._peripheral.rxData; + return rxData[0]; + } + + getirsensor(args){ + var rxData = this._peripheral.rxData; + if(args.LMR == '\uC67C\uCABD') + return rxData[1]; + if(args.LMR == '\uC911\uAC04') + return rxData[2]; + else + return rxData[3]; + } + + getultrasonic(){ + var rxData = this._peripheral.rxData; + return rxData[4]; + } + + getjoystic(args){ + var rxData = this._peripheral.rxData; + if(args.FBLR == '\uC55E\uB4A4') + return rxData[5]; + else + return rxData[6]; + } + + gettilt(args){ + var rxData = this._peripheral.rxData; + if(args.FBLR == '\uC55E\uB4A4') + return rxData[7]; + else + return rxData[8]; + } + + getsound(){ + var rxData = this._peripheral.rxData; + return rxData[9]; + } + + getillum(){ + var rxData = this._peripheral.rxData; + return rxData[10]; + } +} + +module.exports = Scratch3UglyBotBlocks; diff --git a/src/util/scratch-link-websocket.js b/src/util/scratch-link-websocket.js index 035cea566de..b35e349dc31 100644 --- a/src/util/scratch-link-websocket.js +++ b/src/util/scratch-link-websocket.js @@ -25,7 +25,7 @@ class ScratchLinkWebSocket { open () { switch (this._type) { case 'BLE': - this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/ble'); + this._ws = new WebSocket('ws://device-manager.scratch.mit.edu:20110/scratch/ble'); break; case 'BT': this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/bt');