From 2951f7ed1a0019d272adfbc5b14a54adef043a62 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 14 Dec 2023 15:39:30 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20=EB=AA=A8=EB=93=88id=EA=B0=92=20?= =?UTF-8?q?=ED=95=AD=EC=83=81=206=EC=9E=90=EB=A6=AC=20=EB=AA=A8=EB=91=90?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/hardwareLite/block_arduino_lite.js | 2 +- .../blocks/hardwareLite/block_choco_lite.js | 2 +- .../blocks/hardwareLite/block_hamster_lite.js | 4 +- .../hardwareLite/block_microbit2_lite.js | 2 +- .../hardwareLite/block_neo_cannon_lite.js | 2 +- .../blocks/hardwareLite/block_neo_lite.js | 98 +++++++++---------- .../hardwareLite/block_neo_spider_lite.js | 2 +- .../hardwareLite/block_neobot_purple_lite.js | 2 +- .../hardwareLite/block_neobot_soco_lite.js | 2 +- .../block_neobot_thinkcar_lite.js | 2 +- .../hardwareLite/block_sensorboard_lite.js | 2 +- src/playground/blocks/hardwareLite/index.js | 10 +- 12 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_arduino_lite.js b/src/playground/blocks/hardwareLite/block_arduino_lite.js index e40b0a2b95..b3fac7c516 100644 --- a/src/playground/blocks/hardwareLite/block_arduino_lite.js +++ b/src/playground/blocks/hardwareLite/block_arduino_lite.js @@ -10,7 +10,7 @@ import _range from 'lodash/range'; (function() { Entry.ArduinoLite = new (class ArduinoLite { constructor() { - this.id = ['1.1', '4.2', '8.1']; + this.id = ['010101', '4.2', '8.1']; this.name = 'ArduinoLite'; this.url = 'http://www.arduino.cc/'; this.imageName = 'arduinolite.png'; diff --git a/src/playground/blocks/hardwareLite/block_choco_lite.js b/src/playground/blocks/hardwareLite/block_choco_lite.js index 5783b32579..26e12f926a 100644 --- a/src/playground/blocks/hardwareLite/block_choco_lite.js +++ b/src/playground/blocks/hardwareLite/block_choco_lite.js @@ -4,7 +4,7 @@ const Buffer = require('buffer').Buffer; (function() { Entry.ChocoLite = new (class ChocoLite { constructor() { - this.id = '45.1'; + this.id = '450101'; this.name = 'ChocoLite'; this.url = 'http://jjomulrak.com'; this.imageName = 'chocolite.png'; diff --git a/src/playground/blocks/hardwareLite/block_hamster_lite.js b/src/playground/blocks/hardwareLite/block_hamster_lite.js index 37f1aab5b4..994f62523e 100644 --- a/src/playground/blocks/hardwareLite/block_hamster_lite.js +++ b/src/playground/blocks/hardwareLite/block_hamster_lite.js @@ -1,6 +1,6 @@ 'use strict'; -(function () { +(function() { const COLOR_TO_RGB = [ [0, 0, 0], [0, 0, 255], @@ -14,7 +14,7 @@ Entry.HamsterLite = new (class HamsterLite { constructor() { - this.id = '2.4'; + this.id = '020401'; this.url = 'http://www.robomation.net'; this.imageName = 'hamsterlite.png'; this.name = 'HamsterLite'; diff --git a/src/playground/blocks/hardwareLite/block_microbit2_lite.js b/src/playground/blocks/hardwareLite/block_microbit2_lite.js index 0b86d4da22..97de1cd0e0 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2_lite.js @@ -188,7 +188,7 @@ const EVENT_INTERVAL = 150; // Image.SNAKE, '99000:99099:09090:09990:00000', ]; - this.id = '22.3'; + this.id = '220301'; this.url = 'http://microbit.org/ko/'; this.imageName = 'microbit2lite.png'; this.title = { diff --git a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js index 246710f81a..3fbfd677d3 100644 --- a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js @@ -3,7 +3,7 @@ (function() { Entry.NeoCannonLite = new (class NeoCannonLite { constructor() { - this.id = '41.2'; + this.id = '410201'; this.name = 'NeoCannonLite'; this.url = 'http://www.neo3ds.com/'; this.imageName = 'neocannonlite.png'; diff --git a/src/playground/blocks/hardwareLite/block_neo_lite.js b/src/playground/blocks/hardwareLite/block_neo_lite.js index 97ad84ceac..a8113a67a7 100644 --- a/src/playground/blocks/hardwareLite/block_neo_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_lite.js @@ -171,7 +171,7 @@ Entry.NeoLite = new (class NeoLite { constructor() { - this.id = '5.8'; + this.id = '050801'; this.name = 'NeoLite'; this.url = 'http://neobot.co.kr/'; this.imageName = 'neo_lite.png'; @@ -493,8 +493,8 @@ this.sensorValues.in3Values = [analogValue, 0, 0, 0]; } } else if ( - sensorDataKind === SensorKind.DIGITAL || - sensorDataKind === SensorKind.COLOR + sensorDataKind === SensorKind.DIGITAL || + sensorDataKind === SensorKind.COLOR ) { const value1 = buffer.readInt16LE(0); const value2 = buffer.readInt16LE(2); @@ -1176,7 +1176,7 @@ // servo neo_lite_servo_title: 'Servo motor', neo_lite_servo_reset: - 'Reset the current position of %1 servo motor to 0 degree %2', + 'Reset the current position of %1 servo motor to 0 degree %2', neo_lite_servo_angle: 'Change servo angle %1 %2 %3 %4', neo_lite_servo_angle_var: 'Change servo angle %1 %2 %3 %4', neo_lite_servo_angle_wait: 'Wait to change servo angle %1 %2 %3 %4', @@ -1194,7 +1194,7 @@ neo_lite_color_led_on: 'Turn on the color LED %1 %2 %3 %4', neo_lite_color_led_off: 'Turn off the color LED %1 %2', neo_lite_color_led_on_with_sensor: - 'Turn on the color LED %2 with color sensor %1 %3', + 'Turn on the color LED %2 with color sensor %1 %3', // set output neo_lite_set_output_title: 'Set output', @@ -2032,15 +2032,15 @@ script.block_id = blockId; if (speed.indexOf('IN') >= 0) { this.requestExtCommand( - blockId, - NeoBlockType.AUTO_DRIVING_SENSOR_START, - [sensor, speed] + blockId, + NeoBlockType.AUTO_DRIVING_SENSOR_START, + [sensor, speed] ); } else { this.requestCommand( - blockId, - NeoBlockType.AUTO_DRIVING_SENSOR_START, - [sensor, speed] + blockId, + NeoBlockType.AUTO_DRIVING_SENSOR_START, + [sensor, speed] ); } } else if (script.exec_phase === ExecPhase.PENDING_RESPONSE) { @@ -5473,8 +5473,8 @@ data.writeInt16LE(robotCommand, 2); body.push(...data); } else if ( - type === NeoBlockType.MOTOR_STOP || - type === NeoBlockType.AUTO_DRIVING_STOP + type === NeoBlockType.MOTOR_STOP || + type === NeoBlockType.AUTO_DRIVING_STOP ) { const which = params[0]; const direction = 1; @@ -5523,9 +5523,9 @@ const output = params[0]; body.push(this.getUnitId(output), ActorKind.SERVO, ServoCommand.STOP); } else if ( - type === NeoBlockType.LINE_TRACER_START || - type === NeoBlockType.AUTO_DRIVING_START || - type === NeoBlockType.AUTO_DETECT_WALL_START + type === NeoBlockType.LINE_TRACER_START || + type === NeoBlockType.AUTO_DRIVING_START || + type === NeoBlockType.AUTO_DETECT_WALL_START ) { const speed = params[0]; body.push(UnitId.CONTROLLER, ActorKind.CONTROLLER, ControllerCommand.ROBOT); @@ -5577,9 +5577,9 @@ } else if (type === NeoBlockType.AUTO_DETECT_WALL_TURN) { const direction = params[0]; body.push( - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.AUTO_DETECT_WALL + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.AUTO_DETECT_WALL ); const data = Buffer.from([0, 0, 0, 0]); data.writeInt16LE(60, 0); @@ -5667,11 +5667,11 @@ const which = params[0]; const unitId = this.getUnitId(params[1]); body.push( - PduCode.EXTEND_1, - blockId, - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.MOTOR + PduCode.EXTEND_1, + blockId, + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.MOTOR ); const data = Buffer.from([unitId, 0, which, 0, 0, 0]); body.push(...data); @@ -5679,18 +5679,18 @@ const robotCommand = params[0]; const unitId = this.getUnitId(params[1]); body.push( - PduCode.EXTEND_1, - blockId, - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.ROBOT + PduCode.EXTEND_1, + blockId, + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.ROBOT ); const data = Buffer.from([unitId, 0, 0, 0]); data.writeInt16LE(robotCommand, 2); body.push(...data); } else if ( - type === NeoBlockType.SERVO_ANGLE || - type === NeoBlockType.SERVO_ANGLE_WAIT + type === NeoBlockType.SERVO_ANGLE || + type === NeoBlockType.SERVO_ANGLE_WAIT ) { const unitId = this.getUnitId(params[0]); let angle = params[1]; @@ -5709,17 +5709,17 @@ data.writeInt16LE(direction, 2); body.push(...data); } else if ( - type === NeoBlockType.LINE_TRACER_START || - type === NeoBlockType.AUTO_DRIVING_START || - type === NeoBlockType.AUTO_DETECT_WALL_START + type === NeoBlockType.LINE_TRACER_START || + type === NeoBlockType.AUTO_DRIVING_START || + type === NeoBlockType.AUTO_DETECT_WALL_START ) { const unitId = this.getUnitId(params[0]); body.push( - PduCode.EXTEND_1, - blockId, - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.ROBOT + PduCode.EXTEND_1, + blockId, + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.ROBOT ); const data = Buffer.from([unitId, 0, 0x10, 0]); body.push(...data); @@ -5727,11 +5727,11 @@ const sensor = params[0]; const unitId = this.getUnitId(params[1]); body.push( - PduCode.EXTEND_1, - blockId, - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.ROBOT + PduCode.EXTEND_1, + blockId, + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.ROBOT ); const data = Buffer.from([0, 0, 0, 0]); data.writeInt16LE(unitId, 0); @@ -5752,11 +5752,11 @@ } else if (type === NeoBlockType.BUZZER_WITH_SENSOR) { const sensorUnitId = this.getUnitId(params[0]); body.push( - PduCode.EXTEND_1, - blockId, - UnitId.CONTROLLER, - ActorKind.CONTROLLER, - ControllerCommand.BUZZER + PduCode.EXTEND_1, + blockId, + UnitId.CONTROLLER, + ActorKind.CONTROLLER, + ControllerCommand.BUZZER ); const data = Buffer.from([0, 0]); data.writeInt16LE(sensorUnitId, 0); diff --git a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js index efc7e93019..b938fd5537 100644 --- a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js @@ -3,7 +3,7 @@ (function() { Entry.NeoSpiderLite = new (class NeoSpiderLite { constructor() { - this.id = '41.1'; + this.id = '410101'; this.name = 'NeoSpiderLite'; this.url = 'http://www.neo3ds.com/'; this.imageName = 'neospiderlite.png'; diff --git a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js index add61313a3..74d91cfcb8 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js @@ -3,7 +3,7 @@ const HEADER = [0xab, 0xcd]; Entry.NeobotPurpleLite = new (class NeobotPurpleLite { constructor() { - this.id = '5.5'; + this.id = '050501'; this.name = 'NeobotPurpleLite'; this.url = 'http://neobot.co.kr/'; this.imageName = 'neobot_purple_lite.png'; diff --git a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js index de221e2bd7..f31c1f1cd7 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js @@ -3,7 +3,7 @@ const HEADER = [0xab, 0xcd]; Entry.NeobotSocoLite = new (class NeobotSocoLite { constructor() { - this.id = '5.6'; + this.id = '050601'; this.name = 'NeobotSocoLite'; this.url = 'http://neobot.co.kr/'; this.imageName = 'neobot_soco_lite.png'; diff --git a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js index aba040d809..dd218bfd7e 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js @@ -4,7 +4,7 @@ Entry.NeobotThinkCarLite = new (class NeobotThinkCarLite { constructor() { - this.id = '5.7'; + this.id = '050701'; this.name = 'NeobotThinkCarLite'; this.url = 'http://neobot.co.kr/'; this.imageName = 'neobot_thinkcar_lite.png'; diff --git a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js index 2443ccb444..0cec847cc7 100644 --- a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js +++ b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js @@ -5,7 +5,7 @@ import _range from 'lodash/range'; (function() { Entry.SensorboardLite = new (class SensorboardLite { constructor() { - this.id = '1.2'; + this.id = '010201'; this.name = 'SensorboardLite'; this.url = 'http://www.neweducation.co.kr/'; this.imageName = 'sensorboardlite.png'; diff --git a/src/playground/blocks/hardwareLite/index.js b/src/playground/blocks/hardwareLite/index.js index 46f35b9000..cfdf3329fd 100644 --- a/src/playground/blocks/hardwareLite/index.js +++ b/src/playground/blocks/hardwareLite/index.js @@ -28,15 +28,19 @@ function initHardwareLiteList() { metaDataListReq.keys().forEach((fileName) => { const metaData = metaDataListReq(fileName); - const moduleId = `${metaData.moduleId.substring(0, 2).replace(/(^0+)/, "")}.${metaData.moduleId.substring(2, 4).replace(/(^0+)/, "")}`; + // const moduleId = `${metaData.moduleId.substring(0, 2).replace(/(^0+)/, "")}.${metaData.moduleId.substring(2, 4).replace(/(^0+)/, "")}`; + const moduleId = metaData.moduleId; if (Entry.HARDWARE_LITE_LIST[moduleId]) { Entry.HARDWARE_LITE_LIST[moduleId].title = { ko: metaData.title }; Entry.HARDWARE_LITE_LIST[moduleId].description = metaData.description; - Entry.HARDWARE_LITE_LIST[moduleId].linkBox = { desc: '고객센터', url: Entry.HARDWARE_LITE_LIST[moduleId].url } + Entry.HARDWARE_LITE_LIST[moduleId].linkBox = { + desc: '고객센터', + url: Entry.HARDWARE_LITE_LIST[moduleId].url, + }; } else { console.error(`Error, HardwareLiteID ${moduleId} not contain module`); - }; + } }); } From 2562b5fb4bead43c561f89e436bb5a00fe1be840 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Tue, 19 Dec 2023 15:33:51 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=81=AC?= =?UTF-8?q?=EB=A1=9C=EB=B9=84=ED=8A=B8=20=ED=8E=8C=EC=9B=A8=EC=96=B4=20?= =?UTF-8?q?=ED=94=8C=EB=9E=98=EC=8B=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TODO: webusb 로직과 마이크로비트 종속 로직 분리 필요 --- package.json | 2 + pnpm-lock.yaml | 15 +++ src/class/hardware/webUsbFlasher.ts | 185 ++++++++++++++++++++++++++++ src/class/hw_lite.ts | 32 +++++ src/command/commands/object.js | 5 + src/util/hardwareUtils.ts | 6 + types/index.d.ts | 2 + 7 files changed, 247 insertions(+) create mode 100644 src/class/hardware/webUsbFlasher.ts create mode 100644 src/util/hardwareUtils.ts diff --git a/package.json b/package.json index 9409c1ea3f..43986a601b 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,8 @@ "@types/node": "^14.0.11", "@types/pixi.js": "5.0.0", "@types/socket.io-client": "^1.4.33", + "@types/w3c-web-usb": "^1.0.8", + "@types/web-bluetooth": "^0.0.18", "@types/webpack-env": "^1.15.2", "@typescript-eslint/parser": "^3.1.0", "babel-loader": "^8.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b738a721c..921483bd97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,12 @@ devDependencies: '@types/socket.io-client': specifier: ^1.4.33 version: 1.4.33 + '@types/w3c-web-usb': + specifier: ^1.0.8 + version: 1.0.10 + '@types/web-bluetooth': + specifier: ^0.0.18 + version: 0.0.18 '@types/webpack-env': specifier: ^1.15.2 version: 1.15.2 @@ -2206,6 +2212,14 @@ packages: '@types/createjs-lib': 0.0.29 dev: true + /@types/w3c-web-usb@1.0.10: + resolution: {integrity: sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==} + dev: true + + /@types/web-bluetooth@0.0.18: + resolution: {integrity: sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw==} + dev: true + /@types/webgl-ext@0.0.30: resolution: {integrity: sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==} dev: false @@ -3889,6 +3903,7 @@ packages: /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + requiresBuild: true /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} diff --git a/src/class/hardware/webUsbFlasher.ts b/src/class/hardware/webUsbFlasher.ts new file mode 100644 index 0000000000..d5c09b4ece --- /dev/null +++ b/src/class/hardware/webUsbFlasher.ts @@ -0,0 +1,185 @@ +import { stringToUint8Array } from '../../util/hardwareUtils'; + +const filters = [ + { + vendorId: 3368, + productId: 516, + classCode: 255, + subclassCode: 3, + }, + { + vendorId: 3368, + productId: 516, + classCode: 255, + subclassCode: 0, + }, +]; + +export default class WebUsbFlasher { + private device: USBDevice; + private isTransfer: boolean; + private claimInterface: number; + private endpointNumber: number; + private flashingPercent: number; + + private flashState: string; + constructor() { + this.isTransfer = false; + this.claimInterface = -1; + this.endpointNumber = -1; + } + + async flashFirmware(firmwareUrl: string, moduleId: string) { + try { + const response = await fetch(firmwareUrl); + const hexData = await response.text(); + const data = stringToUint8Array(hexData); + this.device = await navigator.usb.requestDevice({ + filters: [{ vendorId: 0x0d28 }], + }); + await this.device.open(); + // selectConfiguration 체크 + await this.device.selectConfiguration(1); + this.findInterface(); + await this.device.claimInterface(this.claimInterface); + + // store.set(flashStateAtom, 'start'); + this.flashState = 'start'; + const result = await this.writeData([0x8a, 1]); + if (result[1] !== 0) { + throw Error('device open failed'); + } + let chunkSize = 62; + let offset = 0; + let sentPages = 0; + + this.flashState = 'flashing'; + // store.set(flashStateAtom, 'flashing'); + // webApi타입 ble인 block 파일에 넣어줘야 할듯 + while (offset < data.length) { + const end = Math.min(data.length, offset + chunkSize); + const nextPageData = data.slice(offset, end); + const cmdData = new Uint8Array(2 + nextPageData.length); + cmdData[0] = 0x8c; + cmdData[1] = nextPageData.length; + cmdData.set(nextPageData, 2); + // TODO: 퍼센트 로직도 분리하기 + if (sentPages % 128 == 0) { + this.flashingPercent = (offset / data.length) * 100; + // store.set(percentAtom, (offset / data.length) * 100); + console.log(this.flashingPercent); + } + await this.writeBuffer(cmdData); + sentPages++; + offset = end; + } + this.flashingPercent = (offset / data.length) * 100; + console.log(this.flashingPercent); + // store.set(percentAtom, (offset / data.length) * 100); + this.flashState = 'end'; + // store.set(flashStateAtom, 'end'); + + // close + const flashResult = await this.writeData([0x8b]); + if (flashResult[1] !== 0) { + throw Error('flash failed'); + } + + // reset + await this.writeData([0x89]); + } finally { + this.flashState = 'idle'; + // store.set(flashStateAtom, 'idle'); + } + } + + // study: 좀 더 확인이 필요 + findInterface() { + const filteredInterfaces = this.device.configurations[0].interfaces.filter( + (interfaceItem) => { + const alternateInterface = interfaceItem.alternates[0]; + + for (const filter of filters) { + if ( + (filter.classCode === null || + alternateInterface.interfaceClass === filter.classCode) && + !( + filter.subclassCode !== null && + alternateInterface.interfaceSubclass !== filter.subclassCode + ) + ) { + if (alternateInterface.endpoints.length === 0) { + return true; + } + if ( + alternateInterface.endpoints.length === 2 && + alternateInterface.endpoints.every( + (endpoint) => endpoint.packetSize === 64 + ) + ) { + return true; + } + } + } + + return false; + } + ); + + const iface = filteredInterfaces[filteredInterfaces.length - 1]; + const altIface = iface.alternates[0]; + if (altIface.endpoints.length) { + // study: 얘도 뭐지? + this.isTransfer = true; + const epIn = altIface.endpoints.filter((e) => 'in' == e.direction)[0]; + this.endpointNumber = epIn.endpointNumber; + } + this.claimInterface = iface.interfaceNumber; + } + + async transfer(data: Uint8Array) { + if (this.isTransfer) { + await this.device.transferOut(this.endpointNumber, new Uint8Array(data)); + return await this.device.transferIn(this.endpointNumber, 64); + } else { + await this.device.controlTransferOut( + { + requestType: 'class', + recipient: 'interface', + request: 9, + value: 512, + index: this.claimInterface, + }, + data + ); + + return await this.device.controlTransferIn( + { + requestType: 'class', + recipient: 'interface', + request: 1, + value: 256, + index: this.claimInterface, + }, + 64 + ); + } + } + + // TODO: buffer랑 합치기? + async writeData(data: Array): Promise { + const response = await this.transfer(new Uint8Array(data)); + if (!response.data?.buffer) { + throw Error('writeData failed'); + } + return new Uint8Array(response.data.buffer); + } + + async writeBuffer(buffer: Uint8Array): Promise { + const response = await this.transfer(buffer); + if (!response.data?.buffer) { + throw Error('writeData failed'); + } + return new Uint8Array(response.data.buffer); + } +} diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index 2b23fd627a..ba74e47504 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -2,6 +2,9 @@ import throttle from 'lodash/throttle'; import ExtraBlockUtils from '../util/extrablockUtils'; import HardwareMonitor from './hardware/hardwareMonitor'; import { EntryHardwareLiteBlockModule } from '../../types/index'; +import WebUsbFlasher from './hardware/webUsbFlasher'; +import WebSerialConnector from './hardware/webSerialConnector'; +import WebBlueToothConnect from './hardware/webBluetoothConnector'; enum HardwareStatement { disconnected = 'disconnected', @@ -42,6 +45,8 @@ export default class HardwareLite { private port: any; // serialport private writer: any; // SerialPort.writer; private reader: any; //SerialPort.reader; + private webConnector: any; // TODO: 추후 WebBlueToothConnect | WebSerialConnector로 변경예정 + private flasher: WebUsbFlasher; private writableStream: any; hwModule: EntryHardwareLiteBlockModule; static setExternalModule: any; @@ -477,6 +482,33 @@ export default class HardwareLite { } } } + + setWebConnector() { + const webapiType = this.hwModule.webapiType; + + switch (webapiType) { + case 'ble': { + this.webConnector = new WebBlueToothConnect(); + break; + } + case 'serial': + case undefined: { + this.webConnector = new WebSerialConnector(); + } + } + } + removeWebConnector() { + this.webConnector = undefined; + } + + setFlasher() { + if (this.hwModule.firmwareFlash) { + this.flasher = new WebUsbFlasher(); + } + } + removeFlasher() { + this.flasher = undefined; + } } Entry.HWLite = HardwareLite; diff --git a/src/command/commands/object.js b/src/command/commands/object.js index e6d8bdd7f8..61dba51f60 100644 --- a/src/command/commands/object.js +++ b/src/command/commands/object.js @@ -5,6 +5,7 @@ const { returnEmptyArr, createTooltip } = require('../command_util'); import VideoUtils from '../../util/videoUtils'; +import WebUsbFlasher from '../../class/hardware/webUsbFlasher'; (function(c) { const COMMAND_TYPES = Entry.STATIC.COMMAND_TYPES; @@ -320,6 +321,8 @@ import VideoUtils from '../../util/videoUtils'; } Entry.hardwareLiteBlocks = _.union(Entry.hardwareLiteBlocks, [module.id]); Entry.hwLite.setExternalModule(module); + Entry.hwLite.setWebConnector(); + Entry.hwLite.setFlasher(); }, state(module) { return [module]; @@ -337,6 +340,8 @@ import VideoUtils from '../../util/videoUtils'; do(module) { Entry.hardwareLiteBlocks = []; Entry.hwLite.disconnect(); + Entry.hwLite.removeConnector(); + Entry.hwLite.removeFlasher(); }, state(module) { return [module]; diff --git a/src/util/hardwareUtils.ts b/src/util/hardwareUtils.ts new file mode 100644 index 0000000000..dd35afe247 --- /dev/null +++ b/src/util/hardwareUtils.ts @@ -0,0 +1,6 @@ +export const stringToUint8Array = (e: string): Uint8Array => { + let t = e.length; + let n = new Uint8Array(t); + for (let i = 0; i < t; ++i) n[i] = 255 & e.charCodeAt(i); + return n; +}; diff --git a/types/index.d.ts b/types/index.d.ts index 3265c876a1..64eb0bbdf5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -226,6 +226,8 @@ export declare interface EntryHardwareLiteBlockModule extends EntryBlockModule { }; type?: 'master' | 'slave'; delimeter?: string | number; + webapiType?: 'ble' | 'usb' | 'serial'; + firmwareFlash?: boolean; // 필수 함수 목록 setZero: () => void; From 7d928cf2f6319642f3021208fb7fdf1fafa8ddd5 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Wed, 20 Dec 2023 16:49:25 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20bluetooth=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bluetoothServices/event-dispatcher.ts | 96 +++++++ src/class/hardware/bluetoothServices/index.ts | 31 ++ .../bluetoothServices/promise-queue.ts | 79 +++++ .../bluetoothServices/service-helper.ts | 113 ++++++++ .../services/accelerometer.ts | 163 +++++++++++ .../bluetoothServices/services/button.ts | 144 ++++++++++ .../services/device-information.ts | 137 +++++++++ .../bluetoothServices/services/dfu-control.ts | 82 ++++++ .../bluetoothServices/services/event.ts | 182 ++++++++++++ .../bluetoothServices/services/io-pin.ts | 269 ++++++++++++++++++ .../bluetoothServices/services/led.ts | 154 ++++++++++ .../services/magnetometer.ts | 245 ++++++++++++++++ .../bluetoothServices/services/temperature.ts | 131 +++++++++ .../bluetoothServices/services/uart.ts | 133 +++++++++ 14 files changed, 1959 insertions(+) create mode 100755 src/class/hardware/bluetoothServices/event-dispatcher.ts create mode 100644 src/class/hardware/bluetoothServices/index.ts create mode 100755 src/class/hardware/bluetoothServices/promise-queue.ts create mode 100755 src/class/hardware/bluetoothServices/service-helper.ts create mode 100755 src/class/hardware/bluetoothServices/services/accelerometer.ts create mode 100755 src/class/hardware/bluetoothServices/services/button.ts create mode 100755 src/class/hardware/bluetoothServices/services/device-information.ts create mode 100755 src/class/hardware/bluetoothServices/services/dfu-control.ts create mode 100755 src/class/hardware/bluetoothServices/services/event.ts create mode 100755 src/class/hardware/bluetoothServices/services/io-pin.ts create mode 100755 src/class/hardware/bluetoothServices/services/led.ts create mode 100755 src/class/hardware/bluetoothServices/services/magnetometer.ts create mode 100755 src/class/hardware/bluetoothServices/services/temperature.ts create mode 100755 src/class/hardware/bluetoothServices/services/uart.ts diff --git a/src/class/hardware/bluetoothServices/event-dispatcher.ts b/src/class/hardware/bluetoothServices/event-dispatcher.ts new file mode 100755 index 0000000000..cc6181cde2 --- /dev/null +++ b/src/class/hardware/bluetoothServices/event-dispatcher.ts @@ -0,0 +1,96 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventEmitter } from 'events'; + +/** + * @hidden + */ +export interface TypedDispatcher { + addEventListener( + type: K, + listener: (event: CustomEvent) => void + ): void; + removeEventListener( + type: K, + callback: (event: CustomEvent) => void + ): void; + dispatchEvent(event: CustomEvent): boolean; + dispatchEvent(type: K, detail: T[K]): boolean; + addListener(event: K, listener: (data: T[K]) => void): this; + on(event: K, listener: (data: T[K]) => void): this; + once(event: K, listener: (data: T[K]) => void): this; + prependListener(event: K, listener: (data: T[K]) => void): this; + prependOnceListener(event: K, listener: (data: T[K]) => void): this; + removeListener(event: K, listener: (data: T[K]) => void): this; + removeAllListeners(event?: K): this; + // tslint:disable-next-line:ban-types + listeners(event: K): Function[]; + emit(event: K, data: T[K]): boolean; + // tslint:disable-next-line:array-type + eventNames(): Array; + listenerCount(type: K): number; + setMaxListeners(n: number): this; + getMaxListeners(): number; +} + +/** + * @hidden + */ +export class EventDispatcher extends EventEmitter implements EventTarget { + private isEventListenerObject = ( + listener: EventListenerOrEventListenerObject + ): listener is EventListenerObject => + (listener as EventListenerObject).handleEvent !== undefined; + + public addEventListener(type: string, listener: EventListenerOrEventListenerObject | null) { + if (listener) { + const handler = this.isEventListenerObject(listener) ? listener.handleEvent : listener; + super.addListener(type, handler); + } + } + + public removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null) { + if (callback) { + const handler = this.isEventListenerObject(callback) ? callback.handleEvent : callback; + super.removeListener(type, handler); + } + } + + public dispatchEvent(event: Event): boolean; + public dispatchEvent(type: string, detail: T): boolean; + public dispatchEvent(eventOrType: Event | string, detail?: T): boolean { + let event: Event; + if (typeof eventOrType === 'string') { + event = new CustomEvent(eventOrType, { + detail, + }); + } else { + event = eventOrType; + } + + return super.emit(event.type, event); + } +} diff --git a/src/class/hardware/bluetoothServices/index.ts b/src/class/hardware/bluetoothServices/index.ts new file mode 100644 index 0000000000..6705f44c61 --- /dev/null +++ b/src/class/hardware/bluetoothServices/index.ts @@ -0,0 +1,31 @@ +// 사용하는 서비스 정리 + +import { AccelerometerService } from './services/accelerometer'; +import { ButtonService } from './services/button'; +import { DeviceInformationService } from './services/device-information'; +import { DfuControlService } from './services/dfu-control'; +import { EventService } from './services/event'; +import { IoPinService } from './services/io-pin'; +import { LedService } from './services/led'; +import { MagnetometerService } from './services/magnetometer'; +import { TemperatureService } from './services/temperature'; +import { UartService } from './services/uart'; + +export const getServiceClassesByModuleId = (moduleId: string) => { + switch (moduleId) { + case '220302': + return [ + DeviceInformationService, + ButtonService, + LedService, + TemperatureService, + AccelerometerService, + MagnetometerService, + UartService, + EventService, + DfuControlService, + IoPinService, + ]; + break; + } +}; diff --git a/src/class/hardware/bluetoothServices/promise-queue.ts b/src/class/hardware/bluetoothServices/promise-queue.ts new file mode 100755 index 0000000000..bcb1f60c1c --- /dev/null +++ b/src/class/hardware/bluetoothServices/promise-queue.ts @@ -0,0 +1,79 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @hidden + */ +interface QueuedPromise { + fn: () => Promise; + resolve: (value?: any | PromiseLike | undefined) => void; + reject: (reason?: any) => void; +} + +/** + * @hidden + */ +export class PromiseQueue { + private queue: QueuedPromise[] = []; + private running = 0; + + constructor(private concurrent = 1) {} + + private async pump(): Promise { + if (this.running >= this.concurrent) { + return; + } + + const promise = this.queue.shift(); + + if (!promise) { + return; + } + + this.running++; + + try { + const result = await promise.fn(); + promise.resolve(result); + } catch (error) { + promise.reject(error); + } + + this.running--; + return this.pump(); + } + + public add(fn: () => Promise): Promise { + return new Promise((resolve, reject) => { + this.queue.push({ + fn, + resolve, + reject, + }); + + return this.pump(); + }); + } +} diff --git a/src/class/hardware/bluetoothServices/service-helper.ts b/src/class/hardware/bluetoothServices/service-helper.ts new file mode 100755 index 0000000000..20b2245463 --- /dev/null +++ b/src/class/hardware/bluetoothServices/service-helper.ts @@ -0,0 +1,113 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventEmitter } from 'events'; +import { PromiseQueue } from './promise-queue'; + +/** + * @hidden + */ +export interface ServiceEventHandler { + characteristic: BluetoothCharacteristicUUID; + handler: (event: Event) => void; +} + +/** + * @hidden + */ +export class ServiceHelper { + private static queue = new PromiseQueue(); + + private characteristics?: BluetoothRemoteGATTCharacteristic[]; + + constructor(private service: BluetoothRemoteGATTService, private emitter?: EventEmitter) {} + + private async getCharacteristic( + uuid: BluetoothCharacteristicUUID + ): Promise { + if (!this.characteristics) { + this.characteristics = await this.service.getCharacteristics(); + } + + return this.characteristics.find((characteristic) => characteristic.uuid === uuid); + } + + public async getCharacteristicValue(uuid: BluetoothCharacteristicUUID): Promise { + const characteristic = await this.getCharacteristic(uuid); + + if (!characteristic) { + throw new Error('Unable to locate characteristic'); + } + + return await ServiceHelper.queue.add(async () => characteristic.readValue()); + } + + public async setCharacteristicValue( + uuid: BluetoothCharacteristicUUID, + value: BufferSource + ): Promise { + const characteristic = await this.getCharacteristic(uuid); + + if (!characteristic) { + throw new Error('Unable to locate characteristic'); + } + + await ServiceHelper.queue.add(async () => characteristic.writeValue(value)); + } + + public async handleListener( + event: string, + uuid: BluetoothCharacteristicUUID, + handler: (event: Event) => void + ) { + const characteristic = await this.getCharacteristic(uuid); + + if (!characteristic) { + return; + } + + await ServiceHelper.queue.add(async () => characteristic.startNotifications()); + + this.emitter!.on('newListener', (emitterEvent: string) => { + if (emitterEvent !== event || this.emitter!.listenerCount(event) > 0) { + return; + } + + return ServiceHelper.queue.add(async () => + characteristic.addEventListener('characteristicvaluechanged', handler) + ); + }); + + this.emitter!.on('removeListener', (emitterEvent: string) => { + if (emitterEvent !== event || this.emitter!.listenerCount(event) > 0) { + return; + } + + return ServiceHelper.queue.add(async () => + characteristic.removeEventListener('characteristicvaluechanged', handler) + ); + }); + } +} diff --git a/src/class/hardware/bluetoothServices/services/accelerometer.ts b/src/class/hardware/bluetoothServices/services/accelerometer.ts new file mode 100755 index 0000000000..ab86a1a98e --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/accelerometer.ts @@ -0,0 +1,163 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum AccelerometerCharacteristic { + accelerometerData = 'e95dca4b-251d-470a-a062-fa1922dfa9a8', + accelerometerPeriod = 'e95dfb24-251d-470a-a062-fa1922dfa9a8', +} + +/** + * Data received from the accelerometer + */ +export interface AccelerometerData { + /** + * Force in direction X + */ + x: number; + /** + * Force in direction Y + */ + y: number; + /** + * Force in direction Z + */ + z: number; +} + +/** + * The sample period to read accelerometer data (milliseconds) + */ +export type AccelerometerPeriod = 1 | 2 | 5 | 10 | 20 | 80 | 160 | 640; + +/** + * Events raised by the accelerometer service + */ +export interface AccelerometerEvents { + /** + * @hidden + */ + newListener: keyof AccelerometerEvents; + /** + * @hidden + */ + removeListener: keyof AccelerometerEvents; + /** + * Accelerometer data changed event + */ + accelerometerdatachanged: AccelerometerData; +} + +/** + * Accelerometer Service + */ +export class AccelerometerService extends (EventDispatcher as new () => TypedDispatcher< + AccelerometerEvents +>) { + /** + * @hidden + */ + public static uuid = 'e95d0753-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new AccelerometerService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'accelerometerdatachanged', + AccelerometerCharacteristic.accelerometerData, + this.accelerometerDataChangedHandler.bind(this) + ); + } + + /** + * Read acceleromter data + */ + public async readAccelerometerData(): Promise { + const view = await this.helper.getCharacteristicValue( + AccelerometerCharacteristic.accelerometerData + ); + return this.dataViewToAccelerometerData(view); + } + + /** + * Get accelerometer sample period + */ + public async getAccelerometerPeriod(): Promise { + const view = await this.helper.getCharacteristicValue( + AccelerometerCharacteristic.accelerometerPeriod + ); + return view.getUint16(0, true) as AccelerometerPeriod; + } + + /** + * Set accelerometer sample period + * @param frequency The frequency interval to use + */ + public async setAccelerometerPeriod(frequency: AccelerometerPeriod): Promise { + const view = new DataView(new ArrayBuffer(2)); + view.setUint16(0, frequency, true); + return this.helper.setCharacteristicValue( + AccelerometerCharacteristic.accelerometerPeriod, + view + ); + } + + private accelerometerDataChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const value = this.dataViewToAccelerometerData(view); + this.dispatchEvent('accelerometerdatachanged', value); + } + + private dataViewToAccelerometerData(view: DataView): AccelerometerData { + return { + x: view.getInt16(0, true) / 1000.0, + y: view.getInt16(2, true) / 1000.0, + z: view.getInt16(4, true) / 1000.0, + }; + } +} diff --git a/src/class/hardware/bluetoothServices/services/button.ts b/src/class/hardware/bluetoothServices/services/button.ts new file mode 100755 index 0000000000..bad8b43f50 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/button.ts @@ -0,0 +1,144 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum ButtonCharacteristic { + buttonAState = 'e95dda90-251d-470a-a062-fa1922dfa9a8', + buttonBState = 'e95dda91-251d-470a-a062-fa1922dfa9a8', +} + +/** + * Button state enum + */ +export enum ButtonState { + /** + * Button released + */ + Release = 0, + /** + * Button pressed - short + */ + ShortPress = 1, + /** + * Button pressed - long + */ + LongPress = 2, +} + +/** + * Events raised by the button service + */ +export interface ButtonEvents { + /** + * @hidden + */ + newListener: keyof ButtonEvents; + /** + * @hidden + */ + removeListener: keyof ButtonEvents; + /** + * Button A state changed event + */ + buttonastatechanged: ButtonState; + /** + * Button B state changed event + */ + buttonbstatechanged: ButtonState; +} + +/** + * Button Service + */ +export class ButtonService extends (EventDispatcher as new () => TypedDispatcher) { + /** + * @hidden + */ + public static uuid = 'e95d9882-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new ButtonService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'buttonastatechanged', + ButtonCharacteristic.buttonAState, + this.buttonAStateChangedHandler.bind(this) + ); + await this.helper.handleListener( + 'buttonbstatechanged', + ButtonCharacteristic.buttonBState, + this.buttonBStateChangedHandler.bind(this) + ); + } + + /** + * Read state of button A + */ + public async readButtonAState(): Promise { + const view = await this.helper.getCharacteristicValue(ButtonCharacteristic.buttonAState); + return view.getUint8(0); + } + + /** + * Read state of button B + */ + public async readButtonBState(): Promise { + const view = await this.helper.getCharacteristicValue(ButtonCharacteristic.buttonBState); + return view.getUint8(0); + } + + private buttonAStateChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + this.dispatchEvent('buttonastatechanged', view.getUint8(0)); + } + + private buttonBStateChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + this.dispatchEvent('buttonbstatechanged', view.getUint8(0)); + } +} diff --git a/src/class/hardware/bluetoothServices/services/device-information.ts b/src/class/hardware/bluetoothServices/services/device-information.ts new file mode 100755 index 0000000000..f5ad3cb9dd --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/device-information.ts @@ -0,0 +1,137 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum DeviceInformationCharacteristic { + modelNumber = '00002a24-0000-1000-8000-00805f9b34fb', + serialNumber = '00002a25-0000-1000-8000-00805f9b34fb', + firmwareRevision = '00002a26-0000-1000-8000-00805f9b34fb', + hardwareRevision = '00002a27-0000-1000-8000-00805f9b34fb', + manufacturer = '00002a29-0000-1000-8000-00805f9b34fb', +} + +/** + * Device information structure + */ +export interface DeviceInformation { + /** + * Model Number + */ + modelNumber?: string; + /** + * Serial Number + */ + serialNumber?: string; + /** + * Firmware Revision + */ + firmwareRevision?: string; + /** + * Hardware Revision + */ + hardwareRevision?: string; + /** + * Manufacturer Name + */ + manufacturer?: string; +} + +/** + * Device Information Service + */ +export class DeviceInformationService { + /** + * @hidden + */ + public static uuid = '0000180a-0000-1000-8000-00805f9b34fb'; + + /** + * @hidden + */ + public static async create( + service: BluetoothRemoteGATTService + ): Promise { + return new DeviceInformationService(service); + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + this.helper = new ServiceHelper(service); + } + + /** + * Read device information + */ + public async readDeviceInformation(): Promise { + const info: DeviceInformation = {}; + + const modelNumber = await this.readStringCharacteristic( + DeviceInformationCharacteristic.modelNumber + ); + if (modelNumber) info.modelNumber = modelNumber; + + const serialNumber = await this.readStringCharacteristic( + DeviceInformationCharacteristic.serialNumber + ); + if (serialNumber) info.serialNumber = serialNumber; + + const firmwareRevision = await this.readStringCharacteristic( + DeviceInformationCharacteristic.firmwareRevision + ); + if (firmwareRevision) info.firmwareRevision = firmwareRevision; + + const hardwareRevision = await this.readStringCharacteristic( + DeviceInformationCharacteristic.hardwareRevision + ); + if (hardwareRevision) info.hardwareRevision = hardwareRevision; + + const manufacturer = await this.readStringCharacteristic( + DeviceInformationCharacteristic.manufacturer + ); + if (manufacturer) info.manufacturer = manufacturer; + + return info; + } + + private async readStringCharacteristic( + uuid: BluetoothCharacteristicUUID + ): Promise { + try { + const view = await this.helper.getCharacteristicValue(uuid); + const buffer = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); + return String.fromCharCode.apply(null, Array.from(new Uint8Array(buffer))); + } catch (_e) { + return undefined; + } + } +} diff --git a/src/class/hardware/bluetoothServices/services/dfu-control.ts b/src/class/hardware/bluetoothServices/services/dfu-control.ts new file mode 100755 index 0000000000..5f73043d1a --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/dfu-control.ts @@ -0,0 +1,82 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum DfuCharacteristic { + dfuControl = 'e95d93b1-251d-470a-a062-fa1922dfa9a8', +} + +/** + * @hidden + */ +export class DfuControlService { + /** + * @hidden + */ + public static uuid = 'e95d93b0-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + return new DfuControlService(service); + } + + /** + * @hidden + */ + public helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + this.helper = new ServiceHelper(service); + } + + /** + * Request device switches to DFU mode + */ + public requestDfu(): Promise { + return this.helper.setCharacteristicValue( + DfuCharacteristic.dfuControl, + new Uint8Array([1]) + ); + } + + /** + * Request flash code + */ + public requestFlashCode(): Promise { + return this.helper.setCharacteristicValue( + DfuCharacteristic.dfuControl, + new Uint8Array([2]) + ); + } +} diff --git a/src/class/hardware/bluetoothServices/services/event.ts b/src/class/hardware/bluetoothServices/services/event.ts new file mode 100755 index 0000000000..584b447e77 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/event.ts @@ -0,0 +1,182 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum EventCharacteristic { + microBitRequirements = 'e95db84c-251d-470a-a062-fa1922dfa9a8', + microBitEvent = 'e95d9775-251d-470a-a062-fa1922dfa9a8', + clientRequirements = 'e95d23c4-251d-470a-a062-fa1922dfa9a8', + clientEvent = 'e95d5404-251d-470a-a062-fa1922dfa9a8', +} + +/** + * micro:bit event + */ +export interface MicrobitEvent { + /** + * The type of event + */ + type: number; + /** + * The value for the event + */ + value: number; +} + +/** + * Events raised by the event service + */ +export interface MicrobitEvents { + /** + * @hidden + */ + newListener: keyof MicrobitEvents; + /** + * @hidden + */ + removeListener: keyof MicrobitEvents; + /** + * micro:bit requirements changed event + */ + microbitrequirementschanged: MicrobitEvent; + /** + * micro:bit event event + */ + microbitevent: MicrobitEvent; +} + +/** + * Event Service + */ +export class EventService extends (EventDispatcher as new () => TypedDispatcher) { + /** + * @hidden + */ + public static uuid = 'e95d93af-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new EventService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'microbitevent', + EventCharacteristic.microBitEvent, + this.eventHandler.bind(this) + ); + await this.helper.handleListener( + 'microbitrequirementschanged', + EventCharacteristic.microBitRequirements, + this.microbitRequirementsChangedHandler.bind(this) + ); + } + + /** + * Get micro:bit event requirements + */ + public async getMicrobitRequirements(): Promise { + const view = await this.helper.getCharacteristicValue( + EventCharacteristic.microBitRequirements + ); + return this.viewToMicrobitEvent(view); + } + + /** + * Set client event requirements + * @param type The type of event to set + * @param value The value to set + */ + public async setClientRequirements(type: number, value: number): Promise { + const view = new DataView(new ArrayBuffer(4)); + view.setUint16(0, type, true); + view.setUint16(1, value, true); + return await this.helper.setCharacteristicValue( + EventCharacteristic.clientRequirements, + view + ); + } + + /** + * Read micro:bit event + */ + public async readMicrobitEvent(): Promise { + const view = await this.helper.getCharacteristicValue(EventCharacteristic.microBitEvent); + return this.viewToMicrobitEvent(view); + } + + /** + * Write client event + * @param type The event type + * @param value The event value + */ + public async writeClientEvent(type: number, value: number): Promise { + const view = new DataView(new ArrayBuffer(4)); + view.setUint16(0, type, true); + view.setUint16(1, value, true); + return await this.helper.setCharacteristicValue(EventCharacteristic.clientEvent, view); + } + + private microbitRequirementsChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const microbitEvent = this.viewToMicrobitEvent(view); + this.dispatchEvent('microbitrequirementschanged', microbitEvent); + } + + private eventHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const microbitEvent = this.viewToMicrobitEvent(view); + this.dispatchEvent('microbitevent', microbitEvent); + } + + private viewToMicrobitEvent(view: DataView): MicrobitEvent { + const type = view.getUint16(0, true); + const value = view.getUint16(1, true); + return { + type, + value, + }; + } +} diff --git a/src/class/hardware/bluetoothServices/services/io-pin.ts b/src/class/hardware/bluetoothServices/services/io-pin.ts new file mode 100755 index 0000000000..7d3086d965 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/io-pin.ts @@ -0,0 +1,269 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { ServiceHelper } from '../service-helper'; +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; + +/** + * @hidden + */ +export enum IoPinCharacteristic { + pinData = 'e95d8d00-251d-470a-a062-fa1922dfa9a8', + pinAdConfiguration = 'e95d5899-251d-470a-a062-fa1922dfa9a8', + pinIoConfiguration = 'e95db9fe-251d-470a-a062-fa1922dfa9a8', + pwmControl = 'e95dd822-251d-470a-a062-fa1922dfa9a8', +} + +const littleEndian = true; + +/** + * Pin data + */ +export interface PinData { + /** + * Pin number + */ + pin: number; + /** + * Pin value + */ + value: number; +} + +/** + * PWM control data + */ +export interface PwmControlData { + /** + * Pin number + */ + pin: number; + /** + * Pin value + */ + value: number; + /** + * Period (in microseconds) + */ + period: number; +} + +/** + * Analogue/Digital Enum + */ +export enum AD { + Digital = 0, + Analogue = 1, +} + +/** + * Input/Output Enum + */ +export enum IO { + Output = 0, + Input = 1, +} + +/** + * Events raised by the magnetometer service + */ +export interface IoPinEvents { + /** + * @hidden + */ + newListener: keyof IoPinEvents; + /** + * @hidden + */ + removeListener: keyof IoPinEvents; + /** + * Pin data changed event + */ + pindatachanged: PinData[]; +} + +/** + * @hidden + */ +export class IoPinService extends (EventDispatcher as new () => TypedDispatcher) { + /** + * @hidden + */ + public static uuid = 'e95d127b-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new IoPinService(service); + await bluetoothService.init(); + return bluetoothService; + } + + /** + * @hidden + */ + public helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'pindatachanged', + IoPinCharacteristic.pinData, + this.pinDataChangedHandler.bind(this) + ); + } + + /** + * Read pin data + */ + public async readPinData(): Promise { + const view = await this.helper.getCharacteristicValue(IoPinCharacteristic.pinData); + return this.dataViewToPinData(view); + } + + /** + * Write pin data + * @param data The pin data to write + */ + public async writePinData(data: PinData[]): Promise { + const view = this.pinDataToDataView(data); + return this.helper.setCharacteristicValue(IoPinCharacteristic.pinData, view); + } + + /** + * Get pin analogue/digital configuration + */ + public async getAdConfiguration(): Promise { + const view = await this.helper.getCharacteristicValue( + IoPinCharacteristic.pinAdConfiguration + ); + return this.dataViewToConfig(view); + } + + /** + * Set pin analogue/digital configuration + * @param config The analogue/digital configuration to set + */ + public async setAdConfiguration(config: AD[]): Promise { + const view = this.configToDataView(config); + return this.helper.setCharacteristicValue(IoPinCharacteristic.pinAdConfiguration, view); + } + + /** + * Get pin input/output configuration + */ + public async getIoConfiguration(): Promise { + const view = await this.helper.getCharacteristicValue( + IoPinCharacteristic.pinIoConfiguration + ); + return this.dataViewToConfig(view); + } + + /** + * Set pin input/output configuration + * @param config The input/output configuration to set + */ + public async setIoConfiguration(config: IO[]): Promise { + const view = this.configToDataView(config); + return this.helper.setCharacteristicValue(IoPinCharacteristic.pinIoConfiguration, view); + } + + /** + * Set pin PWM control + * @param data The PWM control data to set + */ + public async setPwmControl(data: PwmControlData): Promise { + const view = this.pwmControlDataToDataView(data); + return this.helper.setCharacteristicValue(IoPinCharacteristic.pwmControl, view); + } + + private pinDataChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const value = this.dataViewToPinData(view); + this.dispatchEvent('pindatachanged', value); + } + + private dataViewToPinData(view: DataView): PinData[] { + const data = []; + for (let i = 0; i < view.byteLength; i += 2) { + data.push({ + pin: view.getUint8(i), + value: view.getUint8(i + 1), + }); + } + return data; + } + + private pinDataToDataView(data: PinData[]): DataView { + const view = new DataView(new ArrayBuffer(data.length * 2)); + data.forEach((pinData, index) => { + view.setUint8(index * 2, pinData.pin); + view.setUint8(index * 2 + 1, pinData.value); + }); + return view; + } + + private dataViewToConfig(view: DataView): number[] { + const result: number[] = []; + const value = (view.getUint16(0) << 8) + view.getUint8(2); + + for (let i = 0; i < 24; i++) { + result.push(value >> i); + } + + return result; + } + + private configToDataView(config: number[]): DataView { + const view = new DataView(new ArrayBuffer(3)); + let value = 0; + + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < config.length; i++) { + value &= 1 << config[i]; + } + + view.setUint16(0, value >> 8, littleEndian); + view.setUint8(2, value & 0xff); + return view; + } + + private pwmControlDataToDataView(data: PwmControlData): DataView { + const view = new DataView(new ArrayBuffer(7)); + view.setUint8(0, data.pin); + view.setUint16(1, data.value, littleEndian); + view.setUint32(3, data.period, littleEndian); + return view; + } +} diff --git a/src/class/hardware/bluetoothServices/services/led.ts b/src/class/hardware/bluetoothServices/services/led.ts new file mode 100755 index 0000000000..e921c23c35 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/led.ts @@ -0,0 +1,154 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum LedCharacteristic { + ledMatrixState = 'e95d7b77-251d-470a-a062-fa1922dfa9a8', + ledText = 'e95d93ee-251d-470a-a062-fa1922dfa9a8', + scrollingDelay = 'e95d0d2d-251d-470a-a062-fa1922dfa9a8', +} + +/** + * LED matrix structure + */ +export type LedMatrix = [ + [boolean, boolean, boolean, boolean, boolean], + [boolean, boolean, boolean, boolean, boolean], + [boolean, boolean, boolean, boolean, boolean], + [boolean, boolean, boolean, boolean, boolean], + [boolean, boolean, boolean, boolean, boolean] +]; + +/** + * LED Service + */ +export class LedService { + /** + * @hidden + */ + public static uuid = 'e95dd91d-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + return new LedService(service); + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + this.helper = new ServiceHelper(service); + } + + /** + * Write text to the LED matrix + * @param text Te text to display + */ + public async writeText(text: string): Promise { + const encoded = this.encodeString(text); + return this.helper.setCharacteristicValue(LedCharacteristic.ledText, encoded); + } + + /** + * Read matrix state + */ + public async readMatrixState(): Promise { + const view = await this.helper.getCharacteristicValue(LedCharacteristic.ledMatrixState); + return this.viewToLedMatrix(view); + } + + /** + * Write matrix state + * @param state The matrix data to set + */ + public async writeMatrixState(state: LedMatrix): Promise { + const view = this.ledMatrixToView(state); + return this.helper.setCharacteristicValue(LedCharacteristic.ledMatrixState, view); + } + + /** + * Get scrolling delay + */ + public async getScrollingDelay(): Promise { + const view = await this.helper.getCharacteristicValue(LedCharacteristic.scrollingDelay); + return view.getUint16(0, true); + } + + /** + * Set scrolling delay + * @param delay The delay to set (milliseconds) + */ + public async setScrollingDelay(delay: number): Promise { + const view = new DataView(new ArrayBuffer(2)); + view.setUint16(0, delay, true); + return this.helper.setCharacteristicValue(LedCharacteristic.scrollingDelay, view); + } + + private encodeString(text: string): ArrayBuffer { + const buffer = new ArrayBuffer(text.length); + const view = new Uint8Array(buffer); + for (let i = 0; i < text.length; i++) { + view[i] = text.charCodeAt(i); + } + return buffer; + } + + private viewToLedMatrix(view: DataView): LedMatrix { + const matrix: boolean[][] = []; + for (let i = 0; i < 5; i++) { + matrix[i] = this.byteToBoolArray(view.getUint8(i)); + } + return matrix as LedMatrix; + } + + private byteToBoolArray(byte: number): boolean[] { + const bools = [false, false, false, false, false]; + for (let i = 0; i < bools.length; i++) { + bools[i] = (byte & 1) === 1; + byte >>= 1; + } + return bools.reverse(); + } + + private ledMatrixToView(matrix: LedMatrix): DataView { + const view = new DataView(new ArrayBuffer(5)); + for (let i = 0; i < 5; i++) { + view.setUint8(i, this.boolArrayToByte(matrix[i])); + } + return view; + } + + private boolArrayToByte(bools: boolean[]): number { + return bools.reduce((byte, bool) => (byte << 1) | (bool ? 1 : 0), 0); + } +} diff --git a/src/class/hardware/bluetoothServices/services/magnetometer.ts b/src/class/hardware/bluetoothServices/services/magnetometer.ts new file mode 100755 index 0000000000..85d13976ba --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/magnetometer.ts @@ -0,0 +1,245 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum MagnetometerCharacteristic { + magnetometerData = 'e95dfb11-251d-470a-a062-fa1922dfa9a8', + magnetometerPeriod = 'e95d386c-251d-470a-a062-fa1922dfa9a8', + magnetometerBearing = 'e95d9715-251d-470a-a062-fa1922dfa9a8', + magnetometerCalibration = 'e95db358-251d-470a-a062-fa1922dfa9a8', +} + +/** + * Data received from the magnetometer + */ +export interface MagnetometerData { + /** + * Force in direction X + */ + x: number; + /** + * Force in direction Y + */ + y: number; + /** + * Force in direction Z + */ + z: number; +} + +/** + * Magnetometer calibation state + */ +export enum MagnetometerCalibration { + /** + * Unknown state + */ + unknown = 0, + /** + * Calibration has been requestes + */ + requested = 1, + /** + * Calibration completed + */ + completed = 2, + /** + * Calibration had an error + */ + errored = 3, +} + +/** + * The sample period to read magnetometer data (milliseconds) + */ +export type MagnetometerPeriod = 1 | 2 | 5 | 10 | 20 | 80 | 160 | 640; + +/** + * Events raised by the magnetometer service + */ +export interface MagnetometerEvents { + /** + * @hidden + */ + newListener: keyof MagnetometerEvents; + /** + * @hidden + */ + removeListener: keyof MagnetometerEvents; + /** + * Magnetometer data changed event + */ + magnetometerdatachanged: MagnetometerData; + /** + * Magnetometer bearing changed event + */ + magnetometerbearingchanged: number; + /** + * Magnetometer calibration changed event + */ + magnetometercalibrationchanged: MagnetometerCalibration; +} + +/** + * Magnetometer Service + */ +export class MagnetometerService extends (EventDispatcher as new () => TypedDispatcher< + MagnetometerEvents +>) { + /** + * @hidden + */ + public static uuid = 'e95df2d8-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new MagnetometerService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'magnetometerdatachanged', + MagnetometerCharacteristic.magnetometerData, + this.magnetometerDataChangedHandler.bind(this) + ); + await this.helper.handleListener( + 'magnetometerbearingchanged', + MagnetometerCharacteristic.magnetometerBearing, + this.magnetometerBearingChangedHandler.bind(this) + ); + await this.helper.handleListener( + 'magnetometercalibrationchanged', + MagnetometerCharacteristic.magnetometerCalibration, + this.magnetometerCalibrationChangedHandler.bind(this) + ); + } + + /** + * Request magnetometer calibration + */ + public async calibrate() { + return this.helper.setCharacteristicValue( + MagnetometerCharacteristic.magnetometerCalibration, + new Uint8Array([1]) + ); + } + + /** + * Read magnetometer data + */ + public async readMagnetometerData(): Promise { + const view = await this.helper.getCharacteristicValue( + MagnetometerCharacteristic.magnetometerData + ); + return this.dataViewToMagnetometerData(view); + } + + /** + * Read magnetometer bearing + */ + public async readMagnetometerBearing(): Promise { + const view = await this.helper.getCharacteristicValue( + MagnetometerCharacteristic.magnetometerBearing + ); + if (view.byteLength === 2) { + return view.getUint16(0, true); + } + return undefined; + } + + /** + * Get magnetometer sample period + */ + public async getMagnetometerPeriod(): Promise { + const view = await this.helper.getCharacteristicValue( + MagnetometerCharacteristic.magnetometerPeriod + ); + return view.getUint16(0, true) as MagnetometerPeriod; + } + + /** + * Set magnetometer sample period + * @param frequency The frequency interval to use + */ + public async setMagnetometerPeriod(frequency: MagnetometerPeriod): Promise { + const view = new DataView(new ArrayBuffer(2)); + view.setUint16(0, frequency, true); + return this.helper.setCharacteristicValue( + MagnetometerCharacteristic.magnetometerPeriod, + view + ); + } + + private magnetometerDataChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const value = this.dataViewToMagnetometerData(view); + this.dispatchEvent('magnetometerdatachanged', value); + } + + private magnetometerBearingChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + if (view.byteLength === 2) { + this.dispatchEvent('magnetometerbearingchanged', view.getUint16(0, true)); + } + } + + private magnetometerCalibrationChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + if (view.byteLength === 1) { + this.dispatchEvent( + 'magnetometercalibrationchanged', + view.getUint8(0) as MagnetometerCalibration + ); + } + } + + private dataViewToMagnetometerData(view: DataView): MagnetometerData { + return { + x: view.getInt16(0, true), + y: view.getInt16(1, true), + z: view.getInt16(2, true), + }; + } +} diff --git a/src/class/hardware/bluetoothServices/services/temperature.ts b/src/class/hardware/bluetoothServices/services/temperature.ts new file mode 100755 index 0000000000..73e5c3ebc7 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/temperature.ts @@ -0,0 +1,131 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { TypedDispatcher, EventDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum TemperatureCharacteristic { + temperature = 'e95d9250-251d-470a-a062-fa1922dfa9a8', + temperaturePeriod = 'e95d1b25-251d-470a-a062-fa1922dfa9a8', +} + +/** + * Events raised by the temperature service + */ +export interface TemperatureEvents { + /** + * @hidden + */ + newListener: keyof TemperatureEvents; + /** + * @hidden + */ + removeListener: keyof TemperatureEvents; + /** + * Temperature changed event + */ + temperaturechanged: number; +} + +/** + * Temperature Service + */ +export class TemperatureService extends (EventDispatcher as new () => TypedDispatcher< + TemperatureEvents +>) { + /** + * @hidden + */ + public static uuid = 'e95d6100-251d-470a-a062-fa1922dfa9a8'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new TemperatureService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'temperaturechanged', + TemperatureCharacteristic.temperature, + this.temperatureChangedHandler.bind(this) + ); + } + + /** + * Read temperature + */ + public async readTemperature(): Promise { + const view = await this.helper.getCharacteristicValue( + TemperatureCharacteristic.temperature + ); + return view.getInt8(0); + } + + /** + * Get temperature sample period + */ + public async getTemperaturePeriod(): Promise { + const view = await this.helper.getCharacteristicValue( + TemperatureCharacteristic.temperaturePeriod + ); + return view.getUint16(0, true); + } + + /** + * Set temperature sample period + * @param frequency The frequency to use (milliseconds) + */ + public async setTemperaturePeriod(frequency: number): Promise { + const view = new DataView(new ArrayBuffer(2)); + view.setUint16(0, frequency, true); + return await this.helper.setCharacteristicValue( + TemperatureCharacteristic.temperaturePeriod, + view + ); + } + + private temperatureChangedHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + this.dispatchEvent('temperaturechanged', view.getInt8(0)); + } +} diff --git a/src/class/hardware/bluetoothServices/services/uart.ts b/src/class/hardware/bluetoothServices/services/uart.ts new file mode 100755 index 0000000000..a45398fc97 --- /dev/null +++ b/src/class/hardware/bluetoothServices/services/uart.ts @@ -0,0 +1,133 @@ +/* + * micro:bit Web Bluetooth + * Copyright (c) 2019 Rob Moran + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { EventDispatcher, TypedDispatcher } from '../event-dispatcher'; +import { ServiceHelper } from '../service-helper'; + +/** + * @hidden + */ +export enum UartCharacteristic { + tx = '6e400002-b5a3-f393-e0a9-e50e24dcca9e', + rx = '6e400003-b5a3-f393-e0a9-e50e24dcca9e', +} + +/** + * Events raised by the UART service + */ +export interface UartEvents { + /** + * @hidden + */ + newListener: keyof UartEvents; + /** + * @hidden + */ + removeListener: keyof UartEvents; + /** + * Serial data received event + */ + receive: Uint8Array; + /** + * Serial received text event + */ + receiveText: string; +} + +/** + * UART Service + */ +export class UartService extends (EventDispatcher as new () => TypedDispatcher) { + /** + * @hidden + */ + public static uuid = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'; + + /** + * @hidden + */ + public static async create(service: BluetoothRemoteGATTService): Promise { + const bluetoothService = new UartService(service); + await bluetoothService.init(); + return bluetoothService; + } + + private helper: ServiceHelper; + + /** + * @hidden + */ + constructor(service: BluetoothRemoteGATTService) { + super(); + //@ts-ignore + this.helper = new ServiceHelper(service, this); + } + + private async init() { + await this.helper.handleListener( + 'receive', + UartCharacteristic.tx, + this.receiveHandler.bind(this) + ); + await this.helper.handleListener( + 'receiveText', + UartCharacteristic.tx, + this.receiveTextHandler.bind(this) + ); + } + + /** + * Send serial data + * @param value The buffer to send + */ + public async send(value: BufferSource): Promise { + return this.helper.setCharacteristicValue(UartCharacteristic.rx, value); + } + + /** + * Send serial text + * @param value The text to send + */ + public async sendText(value: string): Promise { + const arrayData = value.split('').map((e: string) => e.charCodeAt(0)); + return this.helper.setCharacteristicValue( + UartCharacteristic.rx, + new Uint8Array(arrayData).buffer + ); + } + + private receiveHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const value = new Uint8Array(view.buffer); + this.dispatchEvent('receive', value); + } + + private receiveTextHandler(event: Event) { + const view = (event.target as BluetoothRemoteGATTCharacteristic).value!; + const numberArray = Array.prototype.slice.call(new Uint8Array(view.buffer)); + const value = String.fromCharCode.apply(null, numberArray); + this.dispatchEvent('receiveText', value); + } +} From 220147c5e3fe7ba0e8c00be4d7912d0106cf9fa3 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 21 Dec 2023 18:16:00 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20hwLite=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=99=80=20webApiConnector=EC=9A=A9=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/engine.js | 2 +- src/class/hardware/webApiConnector.ts | 5 + src/class/hardware/webSerialConnector.ts | 283 ++++++++++++++++++ src/class/hw.ts | 4 +- src/class/hw_lite.ts | 353 ++++------------------- types/index.d.ts | 45 ++- 6 files changed, 381 insertions(+), 311 deletions(-) create mode 100644 src/class/hardware/webApiConnector.ts create mode 100644 src/class/hardware/webSerialConnector.ts diff --git a/src/class/engine.js b/src/class/engine.js index efa8ab7922..f3f3c28034 100644 --- a/src/class/engine.js +++ b/src/class/engine.js @@ -747,7 +747,7 @@ Entry.Engine = class Engine { Entry.scene.loadStartSceneSnapshot(); Entry.Func.clearThreads(); Entry.Utils.setVolume(1); - if (Entry.hwLite.status === Entry.hwLite.HardwareStatement.connected) { + if (Entry.hwLite.getStatus() === 'connected') { Entry.hwLite.setZero(); } createjs.Sound.setVolume(1); diff --git a/src/class/hardware/webApiConnector.ts b/src/class/hardware/webApiConnector.ts new file mode 100644 index 0000000000..e3729529d1 --- /dev/null +++ b/src/class/hardware/webApiConnector.ts @@ -0,0 +1,5 @@ +// INFO : 웹연결 api의 라이프사이클 함수를 모아놓은 추상 클래스 +export default class WebApiConnector { + connect() {} + disconnect() {} +} diff --git a/src/class/hardware/webSerialConnector.ts b/src/class/hardware/webSerialConnector.ts new file mode 100644 index 0000000000..0d2b0209e5 --- /dev/null +++ b/src/class/hardware/webSerialConnector.ts @@ -0,0 +1,283 @@ +import { EntryHWLiteBaseModule } from 'types'; +import HardwareLite from '../hw_lite'; +import WebApiConnector from './webApiConnector'; +import throttle from 'lodash/throttle'; + +const Buffer = require('buffer/').Buffer; + +class LineBreakTransformer { + private container: string; + constructor() { + this.container = ''; + } + + transform(chunk: string, controller: any) { + try { + this.container += chunk; + // @ts-ignore + const lines = this.container.split(Entry.hwLite?.hwModule?.delimeter || '\r\n'); + this.container = lines.pop(); + lines.forEach((line) => controller.enqueue(line)); + } catch (e) { + controller.enqueue(chunk); + } + } + + flush(controller: any) { + controller.enqueue(this.container); + } +} + +export default class WebSerialConnector extends WebApiConnector { + private writer: any; // SerialPort.writer; + private reader: any; //SerialPort.reader; + private hwModule: EntryHWLiteBaseModule; // > 추후 전용 타입으로 변경 + private port: any; + private connectionType: 'ascii' | 'bytestream' | undefined; + private writableStream: any; + private isSendAsyncRun: boolean; + private hwLite: HardwareLite; + private sendAsyncWithThrottle: any; + + constructor(hwModule: EntryHWLiteBaseModule, hwLite: HardwareLite) { + super(); + this.hwLite = hwLite; + this.hwModule = hwModule; + this.isSendAsyncRun = false; + this.hwLite.setStatus('disconnected'); + Entry.addEventListener('beforeStop', this.checkConditionBeforeStop.bind(this)); + this.sendAsyncWithThrottle = throttle(this.sendAsync, this.hwModule.duration); + } + + async connect() { + // @ts-ignore + const port = await navigator.serial.requestPort(); + const { portData } = this.hwModule || {}; + await port.open( + portData || { + baudRate: 9600, + dataBits: 8, + parity: 'none', + bufferSize: 256, + stopBits: 1, + } + ); + this.port = port; + const encoder = new TextEncoderStream(); + const writable = port.writable; + + this.connectionType = portData?.connectionType; + if (portData?.writeAscii || portData?.connectionType === 'ascii') { + const writableStream = encoder.readable.pipeTo(writable); + this.writableStream = writableStream; + this.writer = encoder.writable.getWriter(); + } else { + this.writer = port.writable.getWriter(); + } + + let readable = this.port.readable; + if (portData?.readAscii || this.connectionType === 'ascii') { + readable = readable.pipeThrough(new TextDecoderStream()); + } + if (this.hwModule?.delimeter || this.connectionType === 'ascii') { + readable = readable.pipeThrough(new TransformStream(new LineBreakTransformer())); + } + this.reader = readable.getReader(); + this.hwLite.setStatus('connected'); + this.hwLite.refreshHardwareLiteBlockMenu(); + if (this.hwModule?.initialHandshake) { + const result = await this.hwModule.initialHandshake(); + if (!result) { + throw new Error('Handshake Error : 디바이스와 연결에 실패하였습니다.'); + } + } + if (this.hwModule.portData?.constantServing) { + this.constantServing(); + } + } + + async disconnect() { + // INFO: 디바이스가 제거되었을 때, reader만 단독 예외처리 + try { + await this.reader?.cancel().catch((error: any) => { + console.error(error); + }); + + await this.writer?.close(); + if (this.connectionType === 'ascii' && this.writableStream) { + await this.writableStream; + } + } catch (err) { + console.error(err); + } finally { + await this.port?.close(); + this.port = null; + this.reader = null; + this.writer = null; + this.writableStream = null; + this.hwModule = null; + } + } + + /** + * 디바이스와 1회성 통신 + * @param data + * @returns Promise resolves to resulting message + */ + async sendAsync(data?: Buffer | string, isResetReq?: boolean, callback?: Function) { + if (this.isSendAsyncRun) { + return; + } else { + this.isSendAsyncRun = true; + } + if (!data || this.hwLite.getStatus() !== 'connected') { + return; + } + // @ts-ignore + const encodedData = typeof data === 'string' ? data : Buffer.from(data, 'utf8'); + + try { + if (this.hwLite.getStatus() !== 'connected') { + Entry.toast.alert( + Lang.Msgs.hw_connection_failed_title, + Lang.Msgs.hw_connection_failed_desc, + false + ); + throw new Error('HARDWARE LITE NOT CONNECTED'); + } + await this.writer.write(encodedData); + if (isResetReq) { + this.isSendAsyncRun = false; + return; + } + const { value, done } = await this.reader.read(); + if (callback) { + return callback(value); + } + this.hwLite._updatePortData(); + return value; + } catch (err) { + console.error(err); + this.hwLite.getConnectFailedMenu(); + } finally { + this.isSendAsyncRun = false; + } + } + + async constantServing() { + try { + if (this.hwLite.getStatus() === 'disconnected') { + return; + } + if (this.hwModule?.portData?.constantServing !== 'ReadOnly') { + const reqLocal = this.hwModule?.requestLocalData(); + if (reqLocal && this.hwLite.getStatus() === 'connected') { + if (Entry.engine.isState('run')) { + this.writer.write(Buffer.from(reqLocal)); + } + } + } + + const { value, done } = await this.reader.read(); + if (done) { + this.hwLite.getConnectFailedMenu(); + return; + } + this.hwModule?.handleLocalData(value); + this.hwLite._updatePortData(); + + setTimeout(() => { + this.constantServing(); + }, this.hwModule.duration || 0); + } catch (error) { + console.error(error); + this.hwLite.getConnectFailedMenu(); + return; + } + } + + async readPortData() { + try { + if (this.hwLite.getStatus() === 'connected' && Entry.engine.isState('run')) { + const { value, done } = await this.reader.read(); + + if (!value) { + this.reader.cancel(); + throw new Error("reader's value is undefined. check device"); + } + return value; + } + } catch (error) { + console.error(error); + this.hwLite.getConnectFailedMenu(); + } + } + + async writePortData(data: string) { + if (data && this.hwLite.getStatus() === 'connected') { + const result = await this.writer.write(Buffer.from(data)); + } + } + + async removeSerialPort() { + try { + // INFO: 디바이스가 제거되었을 때, reader만 단독 예외처리 + await this.reader?.cancel().catch((error: any) => { + console.error(error); + }); + + await this.writer?.close(); + if (this.connectionType === 'ascii' && this.writableStream) { + await this.writableStream; + } + } catch (error) { + console.error(error); + } finally { + await this.port?.close(); + this.port = null; + this.reader = null; + this.writer = null; + this.writableStream = null; + } + } + + // engine 동작중 에러 발생시 호출, 디바이스에 read, write가 모두 안되는 것이 전제 + async handleConnectErrorInEngineRun() { + // INFO: Engin.toggleStop에서 setZero가 실행되지 않도록 상태변경 + if (this.hwLite.getStatus() === 'willDisconnect') { + return; + } + this.hwLite.setStatus('willDisconnect'); + if (Entry.engine.isState('run')) { + await Entry.engine.toggleStop(); + } + await this.removeSerialPort(); + this.hwLite.getConnectFailedMenu(); + Entry.toast.alert( + Lang.Msgs.hw_connection_failed_title, + Lang.Msgs.hw_connection_failed_desc, + false + ); + } + + update() { + if (this.hwLite.getStatus() !== 'connected') { + console.error('Cannot update hwLite queue. Check connection status.'); + return; + } + if (this.hwModule?.portData?.constantServing) { + const reqLocal = this.hwModule?.requestLocalData(); + if (reqLocal) { + this.writer.write(Buffer.from(reqLocal)); + } + } + } + + sendAsciiAsBuffer(asciiStr: string) { + this.writer.write(Buffer.from(asciiStr, 'utf8')); + } + + checkConditionBeforeStop() { + this.isSendAsyncRun = false; + } +} diff --git a/src/class/hw.ts b/src/class/hw.ts index 3721c7df10..4cf733d2aa 100644 --- a/src/class/hw.ts +++ b/src/class/hw.ts @@ -133,7 +133,7 @@ export default class Hardware { * 현재 하드웨어 로드가 외부 모듈에 의한 것인 경우는 연결이 해제되어도 블록숨김을 실행하지 않는다. */ refreshHardwareBlockMenu() { - if (Entry.hwLite.status !== 'disconnected') { + if (Entry.hwLite.getStatus() !== 'disconnected') { console.log('canel refreshHardwareBlockMenu() for HwLITE'); return; } @@ -510,7 +510,7 @@ export default class Hardware { } // NOTE : 하드웨어 웹연결과 충돌을 방지 - if (Entry.hwLite.status !== 'disconnected') { + if (Entry.hwLite.getStatus() !== 'disconnected') { console.log('canel connectionTry for HwLITE'); return; } diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index ba74e47504..ff78f74496 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -1,72 +1,39 @@ -import throttle from 'lodash/throttle'; +import { EntryHWLiteBaseModule, HWLiteStatus } from '../../types/index'; import ExtraBlockUtils from '../util/extrablockUtils'; import HardwareMonitor from './hardware/hardwareMonitor'; -import { EntryHardwareLiteBlockModule } from '../../types/index'; import WebUsbFlasher from './hardware/webUsbFlasher'; import WebSerialConnector from './hardware/webSerialConnector'; -import WebBlueToothConnect from './hardware/webBluetoothConnector'; - -enum HardwareStatement { - disconnected = 'disconnected', - connected = 'connected', - willDisconnect = 'willDisconnect', - connectFailed = 'connectFailed', -} +// import WebBlueToothConnect from './hardware/webBluetoothConnector'; +import WebApiConnector from './hardware/webApiConnector'; const ARDUINO_BOARD_IDS: string[] = ['1.1', '4.2', '8.1']; -const Buffer = require('buffer/').Buffer; - -class LineBreakTransformer { - private container: string; - constructor() { - this.container = ''; - } - - transform(chunk: string, controller: any) { - try { - this.container += chunk; - // @ts-ignore - const lines = this.container.split(Entry.hwLite?.hwModule?.delimeter || '\r\n'); - this.container = lines.pop(); - lines.forEach((line) => controller.enqueue(line)); - } catch (e) { - controller.enqueue(chunk); - } - } - - flush(controller: any) { - controller.enqueue(this.container); - } -} export default class HardwareLite { - private status: HardwareStatement; - private HardwareStatement: any; - private port: any; // serialport - private writer: any; // SerialPort.writer; - private reader: any; //SerialPort.reader; - private webConnector: any; // TODO: 추후 WebBlueToothConnect | WebSerialConnector로 변경예정 + private status: HWLiteStatus; + // private HardwareStatement: any; + // private port: any; // serialport + // private writer: any; // SerialPort.writer; + // private reader: any; //SerialPort.reader; + private webConnector: WebApiConnector; // TODO: 추후 WebBlueToothConnect | WebSerialConnector로 변경예정 + private serial: WebSerialConnector; + // private bluetooth: WebBlueToothConnector; private flasher: WebUsbFlasher; - private writableStream: any; - hwModule: EntryHardwareLiteBlockModule; + // private writableStream: any; + hwModule: EntryHWLiteBaseModule; static setExternalModule: any; static refreshHardwareLiteBlockMenu: any; static banClassAllHardwareLite: any; static isHwLiteSupportAgent: any; private playground: any; - private connectionType: 'ascii' | 'bytestream' | undefined; + // private connectionType: 'ascii' | 'bytestream' | undefined; private hwMonitor?: HardwareMonitor; - private isSendAsyncRun: boolean; - private sendAsyncWithThrottle: any; + static getStatus: any; constructor(playground: any) { this.playground = playground; this.hwModule = null; - this.status = HardwareStatement.disconnected; - this.HardwareStatement = HardwareStatement; - this.isSendAsyncRun = false; + this.status = 'disconnected'; Entry.addEventListener('hwLiteChanged', this.refreshHardwareLiteBlockMenu.bind(this)); - Entry.addEventListener('beforeStop', this.checkConditionBeforeStop.bind(this)); this.setExternalModule.bind(this); } @@ -74,10 +41,6 @@ export default class HardwareLite { this.hwModule?.setZero(); } - checkConditionBeforeStop() { - this.isSendAsyncRun = false; - } - isActive(name: string) { if (this.hwModule) { const data = {}; @@ -104,19 +67,20 @@ export default class HardwareLite { blockMenu.reDraw(); } - setExternalModule(moduleObject: EntryHardwareLiteBlockModule) { + setExternalModule(moduleObject: EntryHWLiteBaseModule) { this.hwModule = moduleObject; this.banClassAllHardwareLite(); Entry.block.changeBlockText('arduino_lite_device_name', this.hwModule.title.ko); Entry.dispatchEvent('hwLiteChanged'); + this.setWebConnector(); + this.setFlasher(); if (Entry.propertyPanel && this.hwModule.monitorTemplate) { this._setHardwareMonitorTemplate(); } - this.sendAsyncWithThrottle = throttle(this.sendAsync, this.hwModule.duration); } getConnectFailedMenu() { - this.status = HardwareStatement.connectFailed; + this.status = 'connectFailed'; this.refreshHardwareLiteBlockMenu(); } @@ -171,7 +135,7 @@ export default class HardwareLite { } switch (this.status) { - case HardwareStatement.disconnected: + case 'disconnected': { blockMenu.banClass('arduinoLiteConnected', true); blockMenu.banClass('arduinoLiteConnectFailed', true); blockMenu.banClass('arduinoLiteGuide', true); @@ -182,7 +146,8 @@ export default class HardwareLite { : blockMenu.banClass('arduinoLiteSupported', true); this.banClassAllHardwareLite(); break; - case HardwareStatement.connected: + } + case 'connected': blockMenu.banClass('arduinoLiteConnectFailed', true); blockMenu.banClass('arduinoLiteDisconnected', true); blockMenu.banClass('arduinoDisconnected', true); @@ -191,7 +156,7 @@ export default class HardwareLite { blockMenu.unbanClass('arduinoLiteConnected', true); blockMenu.unbanClass(this.hwModule?.name, true); break; - case HardwareStatement.connectFailed: + case 'connectFailed': blockMenu.banClass('arduinoLiteDisconnected', true); blockMenu.banClass('arduinoLiteConnected', true); blockMenu.banClass('arduinoDisconnected', true); @@ -201,14 +166,15 @@ export default class HardwareLite { if (ARDUINO_BOARD_IDS.includes(this.hwModule.id)) { blockMenu.unbanClass('arduinoLiteGuide', true); } - } else if (this.hwModule?.id instanceof Array) { - for (const id in this.hwModule.id) { - if (ARDUINO_BOARD_IDS.includes(this.hwModule.id[id])) { - blockMenu.unbanClass('arduinoLiteGuide', true); - break; - } - } } + // else if (this.hwModule?.id instanceof Array) { + // for (const id in this.hwModule.id) { + // if (ARDUINO_BOARD_IDS.includes(this.hwModule.id[id])) { + // blockMenu.unbanClass('arduinoLiteGuide', true); + // break; + // } + // } + // } this.banClassAllHardwareLite(); break; } @@ -217,157 +183,12 @@ export default class HardwareLite { blockMenu.reDraw(); } - async readPortData() { - try { - if (this.status === HardwareStatement.connected && Entry.engine.isState('run')) { - const { value, done } = await this.reader.read(); - - if (!value) { - this.reader.cancel(); - throw new Error("reader's value is undefined. check device"); - } - return value; - } - } catch (error) { - console.error(error); - this.getConnectFailedMenu(); - } - } - - async writePortData(data: string) { - if (data && this.status === HardwareStatement.connected) { - const result = await this.writer.write(Buffer.from(data)); - } - } - - async removeSerialPort() { - try { - // INFO: 디바이스가 제거되었을 때, reader만 단독 예외처리 - await this.reader?.cancel().catch((error: any) => { - console.error(error); - }); - - await this.writer?.close(); - if (this.connectionType === 'ascii' && this.writableStream) { - await this.writableStream; - } - } catch (error) { - console.error(error); - } finally { - await this.port?.close(); - this.port = null; - this.reader = null; - this.writer = null; - this.writableStream = null; - } - } - - // engine 동작중 에러 발생시 호출, 디바이스에 read, write가 모두 안되는 것이 전제 - async handleConnectErrorInEngineRun() { - // INFO: Engin.toggleStop에서 setZero가 실행되지 않도록 상태변경 - if (this.status === HardwareStatement.willDisconnect) { - return; - } - this.status = HardwareStatement.willDisconnect; - if (Entry.engine.isState('run')) { - await Entry.engine.toggleStop(); - } - await this.removeSerialPort(); - this.getConnectFailedMenu(); - Entry.toast.alert( - Lang.Msgs.hw_connection_failed_title, - Lang.Msgs.hw_connection_failed_desc, - false - ); - } - - /** - * 디바이스와 duration 간격으로 지속적 통신 - */ - async constantServing() { - try { - if (this.status === HardwareStatement.disconnected) { - return; - } - if (this.hwModule?.portData?.constantServing !== 'ReadOnly') { - const reqLocal = this.hwModule?.requestLocalData(); - if (reqLocal && this.status === HardwareStatement.connected) { - if (Entry.engine.isState('run')) { - this.writer.write(Buffer.from(reqLocal)); - } - } - } - - const { value, done } = await this.reader.read(); - if (done) { - this.getConnectFailedMenu(); - return; - } - this.hwModule?.handleLocalData(value); - this._updatePortData(); - - setTimeout(() => { - this.constantServing(); - }, this.hwModule.duration || 0); - } catch (error) { - console.error(error); - this.getConnectFailedMenu(); - return; - } - } - async connect() { - if (this.status === HardwareStatement.connected) { + if (this.status === 'connected') { return; } - try { - // @ts-ignore - const port = await navigator.serial.requestPort(); - const { portData } = this.hwModule || {}; - await port.open( - portData || { - baudRate: 9600, - dataBits: 8, - parity: 'none', - bufferSize: 256, - stopBits: 1, - } - ); - this.port = port; - const encoder = new TextEncoderStream(); - const writable = port.writable; - - this.connectionType = portData?.connectionType; - if (portData?.writeAscii || portData?.connectionType === 'ascii') { - const writableStream = encoder.readable.pipeTo(writable); - this.writableStream = writableStream; - this.writer = encoder.writable.getWriter(); - } else { - this.writer = port.writable.getWriter(); - } - - let readable = port.readable; - if (portData?.readAscii || this.connectionType === 'ascii') { - readable = readable.pipeThrough(new TextDecoderStream()); - } - if (this.hwModule?.delimeter || this.connectionType === 'ascii') { - readable = readable.pipeThrough(new TransformStream(new LineBreakTransformer())); - } - this.reader = readable.getReader(); - - this.status = HardwareStatement.connected; - this.refreshHardwareLiteBlockMenu(); - if (this.hwModule?.initialHandshake) { - const result = await this.hwModule.initialHandshake(); - if (!result) { - throw new Error('Handshake Error : 디바이스와 연결에 실패하였습니다.'); - } - } - if (portData?.constantServing) { - this.constantServing(); - } - + await this.webConnector.connect(); Entry.toast.success( Lang.Msgs.hw_connection_success, Lang.Msgs.hw_connection_success_desc2 @@ -386,27 +207,15 @@ export default class HardwareLite { async disconnect() { try { Entry.hardwareLiteBlocks = []; - this.status = HardwareStatement.willDisconnect; - - // INFO: 디바이스가 제거되었을 때, reader만 단독 예외처리 - await this.reader?.cancel().catch((error: any) => { - console.error(error); - }); - - await this.writer?.close(); - if (this.connectionType === 'ascii' && this.writableStream) { - await this.writableStream; - } + this.status = 'willDisconnect'; + await this.webConnector?.disconnect(); } catch (err) { console.error(err); } finally { - await this.port?.close(); - this.port = null; - this.reader = null; - this.writer = null; - this.writableStream = null; this.hwModule = null; - this.status = HardwareStatement.disconnected; + this.status = 'disconnected'; + this.removeWebConnector(); + this.removeFlasher(); Entry.dispatchEvent('hwLiteChanged'); Entry.toast.alert( Lang.Msgs.hw_connection_termination_title, @@ -416,89 +225,40 @@ export default class HardwareLite { } } - /** - * 디바이스와 1회성 통신 - * @param data - * @returns Promise resolves to resulting message - */ - async sendAsync(data?: Buffer | string, isResetReq?: boolean, callback?: Function) { - if (this.isSendAsyncRun) { - return; - } else { - this.isSendAsyncRun = true; - } - if (!data || this.status !== HardwareStatement.connected) { - return; - } - // @ts-ignore - const encodedData = typeof data === 'string' ? data : Buffer.from(data, 'utf8'); - - try { - if (this.status !== HardwareStatement.connected) { - Entry.toast.alert( - Lang.Msgs.hw_connection_failed_title, - Lang.Msgs.hw_connection_failed_desc, - false - ); - throw new Error('HARDWARE LITE NOT CONNECTED'); - } - await this.writer.write(encodedData); - if (isResetReq) { - this.isSendAsyncRun = false; - return; - } - const { value, done } = await this.reader.read(); - if (callback) { - return callback(value); - } - this._updatePortData(); - return value; - } catch (err) { - console.error(err); - this.getConnectFailedMenu(); - } finally { - this.isSendAsyncRun = false; - } - } - sendAsciiAsBuffer(asciiStr: string) { - this.writer.write(Buffer.from(asciiStr, 'utf8')); - } - addHardwareLiteModule(module: EntryHardwareLiteBlockModule) { + addHardwareLiteModule(module: EntryHWLiteBaseModule) { Entry.do('objectAddHardwareLiteBlocks', module); } removeHardwareLiteModule() { Entry.do('objectRemoveHardwareLiteBlocks', this.hwModule); } - update() { - if (this.status !== HardwareStatement.connected) { - console.error('Cannot update hwLite queue. Check connection status.'); - return; - } - if (this.hwModule?.portData?.constantServing) { - const reqLocal = this.hwModule?.requestLocalData(); - if (reqLocal) { - this.writer.write(Buffer.from(reqLocal)); - } - } - } - setWebConnector() { const webapiType = this.hwModule.webapiType; switch (webapiType) { case 'ble': { - this.webConnector = new WebBlueToothConnect(); + // if(!this.hwModule.bluetoothInfo){return;} + // this.bluetooth = new WebBlueToothConnect(this.hwModule); + // this.webConnector = this.bluetooth; break; } case 'serial': case undefined: { - this.webConnector = new WebSerialConnector(); + if (!this.hwModule.portData) { + console.error('Invalid serial hwModule'); + this.getConnectFailedMenu(); + return; + } + this.serial = new WebSerialConnector(this.hwModule, this); + this.webConnector = this.serial; } } } + removeWebConnector() { this.webConnector = undefined; + this.serial = undefined; + // this.bluetooth = undefined; } setFlasher() { @@ -509,6 +269,13 @@ export default class HardwareLite { removeFlasher() { this.flasher = undefined; } + + getStatus() { + return this.status; + } + setStatus(state: HWLiteStatus) { + this.status = state; + } } Entry.HWLite = HardwareLite; diff --git a/types/index.d.ts b/types/index.d.ts index 64eb0bbdf5..66dea3232f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -201,7 +201,7 @@ export declare interface EntryHardwareBlockModule extends EntryBlockModule { dataHandler?: (data: HardwareMessageData) => void; } -export declare interface EntryHardwareLiteBlockModule extends EntryBlockModule { +export declare interface EntryHWLiteBaseModule extends EntryBlockModule { getMonitorPort(): Object; duration: number; // 홍보용 @@ -209,21 +209,10 @@ export declare interface EntryHardwareLiteBlockModule extends EntryBlockModule { url: string; // 모듈 정의용 - id: string | string[]; + id: string; monitorTemplate?: UnknownAny; - portData: { - baudRate: Number; - dataBits: Number; - parity: 'none' | 'even' | 'odd'; - stopBits: 1 | 2; - bufferSize: Number; - connectionType?: 'bytestream' | 'ascii'; - constantServing?: boolean | 'ReadOnly'; - constantRead?: boolean; - writeAscii?: boolean; - readAscii?: boolean; - flowControl?: 'hardware'; - }; + portData?: HWLiteSerialInfo; + bluetoothInfo?: HWLiteBluetoothInfo; type?: 'master' | 'slave'; delimeter?: string | number; webapiType?: 'ble' | 'usb' | 'serial'; @@ -239,3 +228,29 @@ export declare interface EntryHardwareLiteBlockModule extends EntryBlockModule { initialHandshake: () => any; requestLocalData: () => string; } + +export declare interface HWLiteSerialInfo { + baudRate: number; + dataBits: number; + parity: 'none' | 'even' | 'odd'; + stopBits: 1 | 2; + bufferSize: number; + connectionType?: 'bytestream' | 'ascii'; + constantServing?: boolean | 'ReadOnly'; + constantRead?: boolean; + writeAscii?: boolean; + readAscii?: boolean; + flowControl?: 'hardware'; +} + +export declare interface HWLiteBluetoothInfo { + filters: BluetoothLEScanFilter[]; + optionalServices: string[]; + constantServing?: boolean | 'ReadOnly'; +} + +export declare type HWLiteStatus = + | 'disconnected' + | 'connected' + | 'willDisconnect' + | 'connectFailed'; From 6e58b1e26e86b6e9afefa1149b70dc38a0fa0084 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 21 Dec 2023 18:17:22 +0900 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20webConnector=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/command/commands/object.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/command/commands/object.js b/src/command/commands/object.js index 61dba51f60..da7dbe807b 100644 --- a/src/command/commands/object.js +++ b/src/command/commands/object.js @@ -321,8 +321,6 @@ import WebUsbFlasher from '../../class/hardware/webUsbFlasher'; } Entry.hardwareLiteBlocks = _.union(Entry.hardwareLiteBlocks, [module.id]); Entry.hwLite.setExternalModule(module); - Entry.hwLite.setWebConnector(); - Entry.hwLite.setFlasher(); }, state(module) { return [module]; @@ -340,7 +338,7 @@ import WebUsbFlasher from '../../class/hardware/webUsbFlasher'; do(module) { Entry.hardwareLiteBlocks = []; Entry.hwLite.disconnect(); - Entry.hwLite.removeConnector(); + Entry.hwLite.removeWebConnector(); Entry.hwLite.removeFlasher(); }, state(module) { From e74abb343e087677f2171a9f907a824138a41692 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 21 Dec 2023 18:27:27 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat:=20=EB=B8=94=EB=9F=AD=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=97=90=EC=84=9C=20Entry.hwLite=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=9D=84=20Entry.hwLite.serial=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/hardwareLite/block_arduino_lite.js | 6 +++--- .../blocks/hardwareLite/block_choco_lite.js | 10 +++++----- .../blocks/hardwareLite/block_hamster_lite.js | 8 ++++---- .../blocks/hardwareLite/block_microbit2_lite.js | 12 ++++++------ .../blocks/hardwareLite/block_neo_cannon_lite.js | 4 ++-- src/playground/blocks/hardwareLite/block_neo_lite.js | 10 +++++----- .../blocks/hardwareLite/block_neo_spider_lite.js | 4 ++-- .../blocks/hardwareLite/block_neobot_purple_lite.js | 4 ++-- .../blocks/hardwareLite/block_neobot_soco_lite.js | 4 ++-- .../hardwareLite/block_neobot_thinkcar_lite.js | 4 ++-- .../blocks/hardwareLite/block_sensorboard_lite.js | 4 ++-- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_arduino_lite.js b/src/playground/blocks/hardwareLite/block_arduino_lite.js index b3fac7c516..93f7b03616 100644 --- a/src/playground/blocks/hardwareLite/block_arduino_lite.js +++ b/src/playground/blocks/hardwareLite/block_arduino_lite.js @@ -10,7 +10,7 @@ import _range from 'lodash/range'; (function() { Entry.ArduinoLite = new (class ArduinoLite { constructor() { - this.id = ['010101', '4.2', '8.1']; + this.id = '010101'; this.name = 'ArduinoLite'; this.url = 'http://www.arduino.cc/'; this.imageName = 'arduinolite.png'; @@ -48,8 +48,8 @@ import _range from 'lodash/range'; this.analogValue = new Array(6).fill(0); this.readablePorts = _range(0, 19); - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_choco_lite.js b/src/playground/blocks/hardwareLite/block_choco_lite.js index 26e12f926a..3857bf32f3 100644 --- a/src/playground/blocks/hardwareLite/block_choco_lite.js +++ b/src/playground/blocks/hardwareLite/block_choco_lite.js @@ -269,8 +269,8 @@ const Buffer = require('buffer').Buffer; }, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } @@ -445,7 +445,7 @@ const Buffer = require('buffer').Buffer; while (this.sensorInit.inited === 'sent') { //parsed되었는데 상태가 변경안되면.. 다시 요청을 보낸다. this.log('Send Data:ready'); - await Entry.hwLite.sendAsync(cmdReady, false, (value) => { + await Entry.hwLite.serial.sendAsync(cmdReady, false, (value) => { this._addRecvData(value); }); } @@ -456,12 +456,12 @@ const Buffer = require('buffer').Buffer; }); this.ledStatus = [0, 0, 0]; this.isSendInitData = 'sent'; - await Entry.hwLite.sendAsync(cmdPing); + await Entry.hwLite.serial.sendAsync(cmdPing); while (this.isSendInitData === 'sent') { // this.isSendInitData 가 inited 될 때까지 기다린다. this.log('Send Data:ping2'); - await Entry.hwLite.sendAsync(cmdPing, false, (value) => { + await Entry.hwLite.serial.sendAsync(cmdPing, false, (value) => { this._addRecvData(value); }); } diff --git a/src/playground/blocks/hardwareLite/block_hamster_lite.js b/src/playground/blocks/hardwareLite/block_hamster_lite.js index 994f62523e..0a13994df3 100644 --- a/src/playground/blocks/hardwareLite/block_hamster_lite.js +++ b/src/playground/blocks/hardwareLite/block_hamster_lite.js @@ -343,8 +343,8 @@ this.timeouts = []; this.__removeAllTimeouts(); - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } @@ -6323,7 +6323,7 @@ async initialHandshake() { let status = false; while (true) { - const { value: data, done } = await Entry.hwLite.reader.read(); + const { value: data, done } = await Entry.hwLite.serial.reader.read(); if (done) { return false; } @@ -6347,7 +6347,7 @@ } } } else { - Entry.hwLite.sendAsciiAsBuffer(this.requestInitialData()); + Entry.hwLite.serial.sendAsciiAsBuffer(this.requestInitialData()); } } return status; diff --git a/src/playground/blocks/hardwareLite/block_microbit2_lite.js b/src/playground/blocks/hardwareLite/block_microbit2_lite.js index 97de1cd0e0..42c1f9a50d 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2_lite.js @@ -287,12 +287,12 @@ const EVENT_INTERVAL = 150; } setZero() { this.commandStatus = {}; - return Entry.hwLite.sendAsyncWithThrottle(this.functionKeys.RESET); + return Entry.hwLite.serial.sendAsyncWithThrottle(this.functionKeys.RESET); } async initialHandshake() { const defaultCMD = `${this.functionKeys.LOCALDATA}`; - const response = await Entry.hwLite.sendAsyncWithThrottle(defaultCMD); + const response = await Entry.hwLite.serial.sendAsyncWithThrottle(defaultCMD); if (response && response.indexOf('localdata') > -1) { const version = response.split(';')[1]; if (!version) { @@ -327,7 +327,7 @@ const EVENT_INTERVAL = 150; } const defaultCMD = `${this.functionKeys.LOCALDATA};`; - const response = await Entry.hwLite.sendAsyncWithThrottle(defaultCMD); + const response = await Entry.hwLite.serial.sendAsyncWithThrottle(defaultCMD); // const response = await this.getResponseWithSync(defaultCMD); // INFO: A,B 버튼이벤트 관련 로직 @@ -353,7 +353,7 @@ const EVENT_INTERVAL = 150; return command.split(';')[0]; } else { console.error("Error: microbit's response is not variable, ", command); - Entry.hwLite.handleConnectErrorInEngineRun(); + Entry.hwLite.serial.handleConnectErrorInEngineRun(); } } @@ -368,7 +368,7 @@ const EVENT_INTERVAL = 150; console.log("Microbit's command removed. Too many requests"); } else { console.error("Error: microbit's response is not variable, ", response); - Entry.hwLite.handleConnectErrorInEngineRun(); + Entry.hwLite.serial.handleConnectErrorInEngineRun(); } } @@ -376,7 +376,7 @@ const EVENT_INTERVAL = 150; if (!Entry.engine.isState('run')) { return; } - const result = await Entry.hwLite.sendAsyncWithThrottle(command); + const result = await Entry.hwLite.serial.sendAsyncWithThrottle(command); if ( (!result || this.getCommandType(command) !== this.getCommandType(result)) && // INFO : localdata 명령어는 우선순위가 낮으므로 반복하지 않음 diff --git a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js index 3fbfd677d3..2cc3dfe937 100644 --- a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js @@ -109,8 +109,8 @@ ANGLE: 0, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neo_lite.js b/src/playground/blocks/hardwareLite/block_neo_lite.js index a8113a67a7..016ce59566 100644 --- a/src/playground/blocks/hardwareLite/block_neo_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_lite.js @@ -308,7 +308,7 @@ } setZero() { - if (Entry.hwLite) { + if (Entry.hwLite.serial) { const blockId = this.generateBlockId(); const pdu = this.makePdu([ FrameCode.BASIC, @@ -322,7 +322,7 @@ pdu, }, ]; - Entry.hwLite.update(); + Entry.hwLite.serial.update(); } } @@ -346,7 +346,7 @@ blockId: 0, pdu: initPdu, }); - Entry.hwLite.update(); + Entry.hwLite.serial.update(); return true; } @@ -539,7 +539,7 @@ }; } requestLocalData() { - if (Entry.hwLite) { + if (Entry.hwLite.serial) { if (this.executeList.length > 0) { const executeData = this.executeList.shift(); this.logD(this.byteArrayToHex(executeData.pdu)); @@ -638,7 +638,7 @@ blockId, pdu, }); - // Entry.hwLite.writePortData(pdu); + // Entry.writePortData(pdu); } requestExtCommand(blockId, type, params) { diff --git a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js index b938fd5537..0412e41831 100644 --- a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js @@ -162,8 +162,8 @@ OUT_MOTOR_RIGHT: 0, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js index 74d91cfcb8..7628968525 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js @@ -156,8 +156,8 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js index f31c1f1cd7..4a56aa9fb1 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js @@ -120,8 +120,8 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js index dd218bfd7e..0ab139c973 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js @@ -129,8 +129,8 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js index 0cec847cc7..f7c23dfc7e 100644 --- a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js +++ b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js @@ -43,8 +43,8 @@ import _range from 'lodash/range'; this.analogValue = new Array(6).fill(0); this.readablePorts = _range(0, 19); - if (Entry.hwLite) { - Entry.hwLite.update(); + if (Entry.hwLite.serial) { + Entry.hwLite.serial.update(); } } From a84d955f75faf37817893503b1b83eeb5dda6d87 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 21 Dec 2023 18:40:36 +0900 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20Entry.hwLite=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/playground/blocks/hardwareLite/block_arduino_lite.js | 2 +- src/playground/blocks/hardwareLite/block_choco_lite.js | 2 +- src/playground/blocks/hardwareLite/block_hamster_lite.js | 2 +- src/playground/blocks/hardwareLite/block_neo_cannon_lite.js | 2 +- src/playground/blocks/hardwareLite/block_neo_lite.js | 4 ++-- src/playground/blocks/hardwareLite/block_neo_spider_lite.js | 2 +- .../blocks/hardwareLite/block_neobot_purple_lite.js | 2 +- src/playground/blocks/hardwareLite/block_neobot_soco_lite.js | 2 +- .../blocks/hardwareLite/block_neobot_thinkcar_lite.js | 2 +- src/playground/blocks/hardwareLite/block_sensorboard_lite.js | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_arduino_lite.js b/src/playground/blocks/hardwareLite/block_arduino_lite.js index 93f7b03616..3b59380b7b 100644 --- a/src/playground/blocks/hardwareLite/block_arduino_lite.js +++ b/src/playground/blocks/hardwareLite/block_arduino_lite.js @@ -48,7 +48,7 @@ import _range from 'lodash/range'; this.analogValue = new Array(6).fill(0); this.readablePorts = _range(0, 19); - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_choco_lite.js b/src/playground/blocks/hardwareLite/block_choco_lite.js index 3857bf32f3..8177768bba 100644 --- a/src/playground/blocks/hardwareLite/block_choco_lite.js +++ b/src/playground/blocks/hardwareLite/block_choco_lite.js @@ -269,7 +269,7 @@ const Buffer = require('buffer').Buffer; }, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_hamster_lite.js b/src/playground/blocks/hardwareLite/block_hamster_lite.js index 0a13994df3..840e631540 100644 --- a/src/playground/blocks/hardwareLite/block_hamster_lite.js +++ b/src/playground/blocks/hardwareLite/block_hamster_lite.js @@ -343,7 +343,7 @@ this.timeouts = []; this.__removeAllTimeouts(); - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js index 2cc3dfe937..a8b6c78a22 100644 --- a/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_cannon_lite.js @@ -109,7 +109,7 @@ ANGLE: 0, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neo_lite.js b/src/playground/blocks/hardwareLite/block_neo_lite.js index 016ce59566..8d23f39af5 100644 --- a/src/playground/blocks/hardwareLite/block_neo_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_lite.js @@ -308,7 +308,7 @@ } setZero() { - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { const blockId = this.generateBlockId(); const pdu = this.makePdu([ FrameCode.BASIC, @@ -539,7 +539,7 @@ }; } requestLocalData() { - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { if (this.executeList.length > 0) { const executeData = this.executeList.shift(); this.logD(this.byteArrayToHex(executeData.pdu)); diff --git a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js index 0412e41831..2fa4adc82b 100644 --- a/src/playground/blocks/hardwareLite/block_neo_spider_lite.js +++ b/src/playground/blocks/hardwareLite/block_neo_spider_lite.js @@ -162,7 +162,7 @@ OUT_MOTOR_RIGHT: 0, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js index 7628968525..73d6b424ea 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_purple_lite.js @@ -156,7 +156,7 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js index 4a56aa9fb1..7f63a7027d 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_soco_lite.js @@ -120,7 +120,7 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js index 0ab139c973..7506462816 100644 --- a/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js +++ b/src/playground/blocks/hardwareLite/block_neobot_thinkcar_lite.js @@ -129,7 +129,7 @@ FND: 0, OPT: 0, }; - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } diff --git a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js index f7c23dfc7e..d4589b5f81 100644 --- a/src/playground/blocks/hardwareLite/block_sensorboard_lite.js +++ b/src/playground/blocks/hardwareLite/block_sensorboard_lite.js @@ -43,7 +43,7 @@ import _range from 'lodash/range'; this.analogValue = new Array(6).fill(0); this.readablePorts = _range(0, 19); - if (Entry.hwLite.serial) { + if (Entry.hwLite && Entry.hwLite.serial) { Entry.hwLite.serial.update(); } } From 204e6accfe63dcd2690728f161f6a33cd8f13dcd Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Thu, 21 Dec 2023 21:51:38 +0900 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20WebBluetoothConnector=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hardware/webBluetoothConnector.ts | 74 +++++++++++++++++++++ src/class/hardware/webSerialConnector.ts | 2 - src/class/hardware/webUsbFlasher.ts | 2 +- src/class/hw_lite.ts | 24 +++---- types/index.d.ts | 1 - 5 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 src/class/hardware/webBluetoothConnector.ts diff --git a/src/class/hardware/webBluetoothConnector.ts b/src/class/hardware/webBluetoothConnector.ts new file mode 100644 index 0000000000..b43db907d1 --- /dev/null +++ b/src/class/hardware/webBluetoothConnector.ts @@ -0,0 +1,74 @@ +import { EntryHWLiteBaseModule } from 'types'; +import HardwareLite from '../hw_lite'; +import WebApiConnector from './webApiConnector'; +import { getServiceClassesByModuleId } from './bluetoothServices'; + +export default class WebBluetoothConnector extends WebApiConnector { + private hwModule: EntryHWLiteBaseModule; + private hwLite: HardwareLite; + private device: BluetoothDevice; + private services: any; // TODO: 타입할당 + private serviceClasses: any[]; + + constructor(hwModule: EntryHWLiteBaseModule, hwLite: HardwareLite) { + super(); + this.hwModule = hwModule; + this.setServiceClasses(); + } + + async connect() { + await this.setDevice(); + await this.setServices(); + if (this.hwModule?.initialHandshake) { + this.hwModule?.initialHandshake(); + } + } + + async disconnect() { + await this.device.gatt.disconnect(); + this.hwModule = undefined; + this.device = undefined; + this.services = undefined; + this.serviceClasses = undefined; + } + + async setDevice() { + const filters = this.hwModule.bluetoothInfo.filters; + const optionalServices = this.serviceClasses.map((serviceClass) => { + return serviceClass.uuid; + }); + this.device = await navigator.bluetooth.requestDevice({ filters, optionalServices }); + } + + // removeDevice() { + // //TODO: 송수신 소켓 닫기 + // this.device = undefined; + // } + + setServiceClasses() { + this.serviceClasses = getServiceClassesByModuleId(this.hwModule.id); + } + + async setServices() { + if (!this.device || !this.device.gatt) { + this.hwLite.getConnectFailedMenu(); + return; + } + + if (!this.device.gatt.connected) { + await this.device.gatt.connect(); + } + + this.services = {}; + + const primaryServices = await this.device.gatt.getPrimaryServices(); + + for (const primaryService of primaryServices) { + for (const serviceClass of this.serviceClasses) { + if (primaryService.uuid === serviceClass.uuid) { + this.services[serviceClass.name] = await serviceClass.create(primaryService); + } + } + } + } +} diff --git a/src/class/hardware/webSerialConnector.ts b/src/class/hardware/webSerialConnector.ts index 0d2b0209e5..1f2ea314af 100644 --- a/src/class/hardware/webSerialConnector.ts +++ b/src/class/hardware/webSerialConnector.ts @@ -83,8 +83,6 @@ export default class WebSerialConnector extends WebApiConnector { readable = readable.pipeThrough(new TransformStream(new LineBreakTransformer())); } this.reader = readable.getReader(); - this.hwLite.setStatus('connected'); - this.hwLite.refreshHardwareLiteBlockMenu(); if (this.hwModule?.initialHandshake) { const result = await this.hwModule.initialHandshake(); if (!result) { diff --git a/src/class/hardware/webUsbFlasher.ts b/src/class/hardware/webUsbFlasher.ts index d5c09b4ece..f682231ff1 100644 --- a/src/class/hardware/webUsbFlasher.ts +++ b/src/class/hardware/webUsbFlasher.ts @@ -129,7 +129,7 @@ export default class WebUsbFlasher { const iface = filteredInterfaces[filteredInterfaces.length - 1]; const altIface = iface.alternates[0]; if (altIface.endpoints.length) { - // study: 얘도 뭐지? + // study: endpoints 역할 this.isTransfer = true; const epIn = altIface.endpoints.filter((e) => 'in' == e.direction)[0]; this.endpointNumber = epIn.endpointNumber; diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index ff78f74496..3140f7580e 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -3,29 +3,23 @@ import ExtraBlockUtils from '../util/extrablockUtils'; import HardwareMonitor from './hardware/hardwareMonitor'; import WebUsbFlasher from './hardware/webUsbFlasher'; import WebSerialConnector from './hardware/webSerialConnector'; -// import WebBlueToothConnect from './hardware/webBluetoothConnector'; import WebApiConnector from './hardware/webApiConnector'; +import WebBluetoothConnector from './hardware/webBluetoothConnector'; const ARDUINO_BOARD_IDS: string[] = ['1.1', '4.2', '8.1']; export default class HardwareLite { private status: HWLiteStatus; - // private HardwareStatement: any; - // private port: any; // serialport - // private writer: any; // SerialPort.writer; - // private reader: any; //SerialPort.reader; private webConnector: WebApiConnector; // TODO: 추후 WebBlueToothConnect | WebSerialConnector로 변경예정 private serial: WebSerialConnector; - // private bluetooth: WebBlueToothConnector; + private bluetooth: WebBluetoothConnector; private flasher: WebUsbFlasher; - // private writableStream: any; hwModule: EntryHWLiteBaseModule; static setExternalModule: any; static refreshHardwareLiteBlockMenu: any; static banClassAllHardwareLite: any; static isHwLiteSupportAgent: any; private playground: any; - // private connectionType: 'ascii' | 'bytestream' | undefined; private hwMonitor?: HardwareMonitor; static getStatus: any; @@ -189,6 +183,8 @@ export default class HardwareLite { } try { await this.webConnector.connect(); + this.setStatus('connected'); + this.refreshHardwareLiteBlockMenu(); Entry.toast.success( Lang.Msgs.hw_connection_success, Lang.Msgs.hw_connection_success_desc2 @@ -237,9 +233,13 @@ export default class HardwareLite { switch (webapiType) { case 'ble': { - // if(!this.hwModule.bluetoothInfo){return;} - // this.bluetooth = new WebBlueToothConnect(this.hwModule); - // this.webConnector = this.bluetooth; + if (!this.hwModule.bluetoothInfo) { + console.error('Invalid bluetooth hwModule'); + this.getConnectFailedMenu(); + return; + } + this.bluetooth = new WebBluetoothConnector(this.hwModule, this); + this.webConnector = this.bluetooth; break; } case 'serial': @@ -258,7 +258,7 @@ export default class HardwareLite { removeWebConnector() { this.webConnector = undefined; this.serial = undefined; - // this.bluetooth = undefined; + this.bluetooth = undefined; } setFlasher() { diff --git a/types/index.d.ts b/types/index.d.ts index 66dea3232f..c253a5e913 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -246,7 +246,6 @@ export declare interface HWLiteSerialInfo { export declare interface HWLiteBluetoothInfo { filters: BluetoothLEScanFilter[]; optionalServices: string[]; - constantServing?: boolean | 'ReadOnly'; } export declare type HWLiteStatus = From 05902d52bbcc21cf46e6f401c410ab5f58192758 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 00:39:55 +0900 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20=EC=97=B0=EA=B2=B0=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95,=20webConnector=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=94=84=EC=82=AC=EC=9D=B4=ED=81=B4=EC=97=90=20initia?= =?UTF-8?q?lDevice()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hardware/webApiConnector.ts | 1 + src/class/hardware/webBluetoothConnector.ts | 9 ++++++--- src/class/hardware/webSerialConnector.ts | 21 ++++++++++++--------- src/class/hw_lite.ts | 1 + 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/class/hardware/webApiConnector.ts b/src/class/hardware/webApiConnector.ts index e3729529d1..93cda91c30 100644 --- a/src/class/hardware/webApiConnector.ts +++ b/src/class/hardware/webApiConnector.ts @@ -2,4 +2,5 @@ export default class WebApiConnector { connect() {} disconnect() {} + initialDevice() {} } diff --git a/src/class/hardware/webBluetoothConnector.ts b/src/class/hardware/webBluetoothConnector.ts index b43db907d1..e0f324b6b4 100644 --- a/src/class/hardware/webBluetoothConnector.ts +++ b/src/class/hardware/webBluetoothConnector.ts @@ -19,9 +19,6 @@ export default class WebBluetoothConnector extends WebApiConnector { async connect() { await this.setDevice(); await this.setServices(); - if (this.hwModule?.initialHandshake) { - this.hwModule?.initialHandshake(); - } } async disconnect() { @@ -32,6 +29,12 @@ export default class WebBluetoothConnector extends WebApiConnector { this.serviceClasses = undefined; } + async initialDevice() { + if (this.hwModule?.initialHandshake) { + this.hwModule?.initialHandshake(); + } + } + async setDevice() { const filters = this.hwModule.bluetoothInfo.filters; const optionalServices = this.serviceClasses.map((serviceClass) => { diff --git a/src/class/hardware/webSerialConnector.ts b/src/class/hardware/webSerialConnector.ts index 1f2ea314af..191125a175 100644 --- a/src/class/hardware/webSerialConnector.ts +++ b/src/class/hardware/webSerialConnector.ts @@ -83,15 +83,6 @@ export default class WebSerialConnector extends WebApiConnector { readable = readable.pipeThrough(new TransformStream(new LineBreakTransformer())); } this.reader = readable.getReader(); - if (this.hwModule?.initialHandshake) { - const result = await this.hwModule.initialHandshake(); - if (!result) { - throw new Error('Handshake Error : 디바이스와 연결에 실패하였습니다.'); - } - } - if (this.hwModule.portData?.constantServing) { - this.constantServing(); - } } async disconnect() { @@ -117,6 +108,18 @@ export default class WebSerialConnector extends WebApiConnector { } } + async initialDevice() { + if (this.hwModule.initialHandshake) { + const result = await this.hwModule.initialHandshake(); + if (!result) { + throw new Error('Handshake Error : 디바이스와 연결에 실패하였습니다.'); + } + } + if (this.hwModule.portData?.constantServing) { + this.constantServing(); + } + } + /** * 디바이스와 1회성 통신 * @param data diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index 3140f7580e..5d1fc85f7b 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -185,6 +185,7 @@ export default class HardwareLite { await this.webConnector.connect(); this.setStatus('connected'); this.refreshHardwareLiteBlockMenu(); + await this.webConnector.initialDevice(); Entry.toast.success( Lang.Msgs.hw_connection_success, Lang.Msgs.hw_connection_success_desc2 From e6c627a34656d5d44caab9f7d46c7e0573f9daac Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 01:07:54 +0900 Subject: [PATCH 10/21] =?UTF-8?q?feat:=20=EB=A7=88=EB=B9=972=20ble=20?= =?UTF-8?q?=EB=B8=94=EB=A1=9D=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EB=B8=94=EB=9F=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hardwareLite/block_microbit2ble_lite.js | 2507 +++++++++++++++++ .../metadata_microbit2ble_lite.json | 9 + 2 files changed, 2516 insertions(+) create mode 100644 src/playground/blocks/hardwareLite/block_microbit2ble_lite.js create mode 100644 src/playground/blocks/hardwareLite/metadata_microbit2ble_lite.json diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js new file mode 100644 index 0000000000..d3be714bcb --- /dev/null +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -0,0 +1,2507 @@ +'use strict'; + +const _throttle = require('lodash/throttle'); + +const EVENT_INTERVAL = 150; + +(function() { + Entry.Microbit2BleLite = new (class Microbit2LiteBle { + constructor() { + this.webapiType = 'ble'; + this.firmwareFlash = true; + this.bluetoothInfo = { + filters: [ + { + namePrefix: 'BBC micro:bit', + }, + ], + }; + + this.presetImage = [ + // Image.HEART + '09090:99999:99999:09990:00900', + // Image.HEART_SMALL, + '00000:09090:09990:00900:00000', + // Image.HAPPY, + '00000:09090:00000:90009:09990', + // Image.SMILE, + '00000:00000:00000:90009:09990', + // Image.SAD, + '00000:09090:00000:09990:90009', + // Image.CONFUSED, + '00000:09090:00000:09090:90909', + // Image.ANGRY, + '90009:09090:00000:99999:90909', + // Image.ASLEEP, + '00000:99099:00000:09990:00000', + // Image.SURPRISED, + '09090:00000:00900:09090:00900', + // Image.SILLY, + '90009:00000:99999:00909:00999', + // Image.FABULOUS, + '99999:99099:00000:09090:09990', + // Image.MEH, + '09090:00000:00090:00900:09000', + // Image.YES, + '00000:00009:00090:90900:09000', + // Image.NO, + '90009:09090:00900:09090:90009', + // Image.CLOCK1, + '00090:00090:00900:00000:00000', + // Image.CLOCK2, + '00000:00990:00900:00000:00000', + // Image.CLOCK3, + '00000:00000:00999:00000:00000', + // Image.CLOCK4, + '00000:00000:00900:00990:00000', + // Image.CLOCK5, + '00000:00000:00900:00090:00090', + // Image.CLOCK6, + '00000:00000:00900:00900:00900', + // Image.CLOCK7, + '00000:00000:00900:09000:09000', + // Image.CLOCK8, + '00000:00000:00900:99000:00000', + // Image.CLOCK9, + '00000:00000:99900:00000:00000', + // Image.CLOCK10, + '00000:09900:00900:00000:00000', + // Image.CLOCK11, + '09000:09000:00900:00000:00000', + // Image.CLOCK12, + '00900:00900:00900:00000:00000', + // Image.ARROW_N, + '00900:09990:90909:00900:00900', + // Image.ARROW_NE, + '00999:00099:00909:09000:90000', + // Image.ARROW_E, + '00900:00090:99999:00090:00900', + // Image.ARROW_SE, + '90000:09000:00909:00099:00999', + // Image.ARROW_S, + '00900:00900:90909:09990:00900', + // Image.ARROW_SW, + '00009:00090:90900:99000:99900', + // Image.ARROW_W, + '00900:09000:99999:09000:00900', + // Image.ARROW_NW, + '99900:99000:90900:00090:00009', + // Image.TRIANGLE, + '00000:00900:09090:99999:00000', + // Image.TRIANGLE_LEFT, + '90000:99000:90900:90090:99999', + // Image.CHESSBOARD, + '09090:90909:09090:90909:09090', + // Image.DIAMOND, + '00900:09090:90009:09090:00900', + // Image.DIAMOND_SMALL, + '00000:00900:09090:00900:00000', + // Image.SQUARE, + '99999:90009:90009:90009:99999', + // Image.SQUARE_SMALL, + '00000:09990:09090:09990:00000', + // Image.RABBIT, + '90900:90900:99990:99090:99990', + // Image.COW, + '90009:90009:99999:09990:00900', + // Image.MUSIC_CROTCHET, + '00900:00900:00900:99900:99900', + // Image.MUSIC_QUAVER, + '00900:00990:00909:99900:99900', + // Image.MUSIC_QUAVERS, + '09999:09009:09009:99099:99099', + // Image.PITCHFORK, + '90909:90909:99999:00900:00900', + // Image.XMAS, + '00900:09990:00900:09990:99999', + // Image.PACMAN, + '099999:99090:99900:99990:09999', + // Image.TARGET, + '00900:09990:99099:09990:00900', + // Image.TSHIRT, + '99099:99999:09990:09990:09990', + // Image.ROLLERSKATE, + '00099:00099:99999:99999:09090', + // Image.DUCK, + '00990:99900:09999:09990:00000', + // Image.HOUSE, + '00900:09990:99999:09990:09090', + // Image.TORTOISE, + '00000:09990:99999:09090:00000', + // Image.BUTTERFLY, + '99099:99999:00900:99999:99099', + // Image.STICKFIGURE, + '00900:99999:00900:09090:90009', + // Image.GHOST, + '99999:90909:99999:99999:90909', + // Image.SWORD, + '00900:00900:00900:09990:00900', + // Image.GIRAFFE, + '99000:09000:09000:09990:09090', + // Image.SKULL, + '09990:90909:99999:09990:09990', + // Image.UMBRELLA, + '09990:99999:00900:90900:09900', + // Image.SNAKE, + '99000:99099:09090:09990:00000', + ]; + this.id = '220302'; + this.url = 'http://microbit.org/ko/'; + this.imageName = 'microbit2lite.png'; + this.title = { + en: 'Microbit', + ko: '마이크로비트', + }; + this.name = 'Microbit2BleLite'; + this.functionKeys = { + LOCALDATA: 'localdata', + GET_ANALOG: 'get-analog', + GET_DIGITAL: 'get-digital', + SET_ANALOG: 'set-analog', + SET_DIGITAL: 'set-digital', + SET_LED: 'set-pixel', + GET_LED: 'get-pixel', + RESET: 'reset', + PRESET_IMAGE: 'pre-image', + SET_CUSTOM_IMAGE: 'custom-image', + SET_STRING: 'print', + RESET_SCREEN: 'display-clear', + DISPLAY_ON: 'display-on', + DISPLAY_OFF: 'display-off', + SPEAKER_ON: 'speaker-on', + SPEAKER_OFF: 'speaker-off', + PLAY_TONE: 'play-tone', + PLAY_SOUND: 'pre-sound', + PLAY_MELODY: 'pre-melody', + GET_BTN: 'get-btn', + CHANGE_TEMPO: 'change-tempo', + GET_LOGO: 'get-touch', + GET_ACC: 'get-acc', + GET_GESTURE: 'get-gesture', + GET_DIRECTION: 'direction', + GET_FIELD_STRENGTH: 'field-strength', + GET_FIELD_STRENGTH_AXIS: 'field-axis-strength', + GET_LIGHT_LEVEL: 'light-level', + GET_TEMPERATURE: 'temperature', + GET_SOUND_LEVEL: 'sound-level', + SET_RADIO: 'radio-send', + GET_RADIO: 'radio-receive', + RADIO_ON: 'radio-on', + RADIO_OFF: 'radio-off', + SETTING_RADIO: 'radio-setting', + SET_SERVO_MILLI: 'write-period', + SET_SERVO_MICRO: 'write-micro-period', + SET_SERVO_ANGLE: 'servo-write', + }; + this.presetImage = [ + // Image.HEART + '09090:99999:99999:09990:00900', + // Image.HEART_SMALL, + '00000:09090:09990:00900:00000', + // Image.HAPPY, + '00000:09090:00000:90009:09990', + // Image.SMILE, + '00000:00000:00000:90009:09990', + // Image.SAD, + '00000:09090:00000:09990:90009', + // Image.CONFUSED, + '00000:09090:00000:09090:90909', + // Image.ANGRY, + '90009:09090:00000:99999:90909', + // Image.ASLEEP, + '00000:99099:00000:09990:00000', + // Image.SURPRISED, + '09090:00000:00900:09090:00900', + // Image.SILLY, + '90009:00000:99999:00909:00999', + // Image.FABULOUS, + '99999:99099:00000:09090:09990', + // Image.MEH, + '09090:00000:00090:00900:09000', + // Image.YES, + '00000:00009:00090:90900:09000', + // Image.NO, + '90009:09090:00900:09090:90009', + // Image.CLOCK1, + '00090:00090:00900:00000:00000', + // Image.CLOCK2, + '00000:00990:00900:00000:00000', + // Image.CLOCK3, + '00000:00000:00999:00000:00000', + // Image.CLOCK4, + '00000:00000:00900:00990:00000', + // Image.CLOCK5, + '00000:00000:00900:00090:00090', + // Image.CLOCK6, + '00000:00000:00900:00900:00900', + // Image.CLOCK7, + '00000:00000:00900:09000:09000', + // Image.CLOCK8, + '00000:00000:00900:99000:00000', + // Image.CLOCK9, + '00000:00000:99900:00000:00000', + // Image.CLOCK10, + '00000:09900:00900:00000:00000', + // Image.CLOCK11, + '09000:09000:00900:00000:00000', + // Image.CLOCK12, + '00900:00900:00900:00000:00000', + // Image.ARROW_N, + '00900:09990:90909:00900:00900', + // Image.ARROW_NE, + '00999:00099:00909:09000:90000', + // Image.ARROW_E, + '00900:00090:99999:00090:00900', + // Image.ARROW_SE, + '90000:09000:00909:00099:00999', + // Image.ARROW_S, + '00900:00900:90909:09990:00900', + // Image.ARROW_SW, + '00009:00090:90900:99000:99900', + // Image.ARROW_W, + '00900:09000:99999:09000:00900', + // Image.ARROW_NW, + '99900:99000:90900:00090:00009', + // Image.TRIANGLE, + '00000:00900:09090:99999:00000', + // Image.TRIANGLE_LEFT, + '90000:99000:90900:90090:99999', + // Image.CHESSBOARD, + '09090:90909:09090:90909:09090', + // Image.DIAMOND, + '00900:09090:90009:09090:00900', + // Image.DIAMOND_SMALL, + '00000:00900:09090:00900:00000', + // Image.SQUARE, + '99999:90009:90009:90009:99999', + // Image.SQUARE_SMALL, + '00000:09990:09090:09990:00000', + // Image.RABBIT, + '90900:90900:99990:99090:99990', + // Image.COW, + '90009:90009:99999:09990:00900', + // Image.MUSIC_CROTCHET, + '00900:00900:00900:99900:99900', + // Image.MUSIC_QUAVER, + '00900:00990:00909:99900:99900', + // Image.MUSIC_QUAVERS, + '09999:09009:09009:99099:99099', + // Image.PITCHFORK, + '90909:90909:99999:00900:00900', + // Image.XMAS, + '00900:09990:00900:09990:99999', + // Image.PACMAN, + '099999:99090:99900:99990:09999', + // Image.TARGET, + '00900:09990:99099:09990:00900', + // Image.TSHIRT, + '99099:99999:09990:09990:09990', + // Image.ROLLERSKATE, + '00099:00099:99999:99999:09090', + // Image.DUCK, + '00990:99900:09999:09990:00000', + // Image.HOUSE, + '00900:09990:99999:09990:09090', + // Image.TORTOISE, + '00000:09990:99999:09090:00000', + // Image.BUTTERFLY, + '99099:99999:00900:99999:99099', + // Image.STICKFIGURE, + '00900:99999:00900:09090:90009', + // Image.GHOST, + '99999:90909:99999:99999:90909', + // Image.SWORD, + '00900:00900:00900:09990:00900', + // Image.GIRAFFE, + '99000:09000:09000:09990:09090', + // Image.SKULL, + '09990:90909:99999:09990:09990', + // Image.UMBRELLA, + '09990:99999:00900:90900:09900', + // Image.SNAKE, + '99000:99099:09090:09990:00000', + ]; + this.digitalPins = [ + ['P8', 8], + ['P9', 9], + ['P12', 12], + ['P13', 13], + ['P14', 14], + ['P15', 15], + ['P16', 16], + ]; + this.analogPins = [ + ['P0', 0], + ['P1', 1], + ['P2', 2], + ['P3', 3], + ['P4', 4], + ['P10', 10], + ]; + this.majorPins = [ + ['P0', 0], + ['P1', 1], + ['P2', 2], + ]; + this.ledRows = [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + [4, 4], + ]; + this.defaultLed = [ + [0, 0, 0, 0, 0], + [0, 9, 0, 9, 0], + [0, 0, 0, 0, 0], + [9, 0, 0, 0, 9], + [0, 9, 9, 9, 0], + ]; + this.blockMenuBlocks = [ + 'microbit2blelite_common_title', + 'microbit2blelite_get_analog', + // 'microbit2blelite_set_analog', + // 'microbit2blelite_get_digital', + // 'microbit2blelite_set_digital', + // 'microbit2blelite_screen_toggle', + // 'microbit2blelite_set_led', + // 'microbit2blelite_get_led', + // 'microbit2blelite_show_preset_image', + // 'microbit2blelite_show_custom_image', + // 'microbit2blelite_show_string', + // 'microbit2blelite_reset_screen', + // 'microbit2blelite_radio_toggle', + // 'microbit2blelite_radio_setting', + // 'microbit2blelite_radio_send', + // 'microbit2blelite_radio_received', + // 'microbit2blelite_change_tempo', + // 'microbit2blelite_set_tone', + // 'microbit2blelite_play_preset_music', + // 'microbit2blelite_get_btn', + // 'microbit2blelite_get_acc', + // 'microbit2blelite_get_gesture', + // 'microbit2blelite_get_direction', + // 'microbit2blelite_get_field_strength_axis', + // 'microbit2blelite_get_light_level', + // 'microbit2blelite_get_temperature', + // 'microbit2blelite_set_servo', + // 'microbit2blelite_set_pwm', + // 'microbit2blelite_v2_title', + // 'microbit2blelite_get_logo', + // 'microbit2blelite_btn_event', + 'microbit2blelite_speaker_toggle', + // 'microbit2blelite_play_sound_effect', + // 'microbit2blelite_get_sound_level', + ]; + this.services = undefined; + this.ledState = [ + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + ]; + this.version = '2'; + this.buttonState = { a: 0, b: 0 }; + } + setZero() {} + + async initialHandshake() { + this.services = Entry.hwLite.bluetooth.services; + if (this.services.temperatureService) { + await this.services.temperatureService.setTemperaturePeriod(2000); + } + if (this.services.accelerometerService) { + await this.services.accelerometerService.setAccelerometerPeriod(640); + } + this.setEventListener(); + } + + setEventListener() { + if (!this.services) { + return; + } + this.setButtonEvent(); + } + + setButtonEvent() { + const pressedBoth = () => { + if (this.buttonState.a + this.buttonState.b === 2) { + Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 3); + return true; + } + return false; + }; + if (this.services.ButtonService) { + this.services.ButtonService.addEventListener('buttonastatechanged', (event) => { + this.buttonState.a = event.detail; + if (event.detail === 1 && !pressedBoth()) { + Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 1); + } + }); + this.services.ButtonService.addEventListener('buttonbstatechanged', (event) => { + this.buttonState.b = event.detail; + if (event.detail === 1 && !pressedBoth()) { + Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 2); + } + }); + } + } + + // 언어 적용 + setLanguage() { + return { + ko: { + template: { + microbit2blelite_get_analog: '핀 %1 번 아날로그 값', + microbit2blelite_set_analog: '핀 %1 에 아날로그 값 %2 를 출력하기 %3', + microbit2blelite_get_digital: '핀 %1 번 디지털 값', + microbit2blelite_set_digital: '핀 %1 에 디지털 값 %2 를 출력하기 %3', + microbit2blelite_screen_toggle: 'LED 기능 %1 %2', + microbit2blelite_set_led: 'LED의 X: %1 Y: %2 를 밝기 %3 (으)로 켜기 %4', + microbit2blelite_get_led: 'LED의 X: %1 Y: %2 밝기 값', + microbit2blelite_show_preset_image: 'LED에 %1 모양 나타내기 %2', + microbit2blelite_show_custom_image: 'LED %1 켜기 %2', + microbit2blelite_show_string: 'LED에 %1 을(를) 나타내기 %2', + microbit2blelite_reset_screen: 'LED 모두 지우기 %1', + microbit2blelite_radio_toggle: '라디오 기능 %1 %2', + microbit2blelite_radio_setting: '라디오 채널을 %1 (으)로 바꾸기 %2', + microbit2blelite_radio_send: '라디오로 %1 송신하기 %2', + microbit2blelite_radio_received: '라디오 수신 값', + microbit2blelite_speaker_toggle: '스피커 기능 %1 %2', + microbit2blelite_change_tempo: '연주 속도를 %1 박에 %2 BPM으로 정하기 %3', + microbit2blelite_set_tone: '%1 음을 %2 박만큼 연주하기 %3', + microbit2blelite_play_preset_music: '%1 음악을 연주하기 %2', + microbit2blelite_play_sound_effect: '%1 효과음을 연주하기 %2', + microbit2blelite_get_btn: '%1 버튼이 눌렸는가?', + microbit2blelite_get_logo: '로고를 터치했는가?', + microbit2blelite_get_gesture: '움직임이 %1 인가?', + microbit2blelite_get_acc: '%1 의 가속도 값', + microbit2blelite_btn_event: '%1 %2 버튼을 눌렀을 때', + microbit2blelite_get_direction: '나침반 방향', + microbit2blelite_get_field_strength_axis: '%1 의 자기장 세기 값', + microbit2blelite_get_light_level: '빛 센서 값', + microbit2blelite_get_temperature: '온도 값', + microbit2blelite_get_sound_level: '마이크 소리 크기 값', + microbit2blelite_set_servo: '핀 %1 에 서보 모터 각도를 %2 로 정하기 %3', + microbit2blelite_set_pwm: '핀 %1 에 서보 펄스 폭을 %2 %3초로 정하기 %4', + microbit2blelite_common_title: '마이크로비트 공통', + microbit2blelite_v2_title: '마이크로비트 V2 전용', + }, + Blocks: { + octave: '옥타브', + scalar: '스칼라', + xAxis: 'X축', + yAxis: 'Y축', + zAxis: 'Z축', + up: '위', + down: '아래', + left: '왼쪽', + right: '오른쪽', + face_up: '앞면', + face_down: '뒷면', + freefall: '자유 낙하', + '3g': '3G', + '6g': '6G', + '8g': '8G', + shake: '흔들림', + DADADADUM: '운명 교향곡', + ENTERTAINER: '엔터테이너', + PRELUDE: '바흐 프렐류드 1번', + ODE: '합창 교향곡', + NYAN: '냥캣', + RINGTONE: '벨소리', + FUNK: '펑크', + BLUES: '블루스', + BIRTHDAY: '생일 축하합니다', + WEDDING: '결혼 행진곡', + FUNERAL: '장례식 노래', + PUNCHLINE: '펀치라인', + PYTHON: '서커스', + BADDY: '악당', + CHASE: '추격전', + BA_DING: '동전 GET', + WAWAWAWAA: '실망', + JUMP_UP: '위로 점프', + JUMP_DOWN: '아래로 점프', + POWER_UP: '켜기', + POWER_DOWN: '끄기', + GIGGLE: '웃음', + HAPPY: '행복', + HELLO: '인사', + MYSTERIOUS: '신비로움', + SAD: '슬픔', + SLIDE: '슬라이드', + SOARING: '상승', + SPRING: '봄', + TWINKLE: '반짝반짝', + YAWN: '하품', + plot: '켜기', + unplot: '끄기', + on: '켜기', + off: '끄기', + microbit_2_HEART: '하트', + microbit_2_HEART_SMALL: '작은 하트', + microbit_2_HAPPY: '행복', + microbit_2_SMILE: '웃음', + microbit_2_SAD: '슬픔', + microbit_2_CONFUSED: '혼란', + microbit_2_ANGRY: '화남', + microbit_2_ASLEEP: '졸림', + microbit_2_SURPRISED: '놀람', + microbit_2_SILLY: '멍청함', + microbit_2_FABULOUS: '환상적인', + microbit_2_MEH: '별로', + microbit_2_YES: '예스', + microbit_2_NO: '노', + microbit_2_TRIANGLE: '삼각형', + microbit_2_TRIANGLE_LEFT: '왼쪽 삼각형', + microbit_2_CHESSBOARD: '체스판', + microbit_2_DIAMOND: '다이아몬드', + microbit_2_DIAMOND_SMALL: '작은 다이아몬드', + microbit_2_SQUARE: '사각형', + microbit_2_SQUARE_SMALL: '작은 사각형', + microbit_2_RABBIT: '토끼', + microbit_2_COW: '소', + microbit_2_MUSIC_CROTCHET: '4분음표', + microbit_2_MUSIC_QUAVER: '8분음표', + microbit_2_MUSIC_QUAVERS: '8분음표 2개', + microbit_2_PITCHFORK: '쇠스랑', + microbit_2_XMAS: '크리스마스 트리', + microbit_2_PACMAN: '팩맨', + microbit_2_TARGET: '목표', + microbit_2_TSHIRT: '티셔츠', + microbit_2_ROLLERSKATE: '롤러스케이트', + microbit_2_DUCK: '오리', + microbit_2_HOUSE: '집', + microbit_2_TORTOISE: '거북이', + microbit_2_BUTTERFLY: '나비', + microbit_2_STICKFIGURE: '스틱맨', + microbit_2_GHOST: '유령', + microbit_2_SWORD: '칼', + microbit_2_GIRAFFE: '기린', + microbit_2_SKULL: '해골', + microbit_2_UMBRELLA: '우산', + microbit_2_SNAKE: '뱀', + microbit_2_CLOCK1: '1시', + microbit_2_CLOCK2: '2시', + microbit_2_CLOCK3: '3시', + microbit_2_CLOCK4: '4시', + microbit_2_CLOCK5: '5시', + microbit_2_CLOCK6: '6시', + microbit_2_CLOCK7: '7시', + microbit_2_CLOCK8: '8시', + microbit_2_CLOCK9: '9시', + microbit_2_CLOCK10: '10시', + microbit_2_CLOCK11: '11시', + microbit_2_CLOCK12: '12시', + microbit_2_ARROW_N: '북쪽', + microbit_2_ARROW_NE: '북동쪽', + microbit_2_ARROW_E: '동쪽', + microbit_2_ARROW_SE: '남동쪽', + microbit_2_ARROW_S: '남쪽', + microbit_2_ARROW_SW: '남서쪽', + microbit_2_ARROW_W: '서쪽', + microbit_2_ARROW_NW: '북서쪽', + }, + Helper: { + microbit2blelite_get_analog: '선택한 핀의 아날로그 값입니다. (0 ~ 1023)', + microbit2blelite_set_analog: + '선택한 핀에 입력한 아날로그 값을 출력합니다. (0 ~ 1023)', + microbit2blelite_get_digital: '선택한 핀의 디지털 값입니다. (0, 1)', + microbit2blelite_set_digital: + '선택한 핀에 입력한 디지털 값을 출력합니다. (0, 1)', + microbit2blelite_screen_toggle: 'LED 기능을 켜거나 끕니다.', + microbit2blelite_set_led: 'X, Y 좌표로 선택한 LED를 선택한 밝기로 켭니다.', + microbit2blelite_get_led: 'X, Y 좌표로 선택한 LED의 밝기 값입니다.', + microbit2blelite_show_preset_image: + 'LED에 미리 설정되어 있는 모양을 나타냅니다.', + microbit2blelite_show_custom_image: + '블록에서 선택한 LED를 선택한 밝기로 켭니다. 한번에 모든 LED를 조작할 수 있습니다.', + microbit2blelite_show_string: '입력한 문자열을 LED에 차례대로 나타냅니다.', + microbit2blelite_reset_screen: 'LED에 표시된 내용을 모두 지웁니다.', + microbit2blelite_radio_toggle: '라디오 기능을 켜거나 끕니다.', + microbit2blelite_radio_setting: '라디오 채널을 입력한 숫자로 바꿉니다.', + microbit2blelite_radio_send: '라디오로 입력한 영문과 숫자를 송신합니다.', + microbit2blelite_radio_received: '라디오로 수신된 값입니다.', + microbit2blelite_speaker_toggle: '스피커 기능을 켜거나 끕니다.', + microbit2blelite_change_tempo: + '연주 속도를 선택한 박자와 BPM으로 정합니다.', + microbit2blelite_set_tone: + '선택한 음을 선택한 박만큼 연주합니다. 1~5옥타브 사이의 음계를 선택할 수 있습니다.', + microbit2blelite_play_preset_music: '미리 설정되어 있는 음악을 연주합니다.', + microbit2blelite_play_sound_effect: + '미리 설정되어 있는 효과음을 연주합니다.', + microbit2blelite_get_btn: "선택한 버튼이 눌렸다면 '참'으로 판단합니다.", + microbit2blelite_get_logo: "로고를 터치했다면 '참'으로 판단합니다.", + microbit2blelite_get_gesture: + "선택한 움직임이 감지되면 '참'으로 판단합니다.", + microbit2blelite_get_acc: + '선택한 버튼이 눌리면 아래에 연결된 블록들을 실행합니다.', + microbit2blelite_btn_event: '%1 %2 버튼을 눌렀을 때', + microbit2blelite_get_direction: '나침반 방향 값입니다. (0~360) ', + microbit2blelite_get_field_strength_axis: + '선택한 축의 자기장 세기 값입니다.', + microbit2blelite_get_light_level: '빛 센서의 값입니다.', + microbit2blelite_get_temperature: '현재 온도 값입니다. (℃)', + microbit2blelite_get_sound_level: '마이크 소리 크기 값입니다.', + microbit2blelite_set_servo: + '선택한 핀에 서보 모터 각도를 입력한 값으로 정합니다.', + microbit2blelite_set_pwm: + '선택한 핀의 서보 펄스폭을 선택한 시간으로 정합니다.', + }, + Msgs: { + microbit2blelite_compatible_error: + '마이크로비트 V2에서만 사용할 수 있는 블록입니다.', + microbit2blelite_octave: '옥타브', + }, + }, + en: { + template: { + microbit2blelite_get_analog: 'analog read pin %1', + microbit2blelite_set_analog: 'analog write pin %1 to %2 %3', + microbit2blelite_get_digital: 'digital read pin %1', + microbit2blelite_set_digital: 'digital write pin %1 to %2 %3', + microbit2blelite_screen_toggle: '%1 LED', + microbit2blelite_set_led: 'plot X: %1 Y:%2 brightness %3 %4', + microbit2blelite_get_led: 'point X: %1 Y: %2 brightness of LED', + microbit2blelite_show_preset_image: 'show icon %1 on LED %2', + microbit2blelite_show_custom_image: 'Show %1 on LED %2', + microbit2blelite_show_string: 'show string %1 on LED %2', + microbit2blelite_reset_screen: 'clear LED screen %1', + microbit2blelite_radio_toggle: '%1 radio %2', + microbit2blelite_radio_setting: 'radio change channel to %1 %2', + microbit2blelite_radio_send: 'radio send %1 %2', + microbit2blelite_radio_received: 'radio received value', + microbit2blelite_speaker_toggle: '%1 speaker %2', + microbit2blelite_change_tempo: 'set tempo to %2 BPM per %1 beat %3', + microbit2blelite_set_tone: 'play melody %1 for %2 beat %3', + microbit2blelite_play_preset_music: 'play music %1 %2', + microbit2blelite_play_sound_effect: 'play sound %1 %2', + microbit2blelite_get_btn: '%1 button pressed?', + microbit2blelite_get_logo: 'logo touched?', + microbit2blelite_get_gesture: 'Is the movement %1?', + microbit2blelite_get_acc: 'acceleration value of %1', + microbit2blelite_btn_event: '%1 When %2 button pressed', + microbit2blelite_get_direction: 'compass direction', + microbit2blelite_get_field_strength_axis: + 'magnetic field strength value of %1 ', + microbit2blelite_get_light_level: 'Light sensor value', + microbit2blelite_get_temperature: 'temperature value', + microbit2blelite_get_sound_level: 'microphone volume value', + microbit2blelite_set_servo: 'set servo pin %1 angle to %2 %3', + microbit2blelite_set_pwm: 'set servo pin %1 pulse to %2 %3 %4', + microbit2blelite_common_title: 'Common Blocks', + microbit2blelite_v2_title: 'v2 Only', + }, + Blocks: { + octave: 'octave', + scalar: 'scalar', + xAxis: 'X-axis', + yAxis: 'Y-axis', + zAxis: 'Z-axis', + up: 'up', + down: 'down', + left: 'left', + right: 'right', + face_up: 'face up', + face_down: 'face down', + freefall: 'freefall', + '3g': '3G', + '6g': '6G', + '8g': '8G', + shake: 'shake', + DADADADUM: 'DADADADUM', + ENTERTAINER: 'ENTERTAINER', + PRELUDE: 'PRELUDE', + ODE: 'ODE', + NYAN: 'NYAN', + RINGTONE: 'RINGTONE', + FUNK: 'FUNK', + BLUES: 'BLUES', + BIRTHDAY: 'BIRTHDAY', + WEDDING: 'WEDDING', + FUNERAL: 'FUNERAL', + PUNCHLINE: 'PUNCHLINE', + PYTHON: 'PYTHON', + BADDY: 'BADDY', + CHASE: 'CHASE', + BA_DING: 'BA_DING', + WAWAWAWAA: 'WAWAWAWAA', + JUMP_UP: 'JUMP_UP', + JUMP_DOWN: 'JUMP DOWN', + POWER_UP: 'POWER UP', + POWER_DOWN: 'POWER DOWN', + GIGGLE: 'GIGGLE', + HAPPY: 'HAPPY', + HELLO: 'HELLO', + MYSTERIOUS: 'MYSTERIOUS', + SAD: 'SAD', + SLIDE: 'SLIDE', + SOARING: 'SOARING', + SPRING: 'SPRING', + TWINKLE: 'TWINKLE', + YAWN: 'YAWN', + plot: 'plot', + unplot: 'unplot', + on: 'turn on', + off: 'turn off', + microbit_2_HEART: 'HEART', + microbit_2_HEART_SMALL: 'HEART_SMALL', + microbit_2_HAPPY: 'HAPPY', + microbit_2_SMILE: 'SMILE', + microbit_2_SAD: 'SAD', + microbit_2_CONFUSED: 'CONFUSEd', + microbit_2_ANGRY: 'ANGRY', + microbit_2_ASLEEP: 'ASLEEP', + microbit_2_SURPRISED: 'SURPRISED', + microbit_2_SILLY: 'SILLY', + microbit_2_FABULOUS: 'FABULOUS', + microbit_2_MEH: 'MEH', + microbit_2_YES: 'YES', + microbit_2_NO: 'NO', + microbit_2_TRIANGLE: 'TRIANGLE', + microbit_2_TRIANGLE_LEFT: 'TRIANGLE LEFT', + microbit_2_CHESSBOARD: 'CHESSBOARD', + microbit_2_DIAMOND: 'DIAMOND', + microbit_2_DIAMOND_SMALL: 'DIAMOND SMALL', + microbit_2_SQUARE: 'SQUARE', + microbit_2_SQUARE_SMALL: 'SQUARE SMALL', + microbit_2_RABBIT: 'RABBIT', + microbit_2_COW: 'COW', + microbit_2_MUSIC_CROTCHET: 'CROCHET', + microbit_2_MUSIC_QUAVER: 'QUAVER', + microbit_2_MUSIC_QUAVERS: 'QUAVERS', + microbit_2_PITCHFORK: 'PITCHFORK', + microbit_2_XMAS: 'XMAS', + microbit_2_PACMAN: 'PACMAN', + microbit_2_TARGET: 'TARGET', + microbit_2_TSHIRT: 'TSHIRT', + microbit_2_ROLLERSKATE: 'ROLLERSKATE', + microbit_2_DUCK: 'DUCK', + microbit_2_HOUSE: 'HOUSE', + microbit_2_TORTOISE: 'TORTOISE', + microbit_2_BUTTERFLY: 'BUTTERFLY', + microbit_2_STICKFIGURE: 'STICKFIGURE', + microbit_2_GHOST: 'GHOST', + microbit_2_SWORD: 'SWORD', + microbit_2_GIRAFFE: 'GIRAFFE', + microbit_2_SKULL: 'SKULL', + microbit_2_UMBRELLA: 'UMBRELLA', + microbit_2_SNAKE: 'SNAKE', + microbit_2_CLOCK1: 'CLOCK 1', + microbit_2_CLOCK2: 'CLOCK 2', + microbit_2_CLOCK3: 'CLOCK 3', + microbit_2_CLOCK4: 'CLOCK 4', + microbit_2_CLOCK5: 'CLOCK 5', + microbit_2_CLOCK6: 'CLOCK 6', + microbit_2_CLOCK7: 'CLOCK 7', + microbit_2_CLOCK8: 'CLOCK 8', + microbit_2_CLOCK9: 'CLOCK 9', + microbit_2_CLOCK10: 'CLOCK 10', + microbit_2_CLOCK11: 'CLOCK 11', + microbit_2_CLOCK12: 'CLOCK 12', + microbit_2_ARROW_N: 'ARROW_N', + microbit_2_ARROW_NE: 'ARROW_NE', + microbit_2_ARROW_E: 'ARROW_E', + microbit_2_ARROW_SE: 'ARROW_SE', + microbit_2_ARROW_S: 'ARROW_S', + microbit_2_ARROW_SW: 'ARROW_SW', + microbit_2_ARROW_W: 'ARROW_W', + microbit_2_ARROW_NW: 'ARROW_NW', + }, + Helper: { + microbit2blelite_get_analog: + 'Reads an analog signal from the pin you choose. (0 ~ 1023)', + microbit2blelite_set_analog: + 'Writes an analog signal to the pin you choose. (0 ~ 1023)', + microbit2blelite_get_digital: + 'Reads a digital signal from the pin you choose. (0 ~ 1)', + microbit2blelite_set_digital: + 'Writes a digital signal to the pin you choose. (0 ~ 1)', + microbit2blelite_screen_toggle: 'Turns on or turns off the LED screen.', + microbit2blelite_set_led: + 'Turns on the LED light of the entered X, Y coordinate with the selected brightness.', + microbit2blelite_get_led: + 'The LED light brightness value of the entered X, Y coordinate.', + microbit2blelite_show_preset_image: + 'Shows the selected icon on the LED screen.', + microbit2blelite_show_custom_image: + 'Shows the selected LED and brightness. You can manipulate all the LEDs at once.', + microbit2blelite_show_string: + 'Shows the entered string in order on the LED screen.', + microbit2blelite_reset_screen: 'Clears all LED screen.', + microbit2blelite_radio_toggle: 'Turns on or turns off the radio.', + microbit2blelite_radio_setting: + 'Changes the radio channel to the number entered.', + microbit2blelite_radio_send: + 'Sends the number or the string entered to the radio.', + microbit2blelite_radio_received: 'Value received by the radio.', + microbit2blelite_speaker_toggle: 'Turns on or turns off the speaker.', + microbit2blelite_change_tempo: + 'Sets the tempo to the entered beat and BPM.', + microbit2blelite_set_tone: + 'Plays the entered melody for the entered beat. You can choose a scale between 1 and 5 octaves.', + microbit2blelite_play_preset_music: 'Plays preset music.', + microbit2blelite_play_sound_effect: 'Plays preset sound.', + microbit2blelite_get_btn: + "If the selected button is pressed, it is judged as 'True'.", + microbit2blelite_get_logo: + "If the logo is touched, it is judged as 'True'.", + microbit2blelite_get_gesture: + "When the selected movement is detected, it is judged as 'True'.", + microbit2blelite_get_acc: 'The acceleration value of the selected axis.', + microbit2blelite_btn_event: + 'When the selected button is pressed, the connected blocks below will run', + microbit2blelite_get_direction: 'The compass direction value. (0~360)', + microbit2blelite_get_field_strength_axis: + 'The magnetic field strength value of the selected axis.', + microbit2blelite_get_light_level: 'The value of the light sensor.', + microbit2blelite_get_temperature: 'The current temperature value. (℃)', + microbit2blelite_get_sound_level: 'The microphone volume value.', + microbit2blelite_set_servo: + 'Sets the servo motor angle to the entered value on the selected pin.', + microbit2blelite_set_pwm: + 'Sets the servo pulse to the entered time on the selected pin.', + }, + Msgs: { + microbit2blelite_compatible_error: + 'The corresponding block is not compatible to Microbit V1', + microbit2blelite_octave: 'Octave', + }, + }, + jp: { + template: { + microbit2blelite_get_analog: 'ピン %1 のアナログ値', + microbit2blelite_set_analog: 'ピン %1 にアナログ値 %2 を出力する %3', + microbit2blelite_get_digital: 'ピン %1 のデジタル値', + microbit2blelite_set_digital: 'ピン %1 に デジタル値 %2 を出力する %3', + microbit2blelite_screen_toggle: 'LED機能を %1 %2', + microbit2blelite_set_led: 'LEDの X: %1 Y: %2 を明るさ %3 にする %4', + microbit2blelite_get_led: 'LEDの X: %1 Y: %2 の明るさ', + microbit2blelite_show_preset_image: 'LEDに %1 アイコンを表示する %2', + microbit2blelite_show_custom_image: 'LED %1 を表示する %2', + microbit2blelite_show_string: 'LEDに %1 を表示する %2', + microbit2blelite_reset_screen: 'LEDを全部消す %1', + microbit2blelite_radio_toggle: 'ラジオ機能を %1 %2', + microbit2blelite_radio_setting: 'ラジオチャンネルを %1 に変更する %2', + microbit2blelite_radio_send: 'ラジオに %1 送信する %2', + microbit2blelite_radio_received: 'ラジオ受信値', + microbit2blelite_speaker_toggle: 'スピーカー機能 %1 %2', + microbit2blelite_change_tempo: 'テンポを %1 拍に %2 BPMにする %3', + microbit2blelite_set_tone: '%1 音を %2 拍演奏する %3', + microbit2blelite_play_preset_music: '%1 音楽を演奏する %2', + microbit2blelite_play_sound_effect: '%1 効果音を演奏する %2', + microbit2blelite_get_btn: '%1 ボタンが押したkか?', + microbit2blelite_get_logo: 'ロゴをタッチしたじか?', + microbit2blelite_get_gesture: '動きが %1 なのか?', + microbit2blelite_get_acc: '%1 の加速度', + microbit2blelite_get_direction: 'コンパス方向', + microbit2blelite_get_field_strength_axis: '%1 の磁場強度', + microbit2blelite_get_light_level: '光センサー値', + microbit2blelite_get_temperature: '温度値', + microbit2blelite_get_sound_level: 'マイク音の大きさ', + microbit2blelite_set_servo: + 'ピン %1 に サーボモーターの角度を %2 に設定する %3', + microbit2blelite_set_pwm: 'ピン %1 にサーボパルス幅を2にする %4', + microbit2blelite_common_title: 'Common Blocks', + microbit2blelite_v2_title: 'v2 Only', + }, + Blocks: { + octave: 'オクターブ', + scalar: 'スカラー', + xAxis: 'X軸', + yAxis: 'Y軸', + zAxis: 'Z軸', + up: '上', + down: '下', + left: '右', + right: '左', + face_up: '全面', + face_down: '後面', + freefall: '自由落下', + '3g': '3G', + '6g': '6G', + '8g': '8G', + shake: '振れ', + DADADADUM: '運命交響曲', + ENTERTAINER: 'エンターネーター', + PRELUDE: 'バッハ·プレリュード第1番', + ODE: '合唱交響曲', + NYAN: 'ニャンキャット', + RINGTONE: '着信メロディ', + FUNK: 'ファンク', + BLUES: 'ブルース', + BIRTHDAY: 'お誕生日おめでとう', + WEDDING: '結婚行進曲', + FUNERAL: '葬式の歌', + PUNCHLINE: 'パンチライン', + PYTHON: 'サーカス', + BADDY: '悪党', + CHASE: '追撃戦', + BA_DING: 'コインGET', + WAWAWAWAA: 'ガッカリ', + JUMP_UP: '上にジャンプ', + JUMP_DOWN: '下にジャンプ', + POWER_UP: '点ける', + POWER_DOWN: '消す', + GIGGLE: '笑い', + HAPPY: '幸せ', + HELLO: '挨拶', + MYSTERIOUS: '神秘的', + SAD: '悲しみ', + SLIDE: 'スライド', + SOARING: '上昇', + SPRING: '春', + TWINKLE: 'キラキラ', + YAWN: 'あくび', + plot: 'オンにする', + unplot: 'オフにする', + on: 'オンにする', + off: 'オフにする', + microbit_2_HEART: 'ハート', + microbit_2_HEART_SMALL: '小さなハート', + microbit_2_HAPPY: '幸せ', + microbit_2_SMILE: '笑い', + microbit_2_SAD: '悲しみ', + microbit_2_CONFUSED: '混乱', + microbit_2_ANGRY: '怒り', + microbit_2_ASLEEP: '眠気', + microbit_2_SURPRISED: '驚き', + microbit_2_SILLY: '間抜け', + microbit_2_FABULOUS: '幻想的な', + microbit_2_MEH: '別に', + microbit_2_YES: 'イエス', + microbit_2_NO: 'ノー', + microbit_2_TRIANGLE: '三角形', + microbit_2_TRIANGLE_LEFT: '左三角形', + microbit_2_CHESSBOARD: 'チェスパン', + microbit_2_DIAMOND: 'ダイヤモンド', + microbit_2_DIAMOND_SMALL: '小さなダイヤモンド', + microbit_2_SQUARE: '四角形', + microbit_2_SQUARE_SMALL: '小さな四角形', + microbit_2_RABBIT: 'ウサギ', + microbit_2_COW: '牛', + microbit_2_MUSIC_CROTCHET: '4分音符', + microbit_2_MUSIC_QUAVER: '8分音符', + microbit_2_MUSIC_QUAVERS: '8分音符2個', + microbit_2_PITCHFORK: 'フォーク', + microbit_2_XMAS: 'クリスマスツリー', + microbit_2_PACMAN: 'パックマン', + microbit_2_TARGET: '標的', + microbit_2_TSHIRT: 'Tシャツ', + microbit_2_ROLLERSKATE: 'ローラースケート', + microbit_2_DUCK: 'アヒル', + microbit_2_HOUSE: '家', + microbit_2_TORTOISE: '亀', + microbit_2_BUTTERFLY: '蝶', + microbit_2_STICKFIGURE: 'スティックマン', + microbit_2_GHOST: '幽霊', + microbit_2_SWORD: 'ナイフ', + microbit_2_GIRAFFE: 'キリン', + microbit_2_SKULL: '骸骨', + microbit_2_UMBRELLA: '傘', + microbit_2_SNAKE: '蛇', + microbit_2_CLOCK1: '1時', + microbit_2_CLOCK2: '2時', + microbit_2_CLOCK3: '3時', + microbit_2_CLOCK4: '4時', + microbit_2_CLOCK5: '5時', + microbit_2_CLOCK6: '6時', + microbit_2_CLOCK7: '7時', + microbit_2_CLOCK8: '8時', + microbit_2_CLOCK9: '9時', + microbit_2_CLOCK10: '10時', + microbit_2_CLOCK11: '11時', + microbit_2_CLOCK12: '12時', + microbit_2_ARROW_N: '北', + microbit_2_ARROW_NE: '北東', + microbit_2_ARROW_E: '東', + microbit_2_ARROW_SE: '南東', + microbit_2_ARROW_S: '南', + microbit_2_ARROW_SW: '南西', + microbit_2_ARROW_W: '西', + microbit_2_ARROW_NW: '北西', + }, + Helper: { + microbit2blelite_get_analog: '選択したピンのアナログ値です。(0 ~ 1023)', + microbit2blelite_set_analog: + '選択したピンに入力したアナログ値を出力します。(0 ~ 1023)', + microbit2blelite_get_digital: '選択したピンのデジタル値です。(0, 1)', + microbit2blelite_set_digital: + '選択したピンに入力したデジタル値を出力します。(0, 1)', + microbit2blelite_screen_toggle: 'LED機能をオンまたはオフにします。', + microbit2blelite_set_led: + 'X、Y座標で選択したLEDを選択した明るさで点けます。', + microbit2blelite_get_led: 'X、Y座標で選択したLEDの明るさです。', + microbit2blelite_show_preset_image: 'LEDに先に設定されていた形で点けます。', + microbit2blelite_show_custom_image: + 'ブロックで選択したLEDを選択した明るさで点けます。 一度にすべてのLEDを操作できます。', + microbit2blelite_show_string: '入力した文字列をLEDに順番に表示します。', + microbit2blelite_reset_screen: 'LEDに表示したものをすべて消します。', + microbit2blelite_radio_toggle: 'ラジオ機能をオンまたはオフにします。', + microbit2blelite_radio_setting: + 'ラジオチャンネルを入力した数字に変えます。', + microbit2blelite_radio_send: 'ラジオで入力した英数字を送信します。', + microbit2blelite_radio_received: 'ラジオで受信した値です。', + microbit2blelite_speaker_toggle: 'スピーカー機能をオンまたはオフにします。', + microbit2blelite_change_tempo: 'テンポを選択した拍子とBPMで設定します。', + microbit2blelite_set_tone: + '選択した音を選択した拍子で演奏します。 1~5オクターブ間の音階を選べます。', + microbit2blelite_play_preset_music: '先に設定されていた音楽を演奏します。', + microbit2blelite_play_sound_effect: + '先に設定されていた効果音を演奏します。', + microbit2blelite_get_btn: + '選択したボタンが押されたら、「True」と判断します。', + microbit2blelite_get_logo: 'ロゴをタッチすると、「True」と判断します。', + microbit2blelite_get_gesture: + '選択した動きを感知したら、「True」と判断します。', + microbit2blelite_get_acc: '選択した軸の加速度値です。', + microbit2blelite_get_direction: 'コンパス方向の値です。 (0~360)', + microbit2blelite_get_field_strength_axis: '選択した軸の磁場強度の値です。', + microbit2blelite_get_light_level: '光センサーの値です。', + microbit2blelite_get_temperature: '現在の温度です。 (℃)', + microbit2blelite_get_sound_level: 'マイクボリュームの値です。', + microbit2blelite_set_servo: + '選択したピンにサーボモーターの角度を入力した値で設定します。', + microbit2blelite_set_pwm: + '選択したピンのサーボパルス幅を選択した値にします。', + }, + Msgs: { + microbit2blelite_compatible_error: + '対応するブロックはMicrobitV1と互換性がありません', + microbit2blelite_octave: 'Octave', + }, + }, + }; + } + + getBlocks = function() { + return { + microbit2blelite_common_title: { + skeleton: 'basic_text', + color: EntryStatic.colorSet.common.TRANSPARENT, + fontColor: '#333333', + params: [ + { + type: 'Text', + text: Lang.template.microbit2blelite_common_title, + color: '#333333', + align: 'center', + }, + ], + def: { + type: 'microbit2blelite_common_title', + }, + class: 'microbit2blelite_title', + isNotFor: ['Microbit2BleLite'], + events: {}, + }, + + microbit2blelite_get_analog: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.analogPins, + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2blelitePin', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_analog', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = script.getValue('VALUE'); + + // result = + + // return ; + }, + }, + microbit2blelite_set_analog: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.analogPins, + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2blelitePin', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_set_analog', + }, + paramsKeyMap: { + PIN: 0, + VALUE: 1, + }, + func: async (sprite, script) => { + const pin = script.getValue('PIN'); + const value = Math.round( + this._clamp(script.getNumberValue('VALUE'), 0, 1023) + ); + + const parsedPayload = `${pin};${value}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_ANALOG};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_digital: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.digitalPins, + value: 8, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2blelitePin', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_digital', + }, + paramsKeyMap: { VALUE: 0 }, + func: async (sprite, script) => { + const value = script.getValue('VALUE'); + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_DIGITAL};${value}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_set_digital: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.digitalPins, + value: 8, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Dropdown', + options: [ + [0, 0], + [1, 1], + ], + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2blelitePin', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_set_digital', + }, + paramsKeyMap: { PIN: 0, VALUE: 1 }, + func: async (sprite, script) => { + const pin = script.getValue('PIN'); + const value = script.getValue('VALUE'); + const parsedPayload = `${pin};${value}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_DIGITAL};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_screen_toggle: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.plot, this.functionKeys.DISPLAY_ON], + [Lang.Blocks.unplot, this.functionKeys.DISPLAY_OFF], + ], + value: this.functionKeys.DISPLAY_ON, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_screen_toggle', + }, + paramsKeyMap: { VALUE: 0 }, + func: async (sprite, script) => { + const command = script.getField('VALUE'); + + const response = await this.getResponseWithSync(`${command};`); + return this.getResponse(response); + }, + }, + microbit2blelite_set_led: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 0, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 0, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 9, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + params: [ + { + type: 'text', + params: ['0'], + }, + { + type: 'text', + params: ['0'], + }, + { + type: 'text', + params: ['9'], + }, + ], + type: 'microbit2blelite_set_led', + }, + paramsKeyMap: { + X: 0, + Y: 1, + VALUE: 2, + }, + func: async (sprite, script) => { + const value = script.getNumberValue('VALUE'); + const x = script.getNumberValue('X'); + const y = script.getNumberValue('Y'); + if (x < 0 || y < 0 || x > 4 || y > 4 || value < 0 || value > 9) { + return; + } + + const parsedPayload = `${x};${y};${value}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_LED};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_led: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 0, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 0, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + params: [ + { + type: 'text', + params: ['0'], + }, + { + type: 'text', + params: ['0'], + }, + ], + type: 'microbit2blelite_get_led', + }, + paramsKeyMap: { + X: 0, + Y: 1, + }, + func: async (sprite, script) => { + const x = script.getNumberValue('X'); + const y = script.getNumberValue('Y'); + if (x < 0 || y < 0 || x > 4 || y > 4) { + return -1; + } + const parsedPayload = `${x};${y}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_LED};${parsedPayload}` + ); + const parsedResponse = this.getResponse(response); + + if (parsedResponse == 0) { + return 0; + } else if (parsedResponse == 1) { + return 1; + } + + return Math.round(Math.log2(parsedResponse * 2)); + }, + }, + microbit2blelite_show_preset_image: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.microbit_2_HEART, 0], + [Lang.Blocks.microbit_2_HEART_SMALL, 1], + [Lang.Blocks.microbit_2_HAPPY, 2], + [Lang.Blocks.microbit_2_SMILE, 3], + [Lang.Blocks.microbit_2_SAD, 4], + [Lang.Blocks.microbit_2_CONFUSED, 5], + [Lang.Blocks.microbit_2_ANGRY, 6], + [Lang.Blocks.microbit_2_ASLEEP, 7], + [Lang.Blocks.microbit_2_SURPRISED, 8], + [Lang.Blocks.microbit_2_SILLY, 9], + [Lang.Blocks.microbit_2_FABULOUS, 10], + [Lang.Blocks.microbit_2_MEH, 11], + [Lang.Blocks.microbit_2_YES, 12], + [Lang.Blocks.microbit_2_NO, 13], + [Lang.Blocks.microbit_2_TRIANGLE, 34], + [Lang.Blocks.microbit_2_TRIANGLE_LEFT, 35], + [Lang.Blocks.microbit_2_CHESSBOARD, 36], + [Lang.Blocks.microbit_2_DIAMOND, 37], + [Lang.Blocks.microbit_2_DIAMOND_SMALL, 38], + [Lang.Blocks.microbit_2_SQUARE, 39], + [Lang.Blocks.microbit_2_SQUARE_SMALL, 40], + [Lang.Blocks.microbit_2_RABBIT, 41], + [Lang.Blocks.microbit_2_COW, 42], + [Lang.Blocks.microbit_2_MUSIC_CROTCHET, 43], + [Lang.Blocks.microbit_2_MUSIC_QUAVER, 44], + [Lang.Blocks.microbit_2_MUSIC_QUAVERS, 45], + [Lang.Blocks.microbit_2_PITCHFORK, 46], + [Lang.Blocks.microbit_2_XMAS, 47], + [Lang.Blocks.microbit_2_PACMAN, 48], + [Lang.Blocks.microbit_2_TARGET, 49], + [Lang.Blocks.microbit_2_TSHIRT, 50], + [Lang.Blocks.microbit_2_ROLLERSKATE, 51], + [Lang.Blocks.microbit_2_DUCK, 52], + [Lang.Blocks.microbit_2_HOUSE, 53], + [Lang.Blocks.microbit_2_TORTOISE, 54], + [Lang.Blocks.microbit_2_BUTTERFLY, 55], + [Lang.Blocks.microbit_2_STICKFIGURE, 56], + [Lang.Blocks.microbit_2_GHOST, 57], + [Lang.Blocks.microbit_2_SWORD, 58], + [Lang.Blocks.microbit_2_GIRAFFE, 59], + [Lang.Blocks.microbit_2_SKULL, 60], + [Lang.Blocks.microbit_2_UMBRELLA, 61], + [Lang.Blocks.microbit_2_SNAKE, 62], + [Lang.Blocks.microbit_2_CLOCK1, 14], + [Lang.Blocks.microbit_2_CLOCK2, 15], + [Lang.Blocks.microbit_2_CLOCK3, 16], + [Lang.Blocks.microbit_2_CLOCK4, 17], + [Lang.Blocks.microbit_2_CLOCK5, 18], + [Lang.Blocks.microbit_2_CLOCK6, 19], + [Lang.Blocks.microbit_2_CLOCK7, 20], + [Lang.Blocks.microbit_2_CLOCK8, 21], + [Lang.Blocks.microbit_2_CLOCK9, 22], + [Lang.Blocks.microbit_2_CLOCK10, 23], + [Lang.Blocks.microbit_2_CLOCK11, 24], + [Lang.Blocks.microbit_2_CLOCK12, 25], + [Lang.Blocks.microbit_2_ARROW_N, 26], + [Lang.Blocks.microbit_2_ARROW_NE, 27], + [Lang.Blocks.microbit_2_ARROW_E, 28], + [Lang.Blocks.microbit_2_ARROW_SE, 29], + [Lang.Blocks.microbit_2_ARROW_S, 30], + [Lang.Blocks.microbit_2_ARROW_SW, 31], + [Lang.Blocks.microbit_2_ARROW_W, 32], + [Lang.Blocks.microbit_2_ARROW_NW, 33], + ], + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_show_preset_image', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = this._clamp(script.getNumberValue('VALUE'), 0, 62); + const parsedPayload = `${this.presetImage[value]}`; + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_CUSTOM_IMAGE};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_show_custom_image: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Led2', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_show_custom_image', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = script.getField('VALUE'); + const processedValue = []; + for (const i in value) { + processedValue[i] = value[i].join(); + } + const parsedPayload = `${processedValue.join(':').replace(/,/gi, '')}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_CUSTOM_IMAGE};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_show_string: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + params: [ + { + type: 'text', + params: ['Hello!'], + accept: 'string', + }, + ], + type: 'microbit2blelite_show_string', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + let payload = script.getStringValue('VALUE'); + payload = payload.replace( + /[^A-Za-z0-9_\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\\\{\}\[\]\'\"\;\:\<\,\>\.\?\/\s]/gim, + '' + ); + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_STRING};${payload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_reset_screen: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteLed', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_reset_screen', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.RESET_SCREEN};` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_radio_toggle: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.on, this.functionKeys.RADIO_ON], + [Lang.Blocks.off, this.functionKeys.RADIO_OFF], + ], + value: this.functionKeys.RADIO_ON, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteRadio', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_radio_toggle', + }, + paramsKeyMap: { VALUE: 0 }, + func: async (sprite, script) => { + const command = script.getField('VALUE'); + + const response = await this.getResponseWithSync(`${command};`); + return this.getResponse(response); + }, + }, + microbit2blelite_radio_setting: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + defaultType: 'number', + value: 7, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteRadio', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_radio_setting', + }, + paramsKeyMap: { RATE: 0, CHANNEL: 1 }, + func: async (sprite, script) => { + if (!Entry.Utils.isNumber(script.getNumberValue('CHANNEL'))) { + return; + } + const channel = Math.round( + this._clamp(script.getNumberValue('CHANNEL'), 0, 83) + ); + + const response = await this.getResponseWithSync( + `${this.functionKeys.SETTING_RADIO};${channel}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_radio_send: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteRadio', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_radio_send', + }, + paramsKeyMap: { VALUE: 0 }, + func: async (sprite, script) => { + const value = script.getStringValue('VALUE'); + + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_RADIO};${value}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_radio_received: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [], + events: {}, + class: 'microbit2bleliteRadio', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_radio_received', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_RADIO};` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_change_tempo: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteSound', + isNotFor: ['Microbit2BleLite'], + def: { + params: [ + { + type: 'text', + params: ['4'], + }, + { + type: 'text', + params: ['120'], + }, + ], + type: 'microbit2blelite_change_tempo', + }, + paramsKeyMap: { + BEAT: 0, + BPM: 1, + }, + func: async (sprite, script) => { + const beat = Math.round(this._clamp(script.getNumberValue('BEAT'), 0, 4)); + const bpm = Math.round(this._clamp(script.getNumberValue('BPM'), 1, 230)); + + const parsedPayload = `${beat};${bpm}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.CHANGE_TEMPO};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_set_tone: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'MusicScale', + }, + { + type: 'Dropdown', + options: [ + ['4', 16], + ['2', 8], + ['1', 4], + ['1/2', 2], + ['1/4', 1], + ], + value: 4, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteSound', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_set_tone', + }, + paramsKeyMap: { + SCALE: 0, + NOTE: 1, + }, + func: async (sprite, script) => { + const scale = script.getField('SCALE'); + const note = script.getField('NOTE'); + const parsedPayload = `${scale}:${note}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.PLAY_TONE};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_play_preset_music: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.DADADADUM, 0], + [Lang.Blocks.ENTERTAINER, 1], + [Lang.Blocks.PRELUDE, 2], + [Lang.Blocks.ODE, 3], + [Lang.Blocks.NYAN, 4], + [Lang.Blocks.RINGTONE, 5], + [Lang.Blocks.FUNK, 6], + [Lang.Blocks.BLUES, 7], + [Lang.Blocks.BIRTHDAY, 8], + [Lang.Blocks.WEDDING, 9], + [Lang.Blocks.FUNERAL, 10], + [Lang.Blocks.PUNCHLINE, 11], + [Lang.Blocks.PYTHON, 12], + [Lang.Blocks.BADDY, 13], + [Lang.Blocks.CHASE, 14], + [Lang.Blocks.BA_DING, 15], + [Lang.Blocks.WAWAWAWAA, 16], + [Lang.Blocks.JUMP_UP, 17], + [Lang.Blocks.JUMP_DOWN, 18], + [Lang.Blocks.POWER_UP, 19], + [Lang.Blocks.POWER_DOWN, 20], + ], + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteSound', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_play_preset_music', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = this._clamp(script.getNumberValue('VALUE'), 0, 20); + + const response = await this.getResponseWithSync( + `${this.functionKeys.PLAY_MELODY};${value}` + ); + this.getResponse(response); + return new Promise((resolve, reject) => { + this.waitMilliSec(500, resolve); + }); + }, + }, + + microbit2blelite_get_btn: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_boolean_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + ['A', 'a'], + ['B', 'b'], + ['A+B', 'ab'], + ], + value: 'a', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_btn', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = script.getField('VALUE'); + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_BTN};` + ); + const parsedResponse = this.getResponse(response); + + if (parsedResponse == '1' && value == 'a') { + return 1; + } else if (parsedResponse == '2' && value == 'b') { + return 1; + } else if (parsedResponse == '3' && value == 'ab') { + return 1; + } else { + return 0; + } + }, + }, + microbit2blelite_btn_event: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#fff', + skeleton: 'basic_event', + statements: [], + params: [ + { + type: 'Indicator', + img: 'block_icon/start_icon_hardwarelite.svg', + size: 14, + position: { x: 0, y: -2 }, + }, + { + type: 'Dropdown', + options: [ + ['A', '1'], + ['B', '2'], + ['A+B', '3'], + ], + value: '1', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + def: { + type: 'microbit2blelite_btn_event', + }, + paramsKeyMap: { + VALUE: 1, + }, + class: 'microbit2blelitev2', + isNotFor: ['Microbit2BleLite'], + event: 'microbit2blelite_btn_pressed', + func: (sprite, script) => { + return script.callReturn(); + }, + }, + microbit2blelite_get_acc: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.xAxis, 'x'], + [Lang.Blocks.yAxis, 'y'], + [Lang.Blocks.zAxis, 'z'], + [Lang.Blocks.scalar, 'mag'], + ], + value: 'x', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_acc', + }, + paramsKeyMap: { + AXIS: 0, + }, + func: async (sprite, script) => { + const axis = script.getField('AXIS'); + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_ACC};${axis}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_gesture: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_boolean_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.up, 'up'], + [Lang.Blocks.down, 'down'], + [Lang.Blocks.left, 'left'], + [Lang.Blocks.right, 'right'], + [Lang.Blocks.face_up, 'face up'], + [Lang.Blocks.face_down, 'face down'], + [Lang.Blocks.freefall, 'freefall'], + [Lang.Blocks['3g'], '3g'], + [Lang.Blocks['6g'], '6g'], + [Lang.Blocks['8g'], '8g'], + [Lang.Blocks['shake'], 'shake'], + ], + value: 'up', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_gesture', + }, + paramsKeyMap: { GESTURE: 0 }, + func: async (sprite, script) => { + const gesture = script.getField('GESTURE'); + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_GESTURE};` + ); + const parsedResponse = this.getResponse(response); + + if (gesture === parsedResponse[1]) { + return true; + } + return false; + }, + }, + microbit2blelite_get_direction: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_direction', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_DIRECTION};` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_field_strength_axis: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.xAxis, 'x'], + [Lang.Blocks.yAxis, 'y'], + [Lang.Blocks.zAxis, 'z'], + [Lang.Blocks.scalar, 'mag'], + ], + value: 'x', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + ], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_field_strength_axis', + }, + paramsKeyMap: { + AXIS: 0, + }, + func: async (sprite, script) => { + const axis = script.getField('AXIS'); + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_FIELD_STRENGTH};${axis}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_light_level: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_light_level', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_LIGHT_LEVEL};` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_get_temperature: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [], + events: {}, + class: 'microbit2bleliteSensor', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_temperature', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_TEMPERATURE};` + ); + return this.getResponse(response); + }, + }, + + microbit2blelite_set_servo: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.majorPins, + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteServo', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_set_servo', + }, + paramsKeyMap: { + PIN: 0, + VALUE: 1, + }, + func: async (sprite, script) => { + const pin = script.getValue('PIN'); + const value = Math.round( + this._clamp(script.getNumberValue('VALUE'), 0, 180) + ); + + const parsedPayload = `${pin};${value}`; + const response = await this.getResponseWithSync( + `${this.functionKeys.SET_SERVO_ANGLE};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_set_pwm: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: this.analogPins, + value: 0, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Dropdown', + options: [ + ['ms', 'milli'], + ['µs', 'micro'], + ], + value: 'milli', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2bleliteServo', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_set_pwm', + }, + paramsKeyMap: { + PIN: 0, + VALUE: 1, + UNIT: 2, + }, + func: async (sprite, script) => { + const pin = script.getValue('PIN'); + const unit = script.getValue('UNIT'); + const value = Math.round( + this._clamp(script.getNumberValue('VALUE'), 0, 1023) + ); + const command = + unit === 'milli' + ? this.functionKeys.SET_SERVO_MILLI + : this.functionKeys.SET_SERVO_MICRO; + + const parsedPayload = `${pin};${value}`; + const response = await this.getResponseWithSync( + `${command};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + + microbit2blelite_v2_title: { + skeleton: 'basic_text', + color: EntryStatic.colorSet.common.TRANSPARENT, + fontColor: '#333333', + params: [ + { + type: 'Text', + text: Lang.template.microbit2blelite_v2_title, + color: '#333333', + align: 'center', + }, + ], + def: { + type: 'microbit2blelite_v2_title', + }, + class: 'microbit2blelite_title', + isNotFor: ['Microbit2BleLite'], + events: {}, + }, + microbit2blelite_get_logo: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_boolean_field', + statements: [], + params: [], + events: {}, + class: 'microbit2blelitev2', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_logo', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + if (this.version === '1') { + throw new Entry.Utils.IncompatibleError('IncompatibleError', [ + Lang.Msgs.microbit2blelite_compatible_error, + ]); + } + + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_LOGO};` + ); + const parsedResponse = this.getResponse(response); + if (parsedResponse == '1') { + return 1; + } else { + return 0; + } + }, + }, + microbit2blelite_get_sound_level: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic_string_field', + statements: [], + params: [], + events: {}, + class: 'microbit2blelitev2', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_get_sound_level', + }, + paramsKeyMap: {}, + func: async (sprite, script) => { + const response = await this.getResponseWithSync( + `${this.functionKeys.GET_SOUND_LEVEL};` + ); + return this.getResponse(response); + }, + }, + microbit2blelite_speaker_toggle: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.on, this.functionKeys.SPEAKER_ON], + [Lang.Blocks.off, this.functionKeys.SPEAKER_OFF], + ], + value: this.functionKeys.SPEAKER_ON, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2blelitev2', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_speaker_toggle', + }, + paramsKeyMap: { VALUE: 0 }, + func: async (sprite, script) => { + if (this.version === '1') { + throw new Entry.Utils.IncompatibleError('IncompatibleError', [ + Lang.Msgs.microbit2_compatible_error, + ]); + } + const command = script.getField('VALUE'); + const response = await this.getResponseWithSync(`${command};`); + return this.getResponse(response); + }, + }, + microbit2blelite_play_sound_effect: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + fontColor: '#ffffff', + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + [Lang.Blocks.GIGGLE, 21], + [Lang.Blocks.HAPPY, 22], + [Lang.Blocks.HELLO, 23], + [Lang.Blocks.MYSTERIOUS, 24], + [Lang.Blocks.SAD, 25], + [Lang.Blocks.SLIDE, 26], + [Lang.Blocks.SOARING, 27], + [Lang.Blocks.SPRING, 28], + [Lang.Blocks.TWINKLE, 29], + [Lang.Blocks.YAWN, 30], + ], + value: 21, + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Indicator', + img: 'block_icon/hardwarelite_icon.svg', + size: 12, + }, + ], + events: {}, + class: 'microbit2blelitev2', + isNotFor: ['Microbit2BleLite'], + def: { + type: 'microbit2blelite_play_sound_effect', + }, + paramsKeyMap: { + VALUE: 0, + }, + func: async (sprite, script) => { + const value = this._clamp(script.getNumberValue('VALUE'), 21, 30); + const parsedPayload = `${value}`; + + const response = await this.getResponseWithSync( + `${this.functionKeys.PLAY_SOUND};${parsedPayload}` + ); + return this.getResponse(response); + }, + }, + }; + }; + })(); +})(); + +module.exports = Entry.Microbit2BleLite; diff --git a/src/playground/blocks/hardwareLite/metadata_microbit2ble_lite.json b/src/playground/blocks/hardwareLite/metadata_microbit2ble_lite.json new file mode 100644 index 0000000000..6b77561e34 --- /dev/null +++ b/src/playground/blocks/hardwareLite/metadata_microbit2ble_lite.json @@ -0,0 +1,9 @@ +{ + "name": "Microbit2BleLite", + "version": "2", + "title": "마이크로비트 무선(ble)", + "description": "Micro:bit Educational Foundation", + "imageName": "microbit2lite.png", + "moduleId": "220302", + "connectionType": "ble" +} From 84c72d8ee619d6d55ada63e14736fb0d2e7cc087 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 16:41:07 +0900 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20led=EB=B8=94=EB=9F=AD=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hardwareLite/block_microbit2ble_lite.js | 178 +++++++++--------- 1 file changed, 93 insertions(+), 85 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index d3be714bcb..d590599ea6 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -1,8 +1,22 @@ 'use strict'; -const _throttle = require('lodash/throttle'); +const _clamp = require('lodash/clamp'); const EVENT_INTERVAL = 150; +const getInitialLedState = () => { + return [ + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + ]; +}; +const convertPresetImageToLedState = (preset) => { + const colums = preset.split(':'); + const result = colums.map((row) => row.split('').map((num) => num !== '0')); + return result; +}; (function() { Entry.Microbit2BleLite = new (class Microbit2LiteBle { @@ -350,33 +364,31 @@ const EVENT_INTERVAL = 150; [3, 3], [4, 4], ]; - this.defaultLed = [ - [0, 0, 0, 0, 0], - [0, 9, 0, 9, 0], - [0, 0, 0, 0, 0], - [9, 0, 0, 0, 9], - [0, 9, 9, 9, 0], - ]; this.blockMenuBlocks = [ 'microbit2blelite_common_title', - 'microbit2blelite_get_analog', + + // 'microbit2blelite_get_analog', // 'microbit2blelite_set_analog', // 'microbit2blelite_get_digital', // 'microbit2blelite_set_digital', - // 'microbit2blelite_screen_toggle', - // 'microbit2blelite_set_led', - // 'microbit2blelite_get_led', - // 'microbit2blelite_show_preset_image', - // 'microbit2blelite_show_custom_image', - // 'microbit2blelite_show_string', - // 'microbit2blelite_reset_screen', + + 'microbit2blelite_screen_toggle', + 'microbit2blelite_set_led', + 'microbit2blelite_get_led', + 'microbit2blelite_show_preset_image', + 'microbit2blelite_show_custom_image', + 'microbit2blelite_show_string', + 'microbit2blelite_reset_screen', + // 'microbit2blelite_radio_toggle', // 'microbit2blelite_radio_setting', // 'microbit2blelite_radio_send', // 'microbit2blelite_radio_received', + // 'microbit2blelite_change_tempo', // 'microbit2blelite_set_tone', // 'microbit2blelite_play_preset_music', + // 'microbit2blelite_get_btn', // 'microbit2blelite_get_acc', // 'microbit2blelite_get_gesture', @@ -384,27 +396,26 @@ const EVENT_INTERVAL = 150; // 'microbit2blelite_get_field_strength_axis', // 'microbit2blelite_get_light_level', // 'microbit2blelite_get_temperature', + // 'microbit2blelite_set_servo', // 'microbit2blelite_set_pwm', + // 'microbit2blelite_v2_title', // 'microbit2blelite_get_logo', - // 'microbit2blelite_btn_event', - 'microbit2blelite_speaker_toggle', + 'microbit2blelite_btn_event', + // 'microbit2blelite_speaker_toggle', // 'microbit2blelite_play_sound_effect', // 'microbit2blelite_get_sound_level', ]; this.services = undefined; - this.ledState = [ - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - ]; + this.ledState = getInitialLedState(); this.version = '2'; this.buttonState = { a: 0, b: 0 }; } - setZero() {} + async setZero() { + this.ledState = getInitialLedState(); + await this.services.LedService.writeMatrixState(this.ledState); + } async initialHandshake() { this.services = Entry.hwLite.bluetooth.services; @@ -414,6 +425,20 @@ const EVENT_INTERVAL = 150; if (this.services.accelerometerService) { await this.services.accelerometerService.setAccelerometerPeriod(640); } + if (this.services.IoPinService) { + // await this.services.IoPinService.setPwmControl({ + // pin: + // value: + // period: + // }); + // const Ad_char = 'e95d5899-251d-470a-a062-fa1922dfa9a8'; + // const Io_char = 'e95db9fe-251d-470a-a062-fa1922dfa9a8'; + // let cmd = new Uint32Array([0x07]); + // //await services.ioPinService.helper.setCharacteristicValue(Io_char, cmd); + // //await services.ioPinService.helper.setCharacteristicValue(Ad_char, cmd); + // debugger; + // console.log(await this.services.IoPinService.getAdConfiguration()); + } this.setEventListener(); } @@ -422,8 +447,18 @@ const EVENT_INTERVAL = 150; return; } this.setButtonEvent(); + // this.setPinDataEvent(); } + // setPinDataEvent() { + // if (this.services.IoPinService) { + // debugger; + // this.services.IoPinService.addEventListener('pindatachanged', (event) => { + // console.log(event); + // }); + // } + // } + setButtonEvent() { const pressedBoth = () => { if (this.buttonState.a + this.buttonState.b === 2) { @@ -1125,6 +1160,7 @@ const EVENT_INTERVAL = 150; }, func: async (sprite, script) => { const value = script.getValue('VALUE'); + const pinData = await Entry.hwLite.bluetooth.services.IoPinService.setIoConfiguration(); // result = @@ -1169,9 +1205,7 @@ const EVENT_INTERVAL = 150; }, func: async (sprite, script) => { const pin = script.getValue('PIN'); - const value = Math.round( - this._clamp(script.getNumberValue('VALUE'), 0, 1023) - ); + const value = Math.round(_clamp(script.getNumberValue('VALUE'), 0, 1023)); const parsedPayload = `${pin};${value}`; @@ -1294,9 +1328,11 @@ const EVENT_INTERVAL = 150; paramsKeyMap: { VALUE: 0 }, func: async (sprite, script) => { const command = script.getField('VALUE'); - - const response = await this.getResponseWithSync(`${command};`); - return this.getResponse(response); + if (command === this.functionKeys.DISPLAY_ON) { + await this.services.LedService.writeMatrixState(this.ledState); + } else { + await this.services.LedService.writeMatrixState(getInitialLedState()); + } }, }, microbit2blelite_set_led: { @@ -1361,13 +1397,8 @@ const EVENT_INTERVAL = 150; if (x < 0 || y < 0 || x > 4 || y > 4 || value < 0 || value > 9) { return; } - - const parsedPayload = `${x};${y};${value}`; - - const response = await this.getResponseWithSync( - `${this.functionKeys.SET_LED};${parsedPayload}` - ); - return this.getResponse(response); + this.ledState[y][x] = value === 0 ? false : true; + await this.services.LedService.writeMatrixState(this.ledState); }, }, microbit2blelite_get_led: { @@ -1416,20 +1447,11 @@ const EVENT_INTERVAL = 150; if (x < 0 || y < 0 || x > 4 || y > 4) { return -1; } - const parsedPayload = `${x};${y}`; - const response = await this.getResponseWithSync( - `${this.functionKeys.GET_LED};${parsedPayload}` - ); - const parsedResponse = this.getResponse(response); - - if (parsedResponse == 0) { - return 0; - } else if (parsedResponse == 1) { - return 1; - } - - return Math.round(Math.log2(parsedResponse * 2)); + // TODO: 블록스펙 변경과 함께 true, false 처리 + const deviceLedState = await this.services.LedService.readMatrixState(); + const targetLed = deviceLedState[y][x] ? 1 : 0; + return targetLed; }, }, microbit2blelite_show_preset_image: { @@ -1527,12 +1549,11 @@ const EVENT_INTERVAL = 150; VALUE: 0, }, func: async (sprite, script) => { - const value = this._clamp(script.getNumberValue('VALUE'), 0, 62); - const parsedPayload = `${this.presetImage[value]}`; - const response = await this.getResponseWithSync( - `${this.functionKeys.SET_CUSTOM_IMAGE};${parsedPayload}` - ); - return this.getResponse(response); + const value = _clamp(script.getNumberValue('VALUE'), 0, 62); + const presetImage = this.presetImage[value]; + this.ledState = convertPresetImageToLedState(presetImage); + + await this.services.LedService.writeMatrixState(this.ledState); }, }, microbit2blelite_show_custom_image: { @@ -1566,11 +1587,9 @@ const EVENT_INTERVAL = 150; processedValue[i] = value[i].join(); } const parsedPayload = `${processedValue.join(':').replace(/,/gi, '')}`; + this.ledState = convertPresetImageToLedState(parsedPayload); - const response = await this.getResponseWithSync( - `${this.functionKeys.SET_CUSTOM_IMAGE};${parsedPayload}` - ); - return this.getResponse(response); + await this.services.LedService.writeMatrixState(this.ledState); }, }, microbit2blelite_show_string: { @@ -1606,15 +1625,12 @@ const EVENT_INTERVAL = 150; VALUE: 0, }, func: async (sprite, script) => { - let payload = script.getStringValue('VALUE'); - payload = payload.replace( + const payload = script.getStringValue('VALUE'); + const text = payload.replace( /[^A-Za-z0-9_\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\\\{\}\[\]\'\"\;\:\<\,\>\.\?\/\s]/gim, '' ); - const response = await this.getResponseWithSync( - `${this.functionKeys.SET_STRING};${payload}` - ); - return this.getResponse(response); + await this.services.LedService.writeText(text); }, }, microbit2blelite_reset_screen: { @@ -1637,10 +1653,8 @@ const EVENT_INTERVAL = 150; }, paramsKeyMap: {}, func: async (sprite, script) => { - const response = await this.getResponseWithSync( - `${this.functionKeys.RESET_SCREEN};` - ); - return this.getResponse(response); + this.ledState = getInitialLedState(); + await this.services.LedService.writeMatrixState(this.ledState); }, }, microbit2blelite_radio_toggle: { @@ -1709,9 +1723,7 @@ const EVENT_INTERVAL = 150; if (!Entry.Utils.isNumber(script.getNumberValue('CHANNEL'))) { return; } - const channel = Math.round( - this._clamp(script.getNumberValue('CHANNEL'), 0, 83) - ); + const channel = Math.round(_clamp(script.getNumberValue('CHANNEL'), 0, 83)); const response = await this.getResponseWithSync( `${this.functionKeys.SETTING_RADIO};${channel}` @@ -1815,8 +1827,8 @@ const EVENT_INTERVAL = 150; BPM: 1, }, func: async (sprite, script) => { - const beat = Math.round(this._clamp(script.getNumberValue('BEAT'), 0, 4)); - const bpm = Math.round(this._clamp(script.getNumberValue('BPM'), 1, 230)); + const beat = Math.round(_clamp(script.getNumberValue('BEAT'), 0, 4)); + const bpm = Math.round(_clamp(script.getNumberValue('BPM'), 1, 230)); const parsedPayload = `${beat};${bpm}`; @@ -1929,7 +1941,7 @@ const EVENT_INTERVAL = 150; VALUE: 0, }, func: async (sprite, script) => { - const value = this._clamp(script.getNumberValue('VALUE'), 0, 20); + const value = _clamp(script.getNumberValue('VALUE'), 0, 20); const response = await this.getResponseWithSync( `${this.functionKeys.PLAY_MELODY};${value}` @@ -2257,9 +2269,7 @@ const EVENT_INTERVAL = 150; }, func: async (sprite, script) => { const pin = script.getValue('PIN'); - const value = Math.round( - this._clamp(script.getNumberValue('VALUE'), 0, 180) - ); + const value = Math.round(_clamp(script.getNumberValue('VALUE'), 0, 180)); const parsedPayload = `${pin};${value}`; const response = await this.getResponseWithSync( @@ -2319,9 +2329,7 @@ const EVENT_INTERVAL = 150; func: async (sprite, script) => { const pin = script.getValue('PIN'); const unit = script.getValue('UNIT'); - const value = Math.round( - this._clamp(script.getNumberValue('VALUE'), 0, 1023) - ); + const value = Math.round(_clamp(script.getNumberValue('VALUE'), 0, 1023)); const command = unit === 'milli' ? this.functionKeys.SET_SERVO_MILLI @@ -2490,7 +2498,7 @@ const EVENT_INTERVAL = 150; VALUE: 0, }, func: async (sprite, script) => { - const value = this._clamp(script.getNumberValue('VALUE'), 21, 30); + const value = _clamp(script.getNumberValue('VALUE'), 21, 30); const parsedPayload = `${value}`; const response = await this.getResponseWithSync( From 1a9a6f00f474bf45f8cd91920e98934a2de94c9b Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 17:03:57 +0900 Subject: [PATCH 12/21] =?UTF-8?q?feat:=20=EB=B2=84=ED=8A=BC=20=EB=88=8C?= =?UTF-8?q?=EB=A0=B8=EB=8A=94=EA=B0=80=20=ED=8C=90=EB=8B=A8=EB=B8=94?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hardwareLite/block_microbit2ble_lite.js | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index d590599ea6..e1102f6f9c 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -389,7 +389,7 @@ const convertPresetImageToLedState = (preset) => { // 'microbit2blelite_set_tone', // 'microbit2blelite_play_preset_music', - // 'microbit2blelite_get_btn', + 'microbit2blelite_get_btn', // 'microbit2blelite_get_acc', // 'microbit2blelite_get_gesture', // 'microbit2blelite_get_direction', @@ -459,24 +459,27 @@ const convertPresetImageToLedState = (preset) => { // } // } - setButtonEvent() { - const pressedBoth = () => { - if (this.buttonState.a + this.buttonState.b === 2) { + pressedBothButton(fireEvent) { + if (this.buttonState.a + this.buttonState.b === 2) { + if (fireEvent) { Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 3); - return true; } - return false; - }; + return true; + } + return false; + } + + setButtonEvent() { if (this.services.ButtonService) { this.services.ButtonService.addEventListener('buttonastatechanged', (event) => { this.buttonState.a = event.detail; - if (event.detail === 1 && !pressedBoth()) { + if (event.detail === 1 && !this.pressedBothButton(true)) { Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 1); } }); this.services.ButtonService.addEventListener('buttonbstatechanged', (event) => { this.buttonState.b = event.detail; - if (event.detail === 1 && !pressedBoth()) { + if (event.detail === 1 && !this.pressedBothButton(true)) { Entry.engine.fireEventWithValue('microbit2blelite_btn_pressed', 2); } }); @@ -1985,19 +1988,10 @@ const convertPresetImageToLedState = (preset) => { func: async (sprite, script) => { const value = script.getField('VALUE'); - const response = await this.getResponseWithSync( - `${this.functionKeys.GET_BTN};` - ); - const parsedResponse = this.getResponse(response); - - if (parsedResponse == '1' && value == 'a') { - return 1; - } else if (parsedResponse == '2' && value == 'b') { - return 1; - } else if (parsedResponse == '3' && value == 'ab') { - return 1; + if (value === 'ab') { + return this.pressedBothButton() ? 1 : 0; } else { - return 0; + return this.buttonState[value]; } }, }, From 9adf266c4a001213a7721a44de980399cc1f051e Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 17:11:02 +0900 Subject: [PATCH 13/21] =?UTF-8?q?feat:=20=EA=B0=80=EC=86=8D=EB=8F=84=20?= =?UTF-8?q?=EB=B8=94=EB=9F=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hardwareLite/block_microbit2ble_lite.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index e1102f6f9c..46848071b7 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -390,7 +390,7 @@ const convertPresetImageToLedState = (preset) => { // 'microbit2blelite_play_preset_music', 'microbit2blelite_get_btn', - // 'microbit2blelite_get_acc', + 'microbit2blelite_get_acc', // 'microbit2blelite_get_gesture', // 'microbit2blelite_get_direction', // 'microbit2blelite_get_field_strength_axis', @@ -419,11 +419,11 @@ const convertPresetImageToLedState = (preset) => { async initialHandshake() { this.services = Entry.hwLite.bluetooth.services; - if (this.services.temperatureService) { - await this.services.temperatureService.setTemperaturePeriod(2000); + if (this.services.TemperatureService) { + await this.services.TemperatureService.setTemperaturePeriod(2000); } - if (this.services.accelerometerService) { - await this.services.accelerometerService.setAccelerometerPeriod(640); + if (this.services.AccelerometerService) { + await this.services.AccelerometerService.setAccelerometerPeriod(640); } if (this.services.IoPinService) { // await this.services.IoPinService.setPwmControl({ @@ -2047,7 +2047,7 @@ const convertPresetImageToLedState = (preset) => { [Lang.Blocks.xAxis, 'x'], [Lang.Blocks.yAxis, 'y'], [Lang.Blocks.zAxis, 'z'], - [Lang.Blocks.scalar, 'mag'], + // [Lang.Blocks.scalar, 'mag'], ], value: 'x', fontSize: 11, @@ -2066,11 +2066,8 @@ const convertPresetImageToLedState = (preset) => { }, func: async (sprite, script) => { const axis = script.getField('AXIS'); - - const response = await this.getResponseWithSync( - `${this.functionKeys.GET_ACC};${axis}` - ); - return this.getResponse(response); + const deviceAccData = await this.services.AccelerometerService.readAccelerometerData(); + return deviceAccData[axis]; }, }, microbit2blelite_get_gesture: { From 5740911141eb743bd79b676e1ab85ddba2cefad7 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 17:22:36 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20=EC=9E=90=EA=B8=B0=EC=9E=A5=20?= =?UTF-8?q?=EB=B8=94=EB=9F=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/hardwareLite/block_microbit2ble_lite.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index 46848071b7..2f0aea6dad 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -393,7 +393,7 @@ const convertPresetImageToLedState = (preset) => { 'microbit2blelite_get_acc', // 'microbit2blelite_get_gesture', // 'microbit2blelite_get_direction', - // 'microbit2blelite_get_field_strength_axis', + 'microbit2blelite_get_field_strength_axis', // 'microbit2blelite_get_light_level', // 'microbit2blelite_get_temperature', @@ -2153,7 +2153,7 @@ const convertPresetImageToLedState = (preset) => { [Lang.Blocks.xAxis, 'x'], [Lang.Blocks.yAxis, 'y'], [Lang.Blocks.zAxis, 'z'], - [Lang.Blocks.scalar, 'mag'], + // [Lang.Blocks.scalar, 'mag'], ], value: 'x', fontSize: 11, @@ -2172,11 +2172,9 @@ const convertPresetImageToLedState = (preset) => { }, func: async (sprite, script) => { const axis = script.getField('AXIS'); + const deviceMagnetData = await this.services.MagnetometerService.readMagnetometerData(); - const response = await this.getResponseWithSync( - `${this.functionKeys.GET_FIELD_STRENGTH};${axis}` - ); - return this.getResponse(response); + return deviceMagnetData[axis]; }, }, microbit2blelite_get_light_level: { From 86968ceab5b9550611e7514daf12e17b4b3800fb Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Fri, 22 Dec 2023 17:50:01 +0900 Subject: [PATCH 15/21] =?UTF-8?q?feat:=20=EC=98=A8=EB=8F=84=EB=B8=94?= =?UTF-8?q?=EB=9F=AD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blocks/hardwareLite/block_microbit2ble_lite.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index 2f0aea6dad..92462b8411 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -395,7 +395,7 @@ const convertPresetImageToLedState = (preset) => { // 'microbit2blelite_get_direction', 'microbit2blelite_get_field_strength_axis', // 'microbit2blelite_get_light_level', - // 'microbit2blelite_get_temperature', + 'microbit2blelite_get_temperature', // 'microbit2blelite_set_servo', // 'microbit2blelite_set_pwm', @@ -2213,10 +2213,8 @@ const convertPresetImageToLedState = (preset) => { }, paramsKeyMap: {}, func: async (sprite, script) => { - const response = await this.getResponseWithSync( - `${this.functionKeys.GET_TEMPERATURE};` - ); - return this.getResponse(response); + const temperature = await this.services.TemperatureService.readTemperature(); + return temperature; }, }, From b5a8f239b009345902112f12ffe837612bbc041b Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 01:38:46 +0900 Subject: [PATCH 16/21] =?UTF-8?q?refactor:=20lint=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81,=20=ED=95=80=EA=B8=B0=EB=8A=A5=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hardwareLite/block_microbit2ble_lite.js | 71 ++++++++----------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index 92462b8411..9632e3b56c 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -1,17 +1,14 @@ 'use strict'; - const _clamp = require('lodash/clamp'); const EVENT_INTERVAL = 150; -const getInitialLedState = () => { - return [ - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - [false, false, false, false, false], - ]; -}; +const getInitialLedState = () => [ + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], + [false, false, false, false, false], +]; const convertPresetImageToLedState = (preset) => { const colums = preset.split(':'); const result = colums.map((row) => row.split('').map((num) => num !== '0')); @@ -400,7 +397,7 @@ const convertPresetImageToLedState = (preset) => { // 'microbit2blelite_set_servo', // 'microbit2blelite_set_pwm', - // 'microbit2blelite_v2_title', + 'microbit2blelite_v2_title', // 'microbit2blelite_get_logo', 'microbit2blelite_btn_event', // 'microbit2blelite_speaker_toggle', @@ -426,38 +423,27 @@ const convertPresetImageToLedState = (preset) => { await this.services.AccelerometerService.setAccelerometerPeriod(640); } if (this.services.IoPinService) { - // await this.services.IoPinService.setPwmControl({ - // pin: - // value: - // period: - // }); - // const Ad_char = 'e95d5899-251d-470a-a062-fa1922dfa9a8'; - // const Io_char = 'e95db9fe-251d-470a-a062-fa1922dfa9a8'; - // let cmd = new Uint32Array([0x07]); - // //await services.ioPinService.helper.setCharacteristicValue(Io_char, cmd); - // //await services.ioPinService.helper.setCharacteristicValue(Ad_char, cmd); // debugger; // console.log(await this.services.IoPinService.getAdConfiguration()); } - this.setEventListener(); + await this.setEventListener(); } - setEventListener() { + async setEventListener() { if (!this.services) { return; } - this.setButtonEvent(); - // this.setPinDataEvent(); + await this.setButtonEvent(); + // await this.setPinDataEvent(); } - // setPinDataEvent() { - // if (this.services.IoPinService) { - // debugger; - // this.services.IoPinService.addEventListener('pindatachanged', (event) => { - // console.log(event); - // }); - // } - // } + async setPinDataEvent() { + if (this.services.IoPinService) { + this.services.IoPinService.addEventListener('pindatachanged', (event) => { + console.log(event); + }); + } + } pressedBothButton(fireEvent) { if (this.buttonState.a + this.buttonState.b === 2) { @@ -1163,11 +1149,13 @@ const convertPresetImageToLedState = (preset) => { }, func: async (sprite, script) => { const value = script.getValue('VALUE'); - const pinData = await Entry.hwLite.bluetooth.services.IoPinService.setIoConfiguration(); + // const pinData = await this.services.IoPinService.readPinData(); + // const ad = await this.services.IoPinService.getAdConfiguration(); + // const config = await this.services.IoPinService.getIoConfiguration(); - // result = - - // return ; + // console.log(pinData); + // console.log(ad); + // console.log(config); }, }, microbit2blelite_set_analog: { @@ -1400,7 +1388,7 @@ const convertPresetImageToLedState = (preset) => { if (x < 0 || y < 0 || x > 4 || y > 4 || value < 0 || value > 9) { return; } - this.ledState[y][x] = value === 0 ? false : true; + this.ledState[y][x] = value !== 0; await this.services.LedService.writeMatrixState(this.ledState); }, }, @@ -2030,9 +2018,7 @@ const convertPresetImageToLedState = (preset) => { class: 'microbit2blelitev2', isNotFor: ['Microbit2BleLite'], event: 'microbit2blelite_btn_pressed', - func: (sprite, script) => { - return script.callReturn(); - }, + func: (sprite, script) => script.callReturn(), }, microbit2blelite_get_acc: { color: EntryStatic.colorSet.block.default.HARDWARE, @@ -2066,6 +2052,7 @@ const convertPresetImageToLedState = (preset) => { }, func: async (sprite, script) => { const axis = script.getField('AXIS'); + // eslint-disable-next-line max-len const deviceAccData = await this.services.AccelerometerService.readAccelerometerData(); return deviceAccData[axis]; }, @@ -2172,6 +2159,7 @@ const convertPresetImageToLedState = (preset) => { }, func: async (sprite, script) => { const axis = script.getField('AXIS'); + // eslint-disable-next-line max-len const deviceMagnetData = await this.services.MagnetometerService.readMagnetometerData(); return deviceMagnetData[axis]; @@ -2213,6 +2201,7 @@ const convertPresetImageToLedState = (preset) => { }, paramsKeyMap: {}, func: async (sprite, script) => { + // eslint-disable-next-line max-len const temperature = await this.services.TemperatureService.readTemperature(); return temperature; }, From 0e2eaf86465863c107e59aab76667f0deedc3787 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 19:31:33 +0900 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20=ED=8E=8C=EC=9B=A8=EC=96=B4=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=BD=9C=EB=B0=B1=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B9=84=EB=8F=99=EA=B8=B0=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hw_lite.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index 5d1fc85f7b..42365ecf7b 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -84,7 +84,9 @@ export default class HardwareLite { if (!blockMenu) { return; } - Entry.block.changeBlockEvent('arduino_lite_download_firmware', 'mousedown', callback); + Entry.block.changeBlockEvent('arduino_lite_download_firmware', 'mousedown', async () => { + await callback(); + }); blockMenu.changeTypeThreadByBlockKey('arduino_lite_download_firmware'); } From 71d876ab1692748a7b50f2443d1a9b82fbfbb276 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 19:38:58 +0900 Subject: [PATCH 18/21] =?UTF-8?q?feat:=20=EC=9E=90=EA=B8=B0=EC=9E=A5,=20?= =?UTF-8?q?=EA=B0=80=EC=86=8D=EB=8F=84=20=EB=B8=94=EB=9F=AD=20=EC=8A=A4?= =?UTF-8?q?=EC=B9=BC=EB=9D=BC=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모든값 총합으로 계산 --- .../hardwareLite/block_microbit2ble_lite.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index 9632e3b56c..83c55ec473 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -2033,7 +2033,7 @@ const convertPresetImageToLedState = (preset) => { [Lang.Blocks.xAxis, 'x'], [Lang.Blocks.yAxis, 'y'], [Lang.Blocks.zAxis, 'z'], - // [Lang.Blocks.scalar, 'mag'], + [Lang.Blocks.scalar, 'mag'], ], value: 'x', fontSize: 11, @@ -2054,7 +2054,11 @@ const convertPresetImageToLedState = (preset) => { const axis = script.getField('AXIS'); // eslint-disable-next-line max-len const deviceAccData = await this.services.AccelerometerService.readAccelerometerData(); - return deviceAccData[axis]; + if (axis === 'mag') { + return deviceAccData.x + deviceAccData.y + deviceAccData.z; + } else { + return deviceAccData[axis]; + } }, }, microbit2blelite_get_gesture: { @@ -2140,7 +2144,7 @@ const convertPresetImageToLedState = (preset) => { [Lang.Blocks.xAxis, 'x'], [Lang.Blocks.yAxis, 'y'], [Lang.Blocks.zAxis, 'z'], - // [Lang.Blocks.scalar, 'mag'], + [Lang.Blocks.scalar, 'mag'], ], value: 'x', fontSize: 11, @@ -2161,8 +2165,11 @@ const convertPresetImageToLedState = (preset) => { const axis = script.getField('AXIS'); // eslint-disable-next-line max-len const deviceMagnetData = await this.services.MagnetometerService.readMagnetometerData(); - - return deviceMagnetData[axis]; + if (axis === 'mag') { + return deviceMagnetData.x + deviceMagnetData.y + deviceMagnetData.z; + } else { + return deviceMagnetData[axis]; + } }, }, microbit2blelite_get_light_level: { From ee24a72f2f9ace4f4702f4bdf2c4e3fa3a3f7893 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 19:43:32 +0900 Subject: [PATCH 19/21] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hardware/webBluetoothConnector.ts | 9 +-------- src/class/hw_lite.ts | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/class/hardware/webBluetoothConnector.ts b/src/class/hardware/webBluetoothConnector.ts index e0f324b6b4..97bcb64005 100644 --- a/src/class/hardware/webBluetoothConnector.ts +++ b/src/class/hardware/webBluetoothConnector.ts @@ -37,17 +37,10 @@ export default class WebBluetoothConnector extends WebApiConnector { async setDevice() { const filters = this.hwModule.bluetoothInfo.filters; - const optionalServices = this.serviceClasses.map((serviceClass) => { - return serviceClass.uuid; - }); + const optionalServices = this.serviceClasses.map((serviceClass) => serviceClass.uuid); this.device = await navigator.bluetooth.requestDevice({ filters, optionalServices }); } - // removeDevice() { - // //TODO: 송수신 소켓 닫기 - // this.device = undefined; - // } - setServiceClasses() { this.serviceClasses = getServiceClassesByModuleId(this.hwModule.id); } diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index 42365ecf7b..f0400c131f 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -10,7 +10,7 @@ const ARDUINO_BOARD_IDS: string[] = ['1.1', '4.2', '8.1']; export default class HardwareLite { private status: HWLiteStatus; - private webConnector: WebApiConnector; // TODO: 추후 WebBlueToothConnect | WebSerialConnector로 변경예정 + private webConnector: WebApiConnector; private serial: WebSerialConnector; private bluetooth: WebBluetoothConnector; private flasher: WebUsbFlasher; From fb62c6f77cf1a72d9e38bf57eb7837bcbd4a1891 Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 20:09:36 +0900 Subject: [PATCH 20/21] =?UTF-8?q?fix:=20=EC=95=84=EB=91=90=EC=9D=B4?= =?UTF-8?q?=EB=85=B8=20=EC=97=B0=EA=B2=B0=20=EA=B0=80=EC=9D=B4=EB=93=9C=20?= =?UTF-8?q?=EB=85=B8=EC=B6=9C=20=EC=98=A4=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hw_lite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index f0400c131f..d2d5ebc6ae 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -6,7 +6,7 @@ import WebSerialConnector from './hardware/webSerialConnector'; import WebApiConnector from './hardware/webApiConnector'; import WebBluetoothConnector from './hardware/webBluetoothConnector'; -const ARDUINO_BOARD_IDS: string[] = ['1.1', '4.2', '8.1']; +const ARDUINO_BOARD_IDS: string[] = ['010101', '040201', '080101']; export default class HardwareLite { private status: HWLiteStatus; From 94894af29207eeba3ffcfae105da2c530f3a492f Mon Sep 17 00:00:00 2001 From: Tnks2U Date: Sat, 23 Dec 2023 20:28:24 +0900 Subject: [PATCH 21/21] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/hardware/webUsbFlasher.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/class/hardware/webUsbFlasher.ts b/src/class/hardware/webUsbFlasher.ts index f682231ff1..6abc86bbea 100644 --- a/src/class/hardware/webUsbFlasher.ts +++ b/src/class/hardware/webUsbFlasher.ts @@ -29,6 +29,7 @@ export default class WebUsbFlasher { this.endpointNumber = -1; } + // TODO: 함수분리 리팩토링 async flashFirmware(firmwareUrl: string, moduleId: string) { try { const response = await fetch(firmwareUrl); @@ -38,12 +39,10 @@ export default class WebUsbFlasher { filters: [{ vendorId: 0x0d28 }], }); await this.device.open(); - // selectConfiguration 체크 await this.device.selectConfiguration(1); this.findInterface(); await this.device.claimInterface(this.claimInterface); - // store.set(flashStateAtom, 'start'); this.flashState = 'start'; const result = await this.writeData([0x8a, 1]); if (result[1] !== 0) { @@ -54,8 +53,6 @@ export default class WebUsbFlasher { let sentPages = 0; this.flashState = 'flashing'; - // store.set(flashStateAtom, 'flashing'); - // webApi타입 ble인 block 파일에 넣어줘야 할듯 while (offset < data.length) { const end = Math.min(data.length, offset + chunkSize); const nextPageData = data.slice(offset, end); @@ -63,10 +60,9 @@ export default class WebUsbFlasher { cmdData[0] = 0x8c; cmdData[1] = nextPageData.length; cmdData.set(nextPageData, 2); - // TODO: 퍼센트 로직도 분리하기 + // TODO: 퍼센트 로직도 분리 if (sentPages % 128 == 0) { this.flashingPercent = (offset / data.length) * 100; - // store.set(percentAtom, (offset / data.length) * 100); console.log(this.flashingPercent); } await this.writeBuffer(cmdData); @@ -75,25 +71,22 @@ export default class WebUsbFlasher { } this.flashingPercent = (offset / data.length) * 100; console.log(this.flashingPercent); - // store.set(percentAtom, (offset / data.length) * 100); this.flashState = 'end'; - // store.set(flashStateAtom, 'end'); - // close + // INFO: close const flashResult = await this.writeData([0x8b]); if (flashResult[1] !== 0) { throw Error('flash failed'); } - // reset + // INFO: reset await this.writeData([0x89]); } finally { this.flashState = 'idle'; - // store.set(flashStateAtom, 'idle'); } } - // study: 좀 더 확인이 필요 + findInterface() { const filteredInterfaces = this.device.configurations[0].interfaces.filter( (interfaceItem) => { @@ -166,7 +159,7 @@ export default class WebUsbFlasher { } } - // TODO: buffer랑 합치기? + // TODO: buffer랑 합치기 async writeData(data: Array): Promise { const response = await this.transfer(new Uint8Array(data)); if (!response.data?.buffer) {