Skip to content

Commit

Permalink
Merge pull request #2627 from entrylabs/issue/3424
Browse files Browse the repository at this point in the history
[4.31.0] 하드웨어 웹연결에 bluetooth 기능 추가, 마이크로비트 ble연결 지원
  • Loading branch information
Tnks2U authored Dec 27, 2023
2 parents 00b3951 + 94894af commit b9a7215
Show file tree
Hide file tree
Showing 40 changed files with 5,252 additions and 400 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/class/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
96 changes: 96 additions & 0 deletions src/class/hardware/bluetoothServices/event-dispatcher.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
addEventListener<K extends keyof T>(
type: K,
listener: (event: CustomEvent<T[K]>) => void
): void;
removeEventListener<K extends keyof T>(
type: K,
callback: (event: CustomEvent<T[K]>) => void
): void;
dispatchEvent(event: CustomEvent<T>): boolean;
dispatchEvent<K extends keyof T>(type: K, detail: T[K]): boolean;
addListener<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
once<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
prependListener<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
prependOnceListener<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
removeListener<K extends keyof T>(event: K, listener: (data: T[K]) => void): this;
removeAllListeners<K extends keyof T>(event?: K): this;
// tslint:disable-next-line:ban-types
listeners<K extends keyof T>(event: K): Function[];
emit<K extends keyof T>(event: K, data: T[K]): boolean;
// tslint:disable-next-line:array-type
eventNames<K extends keyof T>(): Array<K>;
listenerCount<K extends keyof T>(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<T>(type: string, detail: T): boolean;
public dispatchEvent<T>(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);
}
}
31 changes: 31 additions & 0 deletions src/class/hardware/bluetoothServices/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
};
79 changes: 79 additions & 0 deletions src/class/hardware/bluetoothServices/promise-queue.ts
Original file line number Diff line number Diff line change
@@ -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<any>;
resolve: (value?: any | PromiseLike<any> | undefined) => void;
reject: (reason?: any) => void;
}

/**
* @hidden
*/
export class PromiseQueue {
private queue: QueuedPromise[] = [];
private running = 0;

constructor(private concurrent = 1) {}

private async pump(): Promise<void> {
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<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push({
fn,
resolve,
reject,
});

return this.pump();
});
}
}
113 changes: 113 additions & 0 deletions src/class/hardware/bluetoothServices/service-helper.ts
Original file line number Diff line number Diff line change
@@ -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<BluetoothRemoteGATTCharacteristic | undefined> {
if (!this.characteristics) {
this.characteristics = await this.service.getCharacteristics();
}

return this.characteristics.find((characteristic) => characteristic.uuid === uuid);
}

public async getCharacteristicValue(uuid: BluetoothCharacteristicUUID): Promise<DataView> {
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<void> {
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)
);
});
}
}
Loading

0 comments on commit b9a7215

Please sign in to comment.