├── .drone.yml ├── .gitignore ├── .gitmodules ├── .jsdoc.json ├── .npmignore ├── LICENSE.md ├── README.md ├── abandonware-noble.d.ts ├── docs ├── AbsoluteMotor.html ├── BaseHub.html ├── BasicMotor.html ├── ColorDistanceSensor.html ├── CurrentSensor.html ├── Device.html ├── DuploTrainBase.html ├── DuploTrainBaseColorSensor.html ├── DuploTrainBaseMotor.html ├── DuploTrainBaseSpeaker.html ├── DuploTraniBaseSpeedometer.html ├── Hub.html ├── HubLED.html ├── LPF2Hub.html ├── Light.html ├── Mario.html ├── MarioAccelerometer.html ├── MarioBarcodeSensor.html ├── MarioPantsSensor.html ├── MediumLinearMotor.html ├── MotionSensor.html ├── MoveHub.html ├── MoveHubMediumLinearMotor.html ├── MoveHubTiltSensor.html ├── PiezoBuzzer.html ├── PoweredUP.html ├── RemoteControl.html ├── RemoteControlButton.html ├── SimpleMediumLinearMotor.html ├── TachoMotor.html ├── Technic3x3ColorLightMatrix.html ├── TechnicColorSensor.html ├── TechnicDistanceSensor.html ├── TechnicForceSensor.html ├── TechnicLargeAngularMotor.html ├── TechnicLargeLinearMotor.html ├── TechnicMediumAngularMotor.html ├── TechnicMediumHub.html ├── TechnicMediumHubAccelerometerSensor.html ├── TechnicMediumHubGyroSensor.html ├── TechnicMediumHubTiltSensor.html ├── TechnicSmallAngularMotor.html ├── TechnicXLargeLinearMotor.html ├── TiltSensor.html ├── TrainMotor.html ├── VoltageSensor.html ├── WeDo2SmartHub.html ├── consts.js.html ├── devices_absolutemotor.js.html ├── devices_basicmotor.js.html ├── devices_colordistancesensor.js.html ├── devices_currentsensor.js.html ├── devices_device.js.html ├── devices_duplotrainbasecolorsensor.js.html ├── devices_duplotrainbasemotor.js.html ├── devices_duplotrainbasespeaker.js.html ├── devices_duplotrainbasespeedometer.js.html ├── devices_hubled.js.html ├── devices_light.js.html ├── devices_marioaccelerometer.js.html ├── devices_mariobarcodesensor.js.html ├── devices_mariopantssensor.js.html ├── devices_mediumlinearmotor.js.html ├── devices_motionsensor.js.html ├── devices_movehubmediumlinearmotor.js.html ├── devices_movehubtiltsensor.js.html ├── devices_piezobuzzer.js.html ├── devices_remotecontrolbutton.js.html ├── devices_simplemediumlinearmotor.js.html ├── devices_tachomotor.js.html ├── devices_technic3x3colorlightmatrix.js.html ├── devices_techniccolorsensor.js.html ├── devices_technicdistancesensor.js.html ├── devices_technicforcesensor.js.html ├── devices_techniclargeangularmotor.js.html ├── devices_techniclargelinearmotor.js.html ├── devices_technicmediumangularmotor.js.html ├── devices_technicmediumhubaccelerometersensor.js.html ├── devices_technicmediumhubgyrosensor.js.html ├── devices_technicmediumhubtiltsensor.js.html ├── devices_technicsmallangularmotor.js.html ├── devices_technicxlargelinearmotor.js.html ├── devices_tiltsensor.js.html ├── devices_trainmotor.js.html ├── devices_voltagesensor.js.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.svg │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.svg │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── global.html ├── hubs_basehub.js.html ├── hubs_duplotrainbase.js.html ├── hubs_hub.js.html ├── hubs_lpf2hub.js.html ├── hubs_mario.js.html ├── hubs_movehub.js.html ├── hubs_remotecontrol.js.html ├── hubs_technicmediumhub.js.html ├── hubs_technicsmallhub.js.html ├── hubs_wedo2smarthub.js.html ├── icons │ ├── home.svg │ └── search.svg ├── index.html ├── poweredup-browser.js.html ├── poweredup-node.js.html ├── scripts │ ├── linenumber.js │ ├── pagelocation.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js └── styles │ ├── collapse.css │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── examples ├── leds.js ├── light_matrix.js ├── mario.js ├── sample_usage.js ├── sensor_test.js ├── slow_train_remote.js ├── vernie_remote.js ├── vernie_roaming.js ├── web_bluetooth.css └── web_bluetooth.html ├── noble-mac.d.ts ├── package-lock.json ├── package.json ├── src ├── color.ts ├── consts.ts ├── devices │ ├── absolutemotor.ts │ ├── basicmotor.ts │ ├── colordistancesensor.ts │ ├── currentsensor.ts │ ├── device.ts │ ├── duplotrainbasecolorsensor.ts │ ├── duplotrainbasemotor.ts │ ├── duplotrainbasespeaker.ts │ ├── duplotrainbasespeedometer.ts │ ├── hubled.ts │ ├── light.ts │ ├── marioaccelerometer.ts │ ├── mariobarcodesensor.ts │ ├── mariopantssensor.ts │ ├── mediumlinearmotor.ts │ ├── motionsensor.ts │ ├── movehubmediumlinearmotor.ts │ ├── movehubtiltsensor.ts │ ├── piezobuzzer.ts │ ├── remotecontrolbutton.ts │ ├── simplemediumlinearmotor.ts │ ├── tachomotor.ts │ ├── technic3x3colorlightmatrix.ts │ ├── techniccolorsensor.ts │ ├── technicdistancesensor.ts │ ├── technicforcesensor.ts │ ├── techniclargeangularmotor.ts │ ├── techniclargelinearmotor.ts │ ├── technicmediumangularmotor.ts │ ├── technicmediumhubaccelerometersensor.ts │ ├── technicmediumhubgyrosensor.ts │ ├── technicmediumhubtiltsensor.ts │ ├── technicsmallangularmotor.ts │ ├── technicxlargelinearmotor.ts │ ├── tiltsensor.ts │ ├── trainmotor.ts │ └── voltagesensor.ts ├── hubs │ ├── basehub.ts │ ├── duplotrainbase.ts │ ├── hub.ts │ ├── lpf2hub.ts │ ├── mario.ts │ ├── movehub.ts │ ├── remotecontrol.ts │ ├── technicmediumhub.ts │ ├── technicsmallhub.ts │ └── wedo2smarthub.ts ├── index-browser.ts ├── index-node.ts ├── interfaces.ts ├── nobleabstraction.ts ├── poweredup-browser.ts ├── poweredup-node.ts ├── utils.ts └── webbleabstraction.ts ├── tsconfig.json ├── tslint.json ├── webble_test.html └── webpack.config.js /.drone.yml: -------------------------------------------------------------------------------- 1 | pipeline: 2 | 3 | install: 4 | image: node:10.15.1 5 | commands: 6 | - apt-get update 7 | - apt-get install -y bluetooth bluez libbluetooth-dev libudev-dev 8 | - git submodule update --init --recursive 9 | - npm install 10 | 11 | build: 12 | image: node:10.15.1 13 | commands: 14 | - npm run all 15 | 16 | publish: 17 | image: plugins/npm 18 | username: nathankellenicki 19 | token: 20 | from_secret: NPM_TOKEN 21 | when: 22 | ref: 23 | - refs/tags/v* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .vscode/ 4 | *.tgz 5 | **/.DS_store 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/.gitmodules -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": [ "README.md", "dist/node" ], 8 | "includePattern": ".js$" 9 | }, 10 | "plugins": ["plugins/markdown"], 11 | "templates": { 12 | "referenceTitle": "node-poweredup Reference", 13 | "logoFile": "", 14 | "cleverLinks": false, 15 | "monospaceLinks": false, 16 | "dateFormat": "ddd MMM Do YYYY", 17 | "outputSourceFiles": true, 18 | "outputSourcePath": true, 19 | "systemName": "node-poweredup", 20 | "footer": "", 21 | "copyright": "node-poweredup by Nathan Kellenicki licensed under the MIT license.", 22 | "navType": "vertical", 23 | "theme": "simplex", 24 | "linenums": true, 25 | "collapseSymbols": false, 26 | "inverseNav": true, 27 | "protocol": "html://", 28 | "methodHeadingReturns": false 29 | }, 30 | "markdown": { 31 | "parser": "gfm", 32 | "hardwrap": true 33 | }, 34 | "opts": { 35 | "destination": "./docs/", 36 | "encoding": "utf8", 37 | "private": true, 38 | "recurse": true 39 | } 40 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | tsconfig.json 3 | tslint.json 4 | .vscode/ 5 | examples/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nathan Kellenicki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /abandonware-noble.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@abandonware/noble" { 2 | // Type definitions for noble 3 | // Project: https://github.com/sandeepmistry/noble 4 | // Definitions by: Seon-Wook Park 5 | // Shantanu Bhadoria 6 | // Luke Libraro 7 | // Dan Chao 8 | // Michal Lower 9 | // Rob Moran 10 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 11 | 12 | /// 13 | 14 | import events = require("events"); 15 | 16 | export function startScanning(callback?: (error?: Error) => void): void; 17 | export function startScanning(serviceUUIDs: string[], callback?: (error?: Error) => void): void; 18 | export function startScanning(serviceUUIDs: string[], allowDuplicates: boolean, callback?: (error?: Error) => void): void; 19 | export function stopScanning(callback?: () => void): void; 20 | 21 | export function on(event: "stateChange", listener: (state: string) => void): events.EventEmitter; 22 | export function on(event: "scanStart", listener: () => void): events.EventEmitter; 23 | export function on(event: "scanStop", listener: () => void): events.EventEmitter; 24 | export function on(event: "discover", listener: (peripheral: Peripheral) => void): events.EventEmitter; 25 | export function on(event: string, listener: Function): events.EventEmitter; 26 | 27 | export function removeListener(event: "stateChange", listener: (state: string) => void): events.EventEmitter; 28 | export function removeListener(event: "scanStart", listener: () => void): events.EventEmitter; 29 | export function removeListener(event: "scanStop", listener: () => void): events.EventEmitter; 30 | export function removeListener(event: "discover", listener: (peripheral: Peripheral) => void): events.EventEmitter; 31 | export function removeListener(event: string, listener: Function): events.EventEmitter; 32 | 33 | export var state:string; 34 | 35 | export class Peripheral extends events.EventEmitter { 36 | id: string; 37 | uuid: string; 38 | address: string; 39 | addressType: string; 40 | connectable: boolean; 41 | advertisement: Advertisement; 42 | rssi: number; 43 | services: Service[]; 44 | state: 'error' | 'connecting' | 'connected' | 'disconnecting' | 'disconnected'; 45 | 46 | connect(callback?: (error: string) => void): void; 47 | disconnect(callback?: () => void): void; 48 | updateRssi(callback?: (error: string, rssi: number) => void): void; 49 | discoverServices(serviceUUIDs: string[], callback?: (error: string, services: Service[]) => void): void; 50 | discoverAllServicesAndCharacteristics(callback?: (error: string, services: Service[], characteristics: Characteristic[]) => void): void; 51 | discoverSomeServicesAndCharacteristics(serviceUUIDs: string[], characteristicUUIDs: string[], callback?: (error: string, services: Service[], characteristics: Characteristic[]) => void): void; 52 | 53 | readHandle(handle: Buffer, callback: (error: string, data: Buffer) => void): void; 54 | writeHandle(handle: Buffer, data: Buffer, withoutResponse: boolean, callback: (error: string) => void): void; 55 | toString(): string; 56 | 57 | on(event: "connect", listener: (error: string) => void): this; 58 | on(event: "disconnect", listener: (error: string) => void): this; 59 | on(event: "rssiUpdate", listener: (rssi: number) => void): this; 60 | on(event: "servicesDiscover", listener: (services: Service[]) => void): this; 61 | on(event: string, listener: Function): this; 62 | } 63 | 64 | export interface Advertisement { 65 | localName: string; 66 | serviceData: { 67 | uuid: string, 68 | data: Buffer 69 | }; 70 | txPowerLevel: number; 71 | manufacturerData: Buffer; 72 | serviceUuids: string[]; 73 | } 74 | 75 | export class Service extends events.EventEmitter { 76 | uuid: string; 77 | name: string; 78 | type: string; 79 | includedServiceUuids: string[]; 80 | characteristics: Characteristic[]; 81 | 82 | discoverIncludedServices(serviceUUIDs: string[], callback?: (error: string, includedServiceUuids: string[]) => void): void; 83 | discoverCharacteristics(characteristicUUIDs: string[], callback?: (error: string, characteristics: Characteristic[]) => void): void; 84 | toString(): string; 85 | 86 | on(event: "includedServicesDiscover", listener: (includedServiceUuids: string[]) => void): this; 87 | on(event: "characteristicsDiscover", listener: (characteristics: Characteristic[]) => void): this; 88 | on(event: string, listener: Function): this; 89 | } 90 | 91 | export class Characteristic extends events.EventEmitter { 92 | uuid: string; 93 | name: string; 94 | type: string; 95 | properties: string[]; 96 | descriptors: Descriptor[]; 97 | 98 | read(callback?: (error: string, data: Buffer) => void): void; 99 | write(data: Buffer, notify: boolean, callback?: (error: string) => void): void; 100 | broadcast(broadcast: boolean, callback?: (error: string) => void): void; 101 | notify(notify: boolean, callback?: (error: string) => void): void; 102 | discoverDescriptors(callback?: (error: string, descriptors: Descriptor[]) => void): void; 103 | toString(): string; 104 | subscribe(callback?: (error: string) => void): void; 105 | unsubscribe(callback?: (error: string) => void): void; 106 | 107 | on(event: "read", listener: (data: Buffer, isNotification: boolean) => void): this; 108 | on(event: "write", withoutResponse: boolean, listener: (error: string) => void): this; 109 | on(event: "broadcast", listener: (state: string) => void): this; 110 | on(event: "notify", listener: (state: string) => void): this; 111 | on(event: "descriptorsDiscover", listener: (descriptors: Descriptor[]) => void): this; 112 | on(event: string, listener: Function): this; 113 | on(event: string, option: boolean, listener: Function): this; 114 | } 115 | 116 | export class Descriptor extends events.EventEmitter { 117 | uuid: string; 118 | name: string; 119 | type: string; 120 | 121 | readValue(callback?: (error: string, data: Buffer) => void): void; 122 | writeValue(data: Buffer, callback?: (error: string) => void): void; 123 | toString(): string; 124 | 125 | on(event: "valueRead", listener: (error: string, data: Buffer) => void): this; 126 | on(event: "valueWrite", listener: (error: string) => void): this; 127 | on(event: string, listener: Function): this; 128 | } 129 | } -------------------------------------------------------------------------------- /docs/devices_mediumlinearmotor.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: devices/mediumlinearmotor.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: devices/mediumlinearmotor.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
30 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
31 |     if (k2 === undefined) k2 = k;
32 |     var desc = Object.getOwnPropertyDescriptor(m, k);
33 |     if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34 |       desc = { enumerable: true, get: function() { return m[k]; } };
35 |     }
36 |     Object.defineProperty(o, k2, desc);
37 | }) : (function(o, m, k, k2) {
38 |     if (k2 === undefined) k2 = k;
39 |     o[k2] = m[k];
40 | }));
41 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
42 |     Object.defineProperty(o, "default", { enumerable: true, value: v });
43 | }) : function(o, v) {
44 |     o["default"] = v;
45 | });
46 | var __importStar = (this && this.__importStar) || function (mod) {
47 |     if (mod && mod.__esModule) return mod;
48 |     var result = {};
49 |     if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
50 |     __setModuleDefault(result, mod);
51 |     return result;
52 | };
53 | Object.defineProperty(exports, "__esModule", { value: true });
54 | exports.MediumLinearMotor = void 0;
55 | const tachomotor_1 = require("./tachomotor");
56 | const Consts = __importStar(require("../consts"));
57 | /**
58 |  * @class MediumLinearMotor
59 |  * @extends TachoMotor
60 |  */
61 | class MediumLinearMotor extends tachomotor_1.TachoMotor {
62 |     constructor(hub, portId) {
63 |         super(hub, portId, {}, Consts.DeviceType.MEDIUM_LINEAR_MOTOR);
64 |     }
65 | }
66 | exports.MediumLinearMotor = MediumLinearMotor;
67 | //# sourceMappingURL=mediumlinearmotor.js.map
68 |
69 |
70 | 71 | 72 | 73 | 74 |
75 | 76 | 79 | 80 |
81 | 82 |
83 | Documentation generated by JSDoc 4.0.2 on Thu Apr 25 2024 16:38:07 GMT-0700 (Pacific Daylight Time) 84 |
85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/devices_trainmotor.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: devices/trainmotor.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: devices/trainmotor.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
30 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
31 |     if (k2 === undefined) k2 = k;
32 |     var desc = Object.getOwnPropertyDescriptor(m, k);
33 |     if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34 |       desc = { enumerable: true, get: function() { return m[k]; } };
35 |     }
36 |     Object.defineProperty(o, k2, desc);
37 | }) : (function(o, m, k, k2) {
38 |     if (k2 === undefined) k2 = k;
39 |     o[k2] = m[k];
40 | }));
41 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
42 |     Object.defineProperty(o, "default", { enumerable: true, value: v });
43 | }) : function(o, v) {
44 |     o["default"] = v;
45 | });
46 | var __importStar = (this && this.__importStar) || function (mod) {
47 |     if (mod && mod.__esModule) return mod;
48 |     var result = {};
49 |     if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
50 |     __setModuleDefault(result, mod);
51 |     return result;
52 | };
53 | Object.defineProperty(exports, "__esModule", { value: true });
54 | exports.TrainMotor = void 0;
55 | const basicmotor_1 = require("./basicmotor");
56 | const Consts = __importStar(require("../consts"));
57 | /**
58 |  * @class TrainMotor
59 |  * @extends BasicMotor
60 |  */
61 | class TrainMotor extends basicmotor_1.BasicMotor {
62 |     constructor(hub, portId) {
63 |         super(hub, portId, {}, Consts.DeviceType.TRAIN_MOTOR);
64 |     }
65 | }
66 | exports.TrainMotor = TrainMotor;
67 | //# sourceMappingURL=trainmotor.js.map
68 |
69 |
70 | 71 | 72 | 73 | 74 |
75 | 76 | 79 | 80 |
81 | 82 |
83 | Documentation generated by JSDoc 4.0.2 on Thu Apr 25 2024 16:38:07 GMT-0700 (Pacific Daylight Time) 84 |
85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathankellenicki/node-poweredup/2fc1622c250bda67ce573695bc151d4fa62b56a3/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (() => { 3 | const source = document.getElementsByClassName('prettyprint source linenums'); 4 | let i = 0; 5 | let lineNumber = 0; 6 | let lineId; 7 | let lines; 8 | let totalLines; 9 | let anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = `line${lineNumber}`; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/pagelocation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(document).ready(function () { 4 | var currentSectionNav, target; 5 | 6 | // If an anchor hash is in the URL highlight the menu item 7 | highlightActiveHash(); 8 | // If a specific page section is in the URL highlight the menu item 9 | highlightActiveSection(); 10 | 11 | // If a specific page section is in the URL scroll that section up to the top 12 | currentSectionNav = $('#' + getCurrentSectionName() + '-nav'); 13 | 14 | if (currentSectionNav.position()) { 15 | $('nav').scrollTop(currentSectionNav.position().top); 16 | } 17 | 18 | // function to scroll to anchor when clicking an anchor linl 19 | $('a[href*="#"]:not([href="#"])').click(function () { 20 | /* eslint-disable no-invalid-this */ 21 | if (location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '') && location.hostname === this.hostname) { 22 | target = $(this.hash); 23 | target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); 24 | if (target.length) { 25 | $('html, body').animate({ 26 | scrollTop: target.offset().top 27 | }, 1000); 28 | } 29 | } 30 | /* eslint-enable no-invalid-this */ 31 | }); 32 | }); 33 | 34 | // If a new anchor section is selected, change the hightlighted menu item 35 | $(window).bind('hashchange', function (event) { 36 | highlightActiveHash(event); 37 | }); 38 | 39 | function highlightActiveHash(event) { 40 | var oldUrl, oldSubSectionElement; 41 | 42 | // check for and remove old hash active state 43 | if (event && event.originalEvent.oldURL) { 44 | oldUrl = event.originalEvent.oldURL; 45 | 46 | if (oldUrl.indexOf('#') > -1) { 47 | oldSubSectionElement = $('#' + getCurrentSectionName() + '-' + oldUrl.substring(oldUrl.indexOf('#') + 1) + '-nav'); 48 | 49 | if (oldSubSectionElement) { 50 | oldSubSectionElement.removeClass('active'); 51 | } 52 | } 53 | } 54 | 55 | if (getCurrentHashName()) { 56 | $('#' + getCurrentSectionName() + '-' + getCurrentHashName() + '-nav').addClass('active'); 57 | } 58 | } 59 | 60 | function highlightActiveSection() { 61 | var pageId = getCurrentSectionName(); 62 | 63 | $('#' + pageId + '-nav').addClass('active'); 64 | } 65 | 66 | function getCurrentSectionName() { 67 | var path = window.location.pathname; 68 | var pageUrl = path.split('/').pop(); 69 | 70 | var sectionName = pageUrl.substring(0, pageUrl.indexOf('.')); 71 | 72 | // remove the wodr module- if its in the url 73 | sectionName = sectionName.replace('module-', ''); 74 | 75 | return sectionName; 76 | } 77 | 78 | function getCurrentHashName() { 79 | var pageSubSectionId; 80 | var pageSubSectionHash = window.location.hash; 81 | 82 | if (pageSubSectionHash) { 83 | pageSubSectionId = pageSubSectionHash.substring(1).replace('.', ''); 84 | 85 | return pageSubSectionId; 86 | } 87 | 88 | return false; 89 | } 90 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/styles/collapse.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 681px) { 2 | nav > ul > li:hover .methods, 3 | .active .methods { 4 | display: block; 5 | } 6 | 7 | .methods { 8 | display: none; 9 | } 10 | 11 | nav > ul > li { 12 | padding: 20px 0; 13 | } 14 | 15 | nav > ul > li > a { 16 | padding: 0; 17 | } 18 | 19 | nav > ul > li.active a { 20 | margin-bottom: 10px; 21 | } 22 | 23 | nav > ul > li:hover > a, 24 | nav > ul > li.active > a { 25 | margin-bottom: 15px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/styles/jsdoc-default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('../fonts/OpenSans-Regular-webfont.eot'); 6 | src: 7 | local('Open Sans'), 8 | local('OpenSans'), 9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), 10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), 11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); 12 | } 13 | 14 | @font-face { 15 | font-family: 'Open Sans Light'; 16 | font-weight: normal; 17 | font-style: normal; 18 | src: url('../fonts/OpenSans-Light-webfont.eot'); 19 | src: 20 | local('Open Sans Light'), 21 | local('OpenSans Light'), 22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), 23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'), 24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg'); 25 | } 26 | 27 | html 28 | { 29 | overflow: auto; 30 | background-color: #fff; 31 | font-size: 14px; 32 | } 33 | 34 | body 35 | { 36 | font-family: 'Open Sans', sans-serif; 37 | line-height: 1.5; 38 | color: #4d4e53; 39 | background-color: white; 40 | } 41 | 42 | a, a:visited, a:active { 43 | color: #0095dd; 44 | text-decoration: none; 45 | } 46 | 47 | a:hover { 48 | text-decoration: underline; 49 | } 50 | 51 | header 52 | { 53 | display: block; 54 | padding: 0px 4px; 55 | } 56 | 57 | tt, code, kbd, samp { 58 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 59 | } 60 | 61 | .class-description { 62 | font-size: 130%; 63 | line-height: 140%; 64 | margin-bottom: 1em; 65 | margin-top: 1em; 66 | } 67 | 68 | .class-description:empty { 69 | margin: 0; 70 | } 71 | 72 | #main { 73 | float: left; 74 | width: 70%; 75 | } 76 | 77 | article dl { 78 | margin-bottom: 40px; 79 | } 80 | 81 | article img { 82 | max-width: 100%; 83 | } 84 | 85 | section 86 | { 87 | display: block; 88 | background-color: #fff; 89 | padding: 12px 24px; 90 | border-bottom: 1px solid #ccc; 91 | margin-right: 30px; 92 | } 93 | 94 | .variation { 95 | display: none; 96 | } 97 | 98 | .signature-attributes { 99 | font-size: 60%; 100 | color: #aaa; 101 | font-style: italic; 102 | font-weight: lighter; 103 | } 104 | 105 | nav 106 | { 107 | display: block; 108 | float: right; 109 | margin-top: 28px; 110 | width: 30%; 111 | box-sizing: border-box; 112 | border-left: 1px solid #ccc; 113 | padding-left: 16px; 114 | } 115 | 116 | nav ul { 117 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 118 | font-size: 100%; 119 | line-height: 17px; 120 | padding: 0; 121 | margin: 0; 122 | list-style-type: none; 123 | } 124 | 125 | nav ul a, nav ul a:visited, nav ul a:active { 126 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 127 | line-height: 18px; 128 | color: #4D4E53; 129 | } 130 | 131 | nav h3 { 132 | margin-top: 12px; 133 | } 134 | 135 | nav li { 136 | margin-top: 6px; 137 | } 138 | 139 | footer { 140 | display: block; 141 | padding: 6px; 142 | margin-top: 12px; 143 | font-style: italic; 144 | font-size: 90%; 145 | } 146 | 147 | h1, h2, h3, h4 { 148 | font-weight: 200; 149 | margin: 0; 150 | } 151 | 152 | h1 153 | { 154 | font-family: 'Open Sans Light', sans-serif; 155 | font-size: 48px; 156 | letter-spacing: -2px; 157 | margin: 12px 24px 20px; 158 | } 159 | 160 | h2, h3.subsection-title 161 | { 162 | font-size: 30px; 163 | font-weight: 700; 164 | letter-spacing: -1px; 165 | margin-bottom: 12px; 166 | } 167 | 168 | h3 169 | { 170 | font-size: 24px; 171 | letter-spacing: -0.5px; 172 | margin-bottom: 12px; 173 | } 174 | 175 | h4 176 | { 177 | font-size: 18px; 178 | letter-spacing: -0.33px; 179 | margin-bottom: 12px; 180 | color: #4d4e53; 181 | } 182 | 183 | h5, .container-overview .subsection-title 184 | { 185 | font-size: 120%; 186 | font-weight: bold; 187 | letter-spacing: -0.01em; 188 | margin: 8px 0 3px 0; 189 | } 190 | 191 | h6 192 | { 193 | font-size: 100%; 194 | letter-spacing: -0.01em; 195 | margin: 6px 0 3px 0; 196 | font-style: italic; 197 | } 198 | 199 | table 200 | { 201 | border-spacing: 0; 202 | border: 0; 203 | border-collapse: collapse; 204 | } 205 | 206 | td, th 207 | { 208 | border: 1px solid #ddd; 209 | margin: 0px; 210 | text-align: left; 211 | vertical-align: top; 212 | padding: 4px 6px; 213 | display: table-cell; 214 | } 215 | 216 | thead tr 217 | { 218 | background-color: #ddd; 219 | font-weight: bold; 220 | } 221 | 222 | th { border-right: 1px solid #aaa; } 223 | tr > th:last-child { border-right: 1px solid #ddd; } 224 | 225 | .ancestors, .attribs { color: #999; } 226 | .ancestors a, .attribs a 227 | { 228 | color: #999 !important; 229 | text-decoration: none; 230 | } 231 | 232 | .clear 233 | { 234 | clear: both; 235 | } 236 | 237 | .important 238 | { 239 | font-weight: bold; 240 | color: #950B02; 241 | } 242 | 243 | .yes-def { 244 | text-indent: -1000px; 245 | } 246 | 247 | .type-signature { 248 | color: #aaa; 249 | } 250 | 251 | .name, .signature { 252 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 253 | } 254 | 255 | .details { margin-top: 14px; border-left: 2px solid #DDD; } 256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } 257 | .details dd { margin-left: 70px; } 258 | .details ul { margin: 0; } 259 | .details ul { list-style-type: none; } 260 | .details li { margin-left: 30px; padding-top: 6px; } 261 | .details pre.prettyprint { margin: 0 } 262 | .details .object-value { padding-top: 0; } 263 | 264 | .description { 265 | margin-bottom: 1em; 266 | margin-top: 1em; 267 | } 268 | 269 | .code-caption 270 | { 271 | font-style: italic; 272 | font-size: 107%; 273 | margin: 0; 274 | } 275 | 276 | .source 277 | { 278 | border: 1px solid #ddd; 279 | width: 80%; 280 | overflow: auto; 281 | } 282 | 283 | .prettyprint.source { 284 | width: inherit; 285 | } 286 | 287 | .source code 288 | { 289 | font-size: 100%; 290 | line-height: 18px; 291 | display: block; 292 | padding: 4px 12px; 293 | margin: 0; 294 | background-color: #fff; 295 | color: #4D4E53; 296 | } 297 | 298 | .prettyprint code span.line 299 | { 300 | display: inline-block; 301 | } 302 | 303 | .prettyprint.linenums 304 | { 305 | padding-left: 70px; 306 | -webkit-user-select: none; 307 | -moz-user-select: none; 308 | -ms-user-select: none; 309 | user-select: none; 310 | } 311 | 312 | .prettyprint.linenums ol 313 | { 314 | padding-left: 0; 315 | } 316 | 317 | .prettyprint.linenums li 318 | { 319 | border-left: 3px #ddd solid; 320 | } 321 | 322 | .prettyprint.linenums li.selected, 323 | .prettyprint.linenums li.selected * 324 | { 325 | background-color: lightyellow; 326 | } 327 | 328 | .prettyprint.linenums li * 329 | { 330 | -webkit-user-select: text; 331 | -moz-user-select: text; 332 | -ms-user-select: text; 333 | user-select: text; 334 | } 335 | 336 | .params .name, .props .name, .name code { 337 | color: #4D4E53; 338 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 339 | font-size: 100%; 340 | } 341 | 342 | .params td.description > p:first-child, 343 | .props td.description > p:first-child 344 | { 345 | margin-top: 0; 346 | padding-top: 0; 347 | } 348 | 349 | .params td.description > p:last-child, 350 | .props td.description > p:last-child 351 | { 352 | margin-bottom: 0; 353 | padding-bottom: 0; 354 | } 355 | 356 | .disabled { 357 | color: #454545; 358 | } 359 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /examples/leds.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This demonstrates connecting multiple hubs to your laptop. Once connected, all the hubs LED lights will cycle through the same colors simultaneously. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); // Start scanning for hubs 11 | 12 | console.log("Looking for Hubs..."); 13 | 14 | poweredUP.on("discover", async (hub) => { // Wait to discover hubs 15 | 16 | await hub.connect(); // Connect to hub 17 | console.log(`Connected to ${hub.name}!`); 18 | 19 | hub.on("disconnect", () => { 20 | console.log("Hub disconnected"); 21 | }) 22 | 23 | }); 24 | 25 | let color = 1; 26 | setInterval(() => { 27 | 28 | const hubs = poweredUP.getHubs(); // Get an array of all connected hubs 29 | hubs.forEach(async (hub) => { 30 | const led = await hub.waitForDeviceByType(PoweredUP.Consts.DeviceType.HUB_LED); 31 | led.setColor(color); // Set the color 32 | }) 33 | color++; 34 | if (color > 10) { 35 | color = 1; 36 | } 37 | 38 | }, 2000); -------------------------------------------------------------------------------- /examples/light_matrix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This demonstrates using the Technic 3x3 Light Matrix, included with the LEGO Education Spike Essentials set. 4 | * 5 | * NOTE: The Technic 3x3 Light Matrix is compatible with all Powered UP Hubs, not just the Technic Small Hub included with Spike Essentials. 6 | */ 7 | 8 | const PoweredUP = require(".."); 9 | 10 | const poweredUP = new PoweredUP.PoweredUP(); 11 | poweredUP.scan(); // Start scanning for hubs 12 | 13 | console.log("Looking for Hubs..."); 14 | 15 | poweredUP.on("discover", async (hub) => { // Wait to discover hubs 16 | 17 | await hub.connect(); // Connect to hub 18 | console.log(`Connected to ${hub.name}!`); 19 | 20 | const matrix = await hub.waitForDeviceByType(PoweredUP.Consts.DeviceType.TECHNIC_3X3_COLOR_LIGHT_MATRIX); 21 | 22 | // setMatrix accepts an array of 9 items - Either standard color values (number), or Color objects with brightness (brightness values are 0 to 100%) 23 | // It can also accept just a single color to set all 9 lights to the same color 24 | matrix.setMatrix([ 25 | // Red at 100%, green at 20%, red at 100% 26 | PoweredUP.Consts.Color.RED, new PoweredUP.Color(PoweredUP.Consts.Color.GREEN, 20), PoweredUP.Consts.Color.RED, 27 | // Green at 100%, yellow at 100%, green at 100% 28 | PoweredUP.Consts.Color.GREEN, PoweredUP.Consts.Color.YELLOW, PoweredUP.Consts.Color.GREEN, 29 | // Red at 100%, green at 20%, red at 100% 30 | PoweredUP.Consts.Color.RED, new PoweredUP.Color(PoweredUP.Consts.Color.GREEN, 20), PoweredUP.Consts.Color.RED, 31 | ]); 32 | 33 | hub.on("disconnect", () => { 34 | console.log("Hub disconnected"); 35 | }) 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /examples/mario.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This demonstrates connecting to LEGO Super Mario. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); // Start scanning for hubs 11 | 12 | console.log("Looking for Mario..."); 13 | 14 | poweredUP.on("discover", async (hub) => { // Wait to discover hubs 15 | 16 | if (hub instanceof PoweredUP.Mario) { 17 | const mario = hub; 18 | await mario.connect(); // Connect to Mario 19 | console.log(`Connected to Mario!`); 20 | 21 | mario.on("gesture", (_, { gesture }) => { 22 | console.log("Gesture", gesture); 23 | }); 24 | 25 | mario.on("pants", (_, { pants }) => { 26 | console.log("Pants detected", pants); 27 | }); 28 | 29 | mario.on("barcode", (_, { barcode, color }) => { 30 | if (color) { 31 | console.log("Color detected", color); 32 | } else if (barcode) { 33 | console.log("Barcode detected", barcode); 34 | } 35 | }); 36 | 37 | mario.on("disconnect", () => { 38 | console.log("Mario disconnected"); 39 | }); 40 | } 41 | 42 | }); -------------------------------------------------------------------------------- /examples/sample_usage.js: -------------------------------------------------------------------------------- 1 | const PoweredUP = require(".."); 2 | const poweredUP = new PoweredUP.PoweredUP(); 3 | 4 | poweredUP.on("discover", async (hub) => { // Wait to discover a Hub 5 | console.log(`Discovered ${hub.name}!`); 6 | await hub.connect(); // Connect to the Hub 7 | const motorA = await hub.waitForDeviceAtPort("A"); // Make sure a motor is plugged into port A 8 | const motorB = await hub.waitForDeviceAtPort("B"); // Make sure a motor is plugged into port B 9 | console.log("Connected"); 10 | 11 | while (true) { // Repeat indefinitely 12 | console.log("Running motor B at speed 50"); 13 | motorB.setPower(50); // Start a motor attached to port B to run a 3/4 speed (75) indefinitely 14 | console.log("Running motor A at speed 100 for 2 seconds"); 15 | motorA.setPower(100); // Run a motor attached to port A for 2 seconds at maximum speed (100) then stop 16 | await hub.sleep(2000); 17 | motorA.brake(); 18 | await hub.sleep(1000); // Do nothing for 1 second 19 | console.log("Running motor A at speed -30 for 1 second"); 20 | motorA.setPower(-30); // Run a motor attached to port A for 2 seconds at 1/2 speed in reverse (-50) then stop 21 | await hub.sleep(2000); 22 | motorA.brake(); 23 | await hub.sleep(1000); // Do nothing for 1 second 24 | } 25 | }); 26 | 27 | poweredUP.scan(); // Start scanning for Hubs 28 | console.log("Scanning for Hubs..."); 29 | -------------------------------------------------------------------------------- /examples/sensor_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This example demonstrates usage of various sensor events. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); 11 | 12 | console.log("Looking for Powered UP Hubs..."); 13 | 14 | let vernie = null; 15 | let remote = null; 16 | 17 | poweredUP.on("discover", async (hub) => { 18 | 19 | await hub.connect(); 20 | console.log(`Connected to ${hub.name}!`); 21 | 22 | hub.on("disconnect", () => { 23 | console.log(`Disconnected ${hub.name}`); 24 | }) 25 | 26 | hub.on("tilt", (device, { x, y, z }) => { 27 | console.log(`Tilt detected on port ${device.portName} (X: ${x}, Y: ${y}${z !== "undefined" ? `, Z: ${z}`: ""})`); 28 | }); 29 | 30 | hub.on("accel", (device, { x, y, z }) => { 31 | console.log(`Accelerometer detected on port ${device.portName} (X: ${x}, Y: ${y}, Z: ${z})`); 32 | }); 33 | 34 | hub.on("distance", (device, { distance }) => { 35 | console.log(`Motion detected on port ${device.portName} (Distance: ${distance})`); 36 | }); 37 | 38 | hub.on("color", (device, { color }) => { 39 | console.log(`Color detected on port ${device.portName} (Color: ${color})`); 40 | }); 41 | 42 | hub.on("rotate", (device, { degrees }) => { 43 | console.log(`Rotation detected on port ${device.portName} (Rotation: ${degrees})`); 44 | }); 45 | 46 | hub.on("force", (device, { force }) => { 47 | console.log(`Force detected on port ${device.portName} (Force: ${force})`); 48 | }); 49 | 50 | hub.on("button", ({ event }) => { 51 | console.log(`Green button press detected (Event: ${event})`); 52 | }); 53 | 54 | hub.on("remoteButton", (device, { event }) => { 55 | console.log(`Remote control button press detected on port ${device.portName} (Event: ${event})`); 56 | }); 57 | 58 | hub.on("attach", (device) => { 59 | console.log(`Device attached to port ${device.portName} (Device ID: ${device.type})`) ; 60 | }); 61 | 62 | hub.on("detach", (device) => { 63 | console.log(`Device detached from port ${device.portName}`) ; 64 | }); 65 | 66 | }); -------------------------------------------------------------------------------- /examples/slow_train_remote.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This example allows you to connect Vernie and a Powered UP Remote Control to your laptop, and enables the control of Vernie with the Remote. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); // Start scanning 11 | 12 | console.log("Looking for train and remote..."); 13 | 14 | const TRAIN_LED_COLOR = PoweredUP.Consts.Color.PURPLE; 15 | 16 | let trainHub = null; 17 | let trainMotor = null; 18 | let remoteHub = null; 19 | let remoteButtonLeft = null; 20 | let remoteButtonRight = null; 21 | 22 | let currentSpeed = 0; 23 | 24 | poweredUP.on("discover", async (hub) => { // Wait to discover Vernie and Remote 25 | 26 | if (hub.type === PoweredUP.Consts.HubType.HUB) { 27 | 28 | trainHub = hub; 29 | await trainHub.connect(); 30 | const led = await trainHub.waitForDeviceByType(PoweredUP.Consts.DeviceType.HUB_LED); 31 | trainMotor = await trainHub.waitForDeviceByType(PoweredUP.Consts.DeviceType.MEDIUM_LINEAR_MOTOR); 32 | led.setColor(TRAIN_LED_COLOR); 33 | console.log(`Connected to train (${trainHub.name})!`); 34 | if (trainHub && remoteHub) { 35 | console.log("You're now ready to go!"); 36 | } 37 | 38 | } else if (hub.type === PoweredUP.Consts.HubType.REMOTE_CONTROL) { 39 | remoteHub = hub; 40 | await remoteHub.connect(); 41 | const led = await remoteHub.waitForDeviceByType(PoweredUP.Consts.DeviceType.HUB_LED); 42 | remoteButtonLeft = await remoteHub.waitForDeviceAtPort("LEFT"); 43 | remoteButtonRight = await remoteHub.waitForDeviceAtPort("RIGHT"); 44 | led.setColor(TRAIN_LED_COLOR); 45 | 46 | remoteButtonLeft.on("remoteButton", ({ event }) => { 47 | if (trainMotor) { 48 | if (event === PoweredUP.Consts.ButtonState.UP) { 49 | currentSpeed += 10; 50 | } else if (event === PoweredUP.Consts.ButtonState.DOWN) { 51 | currentSpeed -= 10; 52 | } else if (event === PoweredUP.Consts.ButtonState.STOP) { 53 | currentSpeed = 0; 54 | } 55 | trainMotor.setSpeed(currentSpeed); 56 | } 57 | }); 58 | 59 | remoteButtonRight.on("remoteButton", ({ event }) => { 60 | if (trainMotor) { 61 | if (event === PoweredUP.Consts.ButtonState.UP) { 62 | currentSpeed += 1; 63 | } else if (event === PoweredUP.Consts.ButtonState.DOWN) { 64 | currentSpeed -= 1; 65 | } else if (event === PoweredUP.Consts.ButtonState.STOP) { 66 | currentSpeed = 0; 67 | } 68 | trainMotor.setSpeed(currentSpeed); 69 | } 70 | }); 71 | 72 | console.log(`Connected to remote (${remoteHub.name})!`); 73 | if (trainHub && remoteHub) { 74 | console.log("You're now ready to go!"); 75 | } 76 | } 77 | 78 | }); -------------------------------------------------------------------------------- /examples/vernie_remote.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This example allows you to connect Vernie and a Powered UP Remote Control to your laptop, and enables the control of Vernie with the Remote. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); // Start scanning 11 | 12 | console.log("Looking for Vernie and Remote..."); 13 | 14 | let vernie = null; 15 | let remote = null; 16 | 17 | poweredUP.on("discover", async (hub) => { // Wait to discover Vernie and Remote 18 | 19 | if (hub.type === PoweredUP.Consts.HubType.MOVE_HUB) { 20 | 21 | vernie = hub; 22 | await vernie.connect(); 23 | const led = await vernie.waitForDeviceByType(PoweredUP.Consts.DeviceType.HUB_LED); 24 | led.setColor(PoweredUP.Consts.Color.BLUE); 25 | console.log(`Connected to Vernie (${vernie.name})!`); 26 | 27 | } else if (hub.type === PoweredUP.Consts.HubType.REMOTE_CONTROL) { 28 | remote = hub; 29 | 30 | remote.on("remoteButton", async (device, { event }) => { 31 | if (vernie) { 32 | const leftTrack = await vernie.waitForDeviceAtPort("A"); 33 | const rightTrack = await vernie.waitForDeviceAtPort("B"); 34 | const head = await vernie.waitForDeviceAtPort("D"); 35 | 36 | console.log(event); 37 | 38 | switch (event) { 39 | case PoweredUP.Consts.ButtonState.UP: // If up is pressed, move the track forward 40 | { 41 | console.log(device.portName); 42 | device.portName === "LEFT" ? leftTrack.setSpeed(50) : rightTrack.setSpeed(50); 43 | break; 44 | } 45 | case PoweredUP.Consts.ButtonState.DOWN: // If down is pressed, move the track backwards 46 | { 47 | device.portName === "LEFT" ? leftTrack.setSpeed(-50) : rightTrack.setSpeed(-50); 48 | break; 49 | } 50 | case PoweredUP.Consts.ButtonState.RELEASED: // Stop the track when the button is released 51 | { 52 | device.portName === "LEFT" ? leftTrack.setPower(0) : rightTrack.setPower(0); 53 | break; 54 | } 55 | case PoweredUP.Consts.ButtonState.STOP: // Move the head left or right when a red button is pressed 56 | { 57 | await head.rotateByDegrees(35, device.portName === "LEFT" ? -20 : 20); 58 | break; 59 | } 60 | } 61 | } 62 | }); 63 | 64 | hub.on("button", async ({ event }) => { 65 | console.log(event); 66 | if (vernie) { 67 | const head = await vernie.waitForDeviceAtPort("D"); 68 | if (event === PoweredUP.Consts.ButtonState.PRESSED) { 69 | await head.rotateByDegrees(80, 20); 70 | await head.rotateByDegrees(80, -20); 71 | } 72 | } 73 | }); 74 | 75 | await remote.connect(); 76 | const led = await remote.waitForDeviceByType(PoweredUP.Consts.DeviceType.HUB_LED); 77 | led.setColor(PoweredUP.Consts.Color.BLUE); 78 | console.log(`Connected to Powered UP Remote (${remote.name})!`); 79 | } 80 | 81 | if (vernie && remote) { 82 | console.log("You're now ready to go!"); 83 | } 84 | 85 | }); -------------------------------------------------------------------------------- /examples/vernie_roaming.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This example allows Vernie to roam a room and avoid obstacles. 4 | * 5 | */ 6 | 7 | const PoweredUP = require(".."); 8 | 9 | const poweredUP = new PoweredUP.PoweredUP(); 10 | poweredUP.scan(); // Start scanning 11 | 12 | console.log("Looking for Vernie..."); 13 | 14 | const Modes = { 15 | ROAMING: 0, 16 | AVOIDING: 1, 17 | STALLED: 2, 18 | CHANGING_DIRECTION: 3 19 | } 20 | 21 | 22 | const turnRight = async (vernie, multiplier = 1) => { 23 | await vernie.wait([ 24 | vernie.setMotorAngle("A", 270 * multiplier, 50), 25 | vernie.setMotorAngle("B", 270 * multiplier, -50) 26 | ]); 27 | } 28 | 29 | 30 | const turnHeadRight = async (vernie, multiplier = 1) => { 31 | await vernie.setMotorAngle("D", 25 * multiplier, 20); 32 | } 33 | 34 | 35 | const turnLeft = async (vernie, multiplier = 1) => { 36 | await vernie.wait([ 37 | vernie.setMotorAngle("A", 270 * multiplier, -50), 38 | vernie.setMotorAngle("B", 270 * multiplier, 50) 39 | ]); 40 | } 41 | 42 | 43 | const turnHeadLeft = async (vernie, multiplier = 1) => { 44 | await vernie.setMotorAngle("D", 25 * multiplier, -20); 45 | } 46 | 47 | 48 | const scan = (vernie) => { 49 | return new Promise(async (resolve) => { 50 | let lastDistance = 0; 51 | vernie.on("distance", async (port, distance) => { 52 | lastDistance = distance; 53 | }); 54 | await turnHeadRight(vernie); 55 | await turnRight(vernie); 56 | await turnHeadLeft(vernie); 57 | await vernie.sleep(1000); 58 | const rightDistance = lastDistance; 59 | await turnHeadLeft(vernie); 60 | await turnLeft(vernie, 2); 61 | await turnHeadRight(vernie); 62 | await vernie.sleep(1000); 63 | const leftDistance = lastDistance; 64 | await turnHeadRight(vernie); 65 | await turnRight(vernie); 66 | await turnHeadLeft(vernie); 67 | await vernie.sleep(1000); 68 | const forwardDistance = lastDistance; 69 | return resolve({ leftDistance, forwardDistance, rightDistance }); 70 | }); 71 | } 72 | 73 | 74 | poweredUP.on("discover", async (vernie) => { // Wait to discover Vernie 75 | 76 | if (vernie instanceof PoweredUP.BoostMoveHub) { 77 | 78 | let mode = Modes.ROAMING; 79 | 80 | await vernie.connect(); 81 | console.log("Connected to Vernie!"); 82 | vernie.setLEDColor(PoweredUP.Consts.Color.BLUE); 83 | await vernie.sleep(1000); 84 | vernie.setMotorSpeed("AB", 50); // Start moving! 85 | 86 | vernie.on("distance", async (port, distance) => { 87 | if (distance < 150 && mode === Modes.ROAMING) { // If we're roaming around and we detect an object in front of us, stop and scan 88 | 89 | mode = Modes.AVOIDING; 90 | vernie.setMotorSpeed("AB", 0); 91 | await vernie.sleep(1000); 92 | 93 | const { leftDistance, forwardDistance, rightDistance } = await scan(vernie); 94 | 95 | if (rightDistance >= forwardDistance && rightDistance >= leftDistance && rightDistance > 200) { 96 | // Go right 97 | await turnHeadRight(vernie); 98 | await turnRight(vernie); 99 | await turnHeadLeft(vernie); 100 | await vernie.sleep(1000); 101 | await vernie.setMotorSpeed("AB", 50); 102 | } else if (leftDistance >= forwardDistance && leftDistance >= rightDistance && leftDistance > 200) { 103 | // Go left 104 | await turnHeadLeft(vernie); 105 | await turnLeft(vernie); 106 | await turnHeadRight(vernie); 107 | await vernie.sleep(1000); 108 | await vernie.setMotorSpeed("AB", 50); 109 | } else { 110 | // Turn around 111 | await turnHeadRight(vernie); 112 | await turnRight(vernie, 2); 113 | await turnHeadLeft(vernie); 114 | await vernie.sleep(1000); 115 | await vernie.setMotorSpeed("AB", 50); 116 | } 117 | 118 | mode = Modes.ROAMING; 119 | 120 | } 121 | }); 122 | 123 | setInterval(async () => { 124 | if (vernie.current > 20 && mode === Modes.ROAMING) { // If current spikes, we've likely stalled on something. Reverse and scan. 125 | 126 | mode = Modes.AVOIDING; 127 | await vernie.sleep(1000); 128 | await vernie.setMotorSpeed("AB", -30, 1500); // Reverse 129 | await vernie.sleep(1000); 130 | 131 | const { leftDistance, forwardDistance, rightDistance } = await scan(vernie); 132 | 133 | if (rightDistance >= forwardDistance && rightDistance >= leftDistance && rightDistance > 200) { 134 | // Go right 135 | await turnHeadRight(vernie); 136 | await turnRight(vernie); 137 | await turnHeadLeft(vernie); 138 | await vernie.sleep(1000); 139 | await vernie.setMotorSpeed("AB", 50); 140 | } else if (leftDistance >= forwardDistance && leftDistance >= rightDistance && leftDistance > 200) { 141 | // Go left 142 | await turnHeadLeft(vernie); 143 | await turnLeft(vernie); 144 | await turnHeadRight(vernie); 145 | await vernie.sleep(1000); 146 | await vernie.setMotorSpeed("AB", 50); 147 | } else { 148 | // Turn around 149 | await turnHeadRight(vernie); 150 | await turnRight(vernie, 2); 151 | await turnHeadLeft(vernie); 152 | await vernie.sleep(1000); 153 | await vernie.setMotorSpeed("AB", 50); 154 | } 155 | 156 | mode = Modes.ROAMING; 157 | 158 | } 159 | }, 2000); 160 | 161 | } 162 | 163 | }); -------------------------------------------------------------------------------- /examples/web_bluetooth.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana; 3 | font-size: 12px; 4 | } 5 | 6 | div { 7 | margin-bottom: 10px; 8 | } 9 | 10 | div#current_color { 11 | position: absolute; 12 | top: 61px; 13 | left: 120px; 14 | } 15 | 16 | div#color { 17 | border: 1px solid #666666; 18 | width: 20px; 19 | height: 20px; 20 | position: absolute; 21 | top: -4px; 22 | left: 92px; 23 | } 24 | 25 | table td { 26 | width: 150px; 27 | padding: 5px; 28 | border-radius: 3px; 29 | } 30 | 31 | table td.disconnect_btn { 32 | width: 60px; 33 | } 34 | 35 | table td.disconnect_btn a { 36 | color: #000000; 37 | } 38 | 39 | table thead.headings td { 40 | background-color: #666666; 41 | color: #ffffff; 42 | } 43 | 44 | table tr td { 45 | background-color: #cccccc; 46 | } 47 | 48 | a.button { 49 | padding: 5px; 50 | border-radius: 3px; 51 | background-color: #666666; 52 | color: #ffffff; 53 | } 54 | 55 | div#console { 56 | padding: 5px; 57 | border-radius: 3px; 58 | background-color: #333333; 59 | color: #ffffff; 60 | width: 700px; 61 | height: 500px; 62 | overflow: scroll; 63 | font-family: "Courier New", Courier; 64 | } -------------------------------------------------------------------------------- /examples/web_bluetooth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Bluetooth node-poweredup Example 6 | 7 | 8 | 9 | 97 | 98 | 99 | 100 |

Web Bluetooth node-poweredup Example

101 |
102 | Add new Hub 103 |
104 |
105 | Current Color:
 
106 |
107 |
108 | 109 | 110 |
NameType
111 |
112 |
113 | 114 | 115 | -------------------------------------------------------------------------------- /noble-mac.d.ts: -------------------------------------------------------------------------------- 1 | declare module "noble-mac" { 2 | export * from "@abandonware/noble"; 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-poweredup", 3 | "version": "8.0.6", 4 | "description": "A Javascript module to interface with LEGO Powered Up components.", 5 | "homepage": "https://github.com/nathankellenicki/node-poweredup/", 6 | "main": "dist/node/index-node.js", 7 | "types": "dist/node/index-node.d.ts", 8 | "scripts": { 9 | "build:node": "tsc", 10 | "build:browser": "webpack --mode=production", 11 | "build:all": "npm run build:node && npm run build:browser", 12 | "lint": "tslint -c tslint.json \"./src/**/*.ts\"", 13 | "build": "npm run lint && npm run build:all", 14 | "docs": "jsdoc -c .jsdoc.json", 15 | "all": "npm run build && npm run docs", 16 | "prepublishOnly": "npm run lint && npm run build:node" 17 | }, 18 | "author": "Nathan Kellenicki ", 19 | "license": "MIT", 20 | "dependencies": { 21 | "@abandonware/noble": "1.9.2-24", 22 | "compare-versions": "^6.1.0", 23 | "debug": "^4.3.4" 24 | }, 25 | "devDependencies": { 26 | "@types/debug": "4.1.12", 27 | "@types/node": "^20.12.7", 28 | "@types/web-bluetooth": "0.0.20", 29 | "buffer": "^6.0.3", 30 | "jsdoc": "^4.0.2", 31 | "jsdoc-to-markdown": "^8.0.1", 32 | "ts-loader": "^9.5.1", 33 | "tslint": "^6.1.3", 34 | "typescript": "^5.4.5", 35 | "webpack": "^5.91.0", 36 | "webpack-cli": "^5.1.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/color.ts: -------------------------------------------------------------------------------- 1 | import * as Consts from "./consts"; 2 | 3 | export class Color { 4 | 5 | private _color: number; 6 | private _brightness: number = 100; 7 | 8 | constructor (color: number, brightness?: number) { 9 | this._color = color; 10 | this._brightness = brightness || 100; 11 | } 12 | 13 | public toValue () { 14 | if (this._color === Consts.Color.NONE) { 15 | return this._color; 16 | } 17 | return this._color + (Math.round(this._brightness / 10) << 4); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/devices/absolutemotor.ts: -------------------------------------------------------------------------------- 1 | import { TachoMotor } from "./tachomotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | import { mapSpeed, normalizeAngle, roundAngleToNearest90 } from "../utils"; 7 | 8 | /** 9 | * @class AbsoluteMotor 10 | * @extends TachoMotor 11 | */ 12 | export class AbsoluteMotor extends TachoMotor { 13 | 14 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { 15 | super(hub, portId, Object.assign({}, modeMap, ModeMap), type); 16 | } 17 | 18 | public receive (message: Buffer) { 19 | const mode = this._mode; 20 | 21 | switch (mode) { 22 | case Mode.ABSOLUTE: 23 | const angle = normalizeAngle(message.readInt16LE(this.isWeDo2SmartHub ? 2 : 4)); 24 | /** 25 | * Emits when a the motors absolute position is changed. 26 | * @event AbsoluteMotor#absolute 27 | * @type {object} 28 | * @param {number} absolute 29 | */ 30 | this.notify("absolute", { angle }); 31 | break; 32 | default: 33 | super.receive(message); 34 | break; 35 | } 36 | } 37 | 38 | /** 39 | * Rotate a motor by a given angle. 40 | * @method AbsoluteMotor#gotoAngle 41 | * @param {number} angle Absolute position the motor should go to (degrees from 0). 42 | * @param {number} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. 43 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 44 | */ 45 | public gotoAngle (angle: [number, number] | number, speed: number = 100) { 46 | if (!this.isVirtualPort && angle instanceof Array) { 47 | throw new Error("Only virtual ports can accept multiple positions"); 48 | } 49 | if (this.isWeDo2SmartHub) { 50 | throw new Error("Absolute positioning is not available on the WeDo 2.0 Smart Hub"); 51 | } 52 | this.cancelEventTimer(); 53 | return new Promise((resolve) => { 54 | if (speed === undefined || speed === null) { 55 | speed = 100; 56 | } 57 | let message; 58 | if (angle instanceof Array) { 59 | message = Buffer.from([0x81, this.portId, 0x11, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, mapSpeed(speed), this._maxPower, this._brakeStyle, this.useProfile()]); 60 | message.writeInt32LE(normalizeAngle(angle[0]), 4); 61 | message.writeInt32LE(normalizeAngle(angle[1]), 8); 62 | } else { 63 | message = Buffer.from([0x81, this.portId, 0x11, 0x0d, 0x00, 0x00, 0x00, 0x00, mapSpeed(speed), this._maxPower, this._brakeStyle, this.useProfile()]); 64 | message.writeInt32LE(normalizeAngle(angle), 4); 65 | } 66 | this.send(message); 67 | this._finishedCallbacks.push(() => { 68 | return resolve(); 69 | }); 70 | }); 71 | } 72 | 73 | 74 | /** 75 | * Rotate motor to real zero position. 76 | * 77 | * Real zero is marked on Technic angular motors (SPIKE Prime). It is also available on Technic linear motors (Control+) but is unmarked. 78 | * @method AbsoluteMotor#gotoRealZero 79 | * @param {number} [speed=100] Speed between 1 - 100. Note that this will always take the shortest path to zero. 80 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 81 | */ 82 | public gotoRealZero (speed: number = 100) { 83 | return new Promise((resolve) => { 84 | const oldMode = this.mode; 85 | let calibrated = false; 86 | this.on("absolute", async ({ angle }) => { 87 | if (!calibrated) { 88 | calibrated = true; 89 | if (angle < 0) { 90 | angle = Math.abs(angle); 91 | } else { 92 | speed = -speed; 93 | } 94 | await this.rotateByDegrees(angle, speed); 95 | if (oldMode) { 96 | this.subscribe(oldMode); 97 | } 98 | return resolve(); 99 | } 100 | }); 101 | this.requestUpdate(); 102 | }); 103 | } 104 | 105 | 106 | /** 107 | * Reset zero to current position 108 | * @method AbsoluteMotor#resetZero 109 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 110 | */ 111 | public resetZero () { 112 | return new Promise((resolve) => { 113 | const data = Buffer.from([0x81, this.portId, 0x11, 0x51, 0x02, 0x00, 0x00, 0x00, 0x00]); 114 | this.send(data); 115 | return resolve(); 116 | }); 117 | } 118 | 119 | 120 | } 121 | 122 | export enum Mode { 123 | ROTATION = 0x02, 124 | ABSOLUTE = 0x03 125 | } 126 | 127 | export const ModeMap: {[event: string]: number} = { 128 | "rotate": Mode.ROTATION, 129 | "absolute": Mode.ABSOLUTE 130 | }; 131 | -------------------------------------------------------------------------------- /src/devices/basicmotor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | import { calculateRamp, mapSpeed } from "../utils"; 8 | 9 | /** 10 | * @class BasicMotor 11 | * @extends Device 12 | */ 13 | export class BasicMotor extends Device { 14 | 15 | 16 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { 17 | super(hub, portId, modeMap, type); 18 | } 19 | 20 | 21 | /** 22 | * Set the motor power. 23 | * @method BasicMotor#setPower 24 | * @param {number} power For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. 25 | * @returns {Promise} Resolved upon successful issuance of the command. 26 | */ 27 | public setPower (power: number, interrupt: boolean = true) { 28 | if (interrupt) { 29 | this.cancelEventTimer(); 30 | } 31 | return this.writeDirect(0x00, Buffer.from([mapSpeed(power)])); 32 | } 33 | 34 | 35 | /** 36 | * Ramp the motor power. 37 | * @method BasicMotor#rampPower 38 | * @param {number} fromPower For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. 39 | * @param {number} toPower For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. 40 | * @param {number} time How long the ramp should last (in milliseconds). 41 | * @returns {Promise} Resolved upon successful completion of command. 42 | */ 43 | public rampPower (fromPower: number, toPower: number, time: number) { 44 | this.cancelEventTimer(); 45 | return new Promise((resolve) => { 46 | calculateRamp(this, fromPower, toPower, time) 47 | .on("changePower", (power) => { 48 | this.setPower(power, false); 49 | }) 50 | .on("finished", resolve); 51 | }); 52 | } 53 | 54 | 55 | /** 56 | * Stop the motor. 57 | * @method BasicMotor#stop 58 | * @returns {Promise} Resolved upon successful issuance of the command. 59 | */ 60 | public stop () { 61 | this.cancelEventTimer(); 62 | return this.setPower(0); 63 | } 64 | 65 | 66 | /** 67 | * Brake the motor. 68 | * @method BasicMotor#brake 69 | * @returns {Promise} Resolved upon successful issuance of the command. 70 | */ 71 | public brake () { 72 | this.cancelEventTimer(); 73 | return this.setPower(Consts.BrakingStyle.BRAKE); 74 | } 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/devices/currentsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class CurrentSensor 9 | * @extends Device 10 | */ 11 | export class CurrentSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.CURRENT_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this.mode; 19 | 20 | switch (mode) { 21 | case Mode.CURRENT: 22 | if (this.isWeDo2SmartHub) { 23 | const current = message.readInt16LE(2) / 1000; 24 | this.notify("current", { current }); 25 | } else { 26 | let maxCurrentValue = MaxCurrentValue[this.hub.type]; 27 | if (maxCurrentValue === undefined) { 28 | maxCurrentValue = MaxCurrentValue[Consts.HubType.UNKNOWN]; 29 | } 30 | let maxCurrentRaw = MaxCurrentRaw[this.hub.type]; 31 | if (maxCurrentRaw === undefined) { 32 | maxCurrentRaw = MaxCurrentRaw[Consts.HubType.UNKNOWN]; 33 | } 34 | const current = message.readUInt16LE(4) * maxCurrentValue / maxCurrentRaw; 35 | /** 36 | * Emits when a current change is detected. 37 | * @event CurrentSensor#current 38 | * @type {object} 39 | * @param {number} current 40 | */ 41 | this.notify("current", { current }); 42 | } 43 | break; 44 | } 45 | } 46 | 47 | } 48 | 49 | export enum Mode { 50 | CURRENT = 0x00 51 | } 52 | 53 | export const ModeMap: {[event: string]: number} = { 54 | "current": Mode.CURRENT 55 | }; 56 | 57 | const MaxCurrentValue: {[hubType: number]: number} = { 58 | [Consts.HubType.UNKNOWN]: 2444, 59 | [Consts.HubType.TECHNIC_MEDIUM_HUB]: 4175, 60 | }; 61 | 62 | const MaxCurrentRaw: {[hubType: number]: number} = { 63 | [Consts.HubType.UNKNOWN]: 4095, 64 | }; 65 | -------------------------------------------------------------------------------- /src/devices/device.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class Device 9 | * @extends EventEmitter 10 | */ 11 | export class Device extends EventEmitter { 12 | 13 | public autoSubscribe: boolean = true; 14 | public values: {[event: string]: any} = {}; 15 | 16 | protected _mode: number | undefined; 17 | protected _busy: boolean = false; 18 | protected _finishedCallbacks: (() => void)[] = []; 19 | 20 | private _hub: IDeviceInterface; 21 | private _portId: number; 22 | private _connected: boolean = true; 23 | private _type: Consts.DeviceType; 24 | private _modeMap: {[event: string]: number} = {}; 25 | 26 | private _isWeDo2SmartHub: boolean; 27 | private _isVirtualPort: boolean = false; 28 | private _eventTimer: NodeJS.Timeout | null = null; 29 | 30 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { 31 | super(); 32 | this._hub = hub; 33 | this._portId = portId; 34 | this._type = type; 35 | this._modeMap = modeMap; 36 | this._isWeDo2SmartHub = (this.hub.type === Consts.HubType.WEDO2_SMART_HUB); 37 | this._isVirtualPort = this.hub.isPortVirtual(portId); 38 | 39 | const eventAttachListener = (event: string) => { 40 | if (event === "detach") { 41 | return; 42 | } 43 | if (this.autoSubscribe) { 44 | if (this._modeMap[event] !== undefined) { 45 | this.subscribe(this._modeMap[event]); 46 | } 47 | } 48 | }; 49 | 50 | const deviceDetachListener = (device: Device) => { 51 | if (device.portId === this.portId) { 52 | this._connected = false; 53 | this.hub.removeListener("detach", deviceDetachListener); 54 | this.emit("detach"); 55 | } 56 | }; 57 | 58 | for (const event in this._modeMap) { 59 | if (this.hub.listenerCount(event) > 0) { 60 | eventAttachListener(event); 61 | } 62 | } 63 | 64 | this.hub.on("newListener", eventAttachListener); 65 | this.on("newListener", eventAttachListener); 66 | this.hub.on("detach", deviceDetachListener); 67 | } 68 | 69 | /** 70 | * @readonly 71 | * @property {boolean} connected Check if the device is still attached. 72 | */ 73 | public get connected () { 74 | return this._connected; 75 | } 76 | 77 | /** 78 | * @readonly 79 | * @property {Hub} hub The Hub the device is attached to. 80 | */ 81 | public get hub () { 82 | return this._hub; 83 | } 84 | 85 | public get portId () { 86 | return this._portId; 87 | } 88 | 89 | /** 90 | * @readonly 91 | * @property {string} portName The port the device is attached to. 92 | */ 93 | public get portName () { 94 | return this.hub.getPortNameForPortId(this.portId); 95 | } 96 | 97 | /** 98 | * @readonly 99 | * @property {number} type The type of the device 100 | */ 101 | public get type () { 102 | return this._type; 103 | } 104 | 105 | public get typeName () { 106 | return Consts.DeviceTypeNames[this.type]; 107 | } 108 | 109 | /** 110 | * @readonly 111 | * @property {number} mode The mode the device is currently in 112 | */ 113 | public get mode () { 114 | return this._mode; 115 | } 116 | 117 | protected get isWeDo2SmartHub () { 118 | return this._isWeDo2SmartHub; 119 | } 120 | 121 | /** 122 | * @readonly 123 | * @property {boolean} isVirtualPort Is this device attached to a virtual port (ie. a combined device) 124 | */ 125 | protected get isVirtualPort () { 126 | return this._isVirtualPort; 127 | } 128 | 129 | public writeDirect (mode: number, data: Buffer) { 130 | if (this.isWeDo2SmartHub) { 131 | return this.send(Buffer.concat([Buffer.from([this.portId, 0x01, 0x02]), data]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); 132 | } else { 133 | return this.send(Buffer.concat([Buffer.from([0x81, this.portId, 0x11, 0x51, mode]), data]), Consts.BLECharacteristic.LPF2_ALL); 134 | } 135 | } 136 | 137 | public send (data: Buffer, characteristic: string = Consts.BLECharacteristic.LPF2_ALL) { 138 | this._ensureConnected(); 139 | return this.hub.send(data, characteristic); 140 | } 141 | 142 | public subscribe (mode: number) { 143 | this._ensureConnected(); 144 | if (mode !== this._mode) { 145 | this._mode = mode; 146 | this.hub.subscribe(this.portId, this.type, mode); 147 | } 148 | } 149 | 150 | public unsubscribe (mode: number) { 151 | this._ensureConnected(); 152 | } 153 | 154 | public receive (message: Buffer) { 155 | this.notify("receive", { message }); 156 | } 157 | 158 | public notify (event: string, values: any) { 159 | this.values[event] = values; 160 | this.emit(event, values); 161 | if (this.hub.listenerCount(event) > 0) { 162 | this.hub.emit(event, this, values); 163 | } 164 | } 165 | 166 | public requestUpdate () { 167 | this.send(Buffer.from([0x21, this.portId, 0x00])); 168 | } 169 | 170 | public finish (message: number) { 171 | if((message & 0x10) === 0x10) return; // "busy/full" 172 | this._busy = (message & 0x01) === 0x01; 173 | while(this._finishedCallbacks.length > Number(this._busy)) { 174 | const callback = this._finishedCallbacks.shift(); 175 | if(callback) { 176 | callback(); 177 | } 178 | } 179 | } 180 | 181 | public setEventTimer (timer: NodeJS.Timeout) { 182 | this._eventTimer = timer; 183 | } 184 | 185 | public cancelEventTimer () { 186 | if (this._eventTimer) { 187 | clearTimeout(this._eventTimer); 188 | this._eventTimer = null; 189 | } 190 | } 191 | 192 | private _ensureConnected () { 193 | if (!this.connected) { 194 | throw new Error("Device is not connected"); 195 | } 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/devices/duplotrainbasecolorsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | import { parseColor } from "../utils"; 7 | 8 | /** 9 | * @class DuploTrainBaseColorSensor 10 | * @extends Device 11 | */ 12 | export class DuploTrainBaseColorSensor extends Device { 13 | 14 | constructor (hub: IDeviceInterface, portId: number) { 15 | super(hub, portId, ModeMap, Consts.DeviceType.DUPLO_TRAIN_BASE_COLOR_SENSOR); 16 | } 17 | 18 | public receive (message: Buffer) { 19 | const mode = this._mode; 20 | 21 | switch (mode) { 22 | case Mode.INTENSITY: 23 | const intensity = message[4]; 24 | 25 | /** 26 | * Emits when intensity of the color/light changes. 27 | * @event DuploTrainBaseColorSensor#intensity 28 | * @type {object} 29 | * @param {number} intensity 30 | */ 31 | this.notify("intensity", { intensity }); 32 | break; 33 | 34 | case Mode.COLOR: 35 | if (message[4] <= 10) { 36 | const color = parseColor(message[4]); 37 | 38 | /** 39 | * Emits when a color sensor is activated. 40 | * @event DuploTrainBaseColorSensor#color 41 | * @type {object} 42 | * @param {Color} color 43 | */ 44 | this.notify("color", { color }); 45 | } 46 | break; 47 | 48 | case Mode.REFLECTIVITY: 49 | const reflect = message[4]; 50 | 51 | /** 52 | * Emits when the light reflectivity changes. 53 | * @event DuploTrainBaseColorSensor#reflect 54 | * @type {object} 55 | * @param {number} reflect Percentage, from 0 to 100. 56 | */ 57 | this.notify("reflect", { reflect }); 58 | break; 59 | 60 | case Mode.RGB: 61 | const red = Math.floor(message.readUInt16LE(4) / 4); 62 | const green = Math.floor(message.readUInt16LE(6) / 4); 63 | const blue = Math.floor(message.readUInt16LE(8) / 4); 64 | 65 | /** 66 | * Emits when the light reflectivity changes. 67 | * @event DuploTrainBaseColorSensor#rgb 68 | * @type {object} 69 | * @param {number} red 70 | * @param {number} green 71 | * @param {number} blue 72 | */ 73 | this.notify("rgb", { red, green, blue }); 74 | break; 75 | 76 | } 77 | } 78 | 79 | } 80 | 81 | export enum Mode { 82 | INTENSITY = 0x00, 83 | COLOR = 0x01, 84 | REFLECTIVITY = 0x02, 85 | RGB = 0x03 86 | } 87 | 88 | export const ModeMap: {[event: string]: number} = { 89 | "intensity": Mode.INTENSITY, 90 | "color": Mode.COLOR, 91 | "reflect": Mode.REFLECTIVITY, 92 | "rgb": Mode.RGB 93 | }; 94 | -------------------------------------------------------------------------------- /src/devices/duplotrainbasemotor.ts: -------------------------------------------------------------------------------- 1 | import { BasicMotor } from "./basicmotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class DuploTrainBaseMotor 9 | * @extends BasicMotor 10 | */ 11 | export class DuploTrainBaseMotor extends BasicMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.DUPLO_TRAIN_BASE_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/duplotrainbasespeaker.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class DuploTrainBaseSpeaker 9 | * @extends Device 10 | */ 11 | export class DuploTrainBaseSpeaker extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.DUPLO_TRAIN_BASE_SPEAKER); 15 | } 16 | 17 | /** 18 | * Play a built-in train sound. 19 | * @method DuploTrainBaseSpeaker#playSound 20 | * @param {DuploTrainBaseSound} sound 21 | * @returns {Promise} Resolved upon successful issuance of the command. 22 | */ 23 | public playSound (sound: Consts.DuploTrainBaseSound) { 24 | return new Promise((resolve) => { 25 | this.subscribe(Mode.SOUND); 26 | this.writeDirect(0x01, Buffer.from([sound])); 27 | return resolve(); 28 | }); 29 | } 30 | 31 | /** 32 | * Play a built-in system tone. 33 | * @method DuploTrainBaseSpeaker#playTone 34 | * @param {number} tone 35 | * @returns {Promise} Resolved upon successful issuance of the command. 36 | */ 37 | public playTone (tone: number) { 38 | this.subscribe(Mode.TONE); 39 | this.writeDirect(0x02, Buffer.from([tone])); 40 | } 41 | 42 | } 43 | 44 | export enum Mode { 45 | SOUND = 0x01, 46 | TONE = 0x02 47 | } 48 | -------------------------------------------------------------------------------- /src/devices/duplotrainbasespeedometer.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class DuploTraniBaseSpeedometer 9 | * @extends Device 10 | */ 11 | export class DuploTrainBaseSpeedometer extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.DUPLO_TRAIN_BASE_SPEEDOMETER); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.SPEED: 22 | const speed = message.readInt16LE(4); 23 | 24 | /** 25 | * Emits on a speed change. 26 | * @event DuploTrainBaseSpeedometer#speed 27 | * @type {object} 28 | * @param {number} speed 29 | */ 30 | this.notify("speed", { speed }); 31 | break; 32 | 33 | } 34 | } 35 | 36 | } 37 | 38 | export enum Mode { 39 | SPEED = 0x00 40 | } 41 | 42 | export const ModeMap: {[event: string]: number} = { 43 | "speed": Mode.SPEED 44 | }; 45 | -------------------------------------------------------------------------------- /src/devices/hubled.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class HubLED 9 | * @extends Device 10 | */ 11 | export class HubLED extends Device { 12 | 13 | 14 | constructor (hub: IDeviceInterface, portId: number) { 15 | super(hub, portId, {}, Consts.DeviceType.HUB_LED); 16 | } 17 | 18 | 19 | /** 20 | * Set the color of the LED on the Hub via a color value. 21 | * @method HubLED#setColor 22 | * @param {Color} color 23 | * @returns {Promise} Resolved upon successful issuance of the command. 24 | */ 25 | public setColor (color: number | boolean) { 26 | return new Promise((resolve) => { 27 | if (typeof color === "boolean") { 28 | color = 0; 29 | } 30 | if (this.isWeDo2SmartHub) { 31 | this.send(Buffer.from([0x06, 0x17, 0x01, 0x01]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); 32 | this.send(Buffer.from([0x06, 0x04, 0x01, color]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); 33 | } else { 34 | this.subscribe(Mode.COLOR); 35 | this.writeDirect(0x00, Buffer.from([color])); 36 | } 37 | return resolve(); 38 | }); 39 | } 40 | 41 | 42 | /** 43 | * Set the color of the LED on the Hub via RGB values. 44 | * @method HubLED#setRGB 45 | * @param {number} red 46 | * @param {number} green 47 | * @param {number} blue 48 | * @returns {Promise} Resolved upon successful issuance of the command. 49 | */ 50 | public setRGB (red: number, green: number, blue: number) { 51 | return new Promise((resolve) => { 52 | if (this.isWeDo2SmartHub) { 53 | this.send(Buffer.from([0x06, 0x17, 0x01, 0x02]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); 54 | this.send(Buffer.from([0x06, 0x04, 0x03, red, green, blue]), Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); 55 | } else { 56 | this.subscribe(Mode.RGB); 57 | this.writeDirect(0x01, Buffer.from([red, green, blue])); 58 | } 59 | return resolve(); 60 | }); 61 | } 62 | 63 | 64 | } 65 | 66 | export enum Mode { 67 | COLOR = 0x00, 68 | RGB = 0x01 69 | } 70 | -------------------------------------------------------------------------------- /src/devices/light.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | import { calculateRamp } from "../utils"; 7 | 8 | /** 9 | * @class Light 10 | * @extends Device 11 | */ 12 | export class Light extends Device { 13 | 14 | 15 | constructor (hub: IDeviceInterface, portId: number) { 16 | super(hub, portId, {}, Consts.DeviceType.LIGHT); 17 | } 18 | 19 | 20 | /** 21 | * Set the light brightness. 22 | * @method Light#setBrightness 23 | * @param {number} brightness Brightness value between 0-100 (0 is off) 24 | * @returns {Promise} Resolved upon successful completion of command. 25 | */ 26 | public setBrightness (brightness: number, interrupt: boolean = true) { 27 | if (interrupt) { 28 | this.cancelEventTimer(); 29 | } 30 | return new Promise((resolve) => { 31 | this.writeDirect(0x00, Buffer.from([brightness])); 32 | return resolve(); 33 | }); 34 | } 35 | 36 | 37 | /** 38 | * Ramp the light brightness. 39 | * @method Light#rampBrightness 40 | * @param {number} fromBrightness Brightness value between 0-100 (0 is off) 41 | * @param {number} toBrightness Brightness value between 0-100 (0 is off) 42 | * @param {number} time How long the ramp should last (in milliseconds). 43 | * @returns {Promise} Resolved upon successful completion of command. 44 | */ 45 | public rampBrightness (fromBrightness: number, toBrightness: number, time: number) { 46 | this.cancelEventTimer(); 47 | return new Promise((resolve) => { 48 | calculateRamp(this, fromBrightness, toBrightness, time) 49 | .on("changePower", (power) => { 50 | this.setBrightness(power, false); 51 | }) 52 | .on("finished", resolve); 53 | }); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/devices/marioaccelerometer.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MarioAccelerometer 9 | * @extends Device 10 | */ 11 | export class MarioAccelerometer extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.MARIO_ACCELEROMETER); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.ACCEL: 22 | /** 23 | * Emits when accelerometer detects movement. Measured in mG. 24 | * @event MarioAccelerometer#accel 25 | * @type {object} 26 | * @param {number} x 27 | * @param {number} y 28 | * @param {number} z 29 | */ 30 | const x = message[4]; 31 | const y = message[5]; 32 | const z = message[6]; 33 | this.notify("accel", { x, y, z }); 34 | break; 35 | case Mode.GEST: 36 | /** 37 | * Emits when a gesture is detected 38 | * @event MarioAccelerometer#gest 39 | * @type {object} 40 | * @param {number} gesture 41 | */ 42 | const gesture = message[4]; 43 | this.notify("gesture", { gesture }); 44 | break; 45 | } 46 | } 47 | 48 | } 49 | 50 | export enum Mode { 51 | ACCEL = 0x00, 52 | GEST = 0x01, 53 | } 54 | 55 | export const ModeMap: {[event: string]: number} = { 56 | "accel": Mode.ACCEL, 57 | "gesture": Mode.GEST, 58 | }; 59 | -------------------------------------------------------------------------------- /src/devices/mariobarcodesensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MarioBarcodeSensor 9 | * @extends Device 10 | */ 11 | export class MarioBarcodeSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.MARIO_BARCODE_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.BARCODE: 22 | /** 23 | * Emits when the barcode sensor sees a barcode. 24 | * @event MarioBarcodeSensor#barcode 25 | * @type {object} 26 | * @param {number} id 27 | */ 28 | const barcode = message.readUInt16LE(4); 29 | const color = message.readUInt16LE(6); 30 | if (color === 0xffff) { 31 | // This is a barcode 32 | this.notify("barcode", { barcode }); 33 | } else if (barcode === 0xffff) { 34 | // This is a color 35 | this.notify("barcode", { color }); 36 | } 37 | break; 38 | case Mode.RGB: 39 | /** 40 | * Emits when the barcode sensor sees a RGB color. 41 | * @event MarioBarcodeSensor#rgb 42 | * @type {object} 43 | * @param {number} r 44 | * @param {number} g 45 | * @param {number} b 46 | */ 47 | const r = message[4]; 48 | const g = message[5]; 49 | const b = message[6]; 50 | this.notify("rgb", { r, g, b }); 51 | break; 52 | 53 | } 54 | } 55 | 56 | } 57 | 58 | export enum Mode { 59 | BARCODE = 0x00, 60 | RGB = 0x01, 61 | } 62 | 63 | export const ModeMap: {[event: string]: number} = { 64 | "barcode": Mode.BARCODE, 65 | "rgb": Mode.RGB, 66 | }; 67 | -------------------------------------------------------------------------------- /src/devices/mariopantssensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MarioPantsSensor 9 | * @extends Device 10 | */ 11 | export class MarioPantsSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.MARIO_PANTS_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.PANTS: 22 | /** 23 | * Emits when the user changes the pants on Mario. 24 | * @event MarioPantsSensor#pants 25 | * @type {object} 26 | * @param {number} pants 27 | */ 28 | const pants = message[4]; 29 | this.notify("pants", { pants }); 30 | break; 31 | } 32 | } 33 | 34 | } 35 | 36 | export enum Mode { 37 | PANTS = 0x00, 38 | } 39 | 40 | export const ModeMap: {[event: string]: number} = { 41 | "pants": Mode.PANTS, 42 | }; 43 | -------------------------------------------------------------------------------- /src/devices/mediumlinearmotor.ts: -------------------------------------------------------------------------------- 1 | import { TachoMotor } from "./tachomotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MediumLinearMotor 9 | * @extends TachoMotor 10 | */ 11 | export class MediumLinearMotor extends TachoMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.MEDIUM_LINEAR_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/motionsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MotionSensor 9 | * @extends Device 10 | */ 11 | export class MotionSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.MOTION_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.DISTANCE: 22 | let distance = message[this.isWeDo2SmartHub ? 2 : 4]; 23 | if (message[this.isWeDo2SmartHub ? 3 : 5] === 1) { 24 | distance = distance + 255; 25 | } 26 | distance *= 10; 27 | /** 28 | * Emits when a distance sensor is activated. 29 | * @event MotionSensor#distance 30 | * @type {object} 31 | * @param {number} distance Distance, in millimeters. 32 | */ 33 | this.notify("distance", { distance }); 34 | break; 35 | } 36 | } 37 | 38 | } 39 | 40 | export enum Mode { 41 | DISTANCE = 0x00 42 | } 43 | 44 | export const ModeMap: {[event: string]: number} = { 45 | "distance": Mode.DISTANCE 46 | }; 47 | -------------------------------------------------------------------------------- /src/devices/movehubmediumlinearmotor.ts: -------------------------------------------------------------------------------- 1 | import { TachoMotor } from "./tachomotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MoveHubMediumLinearMotor 9 | * @extends TachoMotor 10 | */ 11 | export class MoveHubMediumLinearMotor extends TachoMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.MOVE_HUB_MEDIUM_LINEAR_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/movehubtiltsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class MoveHubTiltSensor 9 | * @extends Device 10 | */ 11 | export class MoveHubTiltSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.MOVE_HUB_TILT_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.TILT: 22 | /** 23 | * Emits when a tilt sensor is activated. 24 | * @event MoveHubTiltSensor#tilt 25 | * @type {object} 26 | * @param {number} x 27 | * @param {number} y 28 | */ 29 | const x = -message.readInt8(4); 30 | const y = message.readInt8(5); 31 | this.notify("tilt", { x, y }); 32 | break; 33 | } 34 | } 35 | 36 | } 37 | 38 | export enum Mode { 39 | TILT = 0x00 40 | } 41 | 42 | export const ModeMap: {[event: string]: number} = { 43 | "tilt": Mode.TILT 44 | }; 45 | -------------------------------------------------------------------------------- /src/devices/piezobuzzer.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class PiezoBuzzer 9 | * @extends Device 10 | */ 11 | export class PiezoBuzzer extends Device { 12 | 13 | 14 | constructor (hub: IDeviceInterface, portId: number) { 15 | super(hub, portId, {}, Consts.DeviceType.PIEZO_BUZZER); 16 | } 17 | 18 | 19 | /** 20 | * Play a tone on the Hub's in-built buzzer 21 | * @method PiezoBuzzer#playTone 22 | * @param {number} frequency 23 | * @param {number} time How long the tone should play for (in milliseconds). 24 | * @returns {Promise} Resolved upon successful completion of command (ie. once the tone has finished playing). 25 | */ 26 | public playTone (frequency: number, time: number) { 27 | return new Promise((resolve) => { 28 | const data = Buffer.from([0x05, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00]); 29 | data.writeUInt16LE(frequency, 3); 30 | data.writeUInt16LE(time, 5); 31 | this.send(data, Consts.BLECharacteristic.WEDO2_MOTOR_VALUE_WRITE); 32 | global.setTimeout(resolve, time); 33 | }); 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/devices/remotecontrolbutton.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class RemoteControlButton 9 | * @extends Device 10 | */ 11 | export class RemoteControlButton extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.REMOTE_CONTROL_BUTTON); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.BUTTON_EVENTS: 22 | /** 23 | * Emits when a button on the remote is pressed or released. 24 | * @event RemoteControlButton#remoteButton 25 | * @type {object} 26 | * @param {number} event 27 | */ 28 | const event = message[4]; 29 | this.notify("remoteButton", { event }); 30 | break; 31 | } 32 | } 33 | 34 | } 35 | 36 | export enum Mode { 37 | BUTTON_EVENTS = 0x00 38 | } 39 | 40 | export const ModeMap: {[event: string]: number} = { 41 | "remoteButton": Mode.BUTTON_EVENTS 42 | }; 43 | 44 | export const ButtonState: {[state: string]: number} = { 45 | "UP": 0x01, 46 | "DOWN": 0xff, 47 | "STOP": 0x7f, 48 | "RELEASED": 0x00, 49 | }; 50 | -------------------------------------------------------------------------------- /src/devices/simplemediumlinearmotor.ts: -------------------------------------------------------------------------------- 1 | import { BasicMotor } from "./basicmotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class SimpleMediumLinearMotor 9 | * @extends Device 10 | */ 11 | export class SimpleMediumLinearMotor extends BasicMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.SIMPLE_MEDIUM_LINEAR_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/tachomotor.ts: -------------------------------------------------------------------------------- 1 | import { BasicMotor } from "./basicmotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | import { mapSpeed } from "../utils"; 7 | 8 | /** 9 | * @class TachoMotor 10 | * @extends BasicMotor 11 | */ 12 | export class TachoMotor extends BasicMotor { 13 | 14 | protected _brakeStyle: Consts.BrakingStyle = Consts.BrakingStyle.BRAKE; 15 | protected _maxPower: number = 100; 16 | public useAccelerationProfile: boolean = true; 17 | public useDecelerationProfile: boolean = true; 18 | 19 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.UNKNOWN) { 20 | super(hub, portId, Object.assign({}, modeMap, ModeMap), type); 21 | } 22 | 23 | public receive (message: Buffer) { 24 | const mode = this._mode; 25 | 26 | switch (mode) { 27 | case Mode.ROTATION: 28 | const degrees = message.readInt32LE(this.isWeDo2SmartHub ? 2 : 4); 29 | /** 30 | * Emits when a rotation sensor is activated. 31 | * @event TachoMotor#rotate 32 | * @type {object} 33 | * @param {number} rotation 34 | */ 35 | this.notify("rotate", { degrees }); 36 | break; 37 | } 38 | } 39 | 40 | 41 | /** 42 | * Set the braking style of the motor. 43 | * 44 | * Note: This applies to setSpeed, rotateByDegrees, and gotoAngle. 45 | * @method TachoMotor#setBrakingStyle 46 | * @param {number} style Either BRAKE or HOLD 47 | */ 48 | public setBrakingStyle (style: Consts.BrakingStyle) { 49 | this._brakeStyle = style; 50 | } 51 | 52 | 53 | /** 54 | * Set the max power of the motor. 55 | * 56 | * Note: This applies to setSpeed, rotateByDegrees, and gotoAngle. 57 | * @method TachoMotor#setMaxPower 58 | * @param {number} style Either BRAKE or HOLD 59 | */ 60 | public setMaxPower (maxPower: number) { 61 | this._maxPower = maxPower; 62 | } 63 | 64 | 65 | /** 66 | * Set the global acceleration time 67 | * @method TachoMotor#setAccelerationTime 68 | * @param {number} time How long acceleration should last (in milliseconds). 69 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 70 | */ 71 | public setAccelerationTime (time: number, profile: number = 0x00) { 72 | const message = Buffer.from([0x81, this.portId, 0x11, 0x05, 0x00, 0x00, profile]); 73 | message.writeUInt16LE(time, 4); 74 | this.send(message); 75 | } 76 | 77 | 78 | /** 79 | * Set the global deceleration time 80 | * @method TachoMotor#setDecelerationTime 81 | * @param {number} time How long deceleration should last (in milliseconds). 82 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 83 | */ 84 | public setDecelerationTime (time: number, profile: number = 0x00) { 85 | const message = Buffer.from([0x81, this.portId, 0x11, 0x06, 0x00, 0x00, profile]); 86 | message.writeUInt16LE(time, 4); 87 | this.send(message); 88 | } 89 | 90 | 91 | /** 92 | * Set the motor speed. 93 | * @method TachoMotor#setSpeed 94 | * @param {number} speed For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. Stop is 0. 95 | * @param {number} time How long the motor should run for (in milliseconds). 96 | * @returns {Promise} Resolved upon successful issuance of the command. 97 | */ 98 | public setSpeed (speed: [number, number] | number, time: number | undefined) { 99 | if (!this.isVirtualPort && speed instanceof Array) { 100 | throw new Error("Only virtual ports can accept multiple speeds"); 101 | } 102 | if (this.isWeDo2SmartHub) { 103 | throw new Error("Motor speed is not available on the WeDo 2.0 Smart Hub"); 104 | } 105 | this.cancelEventTimer(); 106 | return new Promise((resolve) => { 107 | if (speed === undefined || speed === null) { 108 | speed = 100; 109 | } 110 | let message; 111 | if (time !== undefined) { 112 | if (speed instanceof Array) { 113 | message = Buffer.from([0x81, this.portId, 0x11, 0x0a, 0x00, 0x00, mapSpeed(speed[0]), mapSpeed(speed[1]), this._maxPower, this._brakeStyle, this.useProfile()]); 114 | } else { 115 | message = Buffer.from([0x81, this.portId, 0x11, 0x09, 0x00, 0x00, mapSpeed(speed), this._maxPower, this._brakeStyle, this.useProfile()]); 116 | } 117 | message.writeUInt16LE(time, 4); 118 | } else { 119 | if (speed instanceof Array) { 120 | message = Buffer.from([0x81, this.portId, 0x11, 0x08, mapSpeed(speed[0]), mapSpeed(speed[1]), this._maxPower, this.useProfile()]); 121 | } else { 122 | message = Buffer.from([0x81, this.portId, 0x11, 0x07, mapSpeed(speed), this._maxPower, this.useProfile()]); 123 | } 124 | } 125 | this.send(message); 126 | this._finishedCallbacks.push(() => { 127 | return resolve(); 128 | }); 129 | }); 130 | } 131 | 132 | /** 133 | * Rotate a motor by a given amount of degrees. 134 | * @method TachoMotor#rotateByDegrees 135 | * @param {number} degrees How much the motor should be rotated (in degrees). 136 | * @param {number} [speed=100] For forward, a value between 1 - 100 should be set. For reverse, a value between -1 to -100. 137 | * @returns {Promise} Resolved upon successful completion of command (ie. once the motor is finished). 138 | */ 139 | public rotateByDegrees (degrees: number, speed: [number, number] | number) { 140 | if (!this.isVirtualPort && speed instanceof Array) { 141 | throw new Error("Only virtual ports can accept multiple speeds"); 142 | } 143 | if (this.isWeDo2SmartHub) { 144 | throw new Error("Rotation is not available on the WeDo 2.0 Smart Hub"); 145 | } 146 | this.cancelEventTimer(); 147 | return new Promise((resolve) => { 148 | if (speed === undefined || speed === null) { 149 | speed = 100; 150 | } 151 | let message; 152 | if (speed instanceof Array) { 153 | message = Buffer.from([0x81, this.portId, 0x11, 0x0c, 0x00, 0x00, 0x00, 0x00, mapSpeed(speed[0]), mapSpeed(speed[1]), this._maxPower, this._brakeStyle, this.useProfile()]); 154 | } else { 155 | message = Buffer.from([0x81, this.portId, 0x11, 0x0b, 0x00, 0x00, 0x00, 0x00, mapSpeed(speed), this._maxPower, this._brakeStyle, this.useProfile()]); 156 | } 157 | message.writeUInt32LE(degrees, 4); 158 | this.send(message); 159 | this._finishedCallbacks.push(() => { 160 | return resolve(); 161 | }); 162 | }); 163 | } 164 | 165 | 166 | protected useProfile () { 167 | let value = 0x00; 168 | if (this.useAccelerationProfile) { 169 | value += 0x01; 170 | } 171 | if (this.useDecelerationProfile) { 172 | value += 0x02; 173 | } 174 | return value; 175 | } 176 | 177 | 178 | } 179 | 180 | export enum Mode { 181 | ROTATION = 0x02 182 | } 183 | 184 | export const ModeMap: {[event: string]: number} = { 185 | "rotate": Mode.ROTATION 186 | }; 187 | -------------------------------------------------------------------------------- /src/devices/technic3x3colorlightmatrix.ts: -------------------------------------------------------------------------------- 1 | import { Color } from "../color"; 2 | 3 | import { Device } from "./device"; 4 | 5 | import { IDeviceInterface } from "../interfaces"; 6 | 7 | import * as Consts from "../consts"; 8 | 9 | /** 10 | * @class Technic3x3ColorLightMatrix 11 | * @extends Device 12 | */ 13 | export class Technic3x3ColorLightMatrix extends Device { 14 | 15 | 16 | constructor (hub: IDeviceInterface, portId: number) { 17 | super(hub, portId, {}, Consts.DeviceType.TECHNIC_3X3_COLOR_LIGHT_MATRIX); 18 | } 19 | 20 | 21 | /** 22 | * Set the LED matrix, one color per LED 23 | * @method Technic3x3ColorLightMatrix#setMatrix 24 | * @param {Color[] | Color} colors Array of 9 colors, 9 Color objects, or a single color 25 | * @returns {Promise} Resolved upon successful issuance of the command. 26 | */ 27 | public setMatrix (colors: number[] | number) { 28 | return new Promise((resolve) => { 29 | this.subscribe(Mode.PIX_0); 30 | const colorArray = new Array(9); 31 | for (let i = 0; i < colorArray.length; i++) { 32 | if (typeof colors === 'number') { 33 | // @ts-ignore 34 | colorArray[i] = colors + (10 << 4); 35 | } 36 | // @ts-ignore 37 | if (colors[i] instanceof Color) { 38 | // @ts-ignore 39 | colorArray[i] = colors[i].toValue(); 40 | } 41 | // @ts-ignore 42 | if (colors[i] === Consts.Color.NONE) { 43 | colorArray[i] = Consts.Color.NONE; 44 | } 45 | // @ts-ignore 46 | if (colors[i] <= 10) { 47 | // @ts-ignore 48 | colorArray[i] = colors[i] + (10 << 4); // If a raw color value, set it to max brightness (10) 49 | } 50 | } 51 | this.writeDirect(Mode.PIX_0, Buffer.from(colorArray)); 52 | return resolve(); 53 | }); 54 | } 55 | 56 | 57 | } 58 | 59 | export enum Mode { 60 | LEV_0 = 0x00, 61 | COL_0 = 0x01, 62 | PIX_0 = 0x02, 63 | TRANS = 0x03 64 | } 65 | -------------------------------------------------------------------------------- /src/devices/techniccolorsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | import { parseColor } from "../utils"; 7 | 8 | /** 9 | * @class TechnicColorSensor 10 | * @extends Device 11 | */ 12 | export class TechnicColorSensor extends Device { 13 | 14 | constructor (hub: IDeviceInterface, portId: number) { 15 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_COLOR_SENSOR); 16 | } 17 | 18 | public receive (message: Buffer) { 19 | const mode = this._mode; 20 | 21 | switch (mode) { 22 | case Mode.COLOR: 23 | if (message[4] <= 10) { 24 | const color = parseColor(message[4]); 25 | 26 | /** 27 | * Emits when a color sensor is activated. 28 | * @event TechnicColorSensor#color 29 | * @type {object} 30 | * @param {Color} color 31 | */ 32 | this.notify("color", { color }); 33 | } 34 | break; 35 | 36 | case Mode.REFLECTIVITY: 37 | const reflect = message[4]; 38 | 39 | /** 40 | * Emits when the light reflectivity changes. 41 | * @event TechnicColorSensor#reflect 42 | * @type {object} 43 | * @param {number} reflect Percentage, from 0 to 100. 44 | */ 45 | this.notify("reflect", { reflect }); 46 | break; 47 | 48 | case Mode.AMBIENT_LIGHT: 49 | const ambient = message[4]; 50 | 51 | /** 52 | * Emits when the ambient light changes. 53 | * @event TechnicColorSensor#ambient 54 | * @type {object} 55 | * @param {number} ambient Percentage, from 0 to 100. 56 | */ 57 | this.notify("ambient", { ambient }); 58 | break; 59 | } 60 | } 61 | 62 | /** 63 | * Set the brightness (or turn on/off) of the lights around the sensor. 64 | * @method TechnicColorSensor#setBrightness 65 | * @param {number} firstSegment First light segment. 0-100 brightness. 66 | * @param {number} secondSegment Second light segment. 0-100 brightness. 67 | * @param {number} thirdSegment Third light segment. 0-100 brightness. 68 | * @returns {Promise} Resolved upon successful issuance of the command. 69 | */ 70 | public setBrightness (firstSegment: number, secondSegment: number, thirdSegment: number) { 71 | this.writeDirect(0x03, Buffer.from([firstSegment, secondSegment, thirdSegment])); 72 | } 73 | 74 | } 75 | 76 | export enum Mode { 77 | COLOR = 0x00, 78 | REFLECTIVITY = 0x01, 79 | AMBIENT_LIGHT = 0x02 80 | } 81 | 82 | export const ModeMap: {[event: string]: number} = { 83 | "color": Mode.COLOR, 84 | "reflect": Mode.REFLECTIVITY, 85 | "ambient": Mode.AMBIENT_LIGHT 86 | }; 87 | -------------------------------------------------------------------------------- /src/devices/technicdistancesensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicDistanceSensor 9 | * @extends Device 10 | */ 11 | export class TechnicDistanceSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_DISTANCE_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.DISTANCE: 22 | const distance = message.readUInt16LE(4); 23 | 24 | /** 25 | * Emits when the detected distance changes (Slow sampling covers 40mm to 2500mm). 26 | * @event TechnicDistanceSensor#distance 27 | * @type {object} 28 | * @param {number} distance Distance, from 40 to 2500mm 29 | */ 30 | this.notify("distance", { distance }); 31 | break; 32 | 33 | case Mode.FAST_DISTANCE: 34 | const fastDistance = message.readUInt16LE(4); 35 | 36 | /** 37 | * Emits when the detected distance changes (Fast sampling covers 50mm to 320mm). 38 | * @event TechnicDistanceSensor#fastDistance 39 | * @type {object} 40 | * @param {number} fastDistance Distance, from 50 to 320mm 41 | */ 42 | this.notify("fastDistance", { fastDistance }); 43 | break; 44 | } 45 | } 46 | 47 | /** 48 | * Set the brightness (or turn on/off) of the lights around the eyes. 49 | * @method TechnicDistanceSensor#setBrightness 50 | * @param {number} topLeft Top left quadrant (above left eye). 0-100 brightness. 51 | * @param {number} bottomLeft Bottom left quadrant (below left eye). 0-100 brightness. 52 | * @param {number} topRight Top right quadrant (above right eye). 0-100 brightness. 53 | * @param {number} bottomRight Bottom right quadrant (below right eye). 0-100 brightness. 54 | * @returns {Promise} Resolved upon successful issuance of the command. 55 | */ 56 | public setBrightness (topLeft: number, bottomLeft: number, topRight: number, bottomRight: number) { 57 | this.writeDirect(0x05, Buffer.from([topLeft, topRight, bottomLeft, bottomRight])); 58 | } 59 | 60 | } 61 | 62 | export enum Mode { 63 | DISTANCE = 0x00, 64 | FAST_DISTANCE = 0x01 65 | } 66 | 67 | export const ModeMap: {[event: string]: number} = { 68 | "distance": Mode.DISTANCE, 69 | "fastDistance": Mode.FAST_DISTANCE 70 | }; 71 | -------------------------------------------------------------------------------- /src/devices/technicforcesensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicForceSensor 9 | * @extends Device 10 | */ 11 | export class TechnicForceSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_FORCE_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.FORCE: 22 | const force = message[this.isWeDo2SmartHub ? 2 : 4] / 10; 23 | 24 | /** 25 | * Emits when force is applied. 26 | * @event TechnicForceSensor#force 27 | * @type {object} 28 | * @param {number} force Force, in newtons (0-10). 29 | */ 30 | this.notify("force", { force }); 31 | break; 32 | 33 | case Mode.TOUCHED: 34 | const touched = message[4] ? true : false; 35 | 36 | /** 37 | * Emits when the sensor is touched. 38 | * @event TechnicForceSensor#touch 39 | * @type {object} 40 | * @param {boolean} touch Touched on/off (boolean). 41 | */ 42 | this.notify("touched", { touched }); 43 | break; 44 | 45 | case Mode.TAPPED: 46 | const tapped = message[4]; 47 | 48 | /** 49 | * Emits when the sensor is tapped. 50 | * @event TechnicForceSensor#tapped 51 | * @type {object} 52 | * @param {number} tapped How hard the sensor was tapped, from 0-3. 53 | */ 54 | this.notify("tapped", { tapped }); 55 | break; 56 | } 57 | } 58 | 59 | } 60 | 61 | export enum Mode { 62 | FORCE = 0x00, 63 | TOUCHED = 0x01, 64 | TAPPED = 0x02 65 | } 66 | 67 | export const ModeMap: {[event: string]: number} = { 68 | "force": Mode.FORCE, 69 | "touched": Mode.TOUCHED, 70 | "tapped": Mode.TAPPED 71 | }; 72 | -------------------------------------------------------------------------------- /src/devices/techniclargeangularmotor.ts: -------------------------------------------------------------------------------- 1 | import { AbsoluteMotor } from "./absolutemotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicLargeAngularMotor 9 | * @extends AbsoluteMotor 10 | */ 11 | export class TechnicLargeAngularMotor extends AbsoluteMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.TECHNIC_LARGE_ANGULAR_MOTOR) { 14 | super(hub, portId, {}, type); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/techniclargelinearmotor.ts: -------------------------------------------------------------------------------- 1 | import { AbsoluteMotor } from "./absolutemotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicLargeLinearMotor 9 | * @extends AbsoluteMotor 10 | */ 11 | export class TechnicLargeLinearMotor extends AbsoluteMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.TECHNIC_LARGE_LINEAR_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/technicmediumangularmotor.ts: -------------------------------------------------------------------------------- 1 | import { AbsoluteMotor } from "./absolutemotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicMediumAngularMotor 9 | * @extends AbsoluteMotor 10 | */ 11 | export class TechnicMediumAngularMotor extends AbsoluteMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.TECHNIC_MEDIUM_ANGULAR_MOTOR) { 14 | super(hub, portId, {}, type); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/technicmediumhubaccelerometersensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicMediumHubAccelerometerSensor 9 | * @extends Device 10 | */ 11 | export class TechnicMediumHubAccelerometerSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_ACCELEROMETER); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.ACCEL: 22 | /** 23 | * Emits when accelerometer detects movement. Measured in mG. 24 | * @event TechnicMediumHubAccelerometerSensor#accel 25 | * @type {object} 26 | * @param {number} x 27 | * @param {number} y 28 | * @param {number} z 29 | */ 30 | const x = Math.round(message.readInt16LE(4) / 4.096); 31 | const y = Math.round(message.readInt16LE(6) / 4.096); 32 | const z = Math.round(message.readInt16LE(8) / 4.096); 33 | this.notify("accel", { x, y, z }); 34 | break; 35 | } 36 | } 37 | 38 | } 39 | 40 | export enum Mode { 41 | ACCEL = 0x00 42 | } 43 | 44 | export const ModeMap: {[event: string]: number} = { 45 | "accel": Mode.ACCEL 46 | }; 47 | -------------------------------------------------------------------------------- /src/devices/technicmediumhubgyrosensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicMediumHubGyroSensor 9 | * @extends Device 10 | */ 11 | export class TechnicMediumHubGyroSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_GYRO_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.GYRO: 22 | /** 23 | * Emits when gyroscope detects movement. Measured in DPS - degrees per second. 24 | * @event TechnicMediumHubGyroSensor#gyro 25 | * @type {object} 26 | * @param {number} x 27 | * @param {number} y 28 | * @param {number} z 29 | */ 30 | const x = Math.round(message.readInt16LE(4) * 7 / 400); 31 | const y = Math.round(message.readInt16LE(6) * 7 / 400); 32 | const z = Math.round(message.readInt16LE(8) * 7 / 400); 33 | this.notify("gyro", { x, y, z }); 34 | break; 35 | } 36 | } 37 | 38 | } 39 | 40 | export enum Mode { 41 | GYRO = 0x00 42 | } 43 | 44 | export const ModeMap: {[event: string]: number} = { 45 | "gyro": Mode.GYRO 46 | }; 47 | -------------------------------------------------------------------------------- /src/devices/technicmediumhubtiltsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicMediumHubTiltSensor 9 | * @extends Device 10 | */ 11 | export class TechnicMediumHubTiltSensor extends Device { 12 | 13 | protected _impactThreshold: number = 10; // guess of default value 14 | protected _impactHoldoff: number = 10; // guess of default value 15 | 16 | constructor (hub: IDeviceInterface, portId: number) { 17 | super(hub, portId, ModeMap, Consts.DeviceType.TECHNIC_MEDIUM_HUB_TILT_SENSOR); 18 | } 19 | 20 | public receive (message: Buffer) { 21 | const mode = this._mode; 22 | 23 | switch (mode) { 24 | case Mode.TILT: 25 | /** 26 | * Emits when a tilt sensor is activated. 27 | * 28 | * @event TechnicMediumHubTiltSensor#tilt 29 | * @type {object} 30 | * @param {number} x 31 | * @param {number} y 32 | * @param {number} z 33 | */ 34 | let z = -message.readInt16LE(4); 35 | const y = message.readInt16LE(6); 36 | const x = message.readInt16LE(8); 37 | 38 | // workaround for calibration problem or bug in technicMediumHub firmware 1.1.00.0000 39 | if(y === 90 || y === -90) { 40 | z = Math.sign(y)*(z + 180); 41 | if(z > 180) z -= 360; 42 | if(z < -180) z += 360; 43 | } 44 | 45 | this.notify("tilt", { x, y, z }); 46 | break; 47 | 48 | case Mode.IMPACT_COUNT: 49 | if (message.length !== 8) { 50 | // if mode of device has not changed to this._mode yet 51 | break; 52 | } 53 | const count = message.readUInt32LE(4); 54 | /** 55 | * Emits when proper acceleration is above threshold (e.g. on impact when being thrown to the ground). 56 | * @event TechnicMediumHubTiltSensor#impactCount 57 | * @type {object} 58 | * @param {number} number of impact events. 59 | */ 60 | this.notify("impactCount", { count }); 61 | break; 62 | } 63 | } 64 | 65 | /** 66 | * Set the impact count value. 67 | * @method TechnicMediumHubTiltSensor#setImpactCount 68 | * @param {count} impact count between 0 and 2^32 69 | * @returns {Promise} Resolved upon successful issuance of the command. 70 | */ 71 | public setImpactCount (count: number) { 72 | return new Promise((resolve) => { 73 | const payload = Buffer.alloc(4); 74 | payload.writeUInt32LE(count % 2**32); 75 | // no need to subscribe, can be set in different mode 76 | this.writeDirect(0x01, payload); 77 | return resolve(); 78 | }); 79 | } 80 | 81 | /** 82 | * Set the impact threshold. 83 | * @method TechnicMediumHubTiltSensor#setImpactThreshold 84 | * @param {threshold} value between 1 and 127 85 | * @returns {Promise} Resolved upon successful issuance of the command. 86 | */ 87 | public setImpactThreshold (threshold: number) { 88 | this._impactThreshold = threshold; 89 | return new Promise((resolve) => { 90 | this.writeDirect(0x02, Buffer.from([this._impactThreshold, this._impactHoldoff])); 91 | return resolve(); 92 | }); 93 | } 94 | 95 | /** 96 | * Set the impact holdoff time. 97 | * @method TechnicMediumHubTiltSensor#setImpactHoldoff 98 | * @param {holdoff} value between 1 and 127 99 | * @returns {Promise} Resolved upon successful issuance of the command. 100 | */ 101 | public setImpactHoldoff (holdoff: number) { 102 | this._impactHoldoff = holdoff; 103 | return new Promise((resolve) => { 104 | this.writeDirect(0x02, Buffer.from([this._impactThreshold, this._impactHoldoff])); 105 | return resolve(); 106 | }); 107 | } 108 | } 109 | 110 | export enum Mode { 111 | TILT = 0x00, 112 | IMPACT_COUNT = 0x01 113 | } 114 | 115 | export const ModeMap: {[event: string]: number} = { 116 | "tilt": Mode.TILT, 117 | "impactCount": Mode.IMPACT_COUNT 118 | }; 119 | -------------------------------------------------------------------------------- /src/devices/technicsmallangularmotor.ts: -------------------------------------------------------------------------------- 1 | import { AbsoluteMotor } from "./absolutemotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicSmallAngularMotor 9 | * @extends AbsoluteMotor 10 | */ 11 | export class TechnicSmallAngularMotor extends AbsoluteMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number, modeMap: {[event: string]: number} = {}, type: Consts.DeviceType = Consts.DeviceType.TECHNIC_SMALL_ANGULAR_MOTOR) { 14 | super(hub, portId, {}, type); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/technicxlargelinearmotor.ts: -------------------------------------------------------------------------------- 1 | import { AbsoluteMotor } from "./absolutemotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TechnicXLargeLinearMotor 9 | * @extends AbsoluteMotor 10 | */ 11 | export class TechnicXLargeLinearMotor extends AbsoluteMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.TECHNIC_XLARGE_LINEAR_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/tiltsensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TiltSensor 9 | * @extends Device 10 | */ 11 | export class TiltSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.TILT_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.TILT: 22 | const x = message.readInt8(this.isWeDo2SmartHub ? 2 : 4); 23 | const y = message.readInt8(this.isWeDo2SmartHub ? 3 : 5); 24 | /** 25 | * Emits when a tilt sensor is activated. 26 | * @event TiltSensor#tilt 27 | * @type {object} 28 | * @param {number} x 29 | * @param {number} y 30 | */ 31 | this.notify("tilt", { x, y }); 32 | break; 33 | } 34 | } 35 | 36 | } 37 | 38 | export enum Mode { 39 | TILT = 0x00 40 | } 41 | 42 | export const ModeMap: {[event: string]: number} = { 43 | "tilt": Mode.TILT 44 | }; 45 | -------------------------------------------------------------------------------- /src/devices/trainmotor.ts: -------------------------------------------------------------------------------- 1 | import { BasicMotor } from "./basicmotor"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class TrainMotor 9 | * @extends BasicMotor 10 | */ 11 | export class TrainMotor extends BasicMotor { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, {}, Consts.DeviceType.TRAIN_MOTOR); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/devices/voltagesensor.ts: -------------------------------------------------------------------------------- 1 | import { Device } from "./device"; 2 | 3 | import { IDeviceInterface } from "../interfaces"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | /** 8 | * @class VoltageSensor 9 | * @extends Device 10 | */ 11 | export class VoltageSensor extends Device { 12 | 13 | constructor (hub: IDeviceInterface, portId: number) { 14 | super(hub, portId, ModeMap, Consts.DeviceType.VOLTAGE_SENSOR); 15 | } 16 | 17 | public receive (message: Buffer) { 18 | const mode = this._mode; 19 | 20 | switch (mode) { 21 | case Mode.VOLTAGE: 22 | if (this.isWeDo2SmartHub) { 23 | const voltage = message.readInt16LE(2) / 40; 24 | this.notify("voltage", { voltage }); 25 | } else { 26 | let maxVoltageValue = MaxVoltageValue[this.hub.type]; 27 | if (maxVoltageValue === undefined) { 28 | maxVoltageValue = MaxVoltageValue[Consts.HubType.UNKNOWN]; 29 | } 30 | let maxVoltageRaw = MaxVoltageRaw[this.hub.type]; 31 | if (maxVoltageRaw === undefined) { 32 | maxVoltageRaw = MaxVoltageRaw[Consts.HubType.UNKNOWN]; 33 | } 34 | const voltage = message.readUInt16LE(4) * maxVoltageValue / maxVoltageRaw; 35 | /** 36 | * Emits when a voltage change is detected. 37 | * @event VoltageSensor#voltage 38 | * @type {object} 39 | * @param {number} voltage 40 | */ 41 | this.notify("voltage", { voltage }); 42 | } 43 | break; 44 | } 45 | } 46 | 47 | } 48 | 49 | export enum Mode { 50 | VOLTAGE = 0x00 51 | } 52 | 53 | export const ModeMap: {[event: string]: number} = { 54 | "voltage": Mode.VOLTAGE 55 | }; 56 | 57 | const MaxVoltageValue: {[hubType: number]: number} = { 58 | [Consts.HubType.UNKNOWN]: 9.615, 59 | [Consts.HubType.DUPLO_TRAIN_BASE]: 6.4, 60 | [Consts.HubType.REMOTE_CONTROL]: 6.4, 61 | }; 62 | 63 | const MaxVoltageRaw: {[hubType: number]: number} = { 64 | [Consts.HubType.UNKNOWN]: 3893, 65 | [Consts.HubType.DUPLO_TRAIN_BASE]: 3047, 66 | [Consts.HubType.REMOTE_CONTROL]: 3200, 67 | [Consts.HubType.TECHNIC_MEDIUM_HUB]: 4095, 68 | }; 69 | -------------------------------------------------------------------------------- /src/hubs/duplotrainbase.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | 3 | import { LPF2Hub } from "./lpf2hub"; 4 | 5 | import * as Consts from "../consts"; 6 | 7 | import Debug = require("debug"); 8 | import { IBLEAbstraction } from "../interfaces"; 9 | const debug = Debug("duplotrainbase"); 10 | 11 | 12 | /** 13 | * The DuploTrainBase is emitted if the discovered device is a Duplo Train Base. 14 | * @class DuploTrainBase 15 | * @extends LPF2Hub 16 | * @extends BaseHub 17 | */ 18 | export class DuploTrainBase extends LPF2Hub { 19 | 20 | 21 | public static IsDuploTrainBase (peripheral: Peripheral) { 22 | return ( 23 | peripheral.advertisement && 24 | peripheral.advertisement.serviceUuids && 25 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 26 | peripheral.advertisement.manufacturerData && 27 | peripheral.advertisement.manufacturerData.length > 3 && 28 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.DUPLO_TRAIN_BASE_ID 29 | ); 30 | } 31 | 32 | 33 | constructor (device: IBLEAbstraction) { 34 | super(device, PortMap, Consts.HubType.DUPLO_TRAIN_BASE); 35 | debug("Discovered Duplo Train Base"); 36 | } 37 | 38 | 39 | public async connect () { 40 | debug("Connecting to Duplo Train Base"); 41 | await super.connect(); 42 | debug("Connect completed"); 43 | } 44 | 45 | 46 | } 47 | 48 | export const PortMap: {[portName: string]: number} = { 49 | "MOTOR": 0, 50 | "COLOR": 18, 51 | "SPEEDOMETER": 19 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /src/hubs/hub.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | import { compareVersions } from "compare-versions"; 3 | 4 | import { IBLEAbstraction } from "../interfaces"; 5 | 6 | import { LPF2Hub } from "./lpf2hub"; 7 | 8 | import * as Consts from "../consts"; 9 | 10 | import Debug = require("debug"); 11 | const debug = Debug("hub"); 12 | 13 | 14 | /** 15 | * The Hub is emitted if the discovered device is a Hub. 16 | * @class Hub 17 | * @extends LPF2Hub 18 | * @extends BaseHub 19 | */ 20 | export class Hub extends LPF2Hub { 21 | 22 | 23 | public static IsHub (peripheral: Peripheral) { 24 | return ( 25 | peripheral.advertisement && 26 | peripheral.advertisement.serviceUuids && 27 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 28 | peripheral.advertisement.manufacturerData && 29 | peripheral.advertisement.manufacturerData.length > 3 && 30 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.HUB_ID 31 | ); 32 | } 33 | 34 | protected _currentPort = 0x3b; 35 | 36 | constructor (device: IBLEAbstraction) { 37 | super(device, PortMap, Consts.HubType.HUB); 38 | debug("Discovered Powered UP Hub"); 39 | } 40 | 41 | 42 | public async connect () { 43 | debug("Connecting to Powered UP Hub"); 44 | await super.connect(); 45 | debug("Connect completed"); 46 | } 47 | 48 | 49 | protected _checkFirmware (version: string) { 50 | if (compareVersions("1.1.00.0004", version) === 1) { 51 | throw new Error(`Your Powered Up Hub's (${this.name}) firmware is out of date and unsupported by this library. Please update it via the official Powered Up app.`); 52 | } 53 | } 54 | 55 | 56 | } 57 | 58 | export const PortMap: {[portName: string]: number} = { 59 | "A": 0, 60 | "B": 1, 61 | "HUB_LED": 50, 62 | "CURRENT_SENSOR": 59, 63 | "VOLTAGE_SENSOR": 60 64 | }; 65 | -------------------------------------------------------------------------------- /src/hubs/mario.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | import compareVersion from "compare-versions"; 3 | 4 | import { IBLEAbstraction } from "../interfaces"; 5 | 6 | import { LPF2Hub } from "./lpf2hub"; 7 | 8 | import * as Consts from "../consts"; 9 | 10 | import Debug = require("debug"); 11 | const debug = Debug("movehub"); 12 | 13 | 14 | /** 15 | * Mario is emitted if the discovered device is a LEGO Super Mario brick. 16 | * @class Mario 17 | * @extends LPF2Hub 18 | * @extends BaseHub 19 | */ 20 | export class Mario extends LPF2Hub { 21 | 22 | 23 | public static IsMario (peripheral: Peripheral) { 24 | return ( 25 | peripheral.advertisement && 26 | peripheral.advertisement.serviceUuids && 27 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 28 | peripheral.advertisement.manufacturerData && 29 | peripheral.advertisement.manufacturerData.length > 3 && 30 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.MARIO_ID 31 | ); 32 | } 33 | 34 | constructor (device: IBLEAbstraction) { 35 | super(device, PortMap, Consts.HubType.MARIO); 36 | debug("Discovered Mario"); 37 | } 38 | 39 | 40 | public async connect () { 41 | debug("Connecting to Mario"); 42 | await super.connect(); 43 | debug("Connect completed"); 44 | } 45 | 46 | 47 | } 48 | 49 | export const PortMap: {[portName: string]: number} = { 50 | }; 51 | -------------------------------------------------------------------------------- /src/hubs/movehub.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | import { compareVersions } from "compare-versions"; 3 | 4 | import { IBLEAbstraction } from "../interfaces"; 5 | 6 | import { LPF2Hub } from "./lpf2hub"; 7 | 8 | import * as Consts from "../consts"; 9 | 10 | import Debug = require("debug"); 11 | const debug = Debug("movehub"); 12 | 13 | 14 | /** 15 | * The MoveHub is emitted if the discovered device is a Move Hub. 16 | * @class MoveHub 17 | * @extends LPF2Hub 18 | * @extends BaseHub 19 | */ 20 | export class MoveHub extends LPF2Hub { 21 | 22 | 23 | public static IsMoveHub (peripheral: Peripheral) { 24 | return ( 25 | peripheral.advertisement && 26 | peripheral.advertisement.serviceUuids && 27 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 28 | peripheral.advertisement.manufacturerData && 29 | peripheral.advertisement.manufacturerData.length > 3 && 30 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.MOVE_HUB_ID 31 | ); 32 | } 33 | 34 | constructor (device: IBLEAbstraction) { 35 | super(device, PortMap, Consts.HubType.MOVE_HUB); 36 | debug("Discovered Move Hub"); 37 | } 38 | 39 | 40 | public async connect () { 41 | debug("Connecting to Move Hub"); 42 | await super.connect(); 43 | debug("Connect completed"); 44 | } 45 | 46 | 47 | protected _checkFirmware (version: string) { 48 | if (compareVersions("2.0.00.0017", version) === 1) { 49 | throw new Error(`Your Move Hub's (${this.name}) firmware is out of date and unsupported by this library. Please update it via the official Powered Up app.`); 50 | } 51 | } 52 | 53 | 54 | } 55 | 56 | export const PortMap: {[portName: string]: number} = { 57 | "A": 0, 58 | "B": 1, 59 | "C": 2, 60 | "D": 3, 61 | "HUB_LED": 50, 62 | "TILT_SENSOR": 58, 63 | "CURRENT_SENSOR": 59, 64 | "VOLTAGE_SENSOR": 60 65 | }; 66 | -------------------------------------------------------------------------------- /src/hubs/remotecontrol.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | 3 | import { IBLEAbstraction } from "../interfaces"; 4 | 5 | import { LPF2Hub } from "./lpf2hub"; 6 | 7 | import * as Consts from "../consts"; 8 | 9 | import Debug = require("debug"); 10 | const debug = Debug("remotecontrol"); 11 | 12 | 13 | /** 14 | * The RemoteControl is emitted if the discovered device is a Remote Control. 15 | * @class RemoteControl 16 | * @extends LPF2Hub 17 | * @extends BaseHub 18 | */ 19 | export class RemoteControl extends LPF2Hub { 20 | 21 | 22 | public static IsRemoteControl (peripheral: Peripheral) { 23 | return ( 24 | peripheral.advertisement && 25 | peripheral.advertisement.serviceUuids && 26 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 27 | peripheral.advertisement.manufacturerData && 28 | peripheral.advertisement.manufacturerData.length > 3 && 29 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.REMOTE_CONTROL_ID 30 | ); 31 | } 32 | 33 | 34 | constructor (device: IBLEAbstraction) { 35 | super(device, PortMap, Consts.HubType.REMOTE_CONTROL); 36 | debug("Discovered Powered UP Remote"); 37 | } 38 | 39 | 40 | public async connect () { 41 | debug("Connecting to Powered UP Remote"); 42 | await super.connect(); 43 | debug("Connect completed"); 44 | } 45 | 46 | 47 | } 48 | 49 | export const PortMap: {[portName: string]: number} = { 50 | "LEFT": 0, 51 | "RIGHT": 1, 52 | "HUB_LED": 52, 53 | "VOLTAGE_SENSOR": 59, 54 | "REMOTE_CONTROL_RSSI": 60 55 | }; 56 | -------------------------------------------------------------------------------- /src/hubs/technicmediumhub.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | 3 | import { IBLEAbstraction } from "../interfaces"; 4 | 5 | import { LPF2Hub } from "./lpf2hub"; 6 | 7 | import * as Consts from "../consts"; 8 | 9 | import Debug = require("debug"); 10 | const debug = Debug("technicmediumhub"); 11 | 12 | 13 | /** 14 | * The TechnicMediumHub is emitted if the discovered device is a Technic Medium Hub. 15 | * @class TechnicMediumHub 16 | * @extends LPF2Hub 17 | * @extends BaseHub 18 | */ 19 | export class TechnicMediumHub extends LPF2Hub { 20 | 21 | 22 | public static IsTechnicMediumHub (peripheral: Peripheral) { 23 | return ( 24 | peripheral.advertisement && 25 | peripheral.advertisement.serviceUuids && 26 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 27 | peripheral.advertisement.manufacturerData && 28 | peripheral.advertisement.manufacturerData.length > 3 && 29 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.TECHNIC_MEDIUM_HUB_ID 30 | ); 31 | } 32 | 33 | constructor (device: IBLEAbstraction) { 34 | super(device, PortMap, Consts.HubType.TECHNIC_MEDIUM_HUB); 35 | debug("Discovered Control+ Hub"); 36 | } 37 | 38 | 39 | public async connect () { 40 | debug("Connecting to Control+ Hub"); 41 | await super.connect(); 42 | debug("Connect completed"); 43 | } 44 | 45 | 46 | } 47 | 48 | export const PortMap: {[portName: string]: number} = { 49 | "A": 0, 50 | "B": 1, 51 | "C": 2, 52 | "D": 3, 53 | "HUB_LED": 50, 54 | "CURRENT_SENSOR": 59, 55 | "VOLTAGE_SENSOR": 60, 56 | "ACCELEROMETER": 97, 57 | "GYRO_SENSOR": 98, 58 | "TILT_SENSOR": 99 59 | }; 60 | -------------------------------------------------------------------------------- /src/hubs/technicsmallhub.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | import compareVersion from "compare-versions"; 3 | 4 | import { IBLEAbstraction } from "../interfaces"; 5 | 6 | import { LPF2Hub } from "./lpf2hub"; 7 | 8 | import * as Consts from "../consts"; 9 | 10 | import Debug = require("debug"); 11 | const debug = Debug("hub"); 12 | 13 | 14 | /** 15 | * The TechnicSmallHub is emitted if the discovered device is a Technic Small Hub. 16 | * @class Hub 17 | * @extends LPF2Hub 18 | * @extends BaseHub 19 | */ 20 | export class TechnicSmallHub extends LPF2Hub { 21 | 22 | 23 | public static IsTechnicSmallHub (peripheral: Peripheral) { 24 | return ( 25 | peripheral.advertisement && 26 | peripheral.advertisement.serviceUuids && 27 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.LPF2_HUB.replace(/-/g, "")) >= 0 && 28 | peripheral.advertisement.manufacturerData && 29 | peripheral.advertisement.manufacturerData.length > 3 && 30 | peripheral.advertisement.manufacturerData[3] === Consts.BLEManufacturerData.TECHNIC_SMALL_HUB_ID 31 | ); 32 | } 33 | 34 | protected _currentPort = 0x3b; 35 | 36 | constructor (device: IBLEAbstraction) { 37 | super(device, PortMap, Consts.HubType.TECHNIC_SMALL_HUB); 38 | debug("Discovered Spike Essential Hub"); 39 | } 40 | 41 | 42 | public async connect () { 43 | debug("Connecting to Spike Essential Hub"); 44 | await super.connect(); 45 | debug("Connect completed"); 46 | } 47 | 48 | 49 | } 50 | 51 | export const PortMap: {[portName: string]: number} = { 52 | "A": 0, 53 | "B": 1, 54 | "HUB_LED": 49, 55 | "CURRENT_SENSOR": 59, 56 | "VOLTAGE_SENSOR": 60, 57 | "ACCELEROMETER": 97, 58 | "GYRO_SENSOR": 98, 59 | "TILT_SENSOR": 99 60 | }; 61 | -------------------------------------------------------------------------------- /src/hubs/wedo2smarthub.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | 3 | import { IBLEAbstraction } from "../interfaces"; 4 | 5 | import { BaseHub } from "./basehub"; 6 | 7 | import * as Consts from "../consts"; 8 | 9 | import { isWebBluetooth } from "../utils"; 10 | 11 | import Debug = require("debug"); 12 | import { HubLED } from "../devices/hubled"; 13 | const debug = Debug("wedo2smarthub"); 14 | 15 | 16 | /** 17 | * The WeDo2SmartHub is emitted if the discovered device is a WeDo 2.0 Smart Hub. 18 | * @class WeDo2SmartHub 19 | * @extends BaseHub 20 | */ 21 | export class WeDo2SmartHub extends BaseHub { 22 | 23 | 24 | public static IsWeDo2SmartHub (peripheral: Peripheral) { 25 | return ( 26 | peripheral.advertisement && 27 | peripheral.advertisement.serviceUuids && 28 | peripheral.advertisement.serviceUuids.indexOf(Consts.BLEService.WEDO2_SMART_HUB.replace(/-/g, "")) >= 0 29 | ); 30 | } 31 | 32 | 33 | private _lastTiltX: number = 0; 34 | private _lastTiltY: number = 0; 35 | 36 | 37 | constructor (device: IBLEAbstraction) { 38 | super(device, PortMap, Consts.HubType.WEDO2_SMART_HUB); 39 | debug("Discovered WeDo 2.0 Smart Hub"); 40 | } 41 | 42 | 43 | public connect () { 44 | return new Promise(async (resolve) => { 45 | debug("Connecting to WeDo 2.0 Smart Hub"); 46 | await super.connect(); 47 | await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB); 48 | await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_2); 49 | if (!isWebBluetooth) { 50 | await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_3); 51 | await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_4); 52 | await this._bleDevice.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB_5); 53 | } else { 54 | await this._bleDevice.discoverCharacteristicsForService("battery_service"); 55 | await this._bleDevice.discoverCharacteristicsForService("device_information"); 56 | } 57 | debug("Connect completed"); 58 | this.emit("connect"); 59 | resolve(); 60 | this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_PORT_TYPE, this._parsePortMessage.bind(this)); 61 | this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_SENSOR_VALUE, this._parseSensorMessage.bind(this)); 62 | this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_BUTTON, this._parseSensorMessage.bind(this)); 63 | if (!isWebBluetooth) { 64 | this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_BATTERY, this._parseBatteryMessage.bind(this)); 65 | this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.WEDO2_BATTERY, (err, data) => { 66 | if (data) { 67 | this._parseBatteryMessage(data); 68 | } 69 | }); 70 | } else { 71 | this._bleDevice.readFromCharacteristic("00002a19-0000-1000-8000-00805f9b34fb", (err, data) => { 72 | if (data) { 73 | this._parseBatteryMessage(data); 74 | } 75 | }); 76 | this._bleDevice.subscribeToCharacteristic("00002a19-0000-1000-8000-00805f9b34fb", this._parseHighCurrentAlert.bind(this)); 77 | } 78 | this._bleDevice.subscribeToCharacteristic(Consts.BLECharacteristic.WEDO2_HIGH_CURRENT_ALERT, this._parseHighCurrentAlert.bind(this)); 79 | if (!isWebBluetooth) { 80 | this._bleDevice.readFromCharacteristic(Consts.BLECharacteristic.WEDO2_FIRMWARE_REVISION, (err, data) => { 81 | if (data) { 82 | this._parseFirmwareRevisionString(data); 83 | } 84 | }); 85 | } else { 86 | this._bleDevice.readFromCharacteristic("00002a26-0000-1000-8000-00805f9b34fb", (err, data) => { 87 | if (data) { 88 | this._parseFirmwareRevisionString(data); 89 | } 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | 96 | /** 97 | * Shutdown the Hub. 98 | * @method WeDo2SmartHub#shutdown 99 | * @returns {Promise} Resolved upon successful disconnect. 100 | */ 101 | public shutdown () { 102 | return this.send(Buffer.from([0x00]), Consts.BLECharacteristic.WEDO2_DISCONNECT); 103 | } 104 | 105 | 106 | /** 107 | * Set the name of the Hub. 108 | * @method WeDo2SmartHub#setName 109 | * @param {string} name New name of the hub (14 characters or less, ASCII only). 110 | * @returns {Promise} Resolved upon successful issuance of command. 111 | */ 112 | public setName (name: string) { 113 | if (name.length > 14) { 114 | throw new Error("Name must be 14 characters or less"); 115 | } 116 | return new Promise((resolve) => { 117 | const data = Buffer.from(name, "ascii"); 118 | // Send this twice, as sometimes the first time doesn't take 119 | this.send(data, Consts.BLECharacteristic.WEDO2_NAME_ID); 120 | this.send(data, Consts.BLECharacteristic.WEDO2_NAME_ID); 121 | this._name = name; 122 | return resolve(); 123 | }); 124 | } 125 | 126 | 127 | public send (message: Buffer, uuid: string) { 128 | if (debug.enabled) { 129 | debug(`Sent Message (${this._getCharacteristicNameFromUUID(uuid)})`, message); 130 | } 131 | return this._bleDevice.writeToCharacteristic(uuid, message); 132 | } 133 | 134 | 135 | public subscribe (portId: number, deviceType: number, mode: number) { 136 | this.send(Buffer.from([0x01, 0x02, portId, deviceType, mode, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); 137 | } 138 | 139 | 140 | public unsubscribe (portId: number, deviceType: number, mode: number) { 141 | this.send(Buffer.from([0x01, 0x02, portId, deviceType, mode, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), Consts.BLECharacteristic.WEDO2_PORT_TYPE_WRITE); 142 | } 143 | 144 | 145 | private _getCharacteristicNameFromUUID (uuid: string) { 146 | const keys = Object.keys(Consts.BLECharacteristic); 147 | for (let i = 0; i < keys.length; i++) { 148 | const key = keys[i]; 149 | if (Consts.BLECharacteristic[key as keyof typeof Consts.BLECharacteristic] === uuid) { 150 | return key; 151 | } 152 | } 153 | return "UNKNOWN"; 154 | } 155 | 156 | 157 | private _parseHighCurrentAlert (data: Buffer) { 158 | debug("Received Message (WEDO2_HIGH_CURRENT_ALERT)", data); 159 | } 160 | 161 | 162 | private _parseBatteryMessage (data: Buffer) { 163 | debug("Received Message (WEDO2_BATTERY)", data); 164 | const batteryLevel = data[0]; 165 | if (batteryLevel !== this._batteryLevel) { 166 | this._batteryLevel = batteryLevel; 167 | this.emit("batteryLevel", { batteryLevel }); 168 | } 169 | } 170 | 171 | 172 | private _parseFirmwareRevisionString (data: Buffer) { 173 | debug("Received Message (WEDO2_FIRMWARE_REVISION)", data); 174 | this._firmwareVersion = data.toString(); 175 | } 176 | 177 | 178 | private _parsePortMessage (data: Buffer) { 179 | debug("Received Message (WEDO2_PORT_TYPE)", data); 180 | 181 | const portId = data[0]; 182 | const event = data[1]; 183 | const deviceType = event ? data[3] : 0; 184 | 185 | if (event === 0x01) { 186 | const device = this._createDevice(deviceType, portId); 187 | this._attachDevice(device); 188 | } else if (event === 0x00) { 189 | const device = this._getDeviceByPortId(portId); 190 | if (device) { 191 | this._detachDevice(device); 192 | } 193 | } 194 | } 195 | 196 | 197 | private _parseSensorMessage (message: Buffer) { 198 | 199 | debug("Received Message (WEDO2_SENSOR_VALUE)", message); 200 | 201 | if (message[0] === 0x01) { 202 | /** 203 | * Emits when a button is pressed. 204 | * @event WeDo2SmartHub#button 205 | * @param {string} button 206 | * @param {ButtonState} state 207 | */ 208 | this.emit("button", { event: Consts.ButtonState.PRESSED }); 209 | return; 210 | } else if (message[0] === 0x00) { 211 | this.emit("button", { event: Consts.ButtonState.RELEASED }); 212 | return; 213 | } 214 | 215 | const portId = message[1]; 216 | const device = this._getDeviceByPortId(portId); 217 | 218 | if (device) { 219 | device.receive(message); 220 | } 221 | 222 | } 223 | 224 | 225 | } 226 | 227 | export const PortMap: {[portName: string]: number} = { 228 | "A": 1, 229 | "B": 2, 230 | "CURRENT_SENSOR": 3, 231 | "VOLTAGE_SENSOR": 4, 232 | "PIEZO_BUZZER": 5, 233 | "HUB_LED": 6 234 | }; 235 | -------------------------------------------------------------------------------- /src/index-browser.ts: -------------------------------------------------------------------------------- 1 | import * as Consts from "./consts"; 2 | 3 | import { PoweredUP } from "./poweredup-browser"; 4 | import { Color } from "./color"; 5 | 6 | import { BaseHub } from "./hubs/basehub"; 7 | import { DuploTrainBase } from "./hubs/duplotrainbase"; 8 | import { Hub } from "./hubs/hub"; 9 | import { Mario } from "./hubs/mario"; 10 | import { MoveHub } from "./hubs/movehub"; 11 | import { RemoteControl } from "./hubs/remotecontrol"; 12 | import { TechnicMediumHub } from "./hubs/technicmediumhub"; 13 | import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; 14 | 15 | import { ColorDistanceSensor } from "./devices/colordistancesensor"; 16 | import { CurrentSensor } from "./devices/currentsensor"; 17 | import { Device } from "./devices/device"; 18 | import { DuploTrainBaseColorSensor } from "./devices/duplotrainbasecolorsensor"; 19 | import { DuploTrainBaseMotor } from "./devices/duplotrainbasemotor"; 20 | import { DuploTrainBaseSpeaker } from "./devices/duplotrainbasespeaker"; 21 | import { DuploTrainBaseSpeedometer } from "./devices/duplotrainbasespeedometer"; 22 | import { HubLED } from "./devices/hubled"; 23 | import { Light } from "./devices/light"; 24 | import { MediumLinearMotor } from "./devices/mediumlinearmotor"; 25 | import { MotionSensor } from "./devices/motionsensor"; 26 | import { MoveHubMediumLinearMotor } from "./devices/movehubmediumlinearmotor"; 27 | import { MoveHubTiltSensor } from "./devices/movehubtiltsensor"; 28 | import { PiezoBuzzer } from "./devices/piezobuzzer"; 29 | import { RemoteControlButton } from "./devices/remotecontrolbutton"; 30 | import { SimpleMediumLinearMotor } from "./devices/simplemediumlinearmotor"; 31 | import { TechnicColorSensor } from "./devices/techniccolorsensor"; 32 | import { TechnicDistanceSensor } from "./devices/technicdistancesensor"; 33 | import { TechnicForceSensor } from "./devices/technicforcesensor"; 34 | import { TechnicLargeAngularMotor } from "./devices/techniclargeangularmotor"; 35 | import { TechnicLargeLinearMotor } from "./devices/techniclargelinearmotor"; 36 | import { TechnicSmallAngularMotor } from "./devices/technicsmallangularmotor"; 37 | import { TechnicMediumAngularMotor } from "./devices/technicmediumangularmotor"; 38 | import { TechnicMediumHubAccelerometerSensor } from "./devices/technicmediumhubaccelerometersensor"; 39 | import { TechnicMediumHubGyroSensor } from "./devices/technicmediumhubgyrosensor"; 40 | import { TechnicMediumHubTiltSensor } from "./devices/technicmediumhubtiltsensor"; 41 | import { TechnicXLargeLinearMotor } from "./devices/technicxlargelinearmotor"; 42 | import { Technic3x3ColorLightMatrix } from "./devices/technic3x3colorlightmatrix"; 43 | import { TiltSensor } from "./devices/tiltsensor"; 44 | import { TrainMotor } from "./devices/trainmotor"; 45 | import { VoltageSensor } from "./devices/voltagesensor"; 46 | import { TachoMotor } from "./devices/tachomotor"; 47 | import { AbsoluteMotor } from "./devices/absolutemotor"; 48 | import { BasicMotor } from "./devices/basicmotor"; 49 | 50 | import { isWebBluetooth } from "./utils"; 51 | 52 | // @ts-ignore 53 | window.PoweredUP = { 54 | PoweredUP, 55 | BaseHub, 56 | WeDo2SmartHub, 57 | TechnicMediumHub, 58 | Hub, 59 | RemoteControl, 60 | DuploTrainBase, 61 | Consts, 62 | Color, 63 | ColorDistanceSensor, 64 | Device, 65 | DuploTrainBaseColorSensor, 66 | DuploTrainBaseMotor, 67 | DuploTrainBaseSpeaker, 68 | DuploTrainBaseSpeedometer, 69 | HubLED, 70 | Light, 71 | Mario, 72 | MediumLinearMotor, 73 | MotionSensor, 74 | MoveHub, 75 | MoveHubMediumLinearMotor, 76 | MoveHubTiltSensor, 77 | PiezoBuzzer, 78 | RemoteControlButton, 79 | SimpleMediumLinearMotor, 80 | TechnicColorSensor, 81 | TechnicDistanceSensor, 82 | TechnicForceSensor, 83 | TechnicMediumHubAccelerometerSensor, 84 | TechnicMediumHubGyroSensor, 85 | TechnicMediumHubTiltSensor, 86 | TechnicSmallAngularMotor, 87 | TechnicMediumAngularMotor, 88 | TechnicLargeAngularMotor, 89 | TechnicLargeLinearMotor, 90 | TechnicXLargeLinearMotor, 91 | Technic3x3ColorLightMatrix, 92 | TiltSensor, 93 | TrainMotor, 94 | VoltageSensor, 95 | CurrentSensor, 96 | TachoMotor, 97 | AbsoluteMotor, 98 | BasicMotor, 99 | isWebBluetooth 100 | }; 101 | 102 | -------------------------------------------------------------------------------- /src/index-node.ts: -------------------------------------------------------------------------------- 1 | import * as Consts from "./consts"; 2 | 3 | import { PoweredUP } from "./poweredup-node"; 4 | import { Color } from "./color"; 5 | 6 | import { BaseHub } from "./hubs/basehub"; 7 | import { DuploTrainBase } from "./hubs/duplotrainbase"; 8 | import { Hub } from "./hubs/hub"; 9 | import { Mario } from "./hubs/mario"; 10 | import { MoveHub } from "./hubs/movehub"; 11 | import { RemoteControl } from "./hubs/remotecontrol"; 12 | import { TechnicMediumHub } from "./hubs/technicmediumhub"; 13 | import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; 14 | 15 | import { ColorDistanceSensor } from "./devices/colordistancesensor"; 16 | import { CurrentSensor } from "./devices/currentsensor"; 17 | import { Device } from "./devices/device"; 18 | import { DuploTrainBaseColorSensor } from "./devices/duplotrainbasecolorsensor"; 19 | import { DuploTrainBaseMotor } from "./devices/duplotrainbasemotor"; 20 | import { DuploTrainBaseSpeaker } from "./devices/duplotrainbasespeaker"; 21 | import { DuploTrainBaseSpeedometer } from "./devices/duplotrainbasespeedometer"; 22 | import { HubLED } from "./devices/hubled"; 23 | import { Light } from "./devices/light"; 24 | import { MediumLinearMotor } from "./devices/mediumlinearmotor"; 25 | import { MotionSensor } from "./devices/motionsensor"; 26 | import { MoveHubMediumLinearMotor } from "./devices/movehubmediumlinearmotor"; 27 | import { MoveHubTiltSensor } from "./devices/movehubtiltsensor"; 28 | import { PiezoBuzzer } from "./devices/piezobuzzer"; 29 | import { RemoteControlButton } from "./devices/remotecontrolbutton"; 30 | import { SimpleMediumLinearMotor } from "./devices/simplemediumlinearmotor"; 31 | import { TechnicColorSensor } from "./devices/techniccolorsensor"; 32 | import { TechnicDistanceSensor } from "./devices/technicdistancesensor"; 33 | import { TechnicForceSensor } from "./devices/technicforcesensor"; 34 | import { TechnicLargeAngularMotor } from "./devices/techniclargeangularmotor"; 35 | import { TechnicLargeLinearMotor } from "./devices/techniclargelinearmotor"; 36 | import { TechnicSmallAngularMotor } from "./devices/technicsmallangularmotor"; 37 | import { TechnicMediumAngularMotor } from "./devices/technicmediumangularmotor"; 38 | import { TechnicMediumHubAccelerometerSensor } from "./devices/technicmediumhubaccelerometersensor"; 39 | import { TechnicMediumHubGyroSensor } from "./devices/technicmediumhubgyrosensor"; 40 | import { TechnicMediumHubTiltSensor } from "./devices/technicmediumhubtiltsensor"; 41 | import { TechnicXLargeLinearMotor } from "./devices/technicxlargelinearmotor"; 42 | import { Technic3x3ColorLightMatrix } from "./devices/technic3x3colorlightmatrix"; 43 | import { TiltSensor } from "./devices/tiltsensor"; 44 | import { TrainMotor } from "./devices/trainmotor"; 45 | import { VoltageSensor } from "./devices/voltagesensor"; 46 | import { TachoMotor } from "./devices/tachomotor"; 47 | import { AbsoluteMotor } from "./devices/absolutemotor"; 48 | import { BasicMotor } from "./devices/basicmotor"; 49 | 50 | import { isWebBluetooth } from "./utils"; 51 | 52 | export default PoweredUP; 53 | export { 54 | PoweredUP, 55 | BaseHub, 56 | WeDo2SmartHub, 57 | TechnicMediumHub, 58 | Hub, 59 | RemoteControl, 60 | DuploTrainBase, 61 | Consts, 62 | Color, 63 | ColorDistanceSensor, 64 | Device, 65 | DuploTrainBaseColorSensor, 66 | DuploTrainBaseMotor, 67 | DuploTrainBaseSpeaker, 68 | DuploTrainBaseSpeedometer, 69 | HubLED, 70 | Light, 71 | Mario, 72 | MediumLinearMotor, 73 | MotionSensor, 74 | MoveHub, 75 | MoveHubMediumLinearMotor, 76 | MoveHubTiltSensor, 77 | PiezoBuzzer, 78 | RemoteControlButton, 79 | SimpleMediumLinearMotor, 80 | TechnicColorSensor, 81 | TechnicDistanceSensor, 82 | TechnicForceSensor, 83 | TechnicMediumHubAccelerometerSensor, 84 | TechnicMediumHubGyroSensor, 85 | TechnicMediumHubTiltSensor, 86 | TechnicSmallAngularMotor, 87 | TechnicMediumAngularMotor, 88 | TechnicLargeAngularMotor, 89 | TechnicLargeLinearMotor, 90 | TechnicXLargeLinearMotor, 91 | Technic3x3ColorLightMatrix, 92 | TiltSensor, 93 | TrainMotor, 94 | VoltageSensor, 95 | CurrentSensor, 96 | TachoMotor, 97 | AbsoluteMotor, 98 | BasicMotor, 99 | isWebBluetooth 100 | }; 101 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | 3 | import * as Consts from "./consts"; 4 | 5 | export interface IBLEAbstraction extends EventEmitter { 6 | uuid: string; 7 | name: string; 8 | connecting: boolean; 9 | connected: boolean; 10 | connect: () => Promise; 11 | disconnect: () => Promise; 12 | discoverCharacteristicsForService: (uuid: string) => Promise; 13 | subscribeToCharacteristic: (uuid: string, callback: (data: Buffer) => void) => void; 14 | addToCharacteristicMailbox: (uuid: string, data: Buffer) => void; 15 | readFromCharacteristic: (uuid: string, callback: (err: string | null, data: Buffer | null) => void) => void; 16 | writeToCharacteristic: (uuid: string, data: Buffer) => Promise; 17 | } 18 | 19 | export interface IDeviceInterface extends EventEmitter { 20 | type: Consts.HubType; 21 | getPortNameForPortId: (portId: number) => string | undefined; 22 | send: (message: Buffer, uuid: string) => Promise; 23 | subscribe: (portId: number, deviceType: number, mode: number) => void; 24 | isPortVirtual: (portId: number) => boolean; 25 | sleep: (delay: number) => Promise; 26 | } 27 | -------------------------------------------------------------------------------- /src/nobleabstraction.ts: -------------------------------------------------------------------------------- 1 | import { Characteristic, Peripheral, Service } from "@abandonware/noble"; 2 | 3 | import Debug = require("debug"); 4 | import { EventEmitter } from "events"; 5 | import { IBLEAbstraction } from "./interfaces"; 6 | const debug = Debug("bledevice"); 7 | 8 | 9 | export class NobleDevice extends EventEmitter implements IBLEAbstraction { 10 | 11 | private _noblePeripheral: Peripheral; 12 | 13 | private _uuid: string; 14 | private _name: string = ""; 15 | 16 | private _listeners: {[uuid: string]: any} = {}; 17 | private _characteristics: {[uuid: string]: Characteristic} = {}; 18 | 19 | private _queue: Promise = Promise.resolve(); 20 | private _mailbox: Buffer[] = []; 21 | 22 | private _connected: boolean = false; 23 | private _connecting: boolean = false; 24 | 25 | 26 | constructor (device: any) { 27 | super(); 28 | this._noblePeripheral = device; 29 | this._uuid = device.uuid; 30 | device.on("disconnect", () => { 31 | this._connecting = false; 32 | this._connected = false; 33 | this.emit("disconnect"); 34 | }); 35 | // NK: This hack allows LPF2.0 hubs to send a second advertisement packet consisting of the hub name before we try to read it 36 | setTimeout(() => { 37 | this._name = device.advertisement.localName; 38 | this.emit("discoverComplete"); 39 | }, 1000); 40 | } 41 | 42 | 43 | public get uuid () { 44 | return this._uuid; 45 | } 46 | 47 | 48 | public get name () { 49 | return this._name; 50 | } 51 | 52 | 53 | public get connecting () { 54 | return this._connecting; 55 | } 56 | 57 | 58 | public get connected () { 59 | return this._connected; 60 | } 61 | 62 | 63 | public connect () { 64 | return new Promise((resolve, reject) => { 65 | this._connecting = true; 66 | this._noblePeripheral.connect((err: string) => { 67 | if(err) { 68 | return reject(err); 69 | } 70 | 71 | this._connecting = false; 72 | this._connected = true; 73 | return resolve(); 74 | }); 75 | }); 76 | } 77 | 78 | 79 | public disconnect () { 80 | return new Promise((resolve) => { 81 | this._noblePeripheral.disconnect(); 82 | this._connecting = false; 83 | this._connected = false; 84 | return resolve(); 85 | }); 86 | } 87 | 88 | 89 | public discoverCharacteristicsForService (uuid: string) { 90 | return new Promise(async (discoverResolve, discoverReject) => { 91 | uuid = this._sanitizeUUID(uuid); 92 | this._noblePeripheral.discoverServices([uuid], (err: string, services: Service[]) => { 93 | if (err) { 94 | return discoverReject(err); 95 | } 96 | debug("Service/characteristic discovery started"); 97 | const servicePromises: Promise[] = []; 98 | services.forEach((service) => { 99 | servicePromises.push(new Promise((resolve) => { 100 | service.discoverCharacteristics([], (err, characteristics) => { 101 | characteristics.forEach((characteristic) => { 102 | this._characteristics[characteristic.uuid] = characteristic; 103 | }); 104 | return resolve(); 105 | }); 106 | })); 107 | }); 108 | 109 | Promise.all(servicePromises).then(() => { 110 | debug("Service/characteristic discovery finished"); 111 | return discoverResolve(); 112 | }); 113 | }); 114 | }); 115 | } 116 | 117 | 118 | public subscribeToCharacteristic (uuid: string, callback: (data: Buffer) => void) { 119 | uuid = this._sanitizeUUID(uuid); 120 | this._characteristics[uuid].on("data", (data: Buffer) => { 121 | return callback(data); 122 | }); 123 | this._characteristics[uuid].subscribe((err) => { 124 | if (err) { 125 | throw new Error(err); 126 | } 127 | }); 128 | } 129 | 130 | 131 | public addToCharacteristicMailbox (uuid: string, data: Buffer) { 132 | this._mailbox.push(data); 133 | } 134 | 135 | 136 | public readFromCharacteristic (uuid: string, callback: (err: string | null, data: Buffer | null) => void) { 137 | uuid = this._sanitizeUUID(uuid); 138 | this._characteristics[uuid].read((err: string, data: Buffer) => { 139 | return callback(err, data); 140 | }); 141 | } 142 | 143 | 144 | public writeToCharacteristic (uuid: string, data: Buffer) { 145 | return new Promise((resolve, reject) => { 146 | uuid = this._sanitizeUUID(uuid); 147 | this._characteristics[uuid].write(data, false, (error) => { 148 | if(error) { 149 | return reject(error); 150 | } 151 | 152 | return resolve(); 153 | }); 154 | }) 155 | } 156 | 157 | 158 | private _sanitizeUUID (uuid: string) { 159 | return uuid.replace(/-/g, ""); 160 | } 161 | 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/poweredup-browser.ts: -------------------------------------------------------------------------------- 1 | import { WebBLEDevice } from "./webbleabstraction"; 2 | 3 | import { BaseHub } from "./hubs/basehub"; 4 | import { DuploTrainBase } from "./hubs/duplotrainbase"; 5 | import { Hub } from "./hubs/hub"; 6 | import { Mario } from "./hubs/mario"; 7 | import { MoveHub } from "./hubs/movehub"; 8 | import { RemoteControl } from "./hubs/remotecontrol"; 9 | import { TechnicMediumHub } from "./hubs/technicmediumhub"; 10 | import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; 11 | 12 | import * as Consts from "./consts"; 13 | 14 | import { EventEmitter } from "events"; 15 | 16 | import Debug = require("debug"); 17 | import { IBLEAbstraction } from "./interfaces"; 18 | import { TechnicSmallHub } from "./hubs/technicsmallhub"; 19 | const debug = Debug("poweredup"); 20 | 21 | 22 | /** 23 | * @class PoweredUP 24 | * @extends EventEmitter 25 | */ 26 | export class PoweredUP extends EventEmitter { 27 | 28 | 29 | private _connectedHubs: {[uuid: string]: BaseHub} = {}; 30 | 31 | 32 | constructor () { 33 | super(); 34 | this._discoveryEventHandler = this._discoveryEventHandler.bind(this); 35 | } 36 | 37 | 38 | /** 39 | * Begin scanning for Powered UP Hub devices. 40 | * @method PoweredUP#scan 41 | */ 42 | public async scan () { 43 | 44 | try { 45 | 46 | const device = await navigator.bluetooth.requestDevice({ 47 | filters: [ 48 | { 49 | services: [ 50 | Consts.BLEService.WEDO2_SMART_HUB 51 | ] 52 | }, 53 | { 54 | services: [ 55 | Consts.BLEService.LPF2_HUB 56 | ] 57 | } 58 | ], 59 | optionalServices: [ 60 | Consts.BLEService.WEDO2_SMART_HUB_2, 61 | "battery_service", 62 | "device_information" 63 | ] 64 | }); 65 | 66 | // @ts-ignore 67 | const server = await device.gatt.connect(); 68 | this._discoveryEventHandler.call(this, server); 69 | return true; 70 | 71 | } catch (err) { 72 | return false; 73 | } 74 | 75 | } 76 | 77 | 78 | /** 79 | * Retrieve a list of Powered UP Hubs. 80 | * @method PoweredUP#getHubs 81 | * @returns {BaseHub[]} 82 | */ 83 | public getHubs () { 84 | return Object.values(this._connectedHubs); 85 | } 86 | 87 | 88 | /** 89 | * Retrieve a Powered UP Hub by UUID. 90 | * @method PoweredUP#getHubByUUID 91 | * @param {string} uuid 92 | * @returns {BaseHub | null} 93 | */ 94 | public getHubByUUID (uuid: string) { 95 | return this._connectedHubs[uuid]; 96 | } 97 | 98 | 99 | /** 100 | * Retrieve a Powered UP Hub by primary MAC address. 101 | * @method PoweredUP#getHubByPrimaryMACAddress 102 | * @param {string} address 103 | * @returns {BaseHub} 104 | */ 105 | public getHubByPrimaryMACAddress (address: string) { 106 | return Object.values(this._connectedHubs).filter((hub) => hub.primaryMACAddress === address)[0]; 107 | } 108 | 109 | 110 | /** 111 | * Retrieve a list of Powered UP Hub by name. 112 | * @method PoweredUP#getHubsByName 113 | * @param {string} name 114 | * @returns {BaseHub[]} 115 | */ 116 | public getHubsByName (name: string) { 117 | return Object.values(this._connectedHubs).filter((hub) => hub.name === name); 118 | } 119 | 120 | 121 | /** 122 | * Retrieve a list of Powered UP Hub by type. 123 | * @method PoweredUP#getHubsByType 124 | * @param {string} name 125 | * @returns {BaseHub[]} 126 | */ 127 | public getHubsByType (hubType: number) { 128 | return Object.values(this._connectedHubs).filter((hub) => hub.type === hubType); 129 | } 130 | 131 | 132 | private _determineLPF2HubType (device: IBLEAbstraction): Promise { 133 | return new Promise(async (resolve) => { 134 | let buf: Buffer = Buffer.alloc(0); 135 | await device.subscribeToCharacteristic(Consts.BLECharacteristic.LPF2_ALL, (data: Buffer) => { 136 | buf = Buffer.concat([buf, data]); 137 | while (buf[0] <= buf.length) { 138 | const len = buf[0]; 139 | const message = buf.slice(0, len); 140 | buf = buf.slice(len); 141 | if (message[2] === 0x01 && message[3] === 0x0b) { 142 | switch (message[5]) { 143 | case Consts.BLEManufacturerData.REMOTE_CONTROL_ID: 144 | resolve(Consts.HubType.REMOTE_CONTROL); 145 | break; 146 | case Consts.BLEManufacturerData.MOVE_HUB_ID: 147 | resolve(Consts.HubType.MOVE_HUB); 148 | break; 149 | case Consts.BLEManufacturerData.HUB_ID: 150 | resolve(Consts.HubType.HUB); 151 | break; 152 | case Consts.BLEManufacturerData.DUPLO_TRAIN_BASE_ID: 153 | resolve(Consts.HubType.DUPLO_TRAIN_BASE); 154 | break; 155 | case Consts.BLEManufacturerData.TECHNIC_SMALL_HUB_ID: 156 | resolve(Consts.HubType.TECHNIC_SMALL_HUB); 157 | break; 158 | case Consts.BLEManufacturerData.TECHNIC_MEDIUM_HUB_ID: 159 | resolve(Consts.HubType.TECHNIC_MEDIUM_HUB); 160 | break; 161 | case Consts.BLEManufacturerData.MARIO_ID: 162 | resolve(Consts.HubType.MARIO); 163 | break; 164 | } 165 | debug("Hub type determined"); 166 | } else { 167 | debug("Stashed in mailbox (LPF2_ALL)", message); 168 | device.addToCharacteristicMailbox(Consts.BLECharacteristic.LPF2_ALL, message); 169 | } 170 | } 171 | }); 172 | device.writeToCharacteristic(Consts.BLECharacteristic.LPF2_ALL, Buffer.from([0x05, 0x00, 0x01, 0x0b, 0x05])); 173 | }); 174 | } 175 | 176 | 177 | private async _discoveryEventHandler (server: BluetoothRemoteGATTServer) { 178 | 179 | const device = new WebBLEDevice(server); 180 | 181 | let hub: BaseHub; 182 | 183 | let hubType = Consts.HubType.UNKNOWN; 184 | let isLPF2Hub = false; 185 | try { 186 | await device.discoverCharacteristicsForService(Consts.BLEService.WEDO2_SMART_HUB); 187 | hubType = Consts.HubType.WEDO2_SMART_HUB; 188 | // tslint:disable-next-line 189 | } catch (error) {} 190 | try { 191 | if (hubType !== Consts.HubType.WEDO2_SMART_HUB) { 192 | await device.discoverCharacteristicsForService(Consts.BLEService.LPF2_HUB); 193 | isLPF2Hub = true; 194 | } 195 | // tslint:disable-next-line 196 | } catch (error) {} 197 | 198 | if (isLPF2Hub) { 199 | hubType = await this._determineLPF2HubType(device); 200 | } 201 | 202 | switch (hubType) { 203 | case Consts.HubType.WEDO2_SMART_HUB: 204 | hub = new WeDo2SmartHub(device); 205 | break; 206 | case Consts.HubType.MOVE_HUB: 207 | hub = new MoveHub(device); 208 | break; 209 | case Consts.HubType.HUB: 210 | hub = new Hub(device); 211 | break; 212 | case Consts.HubType.REMOTE_CONTROL: 213 | hub = new RemoteControl(device); 214 | break; 215 | case Consts.HubType.DUPLO_TRAIN_BASE: 216 | hub = new DuploTrainBase(device); 217 | break; 218 | case Consts.HubType.TECHNIC_SMALL_HUB: 219 | hub = new TechnicSmallHub(device); 220 | break; 221 | case Consts.HubType.TECHNIC_MEDIUM_HUB: 222 | hub = new TechnicMediumHub(device); 223 | break; 224 | case Consts.HubType.MARIO: 225 | hub = new Mario(device); 226 | break; 227 | default: 228 | return; 229 | } 230 | 231 | device.on("discoverComplete", () => { 232 | 233 | hub.on("connect", () => { 234 | debug(`Hub ${hub.uuid} connected`); 235 | this._connectedHubs[hub.uuid] = hub; 236 | }); 237 | 238 | hub.on("disconnect", () => { 239 | debug(`Hub ${hub.uuid} disconnected`); 240 | delete this._connectedHubs[hub.uuid]; 241 | }); 242 | 243 | debug(`Hub ${hub.uuid} discovered`); 244 | 245 | /** 246 | * Emits when a Powered UP Hub device is found. 247 | * @event PoweredUP#discover 248 | * @param {WeDo2SmartHub | MoveHub | TechnicMediumHub | RemoteControl | DuploTrainBase} hub 249 | */ 250 | this.emit("discover", hub); 251 | 252 | }); 253 | 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /src/poweredup-node.ts: -------------------------------------------------------------------------------- 1 | import { Peripheral } from "@abandonware/noble"; 2 | 3 | import { NobleDevice } from "./nobleabstraction"; 4 | 5 | import { BaseHub } from "./hubs/basehub"; 6 | import { DuploTrainBase } from "./hubs/duplotrainbase"; 7 | import { Hub } from "./hubs/hub"; 8 | import { Mario } from "./hubs/mario"; 9 | import { MoveHub } from "./hubs/movehub"; 10 | import { RemoteControl } from "./hubs/remotecontrol"; 11 | import { TechnicMediumHub } from "./hubs/technicmediumhub"; 12 | import { WeDo2SmartHub } from "./hubs/wedo2smarthub"; 13 | 14 | import * as Consts from "./consts"; 15 | 16 | import { EventEmitter } from "events"; 17 | 18 | import Debug = require("debug"); 19 | const debug = Debug("poweredup"); 20 | import noble = require("@abandonware/noble"); 21 | import { TechnicSmallHub } from "./hubs/technicsmallhub"; 22 | 23 | let ready = false; 24 | let wantScan = false; 25 | 26 | const startScanning = () => { 27 | noble.startScanning([ 28 | Consts.BLEService.LPF2_HUB, 29 | Consts.BLEService.LPF2_HUB.replace(/-/g, ""), 30 | Consts.BLEService.WEDO2_SMART_HUB, 31 | Consts.BLEService.WEDO2_SMART_HUB.replace(/-/g, "") 32 | ]); 33 | }; 34 | 35 | noble.on("stateChange", (state: string) => { 36 | ready = (state === "poweredOn"); 37 | if (ready) { 38 | if (wantScan) { 39 | debug("Scanning started"); 40 | startScanning(); 41 | } 42 | noble.on('scanStop', () => { 43 | setTimeout(() => { 44 | startScanning(); 45 | }, 1000); 46 | }); 47 | } else { 48 | noble.stopScanning(); 49 | } 50 | }); 51 | 52 | /** 53 | * @class PoweredUP 54 | * @extends EventEmitter 55 | */ 56 | export class PoweredUP extends EventEmitter { 57 | 58 | 59 | private _connectedHubs: {[uuid: string]: BaseHub} = {}; 60 | 61 | 62 | constructor () { 63 | super(); 64 | this._discoveryEventHandler = this._discoveryEventHandler.bind(this); 65 | } 66 | 67 | 68 | /** 69 | * Begin scanning for Powered UP Hub devices. 70 | * @method PoweredUP#scan 71 | */ 72 | public async scan () { 73 | wantScan = true; 74 | // @ts-ignore 75 | noble.on("discover", this._discoveryEventHandler); 76 | 77 | if (ready) { 78 | debug("Scanning started"); 79 | startScanning(); 80 | } 81 | 82 | return true; 83 | } 84 | 85 | 86 | /** 87 | * Stop scanning for Powered UP Hub devices. 88 | * @method PoweredUP#stop 89 | */ 90 | public stop () { 91 | wantScan = false; 92 | // @ts-ignore 93 | noble.removeListener("discover", this._discoveryEventHandler); 94 | noble.stopScanning(); 95 | } 96 | 97 | 98 | /** 99 | * Retrieve a list of Powered UP Hubs. 100 | * @method PoweredUP#getHubs 101 | * @returns {BaseHub[]} 102 | */ 103 | public getHubs () { 104 | return Object.values(this._connectedHubs); 105 | } 106 | 107 | 108 | /** 109 | * Retrieve a Powered UP Hub by UUID. 110 | * @method PoweredUP#getHubByUUID 111 | * @param {string} uuid 112 | * @returns {BaseHub | null} 113 | */ 114 | public getHubByUUID (uuid: string) { 115 | return this._connectedHubs[uuid]; 116 | } 117 | 118 | 119 | /** 120 | * Retrieve a Powered UP Hub by primary MAC address. 121 | * @method PoweredUP#getHubByPrimaryMACAddress 122 | * @param {string} address 123 | * @returns {BaseHub} 124 | */ 125 | public getHubByPrimaryMACAddress (address: string) { 126 | return Object.values(this._connectedHubs).filter((hub) => hub.primaryMACAddress === address)[0]; 127 | } 128 | 129 | 130 | /** 131 | * Retrieve a list of Powered UP Hub by name. 132 | * @method PoweredUP#getHubsByName 133 | * @param {string} name 134 | * @returns {BaseHub[]} 135 | */ 136 | public getHubsByName (name: string) { 137 | return Object.values(this._connectedHubs).filter((hub) => hub.name === name); 138 | } 139 | 140 | 141 | /** 142 | * Retrieve a list of Powered UP Hub by type. 143 | * @method PoweredUP#getHubsByType 144 | * @param {string} name 145 | * @returns {BaseHub[]} 146 | */ 147 | public getHubsByType (hubType: number) { 148 | return Object.values(this._connectedHubs).filter((hub) => hub.type === hubType); 149 | } 150 | 151 | 152 | private async _discoveryEventHandler (peripheral: Peripheral) { 153 | 154 | peripheral.removeAllListeners(); 155 | const device = new NobleDevice(peripheral); 156 | 157 | let hub: BaseHub; 158 | 159 | if (WeDo2SmartHub.IsWeDo2SmartHub(peripheral)) { 160 | hub = new WeDo2SmartHub(device); 161 | } else if (MoveHub.IsMoveHub(peripheral)) { 162 | hub = new MoveHub(device); 163 | } else if (Hub.IsHub(peripheral)) { 164 | hub = new Hub(device); 165 | } else if (RemoteControl.IsRemoteControl(peripheral)) { 166 | hub = new RemoteControl(device); 167 | } else if (DuploTrainBase.IsDuploTrainBase(peripheral)) { 168 | hub = new DuploTrainBase(device); 169 | } else if (TechnicSmallHub.IsTechnicSmallHub(peripheral)) { 170 | hub = new TechnicSmallHub(device); 171 | } else if (TechnicMediumHub.IsTechnicMediumHub(peripheral)) { 172 | hub = new TechnicMediumHub(device); 173 | } else if (Mario.IsMario(peripheral)) { 174 | hub = new Mario(device); 175 | } else { 176 | return; 177 | } 178 | 179 | device.on("discoverComplete", () => { 180 | 181 | hub.on("connect", () => { 182 | debug(`Hub ${hub.uuid} connected`); 183 | this._connectedHubs[hub.uuid] = hub; 184 | }); 185 | 186 | hub.on("disconnect", () => { 187 | debug(`Hub ${hub.uuid} disconnected`); 188 | delete this._connectedHubs[hub.uuid]; 189 | 190 | if (wantScan) { 191 | startScanning(); 192 | } 193 | }); 194 | 195 | debug(`Hub ${hub.uuid} discovered`); 196 | 197 | /** 198 | * Emits when a Powered UP Hub device is found. 199 | * @event PoweredUP#discover 200 | * @param {WeDo2SmartHub | MoveHub | TechnicMediumHub | RemoteControl | DuploTrainBase} hub 201 | */ 202 | this.emit("discover", hub); 203 | 204 | }); 205 | 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { Device } from "./devices/device"; 3 | 4 | // @ts-ignore 5 | export const isWebBluetooth = !!(typeof navigator !== "undefined" && navigator && navigator.bluetooth); 6 | 7 | export const toHex = (value: number, length: number = 2) => { 8 | return value.toString(16).padStart(length, "0"); 9 | }; 10 | 11 | export const toBin = (value: number, length: number = 8) => { 12 | return value.toString(2).padStart(length, "0"); 13 | }; 14 | 15 | export const mapSpeed = (speed: number) => { 16 | if (speed === 127) { 17 | return 127; 18 | } 19 | if (speed > 100) { 20 | speed = 100; 21 | } else if (speed < -100) { 22 | speed = -100; 23 | } 24 | return speed; 25 | }; 26 | 27 | export const decodeVersion = (version: number) => { 28 | const parts = version.toString(16).padStart(8, "0"); 29 | return [parts[0], parts[1], parts.substring(2, 4), parts.substring(4)].join("."); 30 | }; 31 | 32 | export const decodeMACAddress = (address: Uint8Array) => { 33 | return Array.from(address).map((part) => toHex(part, 2)).join(":"); 34 | }; 35 | 36 | export const normalizeAngle = (angle: number) => { 37 | if (angle >= 180) { 38 | return angle - (360 * ((angle + 180) / 360)); 39 | } else if (angle < -180) { 40 | return angle + (360 * ((180 - angle) / 360)); 41 | } 42 | return angle; 43 | }; 44 | 45 | export const roundAngleToNearest90 = (angle: number) => { 46 | angle = normalizeAngle(angle); 47 | if (angle < -135) { 48 | return -180; 49 | } 50 | if (angle < -45) { 51 | return -90; 52 | } 53 | if (angle < 45) { 54 | return 0; 55 | } 56 | if (angle < 135) { 57 | return 90; 58 | } 59 | return -180; 60 | }; 61 | 62 | export const calculateRamp = (device: Device, fromPower: number, toPower: number, time: number) => { 63 | const emitter = new EventEmitter(); 64 | const steps = Math.abs(toPower - fromPower); 65 | let delay = time / steps; 66 | let increment = 1; 67 | if (delay < 50 && steps > 0) { 68 | increment = 50 / delay; 69 | delay = 50; 70 | } 71 | if (fromPower > toPower) { 72 | increment = -increment; 73 | } 74 | let i = 0; 75 | const interval = setInterval(() => { 76 | let power = Math.round(fromPower + (++i * increment)); 77 | if (toPower > fromPower && power > toPower) { 78 | power = toPower; 79 | } else if (fromPower > toPower && power < toPower) { 80 | power = toPower; 81 | } 82 | emitter.emit("changePower", power); 83 | if (power === toPower) { 84 | clearInterval(interval); 85 | emitter.emit("finished"); 86 | } 87 | }, delay); 88 | device.setEventTimer(interval); 89 | return emitter; 90 | }; 91 | 92 | export const parseColor = (color: number) => { 93 | if (color === 1 || color === 5) { 94 | color = color + 1; 95 | } 96 | return color; 97 | } -------------------------------------------------------------------------------- /src/webbleabstraction.ts: -------------------------------------------------------------------------------- 1 | import Debug = require("debug"); 2 | import { EventEmitter } from "events"; 3 | import { IBLEAbstraction } from "./interfaces"; 4 | const debug = Debug("bledevice"); 5 | 6 | 7 | export class WebBLEDevice extends EventEmitter implements IBLEAbstraction { 8 | 9 | private _webBLEServer: any; 10 | 11 | private _uuid: string; 12 | private _name: string = ""; 13 | 14 | private _listeners: {[uuid: string]: any} = {}; 15 | private _characteristics: {[uuid: string]: any} = {}; 16 | 17 | private _queue: Promise = Promise.resolve(); 18 | private _mailbox: Buffer[] = []; 19 | 20 | private _connected: boolean = false; 21 | private _connecting: boolean = false; 22 | 23 | 24 | constructor (device: any) { 25 | super(); 26 | this._webBLEServer = device; 27 | this._uuid = device.device.id; 28 | this._name = device.device.name; 29 | device.device.addEventListener("gattserverdisconnected", () => { 30 | this._connecting = false; 31 | this._connected = false; 32 | this.emit("disconnect"); 33 | }); 34 | setTimeout(() => { 35 | this.emit("discoverComplete"); 36 | }, 2000); 37 | } 38 | 39 | 40 | public get uuid () { 41 | return this._uuid; 42 | } 43 | 44 | 45 | public get name () { 46 | return this._name; 47 | } 48 | 49 | 50 | public get connecting () { 51 | return this._connecting; 52 | } 53 | 54 | 55 | public get connected () { 56 | return this._connected; 57 | } 58 | 59 | 60 | public connect () { 61 | return new Promise((resolve) => { 62 | this._connected = true; 63 | return resolve(); 64 | }); 65 | } 66 | 67 | 68 | public disconnect () { 69 | return new Promise((resolve) => { 70 | this._webBLEServer.device.gatt.disconnect(); 71 | this._connected = false; 72 | return resolve(); 73 | }); 74 | } 75 | 76 | 77 | public async discoverCharacteristicsForService (uuid: string) { 78 | debug("Service/characteristic discovery started"); 79 | const service = await this._webBLEServer.getPrimaryService(uuid); 80 | const characteristics = await service.getCharacteristics(); 81 | for (const characteristic of characteristics) { 82 | this._characteristics[characteristic.uuid] = characteristic; 83 | } 84 | debug("Service/characteristic discovery finished"); 85 | } 86 | 87 | 88 | public subscribeToCharacteristic (uuid: string, callback: (data: Buffer) => void): Promise { 89 | if (this._listeners[uuid]) { 90 | this._characteristics[uuid].removeEventListener("characteristicvaluechanged", this._listeners[uuid]); 91 | } 92 | // @ts-ignore 93 | this._listeners[uuid] = (event) => { 94 | const buf = Buffer.alloc(event.target.value.buffer.byteLength); 95 | const view = new Uint8Array(event.target.value.buffer); 96 | for (let i = 0; i < buf.length; i++) { 97 | buf[i] = view[i]; 98 | } 99 | debug("Incoming data", buf); 100 | return callback(buf); 101 | }; 102 | this._characteristics[uuid].addEventListener("characteristicvaluechanged", this._listeners[uuid]); 103 | 104 | const mailbox = Array.from(this._mailbox); 105 | this._mailbox = []; 106 | for (const data of mailbox) { 107 | debug("Replayed from mailbox (LPF2_ALL)", data); 108 | callback(data); 109 | } 110 | 111 | return this._characteristics[uuid].startNotifications(); 112 | } 113 | 114 | 115 | public addToCharacteristicMailbox (uuid: string, data: Buffer) { 116 | this._mailbox.push(data); 117 | } 118 | 119 | 120 | public readFromCharacteristic (uuid: string, callback: (err: string | null, data: Buffer | null) => void) { 121 | // @ts-ignore 122 | this._characteristics[uuid].readValue().then((data) => { 123 | const buf = Buffer.alloc(data.buffer.byteLength); 124 | const view = new Uint8Array(data.buffer); 125 | for (let i = 0; i < buf.length; i++) { 126 | buf[i] = view[i]; 127 | } 128 | callback(null, buf); 129 | }); 130 | } 131 | 132 | 133 | public writeToCharacteristic (uuid: string, data: Buffer) { 134 | return this._queue = this._queue.then(() => this._characteristics[uuid].writeValueWithoutResponse(data)); 135 | } 136 | 137 | 138 | private _sanitizeUUID (uuid: string) { 139 | return uuid.replace(/-/g, ""); 140 | } 141 | 142 | 143 | } 144 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist/node", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | } 59 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-consecutive-blank-lines": false, 9 | "space-before-function-paren": false, 10 | "no-bitwise": false, 11 | "trailing-comma": false, 12 | "max-line-length": false, 13 | "prefer-for-of": false, 14 | "typedef": true, 15 | "no-console": false, 16 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "require-const-for-all-caps", "allow-pascal-case"], 17 | "object-literal-key-quotes": false, 18 | "object-literal-sort-keys": false, 19 | "no-string-literal": false, 20 | "no-shadowed-variable": [false] 21 | }, 22 | "rulesDirectory": [] 23 | } -------------------------------------------------------------------------------- /webble_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | node-poweredup Web Bluetooth Test 6 | 65 | 66 | 67 | 68 |
69 | 70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | entry: "./src/index-browser.ts", 6 | devtool: "source-map", 7 | plugins: [ 8 | new webpack.ProvidePlugin({ 9 | Buffer: ['buffer', 'Buffer'], 10 | }) 11 | ], 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.ts?$/, 16 | use: "ts-loader", 17 | exclude: /node_modules/ 18 | } 19 | ], 20 | }, 21 | externals: { 22 | "noble": "noble", 23 | "noble-mac": "noble-mac" 24 | }, 25 | resolve: { 26 | alias: { 27 | buffer: 'buffer' 28 | }, 29 | extensions: [".ts", ".js"] 30 | }, 31 | output: { 32 | filename: "poweredup.js", 33 | path: path.resolve(__dirname, "dist", "browser") 34 | } 35 | }; --------------------------------------------------------------------------------