├── .gitignore ├── .travis.yml ├── assets └── logo.png ├── dist ├── index.js ├── index.js.map ├── models │ ├── index.js │ ├── index.js.map │ ├── subcommand.js │ └── subcommand.js.map ├── sample.js ├── sample.js.map └── utils │ ├── packet-parser.js │ ├── packet-parser.js.map │ ├── subcommand-sender.js │ └── subcommand-sender.js.map ├── jest.config.js ├── package-lock.json ├── package.json ├── readme.md ├── src ├── index.ts ├── models │ ├── index.ts │ └── subcommand.ts ├── sample.ts └── utils │ ├── packet-parser.ts │ └── subcommand-sender.ts ├── tests ├── data │ ├── parseAccelerometers.1.out.json │ ├── parseCompleteButtonStatus.1.out.json │ └── parseGyroscopes.1.out.json └── rawdata-convertion.test.js ├── tsconfig.json ├── tslint.json └── types ├── index.d.ts ├── models ├── index.d.ts └── subcommand.d.ts ├── sample.d.ts └── utils ├── packet-parser.d.ts └── subcommand-sender.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | before_install: 5 | - sudo apt-get update 6 | - sudo apt-get install -y g++-4.8 g++-4.8-multilib gcc-multilib 7 | - sudo apt-get install -y libusb-1.0-0-dev 8 | - sudo apt-get install -y libudev-dev -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wazho/ns-joycon/bde4c68100cc3844fd9bcf73d736630ec9ec3e3a/assets/logo.png -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | var __importStar = (this && this.__importStar) || function (mod) { 11 | if (mod && mod.__esModule) return mod; 12 | var result = {}; 13 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 14 | result["default"] = mod; 15 | return result; 16 | }; 17 | Object.defineProperty(exports, "__esModule", { value: true }); 18 | // Node modules. 19 | const node_hid_1 = require("node-hid"); 20 | const PacketParser = __importStar(require("./utils/packet-parser")); 21 | const SubcommandSender = __importStar(require("./utils/subcommand-sender")); 22 | function getType(product) { 23 | if (product === undefined) { 24 | return 'unknown'; 25 | } 26 | switch (true) { 27 | case /Pro Controller/i.test(product): 28 | return 'pro-controller'; 29 | case /Joy-Con \([LR]\)/i.test(product): 30 | return 'joy-con'; 31 | default: 32 | return 'unknown'; 33 | } 34 | } 35 | class NsSwitchHID { 36 | constructor(device) { 37 | this.listeners = []; 38 | this.vendorId = device.vendorId; 39 | this.productId = device.productId; 40 | this.serialNumber = device.serialNumber; 41 | this.product = device.product; 42 | this.type = getType(device.product); 43 | this.path = device.path; 44 | this.usage = device.usage; 45 | this.hid = new node_hid_1.HID(device.vendorId, device.productId); 46 | // System handler. 47 | if (this.type === 'joy-con') { 48 | this.activateJoyConStream(); 49 | } 50 | } 51 | get meta() { 52 | return { 53 | vendorId: this.vendorId, 54 | productId: this.productId, 55 | serialNumber: this.serialNumber, 56 | product: this.product, 57 | type: this.type, 58 | path: this.path, 59 | usage: this.usage, 60 | }; 61 | } 62 | /** 63 | * Add / remove a handler to recevice packets when device send streaming data. 64 | */ 65 | manageHandler(action, callback) { 66 | if (action === 'add') { 67 | this.listeners.push(callback); 68 | } 69 | else { 70 | this.listeners = this.listeners.filter((listener) => listener !== callback); 71 | } 72 | } 73 | /** 74 | * Request device info to Jon-Con. 75 | */ 76 | requestDeviceInfo() { 77 | return __awaiter(this, void 0, void 0, function* () { 78 | if (this.type === 'joy-con') { 79 | const manageHandler = this.manageHandler.bind(this); 80 | const deviceInfo = yield SubcommandSender.requestDeviceInfo(this.hid, manageHandler); 81 | return deviceInfo; 82 | } 83 | }); 84 | } 85 | /** 86 | * Enable IMU data will make Jon-Con sends **Input Report 0x30**. 87 | */ 88 | enableIMU() { 89 | return __awaiter(this, void 0, void 0, function* () { 90 | if (this.type === 'joy-con') { 91 | yield SubcommandSender.enableIMU(this.hid, this.manageHandler.bind(this), true); 92 | yield SubcommandSender.setInputReportMode(this.hid, this.manageHandler.bind(this), 'standard-full-mode'); 93 | console.info(`Device ${this.product} (${this.serialNumber}) enabled IMU.`); 94 | } 95 | }); 96 | } 97 | /** 98 | * Disable IMU data will cancel Jon-Con to send **Input Report 0x30**. 99 | */ 100 | disableIMU() { 101 | return __awaiter(this, void 0, void 0, function* () { 102 | if (this.type === 'joy-con') { 103 | yield SubcommandSender.enableIMU(this.hid, this.manageHandler.bind(this), false); 104 | yield SubcommandSender.setInputReportMode(this.hid, this.manageHandler.bind(this), 'simple-hid-mode'); 105 | console.info(`Device ${this.product} (${this.serialNumber}) disabled IMU.`); 106 | } 107 | }); 108 | } 109 | /** 110 | * Enable Jon-Con's vibration. 111 | */ 112 | enableVibration() { 113 | return __awaiter(this, void 0, void 0, function* () { 114 | if (this.type === 'joy-con') { 115 | yield SubcommandSender.enableVibration(this.hid, this.manageHandler.bind(this), true); 116 | console.info(`Device ${this.product} (${this.serialNumber}) enabled vibration.`); 117 | } 118 | }); 119 | } 120 | /** 121 | * Disable Jon-Con's vibration. 122 | */ 123 | disableVibration() { 124 | return __awaiter(this, void 0, void 0, function* () { 125 | if (this.type === 'joy-con') { 126 | yield SubcommandSender.enableVibration(this.hid, this.manageHandler.bind(this), false); 127 | console.info(`Device ${this.product} (${this.serialNumber}) disabled vibration.`); 128 | } 129 | }); 130 | } 131 | activateJoyConStream() { 132 | return __awaiter(this, void 0, void 0, function* () { 133 | this.hid.on('data', (rawData) => { 134 | const data = rawData.toString('hex').match(/.{2}/g); 135 | if (!data) { 136 | return; 137 | } 138 | const inputReportID = parseInt(data[0], 16); 139 | let packet = { 140 | inputReportID: PacketParser.parseInputReportID(rawData, data), 141 | }; 142 | switch (inputReportID) { 143 | case 0x3f: { 144 | packet = Object.assign({}, packet, { buttonStatus: PacketParser.parseButtonStatus(rawData, data), analogStick: PacketParser.parseAnalogStick(rawData, data), filter: PacketParser.parseFilter(rawData, data) }); 145 | break; 146 | } 147 | case 0x21: 148 | case 0x30: { 149 | packet = Object.assign({}, packet, { timer: PacketParser.parseTimer(rawData, data), batteryLevel: PacketParser.parseBatteryLevel(rawData, data), connectionInfo: PacketParser.parseConnectionInfo(rawData, data), buttonStatus: PacketParser.parseCompleteButtonStatus(rawData, data), analogStickLeft: PacketParser.parseAnalogStickLeft(rawData, data), analogStickRight: PacketParser.parseAnalogStickRight(rawData, data), vibrator: PacketParser.parseVibrator(rawData, data) }); 150 | if (inputReportID === 0x21) { 151 | packet = Object.assign({}, packet, { ack: PacketParser.parseAck(rawData, data), subcommandID: PacketParser.parseSubcommandID(rawData, data), subcommandReplyData: PacketParser.parseSubcommandReplyData(rawData, data) }); 152 | } 153 | if (inputReportID === 0x30) { 154 | const accelerometers = PacketParser.parseAccelerometers(rawData, data); 155 | const gyroscopes = PacketParser.parseGyroscopes(rawData, data); 156 | packet = Object.assign({}, packet, { accelerometers, 157 | gyroscopes, actualAccelerometer: { 158 | acc: PacketParser.calculateActualAccelerometer(accelerometers.map(a => [a.x.acc, a.y.acc, a.z.acc])), 159 | }, actualGyroscope: { 160 | dps: PacketParser.calculateActualGyroscope(gyroscopes.map(g => g.map(v => v.dps))), 161 | rps: PacketParser.calculateActualGyroscope(gyroscopes.map(g => g.map(v => v.rps))), 162 | } }); 163 | } 164 | break; 165 | } 166 | } 167 | // Broadcast. 168 | this.listeners.forEach((listener) => listener(packet)); 169 | }); 170 | this.hid.on('error', (error) => { 171 | console.warn(Object.assign({}, this.meta, { error })); 172 | }); 173 | }); 174 | } 175 | } 176 | function findControllers(callback) { 177 | let deviceList = new Set(); 178 | const work = () => { 179 | const tempDeviceList = new Set(); 180 | const devices = node_hid_1.devices().reduce((prev, d) => { 181 | if (getType(d.product) !== 'unknown') { 182 | prev.push(new NsSwitchHID(d)); 183 | } 184 | return prev; 185 | }, []); 186 | devices.forEach((d) => { 187 | const distinctId = `${d.meta.vendorId},${d.meta.productId},${d.meta.type}`; 188 | tempDeviceList.add(distinctId); 189 | if (!deviceList.has(distinctId)) { 190 | callback(devices); 191 | } 192 | }); 193 | deviceList = tempDeviceList; 194 | }; 195 | work(); 196 | setInterval(work, 1000); 197 | } 198 | exports.findControllers = findControllers; 199 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/models/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | ; 4 | ; 5 | ; 6 | ; 7 | ; 8 | ; 9 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/models/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":";;AAGC,CAAC;AAMD,CAAC;AA4CD,CAAC;AAiBD,CAAC;AAgBD,CAAC;AAOD,CAAC"} -------------------------------------------------------------------------------- /dist/models/subcommand.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=subcommand.js.map -------------------------------------------------------------------------------- /dist/models/subcommand.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"subcommand.js","sourceRoot":"","sources":["../../src/models/subcommand.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/sample.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | var __importStar = (this && this.__importStar) || function (mod) { 11 | if (mod && mod.__esModule) return mod; 12 | var result = {}; 13 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 14 | result["default"] = mod; 15 | return result; 16 | }; 17 | Object.defineProperty(exports, "__esModule", { value: true }); 18 | // Local modules. 19 | const JoyCon = __importStar(require("./index")); 20 | JoyCon.findControllers((devices) => { 21 | // When found any device. 22 | devices.forEach((device) => __awaiter(this, void 0, void 0, function* () { 23 | console.log(`Found a device (${device.meta.serialNumber})`); 24 | // Add a handler for new device. 25 | device.manageHandler('add', (packet) => { 26 | console.log(device.meta.product, packet); 27 | }); 28 | // const deviceInfo = await device.requestDeviceInfo(); 29 | yield device.enableIMU(); 30 | // await device.disableIMU(); 31 | // await device.enableVibration(); 32 | // await device.disableVibration(); 33 | })); 34 | }); 35 | //# sourceMappingURL=sample.js.map -------------------------------------------------------------------------------- /dist/sample.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"sample.js","sourceRoot":"","sources":["../src/sample.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iBAAiB;AACjB,gDAAkC;AAElC,MAAM,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,EAAE;IAC/B,yBAAyB;IACzB,OAAO,CAAC,OAAO,CAAC,CAAO,MAAM,EAAE,EAAE;QAC7B,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QAE5D,gCAAgC;QAChC,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE;YACnC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,uDAAuD;QACvD,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,6BAA6B;QAC7B,kCAAkC;QAClC,mCAAmC;IACvC,CAAC,CAAA,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"} -------------------------------------------------------------------------------- /dist/utils/packet-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | // Node modules. 7 | const mean_1 = __importDefault(require("lodash/mean")); 8 | function calculateBatteryLevel(value) { 9 | let level; 10 | switch (value) { 11 | case '8': 12 | level = 'full'; 13 | break; 14 | case '4': 15 | level = 'medium'; 16 | break; 17 | case '2': 18 | level = 'low'; 19 | break; 20 | case '1': 21 | level = 'critical'; 22 | break; 23 | case '0': 24 | level = 'empty'; 25 | break; 26 | default: 27 | level = 'charging'; 28 | } 29 | ; 30 | return level; 31 | } 32 | /** 33 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#accelerometer---acceleration-in-g) 34 | * @param {Buffer} value 35 | */ 36 | function toAcceleration(value) { 37 | return parseFloat((0.000244 * value.readInt16LE(0)).toFixed(6)); 38 | } 39 | /** 40 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#gyroscope---rotation-in-degreess---dps) 41 | * @param {Buffer} value 42 | */ 43 | function toDegreesPerSecond(value) { 44 | return parseFloat((0.06103 * value.readInt16LE(0)).toFixed(6)); 45 | } 46 | /** 47 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#gyroscope---rotation-in-revolutionss) 48 | * @param {Buffer} value 49 | */ 50 | function toRevolutionsPerSecond(value) { 51 | return parseFloat((0.0001694 * value.readInt16LE(0)).toFixed(6)); 52 | } 53 | function parseInputReportID(rawData, data) { 54 | const inputReportID = { 55 | _raw: rawData.slice(0, 1), 56 | _hex: data.slice(0, 1), 57 | }; 58 | return inputReportID; 59 | } 60 | exports.parseInputReportID = parseInputReportID; 61 | function parseTimer(rawData, data) { 62 | const timer = { 63 | _raw: rawData.slice(1, 2), 64 | _hex: data.slice(1, 2), 65 | }; 66 | return timer; 67 | } 68 | exports.parseTimer = parseTimer; 69 | function parseBatteryLevel(rawData, data) { 70 | const batteryLevel = { 71 | _raw: rawData.slice(2, 3), 72 | _hex: data[2][0], 73 | level: calculateBatteryLevel(data[2][0]), 74 | }; 75 | return batteryLevel; 76 | } 77 | exports.parseBatteryLevel = parseBatteryLevel; 78 | function parseConnectionInfo(rawData, data) { 79 | const connectionInfo = { 80 | _raw: rawData.slice(2, 3), 81 | _hex: data[2][1], 82 | }; 83 | return connectionInfo; 84 | } 85 | exports.parseConnectionInfo = parseConnectionInfo; 86 | function parseButtonStatus(rawData, data) { 87 | const buttonStatus = { 88 | _raw: rawData.slice(1, 3), 89 | _hex: data.slice(1, 3), 90 | }; 91 | return buttonStatus; 92 | } 93 | exports.parseButtonStatus = parseButtonStatus; 94 | function parseCompleteButtonStatus(rawData, data) { 95 | const buttonStatus = { 96 | _raw: rawData.slice(3, 6), 97 | _hex: data.slice(3, 6), 98 | // Byte 3 (Right Joy-Con) 99 | y: Boolean(0x01 & rawData[3]), 100 | x: Boolean(0x02 & rawData[3]), 101 | b: Boolean(0x04 & rawData[3]), 102 | a: Boolean(0x08 & rawData[3]), 103 | r: Boolean(0x40 & rawData[3]), 104 | zr: Boolean(0x80 & rawData[3]), 105 | // Byte 5 (Left Joy-Con) 106 | down: Boolean(0x01 & rawData[5]), 107 | up: Boolean(0x02 & rawData[5]), 108 | right: Boolean(0x04 & rawData[5]), 109 | left: Boolean(0x08 & rawData[5]), 110 | l: Boolean(0x40 & rawData[5]), 111 | zl: Boolean(0x80 & rawData[5]), 112 | // Byte 3,5 (Shared) 113 | sr: Boolean(0x10 & rawData[3]) || Boolean(0x10 & rawData[5]), 114 | sl: Boolean(0x20 & rawData[3]) || Boolean(0x20 & rawData[5]), 115 | // Byte 4 (Shared) 116 | minus: Boolean(0x01 & rawData[4]), 117 | plus: Boolean(0x02 & rawData[4]), 118 | rightStick: Boolean(0x04 & rawData[4]), 119 | leftStick: Boolean(0x08 & rawData[4]), 120 | home: Boolean(0x10 & rawData[4]), 121 | caputure: Boolean(0x20 & rawData[4]), 122 | chargingGrip: Boolean(0x80 & rawData[4]), 123 | }; 124 | return buttonStatus; 125 | } 126 | exports.parseCompleteButtonStatus = parseCompleteButtonStatus; 127 | function parseAnalogStick(rawData, data) { 128 | const analogStick = { 129 | _raw: rawData.slice(3, 4), 130 | _hex: data.slice(3, 4), 131 | }; 132 | return analogStick; 133 | } 134 | exports.parseAnalogStick = parseAnalogStick; 135 | function parseAnalogStickLeft(rawData, data) { 136 | const analogStickLeft = { 137 | _raw: rawData.slice(6, 9), 138 | _hex: data.slice(6, 9), 139 | horizontal: rawData[6] | ((rawData[7] & 0xF) << 8), 140 | vertical: (rawData[7] >> 4) | (rawData[8] << 4), 141 | }; 142 | return analogStickLeft; 143 | } 144 | exports.parseAnalogStickLeft = parseAnalogStickLeft; 145 | function parseAnalogStickRight(rawData, data) { 146 | const analogStickRight = { 147 | _raw: rawData.slice(9, 12), 148 | _hex: data.slice(9, 12), 149 | horizontal: rawData[9] | ((rawData[10] & 0xF) << 8), 150 | vertical: (rawData[10] >> 4) | (rawData[11] << 4), 151 | }; 152 | return analogStickRight; 153 | } 154 | exports.parseAnalogStickRight = parseAnalogStickRight; 155 | function parseFilter(rawData, data) { 156 | const filter = { 157 | _raw: rawData.slice(4), 158 | _hex: data.slice(4), 159 | }; 160 | return filter; 161 | } 162 | exports.parseFilter = parseFilter; 163 | function parseVibrator(rawData, data) { 164 | const vibrator = { 165 | _raw: rawData.slice(12, 13), 166 | _hex: data.slice(12, 13), 167 | }; 168 | return vibrator; 169 | } 170 | exports.parseVibrator = parseVibrator; 171 | function parseAck(rawData, data) { 172 | const ack = { 173 | _raw: rawData.slice(13, 14), 174 | _hex: data.slice(13, 14), 175 | }; 176 | return ack; 177 | } 178 | exports.parseAck = parseAck; 179 | function parseSubcommandID(rawData, data) { 180 | const subcommandID = { 181 | _raw: rawData.slice(14, 15), 182 | _hex: data.slice(14, 15), 183 | }; 184 | return subcommandID; 185 | } 186 | exports.parseSubcommandID = parseSubcommandID; 187 | function parseSubcommandReplyData(rawData, data) { 188 | const subcommandReplyData = { 189 | _raw: rawData.slice(15), 190 | _hex: data.slice(15), 191 | }; 192 | return subcommandReplyData; 193 | } 194 | exports.parseSubcommandReplyData = parseSubcommandReplyData; 195 | function parseAccelerometers(rawData, data) { 196 | const accelerometers = [ 197 | { 198 | x: { 199 | _raw: rawData.slice(13, 15), 200 | _hex: data.slice(13, 15), 201 | acc: toAcceleration(rawData.slice(13, 15)), 202 | }, 203 | y: { 204 | _raw: rawData.slice(15, 17), 205 | _hex: data.slice(15, 17), 206 | acc: toAcceleration(rawData.slice(15, 17)), 207 | }, 208 | z: { 209 | _raw: rawData.slice(17, 19), 210 | _hex: data.slice(17, 19), 211 | acc: toAcceleration(rawData.slice(17, 19)), 212 | }, 213 | }, 214 | { 215 | x: { 216 | _raw: rawData.slice(25, 27), 217 | _hex: data.slice(25, 27), 218 | acc: toAcceleration(rawData.slice(25, 27)), 219 | }, 220 | y: { 221 | _raw: rawData.slice(27, 29), 222 | _hex: data.slice(27, 29), 223 | acc: toAcceleration(rawData.slice(27, 29)), 224 | }, 225 | z: { 226 | _raw: rawData.slice(29, 31), 227 | _hex: data.slice(29, 31), 228 | acc: toAcceleration(rawData.slice(29, 31)), 229 | }, 230 | }, 231 | { 232 | x: { 233 | _raw: rawData.slice(37, 39), 234 | _hex: data.slice(37, 39), 235 | acc: toAcceleration(rawData.slice(37, 39)), 236 | }, 237 | y: { 238 | _raw: rawData.slice(39, 41), 239 | _hex: data.slice(39, 41), 240 | acc: toAcceleration(rawData.slice(39, 41)), 241 | }, 242 | z: { 243 | _raw: rawData.slice(41, 43), 244 | _hex: data.slice(41, 43), 245 | acc: toAcceleration(rawData.slice(41, 43)), 246 | }, 247 | }, 248 | ]; 249 | return accelerometers; 250 | } 251 | exports.parseAccelerometers = parseAccelerometers; 252 | function parseGyroscopes(rawData, data) { 253 | const gyroscopes = [ 254 | [ 255 | { 256 | _raw: rawData.slice(19, 21), 257 | _hex: data.slice(19, 21), 258 | dps: toDegreesPerSecond(rawData.slice(19, 21)), 259 | rps: toRevolutionsPerSecond(rawData.slice(19, 21)), 260 | }, 261 | { 262 | _raw: rawData.slice(21, 23), 263 | _hex: data.slice(21, 23), 264 | dps: toDegreesPerSecond(rawData.slice(21, 23)), 265 | rps: toRevolutionsPerSecond(rawData.slice(21, 23)), 266 | }, 267 | { 268 | _raw: rawData.slice(23, 25), 269 | _hex: data.slice(23, 25), 270 | dps: toDegreesPerSecond(rawData.slice(23, 25)), 271 | rps: toRevolutionsPerSecond(rawData.slice(23, 25)), 272 | }, 273 | ], 274 | [ 275 | { 276 | _raw: rawData.slice(31, 33), 277 | _hex: data.slice(31, 33), 278 | dps: toDegreesPerSecond(rawData.slice(31, 33)), 279 | rps: toRevolutionsPerSecond(rawData.slice(31, 33)), 280 | }, 281 | { 282 | _raw: rawData.slice(33, 35), 283 | _hex: data.slice(33, 35), 284 | dps: toDegreesPerSecond(rawData.slice(33, 35)), 285 | rps: toRevolutionsPerSecond(rawData.slice(33, 35)), 286 | }, 287 | { 288 | _raw: rawData.slice(35, 37), 289 | _hex: data.slice(35, 37), 290 | dps: toDegreesPerSecond(rawData.slice(35, 37)), 291 | rps: toRevolutionsPerSecond(rawData.slice(35, 37)), 292 | }, 293 | ], 294 | [ 295 | { 296 | _raw: rawData.slice(43, 45), 297 | _hex: data.slice(43, 45), 298 | dps: toDegreesPerSecond(rawData.slice(43, 45)), 299 | rps: toRevolutionsPerSecond(rawData.slice(43, 45)), 300 | }, 301 | { 302 | _raw: rawData.slice(45, 47), 303 | _hex: data.slice(45, 47), 304 | dps: toDegreesPerSecond(rawData.slice(45, 47)), 305 | rps: toRevolutionsPerSecond(rawData.slice(45, 47)), 306 | }, 307 | { 308 | _raw: rawData.slice(47, 49), 309 | _hex: data.slice(47, 49), 310 | dps: toDegreesPerSecond(rawData.slice(47, 49)), 311 | rps: toRevolutionsPerSecond(rawData.slice(47, 49)), 312 | }, 313 | ], 314 | ]; 315 | return gyroscopes; 316 | } 317 | exports.parseGyroscopes = parseGyroscopes; 318 | function calculateActualAccelerometer(accelerometers) { 319 | const elapsedTime = 0.005 * accelerometers.length; // Spent 5ms to collect each data. 320 | const actualAccelerometer = { 321 | x: parseFloat((mean_1.default(accelerometers.map(g => g[0])) * elapsedTime).toFixed(6)), 322 | y: parseFloat((mean_1.default(accelerometers.map(g => g[1])) * elapsedTime).toFixed(6)), 323 | z: parseFloat((mean_1.default(accelerometers.map(g => g[2])) * elapsedTime).toFixed(6)), 324 | }; 325 | return actualAccelerometer; 326 | } 327 | exports.calculateActualAccelerometer = calculateActualAccelerometer; 328 | function calculateActualGyroscope(gyroscopes) { 329 | const elapsedTime = 0.005 * gyroscopes.length; // Spent 5ms to collect each data. 330 | const actualGyroscopes = [ 331 | mean_1.default(gyroscopes.map(g => g[0])), 332 | mean_1.default(gyroscopes.map(g => g[1])), 333 | mean_1.default(gyroscopes.map(g => g[2])), 334 | ].map(v => parseFloat((v * elapsedTime).toFixed(6))); 335 | return actualGyroscopes; 336 | } 337 | exports.calculateActualGyroscope = calculateActualGyroscope; 338 | //# sourceMappingURL=packet-parser.js.map -------------------------------------------------------------------------------- /dist/utils/packet-parser.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"packet-parser.js","sourceRoot":"","sources":["../../src/utils/packet-parser.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/utils/subcommand-sender.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // Subcommand format: 4 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_notes.md#output-0x01 5 | var ControllerType; 6 | (function (ControllerType) { 7 | ControllerType[ControllerType["Left Joy-Con"] = 1] = "Left Joy-Con"; 8 | ControllerType[ControllerType["Right Joy-Con"] = 2] = "Right Joy-Con"; 9 | ControllerType[ControllerType["Pro Controller"] = 3] = "Pro Controller"; 10 | })(ControllerType || (ControllerType = {})); 11 | /** 12 | * **Subcommand 0x02**: Request device info 13 | * 14 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x02-request-device-info 15 | */ 16 | function requestDeviceInfo(hid, manageHandler) { 17 | const outputReportID = 0x01; 18 | const subcommand = [0x02]; 19 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 20 | return new Promise((resolve) => { 21 | const handler = (packet) => { 22 | const { inputReportID, subcommandID, subcommandReplyData } = packet; 23 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x02) { 24 | // Remove the handler first. 25 | manageHandler('remove', handler); 26 | // Parse the packet. 27 | const firmwareMajorVersionRaw = subcommandReplyData._raw.slice(0, 1); // index 0 28 | const firmwareMinorVersionRaw = subcommandReplyData._raw.slice(1, 2); // index 1 29 | const typeRaw = subcommandReplyData._raw.slice(2, 3); // index 2 30 | const macAddressRaw = subcommandReplyData._raw.slice(4, 10); // index 4-9 31 | const spiColorInUsedRaw = subcommandReplyData._raw.slice(11, 12); // index 11 32 | const result = { 33 | firmwareVersion: { 34 | major: firmwareMajorVersionRaw.readUInt8(0), 35 | minor: firmwareMinorVersionRaw.readUInt8(0), 36 | }, 37 | type: ControllerType[typeRaw[0]], 38 | macAddress: macAddressRaw.toString('hex').match(/(.{2})/g).join(':'), 39 | spiColorInUsed: spiColorInUsedRaw[0] === 0x1, 40 | }; 41 | resolve(result); 42 | } 43 | }; 44 | manageHandler('add', handler); 45 | }); 46 | } 47 | exports.requestDeviceInfo = requestDeviceInfo; 48 | /** 49 | * **Subcommand 0x40**: Enable IMU (6-Axis sensor) 50 | * 51 | * **Argument 0x00**: Disable 52 | * 53 | * **Argument 0x01**: Enable 54 | * 55 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x40-enable-imu-6-axis-sensor 56 | */ 57 | function enableIMU(hid, manageHandler, enable) { 58 | const outputReportID = 0x01; 59 | const subcommand = enable 60 | ? [0x40, 0x01] 61 | : [0x40, 0x00]; 62 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 63 | return new Promise((resolve) => { 64 | const handler = (packet) => { 65 | const { inputReportID, subcommandID } = packet; 66 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x40) { 67 | manageHandler('remove', handler); 68 | resolve(); 69 | } 70 | }; 71 | manageHandler('add', handler); 72 | }); 73 | } 74 | exports.enableIMU = enableIMU; 75 | /** 76 | * **Subcommand 0x03**: Set input report mode 77 | * 78 | * **Argument 0x30**: Standard full mode. Pushes current state @60Hz 79 | * 80 | * **Argument 0x3f**: Simple HID mode. Pushes updates with every button press 81 | * 82 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x03-set-input-report-mode 83 | */ 84 | function setInputReportMode(hid, manageHandler, mode) { 85 | const outputReportID = 0x01; 86 | const subcommand = mode === 'standard-full-mode' 87 | ? [0x03, 0x30] 88 | : [0x03, 0x3f]; 89 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 90 | return new Promise((resolve) => { 91 | const handler = (packet) => { 92 | const { inputReportID, subcommandID } = packet; 93 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x03) { 94 | manageHandler('remove', handler); 95 | resolve(); 96 | } 97 | }; 98 | manageHandler('add', handler); 99 | }); 100 | } 101 | exports.setInputReportMode = setInputReportMode; 102 | /** 103 | * **Subcommand 0x48**: Enable vibration 104 | * 105 | * **Argument 0x00**: Disable 106 | * 107 | * **Argument 0x01**: Enable 108 | * 109 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x48-enable-vibration 110 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_notes.md#rumble-data 111 | */ 112 | function enableVibration(hid, manageHandler, enable) { 113 | const outputReportID = 0x01; 114 | const subcommand = enable 115 | ? [0x48, 0x01] 116 | : [0x48, 0x00]; 117 | // TODO: Make more rumble styles in future. 118 | hid.write([outputReportID, 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40, 0x00, ...subcommand]); 119 | return new Promise((resolve) => { 120 | const handler = (packet) => { 121 | const { inputReportID, subcommandID } = packet; 122 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x48) { 123 | manageHandler('remove', handler); 124 | resolve(); 125 | } 126 | }; 127 | manageHandler('add', handler); 128 | }); 129 | } 130 | exports.enableVibration = enableVibration; 131 | //# sourceMappingURL=subcommand-sender.js.map -------------------------------------------------------------------------------- /dist/utils/subcommand-sender.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"subcommand-sender.js","sourceRoot":"","sources":["../../src/utils/subcommand-sender.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ns-joycon", 3 | "version": "0.2.5", 4 | "description": "Extract data from connected Jon-Con of Nintendo Switch.", 5 | "author": "Ze-Hao Wang (Salmon) (https://salmon.tw)", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/wazho/ns-joycon.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/wazho/ns-joycon/issues" 12 | }, 13 | "license": "MIT", 14 | "keywords": [ 15 | "nintendo", 16 | "switch", 17 | "joycon" 18 | ], 19 | "main": "dist/index.js", 20 | "scripts": { 21 | "build": "rimraf -rf dist types && tsc", 22 | "start": "node dist/sample.js", 23 | "dev": "ts-node src/sample.ts", 24 | "test": "jest" 25 | }, 26 | "dependencies": { 27 | "lodash": "^4.17.11", 28 | "node-hid": "^0.7.7", 29 | "rimraf": "^2.6.3" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^24.0.11", 33 | "@types/lodash": "^4.14.123", 34 | "@types/node": "^11.13.4", 35 | "@types/node-hid": "^0.7.0", 36 | "jest": "^24.7.1", 37 | "ts-jest": "^24.0.2", 38 | "ts-node": "^8.0.3", 39 | "typescript": "^3.4.3" 40 | }, 41 | "engines": { 42 | "node": ">=6.0.0" 43 | }, 44 | "types": "./types/index.d.ts" 45 | } 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # **ns-joycon** - Bridge between Joy-Con and Node.js 2 | 3 |

4 | 5 |

6 | 7 | **ns-joycon** controls buffer stream by HID, and extracts the data from accelerometer, gyroscope and HD Rumble. 8 | 9 | This project is an implementation from [dekuNukem/Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering) by Node.js. 10 | 11 | ![Node Limitation](https://img.shields.io/node/v/ns-joycon.svg) [![Build Status](https://travis-ci.org/wazho/ns-joycon.svg?branch=master)](https://travis-ci.org/wazho/ns-joycon) [![Package Downloads](https://img.shields.io/npm/dm/ns-joycon.svg)](https://www.npmjs.com/package/ns-joycon) 12 | 13 | --- 14 | 15 | ## Quick start 16 | 17 | ### Installation 18 | 19 | ```shell 20 | npm install ns-joycon 21 | # If you encounter some problems when `npm install ns-joycon`. 22 | npm install ns-joycon --unsafe-perm 23 | ``` 24 | 25 | ### DEMO 26 | 27 | Checkout the demo project at [wazho/ns-joycon-showcases](https://github.com/wazho/ns-joycon-showcases). 28 | 29 | ### Usage 30 | 31 | Connect your Joy-Con(s) using bluetooth with PC (currently Linux only). 32 | 33 | And then execute program by administrator below (root can access hardware). 34 | 35 | ```typescript 36 | const JoyCon = require('ns-joycon'); // JavaScript 37 | // import * as JoyCon from 'ns-joycon'; // TypeScript or Babel 38 | 39 | JoyCon.findControllers((devices) => { 40 | // When found any device. 41 | devices.forEach(async (device) => { 42 | console.log(`Found a device (${device.meta.serialNumber})`); 43 | // Add a handler for new device. 44 | device.manageHandler('add', (packet) => { 45 | console.log(device.meta.product, packet); 46 | }); 47 | await device.enableIMU(); 48 | }); 49 | }); 50 | ``` 51 | 52 | ### Tests 53 | 54 | ```shell 55 | npm test 56 | ``` 57 | 58 | --- 59 | 60 | ## APIs 61 | 62 | ### **JoyCon.findControllers(callback)** 63 | 64 | Find controllers that are detected. 65 | 66 | * **Arguments** 67 | 68 | `callback` is of the form `callback(devices)` 69 | 70 | ### **device.manageHandler(action, callback)** 71 | 72 | Add a handler to process packet data. 73 | 74 | * **Arguments** 75 | 76 | `action` is `add | remove` 77 | 78 | `callback` is of the form `callback(data)` 79 | 80 | ### **device.requestDeviceInfo()** 81 | 82 | Request Joy-Con to provide its device info. 83 | 84 | * **Return value** 85 | 86 | `Promise` is `{ firmwareVersion, type, macAddress, spiColorInUsed }` 87 | 88 | ### **device.enableIMU()** 89 | 90 | Enable inertial measurement unit (IMU). 91 | 92 | After this script, controller will send **Input Report 0x30** per 15 ms. 93 | 94 | * **Return value** 95 | 96 | `Promise` 97 | 98 | ### **device.disableIMU()** 99 | 100 | Disable inertial measurement unit (IMU). 101 | 102 | * **Return value** 103 | 104 | `Promise` 105 | 106 | ### **device.enableVibration()** 107 | 108 | Enable vibration for a while. 109 | 110 | * **Return value** 111 | 112 | `Promise` 113 | 114 | ### **device.disableVibration()** 115 | 116 | Disable vibration in immediately. 117 | 118 | * **Return value** 119 | 120 | `Promise` 121 | 122 | --- 123 | 124 | ## Extracted data 125 | 126 | Real data from Joy-Con (R) below. 127 | 128 | ``` 129 | 136 | ``` 137 | 138 | ### Input Report ID 139 | 140 | ```jsonc 141 | // inputReportID 142 | { 143 | "_raw": "", 144 | "_hex": ["30"] 145 | } 146 | ``` 147 | 148 | ### Timer 149 | 150 | ```jsonc 151 | // Timer 152 | { 153 | "_raw": "", 154 | "_hex": ["2c"] 155 | } 156 | ``` 157 | 158 | ### Battery Level 159 | 160 | ```jsonc 161 | // batteryLevel 162 | { 163 | "_raw": "", 164 | "_hex": ["4"], 165 | "level": "medium" 166 | } 167 | ``` 168 | 169 | ### Connection Info 170 | 171 | ```jsonc 172 | // connectionInfo 173 | { 174 | "_raw": "", 175 | "_hex": ["e"] 176 | } 177 | ``` 178 | 179 | ### Button Status 180 | 181 | ```jsonc 182 | // buttonStatus 183 | { 184 | "_raw": "", 185 | "_hex": [ "00", "00", "00" ], 186 | "y": false, 187 | "x": false, 188 | "b": false, 189 | "a": false, 190 | "r": false, 191 | "zr": false, 192 | "down": false, 193 | "up": false, 194 | "right": false, 195 | "left": false, 196 | "l": false, 197 | "zl": false, 198 | "sr": false, 199 | "sl": false, 200 | "minus": false, 201 | "plus": false, 202 | "rightStick": false, 203 | "leftStick": false, 204 | "home": false, 205 | "caputure": false, 206 | "chargingGrip": false 207 | } 208 | ``` 209 | 210 | ### Analog Sticks 211 | 212 | #### Stick Left 213 | 214 | ```jsonc 215 | // analogStickLeft 216 | { 217 | "_raw": "", 218 | "_hex": [ "00", "00", "00" ], 219 | "horizontal": 0, 220 | "vertical": 0 221 | } 222 | ``` 223 | 224 | #### Stick Right 225 | 226 | ```jsonc 227 | // analogStickRight 228 | { 229 | "_raw": "", 230 | "_hex": [ "70", "c8", "75" ], 231 | "horizontal": 2160, 232 | "vertical": 1884 233 | } 234 | ``` 235 | 236 | ### Vibrator 237 | 238 | ```jsonc 239 | // vibrator 240 | { 241 | "_raw": "", 242 | "_hex": [ "0a" ] 243 | } 244 | ``` 245 | 246 | ### Accelerometers 247 | 248 | ```jsonc 249 | // accelerometers 250 | [ 251 | { 252 | "x": { 253 | "_raw": "", 254 | "_hex": [ "67", "01" ], 255 | "acc": 0.087596 256 | }, 257 | "y": { 258 | "_raw": "", 259 | "_hex": [ "23", "00" ], 260 | "acc": 0.00854 261 | }, 262 | "z": { 263 | "_raw": "", 264 | "_hex": [ "61", "f0" ], 265 | "acc": -0.975756 266 | } 267 | }, 268 | // Repeat two more, totally collected three times. 269 | ] 270 | ``` 271 | 272 | ### Gyroscopes 273 | 274 | ```jsonc 275 | // gyroscopes 276 | [ 277 | [ 278 | { 279 | "_raw": "", 280 | "_hex": [ "17", "00" ], 281 | "dps": 1.40369, 282 | "rps": 0.003896 283 | }, 284 | { 285 | "_raw": "", 286 | "_hex": [ "ca", "ff" ], 287 | "dps": -3.29562, 288 | "rps": -0.009148 289 | }, 290 | { 291 | "_raw": "", 292 | "_hex": [ "d8", "ff" ], 293 | "dps": -2.4412, 294 | "rps": -0.006776 295 | } 296 | ], 297 | // Repeat two more, totally collected three times. 298 | ] 299 | ``` 300 | 301 | ### [Extra] Actual Accelerometer 302 | 303 | Average of 3 times **accelerometers**. 304 | 305 | ```jsonc 306 | // actualAccelerometer 307 | { 308 | "acc": { 309 | "x": 0.001305, 310 | "y": 0.000137, 311 | "z": -0.014623 312 | } 313 | } 314 | ``` 315 | 316 | ### [Extra] Actual Gyroscope 317 | 318 | Average of 3 times **gyroscopes**. 319 | 320 | ```jsonc 321 | // actualGyroscope 322 | { 323 | "dps": [ 0.02014, -0.049129, -0.035703 ], 324 | "rps": [ 0.000056, -0.000136, -0.000099 ] 325 | } 326 | ``` 327 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Node modules. 2 | import { HID, Device, devices as findDevices } from 'node-hid'; 3 | import { IInputReport0x3f, IInputReport0x21, IInputReport0x30, InputReport } from './models/'; 4 | // Local modules. 5 | import { IDeviceInfo } from './models/subcommand'; 6 | import * as PacketParser from './utils/packet-parser'; 7 | import * as SubcommandSender from './utils/subcommand-sender'; 8 | 9 | function getType(product?: string) { 10 | if (product === undefined) { 11 | return 'unknown'; 12 | } 13 | 14 | switch (true) { 15 | case /Pro Controller/i.test(product): 16 | return 'pro-controller'; 17 | case /Joy-Con \([LR]\)/i.test(product): 18 | return 'joy-con'; 19 | default: 20 | return 'unknown'; 21 | } 22 | } 23 | 24 | class NsSwitchHID { 25 | private vendorId: number; 26 | private productId: number; 27 | private serialNumber?: string; 28 | private product?: string; 29 | private type: 'joy-con' | 'pro-controller' | 'unknown'; 30 | private path?: string; 31 | private usage?: number; 32 | private hid: HID; 33 | private listeners: Array<(packet: InputReport) => void> = []; 34 | 35 | constructor(device: Device) { 36 | this.vendorId = device.vendorId; 37 | this.productId = device.productId; 38 | this.serialNumber = device.serialNumber; 39 | this.product = device.product; 40 | this.type = getType(device.product); 41 | this.path = device.path; 42 | this.usage = device.usage; 43 | this.hid = new HID(device.vendorId, device.productId); 44 | // System handler. 45 | if (this.type === 'joy-con') { 46 | this.activateJoyConStream(); 47 | } 48 | } 49 | 50 | public get meta() { 51 | return { 52 | vendorId: this.vendorId, 53 | productId: this.productId, 54 | serialNumber: this.serialNumber, 55 | product: this.product, 56 | type: this.type, 57 | path: this.path, 58 | usage: this.usage, 59 | }; 60 | } 61 | 62 | /** 63 | * Add / remove a handler to recevice packets when device send streaming data. 64 | */ 65 | public manageHandler(action: 'add' | 'remove', callback: (packet: InputReport) => void) { 66 | if (action === 'add') { 67 | this.listeners.push(callback); 68 | } else { 69 | this.listeners = this.listeners.filter((listener) => listener !== callback); 70 | } 71 | } 72 | 73 | /** 74 | * Request device info to Jon-Con. 75 | */ 76 | public async requestDeviceInfo() { 77 | if (this.type === 'joy-con') { 78 | const manageHandler = this.manageHandler.bind(this); 79 | const deviceInfo: IDeviceInfo = await SubcommandSender.requestDeviceInfo(this.hid, manageHandler); 80 | 81 | return deviceInfo; 82 | } 83 | } 84 | 85 | /** 86 | * Enable IMU data will make Jon-Con sends **Input Report 0x30**. 87 | */ 88 | public async enableIMU() { 89 | if (this.type === 'joy-con') { 90 | await SubcommandSender.enableIMU(this.hid, this.manageHandler.bind(this), true); 91 | await SubcommandSender.setInputReportMode(this.hid, this.manageHandler.bind(this), 'standard-full-mode'); 92 | 93 | console.info(`Device ${this.product} (${this.serialNumber}) enabled IMU.`); 94 | } 95 | } 96 | 97 | /** 98 | * Disable IMU data will cancel Jon-Con to send **Input Report 0x30**. 99 | */ 100 | public async disableIMU() { 101 | if (this.type === 'joy-con') { 102 | await SubcommandSender.enableIMU(this.hid, this.manageHandler.bind(this), false); 103 | await SubcommandSender.setInputReportMode(this.hid, this.manageHandler.bind(this), 'simple-hid-mode'); 104 | 105 | console.info(`Device ${this.product} (${this.serialNumber}) disabled IMU.`); 106 | } 107 | } 108 | 109 | /** 110 | * Enable Jon-Con's vibration. 111 | */ 112 | public async enableVibration() { 113 | if (this.type === 'joy-con') { 114 | await SubcommandSender.enableVibration(this.hid, this.manageHandler.bind(this), true); 115 | 116 | console.info(`Device ${this.product} (${this.serialNumber}) enabled vibration.`); 117 | } 118 | } 119 | 120 | /** 121 | * Disable Jon-Con's vibration. 122 | */ 123 | public async disableVibration() { 124 | if (this.type === 'joy-con') { 125 | await SubcommandSender.enableVibration(this.hid, this.manageHandler.bind(this), false); 126 | 127 | console.info(`Device ${this.product} (${this.serialNumber}) disabled vibration.`); 128 | } 129 | } 130 | 131 | private async activateJoyConStream() { 132 | this.hid.on('data', (rawData: Buffer) => { 133 | const data = rawData.toString('hex').match(/.{2}/g); 134 | 135 | if (!data) { return; } 136 | 137 | const inputReportID = parseInt(data[0], 16); 138 | 139 | let packet: Partial = { 140 | inputReportID: PacketParser.parseInputReportID(rawData, data), 141 | }; 142 | 143 | switch (inputReportID) { 144 | case 0x3f: { 145 | packet = { 146 | ...packet, 147 | buttonStatus: PacketParser.parseButtonStatus(rawData, data), 148 | analogStick: PacketParser.parseAnalogStick(rawData, data), 149 | filter: PacketParser.parseFilter(rawData, data), 150 | } as IInputReport0x3f; 151 | break; 152 | } 153 | case 0x21: 154 | case 0x30: { 155 | packet = { 156 | ...packet, 157 | timer: PacketParser.parseTimer(rawData, data), 158 | batteryLevel: PacketParser.parseBatteryLevel(rawData, data), 159 | connectionInfo: PacketParser.parseConnectionInfo(rawData, data), 160 | buttonStatus: PacketParser.parseCompleteButtonStatus(rawData, data), 161 | analogStickLeft: PacketParser.parseAnalogStickLeft(rawData, data), 162 | analogStickRight: PacketParser.parseAnalogStickRight(rawData, data), 163 | vibrator: PacketParser.parseVibrator(rawData, data), 164 | }; 165 | 166 | if (inputReportID === 0x21) { 167 | packet = { 168 | ...packet, 169 | ack: PacketParser.parseAck(rawData, data), 170 | subcommandID: PacketParser.parseSubcommandID(rawData, data), 171 | subcommandReplyData: PacketParser.parseSubcommandReplyData(rawData, data), 172 | } as IInputReport0x21; 173 | } 174 | 175 | if (inputReportID === 0x30) { 176 | const accelerometers = PacketParser.parseAccelerometers(rawData, data); 177 | const gyroscopes = PacketParser.parseGyroscopes(rawData, data); 178 | 179 | packet = { 180 | ...packet, 181 | accelerometers, 182 | gyroscopes, 183 | actualAccelerometer: { 184 | acc: PacketParser.calculateActualAccelerometer(accelerometers.map(a => [a.x.acc, a.y.acc, a.z.acc])), 185 | }, 186 | actualGyroscope: { 187 | dps: PacketParser.calculateActualGyroscope(gyroscopes.map(g => g.map(v => v.dps))), 188 | rps: PacketParser.calculateActualGyroscope(gyroscopes.map(g => g.map(v => v.rps))), 189 | }, 190 | } as IInputReport0x30; 191 | } 192 | break; 193 | } 194 | } 195 | 196 | // Broadcast. 197 | this.listeners.forEach((listener) => listener(packet as InputReport)); 198 | }); 199 | 200 | this.hid.on('error', (error) => { 201 | console.warn({ 202 | ...this.meta, 203 | error, 204 | }); 205 | }); 206 | } 207 | } 208 | 209 | export function findControllers(callback: (controllers: NsSwitchHID[]) => void) { 210 | let deviceList = new Set(); 211 | 212 | const work = () => { 213 | const tempDeviceList = new Set(); 214 | const devices = findDevices().reduce((prev, d) => { 215 | if (getType(d.product) !== 'unknown') { 216 | prev.push(new NsSwitchHID(d)); 217 | } 218 | 219 | return prev; 220 | }, [] as NsSwitchHID[]); 221 | 222 | devices.forEach((d) => { 223 | const distinctId = `${d.meta.vendorId},${d.meta.productId},${d.meta.type}`; 224 | tempDeviceList.add(distinctId); 225 | 226 | if (!deviceList.has(distinctId)) { 227 | callback(devices); 228 | } 229 | }); 230 | 231 | deviceList = tempDeviceList; 232 | }; 233 | 234 | work(); 235 | setInterval(work, 1000); 236 | } 237 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | interface IPacketBuffer { 2 | _raw: Buffer; 3 | _hex: String | String[]; 4 | }; 5 | 6 | export type BatteryLevel = 'full' | 'medium' | 'low' | 'critical' | 'empty' | 'charging'; 7 | 8 | interface IBatteryLevel extends IPacketBuffer { 9 | level: BatteryLevel; 10 | }; 11 | 12 | interface IButtonStatus extends IPacketBuffer { 13 | // Byte 3 (Right Joy-Con) 14 | y: Boolean; 15 | x: Boolean; 16 | b: Boolean; 17 | a: Boolean; 18 | r: Boolean; 19 | zr: Boolean; 20 | // Byte 5 (Left Joy-Con) 21 | down: Boolean; 22 | up: Boolean; 23 | right: Boolean; 24 | left: Boolean; 25 | l: Boolean; 26 | zl: Boolean; 27 | // Byte 3,5 (Shared) 28 | sr: Boolean; 29 | sl: Boolean; 30 | // Byte 4 (Shared) 31 | minus: Boolean; 32 | plus: Boolean; 33 | rightStick: Boolean; 34 | leftStick: Boolean; 35 | home: Boolean; 36 | caputure: Boolean; 37 | chargingGrip: Boolean; 38 | } 39 | 40 | interface IAnalogStick extends IPacketBuffer { 41 | horizontal: number; 42 | vertical: number; 43 | } 44 | 45 | interface IStandardInputReport { 46 | inputReportID: IPacketBuffer; 47 | timer: IPacketBuffer; 48 | batteryLevel: IBatteryLevel; 49 | connectionInfo: IPacketBuffer; 50 | buttonStatus: IButtonStatus; 51 | analogStickLeft: IAnalogStick; 52 | analogStickRight: IAnalogStick; 53 | vibrator: IPacketBuffer; 54 | }; 55 | 56 | export type Accelerometer = { 57 | x: IPacketBuffer & { acc: number }; 58 | y: IPacketBuffer & { acc: number }; 59 | z: IPacketBuffer & { acc: number }; 60 | }; 61 | 62 | export type Gyroscope = Array; 66 | 67 | export interface IInputReport0x21 extends IStandardInputReport { 68 | ack: IPacketBuffer; 69 | subcommandID: IPacketBuffer; 70 | subcommandReplyData: IPacketBuffer; 71 | }; 72 | 73 | export interface IInputReport0x30 extends IStandardInputReport { 74 | accelerometers: Accelerometer[]; 75 | gyroscopes: Gyroscope[]; 76 | actualAccelerometer: { 77 | acc: { 78 | x: number; 79 | y: number; 80 | z: number; 81 | }; 82 | }; 83 | actualGyroscope: { 84 | dps: number[]; 85 | rps: number[]; 86 | }; 87 | }; 88 | 89 | export interface IInputReport0x3f { 90 | inputReportID: IPacketBuffer; 91 | buttonStatus: IPacketBuffer; 92 | analogStick: IPacketBuffer; 93 | filter: IPacketBuffer; 94 | }; 95 | 96 | export type InputReport = IInputReport0x3f | IInputReport0x21 | IInputReport0x30; 97 | -------------------------------------------------------------------------------- /src/models/subcommand.ts: -------------------------------------------------------------------------------- 1 | export interface IDeviceInfo { 2 | firmwareVersion: { 3 | major: number; 4 | minor: number; 5 | }; 6 | type: string; 7 | macAddress: string; 8 | spiColorInUsed: boolean; 9 | } 10 | 11 | export type InputReportMode = 'standard-full-mode' | 'simple-hid-mode'; 12 | -------------------------------------------------------------------------------- /src/sample.ts: -------------------------------------------------------------------------------- 1 | // Local modules. 2 | import * as JoyCon from './index'; 3 | 4 | JoyCon.findControllers((devices) => { 5 | // When found any device. 6 | devices.forEach(async (device) => { 7 | console.log(`Found a device (${device.meta.serialNumber})`); 8 | 9 | // Add a handler for new device. 10 | device.manageHandler('add', (packet) => { 11 | console.log(device.meta.product, packet); 12 | }); 13 | 14 | // const deviceInfo = await device.requestDeviceInfo(); 15 | await device.enableIMU(); 16 | // await device.disableIMU(); 17 | // await device.enableVibration(); 18 | // await device.disableVibration(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/packet-parser.ts: -------------------------------------------------------------------------------- 1 | // Node modules. 2 | import mean from 'lodash/mean'; 3 | // Local modules. 4 | import { BatteryLevel } from '../models/'; 5 | 6 | function calculateBatteryLevel(value: string) { 7 | let level: BatteryLevel; 8 | 9 | switch (value) { 10 | case '8': 11 | level = 'full'; 12 | break; 13 | case '4': 14 | level = 'medium'; 15 | break; 16 | case '2': 17 | level = 'low'; 18 | break; 19 | case '1': 20 | level = 'critical'; 21 | break; 22 | case '0': 23 | level = 'empty'; 24 | break; 25 | default: 26 | level = 'charging'; 27 | }; 28 | 29 | return level; 30 | } 31 | 32 | /** 33 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#accelerometer---acceleration-in-g) 34 | * @param {Buffer} value 35 | */ 36 | function toAcceleration(value: Buffer) { 37 | return parseFloat((0.000244 * value.readInt16LE(0)).toFixed(6)); 38 | } 39 | 40 | /** 41 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#gyroscope---rotation-in-degreess---dps) 42 | * @param {Buffer} value 43 | */ 44 | function toDegreesPerSecond(value: Buffer) { 45 | return parseFloat((0.06103 * value.readInt16LE(0)).toFixed(6)); 46 | } 47 | 48 | /** 49 | * Check on [Documetation of Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#gyroscope---rotation-in-revolutionss) 50 | * @param {Buffer} value 51 | */ 52 | function toRevolutionsPerSecond(value: Buffer) { 53 | return parseFloat((0.0001694 * value.readInt16LE(0)).toFixed(6)); 54 | } 55 | 56 | export function parseInputReportID(rawData: Buffer, data: RegExpMatchArray) { 57 | const inputReportID = { 58 | _raw: rawData.slice(0, 1), // index 0 59 | _hex: data.slice(0, 1), 60 | }; 61 | 62 | return inputReportID; 63 | } 64 | 65 | export function parseTimer(rawData: Buffer, data: RegExpMatchArray) { 66 | const timer = { 67 | _raw: rawData.slice(1, 2), // index 1 68 | _hex: data.slice(1, 2), 69 | }; 70 | 71 | return timer; 72 | } 73 | 74 | export function parseBatteryLevel(rawData: Buffer, data: RegExpMatchArray) { 75 | const batteryLevel = { 76 | _raw: rawData.slice(2, 3), // high nibble 77 | _hex: data[2][0], 78 | level: calculateBatteryLevel(data[2][0]), 79 | }; 80 | 81 | return batteryLevel; 82 | } 83 | 84 | export function parseConnectionInfo(rawData: Buffer, data: RegExpMatchArray) { 85 | const connectionInfo = { 86 | _raw: rawData.slice(2, 3), // low nibble 87 | _hex: data[2][1], 88 | }; 89 | 90 | return connectionInfo; 91 | } 92 | 93 | export function parseButtonStatus(rawData: Buffer, data: RegExpMatchArray) { 94 | const buttonStatus = { 95 | _raw: rawData.slice(1, 3), // index 1,2 96 | _hex: data.slice(1, 3), 97 | }; 98 | 99 | return buttonStatus; 100 | } 101 | 102 | export function parseCompleteButtonStatus(rawData: Buffer, data: RegExpMatchArray) { 103 | const buttonStatus = { 104 | _raw: rawData.slice(3, 6), // index 3,4,5 105 | _hex: data.slice(3, 6), 106 | // Byte 3 (Right Joy-Con) 107 | y: Boolean(0x01 & rawData[3]), 108 | x: Boolean(0x02 & rawData[3]), 109 | b: Boolean(0x04 & rawData[3]), 110 | a: Boolean(0x08 & rawData[3]), 111 | r: Boolean(0x40 & rawData[3]), 112 | zr: Boolean(0x80 & rawData[3]), 113 | // Byte 5 (Left Joy-Con) 114 | down: Boolean(0x01 & rawData[5]), 115 | up: Boolean(0x02 & rawData[5]), 116 | right: Boolean(0x04 & rawData[5]), 117 | left: Boolean(0x08 & rawData[5]), 118 | l: Boolean(0x40 & rawData[5]), 119 | zl: Boolean(0x80 & rawData[5]), 120 | // Byte 3,5 (Shared) 121 | sr: Boolean(0x10 & rawData[3]) || Boolean(0x10 & rawData[5]), 122 | sl: Boolean(0x20 & rawData[3]) || Boolean(0x20 & rawData[5]), 123 | // Byte 4 (Shared) 124 | minus: Boolean(0x01 & rawData[4]), 125 | plus: Boolean(0x02 & rawData[4]), 126 | rightStick: Boolean(0x04 & rawData[4]), 127 | leftStick: Boolean(0x08 & rawData[4]), 128 | home: Boolean(0x10 & rawData[4]), 129 | caputure: Boolean(0x20 & rawData[4]), 130 | chargingGrip: Boolean(0x80 & rawData[4]), 131 | }; 132 | 133 | return buttonStatus; 134 | } 135 | 136 | export function parseAnalogStick(rawData: Buffer, data: RegExpMatchArray) { 137 | const analogStick = { 138 | _raw: rawData.slice(3, 4), // index 3 139 | _hex: data.slice(3, 4), 140 | }; 141 | 142 | return analogStick; 143 | } 144 | 145 | export function parseAnalogStickLeft(rawData: Buffer, data: RegExpMatchArray) { 146 | const analogStickLeft = { 147 | _raw: rawData.slice(6, 9), // index 6,7,8 148 | _hex: data.slice(6, 9), 149 | horizontal: rawData[6] | ((rawData[7] & 0xF) << 8), 150 | vertical: (rawData[7] >> 4) | (rawData[8] << 4), 151 | }; 152 | 153 | return analogStickLeft; 154 | } 155 | 156 | export function parseAnalogStickRight(rawData: Buffer, data: RegExpMatchArray) { 157 | const analogStickRight = { 158 | _raw: rawData.slice(9, 12), // index 9,10,11 159 | _hex: data.slice(9, 12), 160 | horizontal: rawData[9] | ((rawData[10] & 0xF) << 8), 161 | vertical: (rawData[10] >> 4) | (rawData[11] << 4), 162 | }; 163 | 164 | return analogStickRight; 165 | } 166 | 167 | export function parseFilter(rawData: Buffer, data: RegExpMatchArray) { 168 | const filter = { 169 | _raw: rawData.slice(4), // index 4 ~ 170 | _hex: data.slice(4), 171 | }; 172 | 173 | return filter; 174 | } 175 | 176 | export function parseVibrator(rawData: Buffer, data: RegExpMatchArray) { 177 | const vibrator = { 178 | _raw: rawData.slice(12, 13), // index 12 179 | _hex: data.slice(12, 13), 180 | }; 181 | 182 | return vibrator; 183 | } 184 | 185 | export function parseAck(rawData: Buffer, data: RegExpMatchArray) { 186 | const ack = { 187 | _raw: rawData.slice(13, 14), // index 13 188 | _hex: data.slice(13, 14), 189 | }; 190 | 191 | return ack; 192 | } 193 | 194 | export function parseSubcommandID(rawData: Buffer, data: RegExpMatchArray) { 195 | const subcommandID = { 196 | _raw: rawData.slice(14, 15), // index 14 197 | _hex: data.slice(14, 15), 198 | }; 199 | 200 | return subcommandID; 201 | } 202 | 203 | export function parseSubcommandReplyData(rawData: Buffer, data: RegExpMatchArray) { 204 | const subcommandReplyData = { 205 | _raw: rawData.slice(15), // index 15 ~ 206 | _hex: data.slice(15), 207 | }; 208 | 209 | return subcommandReplyData; 210 | } 211 | 212 | export function parseAccelerometers(rawData: Buffer, data: RegExpMatchArray) { 213 | const accelerometers = [ 214 | { 215 | x: { 216 | _raw: rawData.slice(13, 15), // index 13,14 217 | _hex: data.slice(13, 15), 218 | acc: toAcceleration(rawData.slice(13, 15)), 219 | }, 220 | y: { 221 | _raw: rawData.slice(15, 17), // index 15,16 222 | _hex: data.slice(15, 17), 223 | acc: toAcceleration(rawData.slice(15, 17)), 224 | }, 225 | z: { 226 | _raw: rawData.slice(17, 19), // index 17,18 227 | _hex: data.slice(17, 19), 228 | acc: toAcceleration(rawData.slice(17, 19)), 229 | }, 230 | }, 231 | { 232 | x: { 233 | _raw: rawData.slice(25, 27), // index 25,26 234 | _hex: data.slice(25, 27), 235 | acc: toAcceleration(rawData.slice(25, 27)), 236 | }, 237 | y: { 238 | _raw: rawData.slice(27, 29), // index 27,28 239 | _hex: data.slice(27, 29), 240 | acc: toAcceleration(rawData.slice(27, 29)), 241 | }, 242 | z: { 243 | _raw: rawData.slice(29, 31), // index 29,30 244 | _hex: data.slice(29, 31), 245 | acc: toAcceleration(rawData.slice(29, 31)), 246 | }, 247 | }, 248 | { 249 | x: { 250 | _raw: rawData.slice(37, 39), // index 37,38 251 | _hex: data.slice(37, 39), 252 | acc: toAcceleration(rawData.slice(37, 39)), 253 | }, 254 | y: { 255 | _raw: rawData.slice(39, 41), // index 39,40 256 | _hex: data.slice(39, 41), 257 | acc: toAcceleration(rawData.slice(39, 41)), 258 | }, 259 | z: { 260 | _raw: rawData.slice(41, 43), // index 41,42 261 | _hex: data.slice(41, 43), 262 | acc: toAcceleration(rawData.slice(41, 43)), 263 | }, 264 | }, 265 | ]; 266 | 267 | return accelerometers; 268 | } 269 | 270 | export function parseGyroscopes(rawData: Buffer, data: RegExpMatchArray) { 271 | const gyroscopes = [ 272 | [ 273 | { 274 | _raw: rawData.slice(19, 21), // index 19,20 275 | _hex: data.slice(19, 21), 276 | dps: toDegreesPerSecond(rawData.slice(19, 21)), 277 | rps: toRevolutionsPerSecond(rawData.slice(19, 21)), 278 | }, 279 | { 280 | _raw: rawData.slice(21, 23), // index 21,22 281 | _hex: data.slice(21, 23), 282 | dps: toDegreesPerSecond(rawData.slice(21, 23)), 283 | rps: toRevolutionsPerSecond(rawData.slice(21, 23)), 284 | }, 285 | { 286 | _raw: rawData.slice(23, 25), // index 23,24 287 | _hex: data.slice(23, 25), 288 | dps: toDegreesPerSecond(rawData.slice(23, 25)), 289 | rps: toRevolutionsPerSecond(rawData.slice(23, 25)), 290 | }, 291 | ], 292 | [ 293 | { 294 | _raw: rawData.slice(31, 33), // index 31,32 295 | _hex: data.slice(31, 33), 296 | dps: toDegreesPerSecond(rawData.slice(31, 33)), 297 | rps: toRevolutionsPerSecond(rawData.slice(31, 33)), 298 | }, 299 | { 300 | _raw: rawData.slice(33, 35), // index 33,34 301 | _hex: data.slice(33, 35), 302 | dps: toDegreesPerSecond(rawData.slice(33, 35)), 303 | rps: toRevolutionsPerSecond(rawData.slice(33, 35)), 304 | }, 305 | { 306 | _raw: rawData.slice(35, 37), // index 35,36 307 | _hex: data.slice(35, 37), 308 | dps: toDegreesPerSecond(rawData.slice(35, 37)), 309 | rps: toRevolutionsPerSecond(rawData.slice(35, 37)), 310 | }, 311 | ], 312 | [ 313 | { 314 | _raw: rawData.slice(43, 45), // index 43,44 315 | _hex: data.slice(43, 45), 316 | dps: toDegreesPerSecond(rawData.slice(43, 45)), 317 | rps: toRevolutionsPerSecond(rawData.slice(43, 45)), 318 | }, 319 | { 320 | _raw: rawData.slice(45, 47), // index 45,46 321 | _hex: data.slice(45, 47), 322 | dps: toDegreesPerSecond(rawData.slice(45, 47)), 323 | rps: toRevolutionsPerSecond(rawData.slice(45, 47)), 324 | }, 325 | { 326 | _raw: rawData.slice(47, 49), // index 47,48 327 | _hex: data.slice(47, 49), 328 | dps: toDegreesPerSecond(rawData.slice(47, 49)), 329 | rps: toRevolutionsPerSecond(rawData.slice(47, 49)), 330 | }, 331 | ], 332 | ]; 333 | 334 | return gyroscopes; 335 | } 336 | 337 | export function calculateActualAccelerometer(accelerometers: number[][]) { 338 | const elapsedTime = 0.005 * accelerometers.length; // Spent 5ms to collect each data. 339 | 340 | const actualAccelerometer = { 341 | x: parseFloat((mean(accelerometers.map(g => g[0])) * elapsedTime).toFixed(6)), 342 | y: parseFloat((mean(accelerometers.map(g => g[1])) * elapsedTime).toFixed(6)), 343 | z: parseFloat((mean(accelerometers.map(g => g[2])) * elapsedTime).toFixed(6)), 344 | }; 345 | 346 | return actualAccelerometer; 347 | } 348 | 349 | export function calculateActualGyroscope(gyroscopes: number[][]) { 350 | const elapsedTime = 0.005 * gyroscopes.length; // Spent 5ms to collect each data. 351 | 352 | const actualGyroscopes = [ 353 | mean(gyroscopes.map(g => g[0])), 354 | mean(gyroscopes.map(g => g[1])), 355 | mean(gyroscopes.map(g => g[2])), 356 | ].map(v => parseFloat((v * elapsedTime).toFixed(6))); 357 | 358 | return actualGyroscopes; 359 | } 360 | -------------------------------------------------------------------------------- /src/utils/subcommand-sender.ts: -------------------------------------------------------------------------------- 1 | // Node modules. 2 | import { HID } from 'node-hid'; 3 | // Local modules. 4 | import { IInputReport0x21 } from '../models'; 5 | import { IDeviceInfo, InputReportMode } from '../models/subcommand'; 6 | 7 | // Subcommand format: 8 | // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_notes.md#output-0x01 9 | 10 | enum ControllerType { 11 | 'Left Joy-Con' = 0x1, 12 | 'Right Joy-Con' = 0x2, 13 | 'Pro Controller' = 0x3, 14 | } 15 | 16 | /** 17 | * **Subcommand 0x02**: Request device info 18 | * 19 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x02-request-device-info 20 | */ 21 | export function requestDeviceInfo(hid: HID, manageHandler: Function) { 22 | const outputReportID = 0x01; 23 | const subcommand = [0x02]; 24 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 25 | 26 | return new Promise((resolve) => { 27 | const handler = (packet: IInputReport0x21) => { 28 | const { inputReportID, subcommandID, subcommandReplyData } = packet; 29 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x02) { 30 | // Remove the handler first. 31 | manageHandler('remove', handler); 32 | 33 | // Parse the packet. 34 | const firmwareMajorVersionRaw = subcommandReplyData._raw.slice(0, 1) // index 0 35 | const firmwareMinorVersionRaw = subcommandReplyData._raw.slice(1, 2) // index 1 36 | const typeRaw = subcommandReplyData._raw.slice(2, 3) // index 2 37 | const macAddressRaw = subcommandReplyData._raw.slice(4, 10) // index 4-9 38 | const spiColorInUsedRaw = subcommandReplyData._raw.slice(11, 12) // index 11 39 | 40 | const result = { 41 | firmwareVersion: { 42 | major: firmwareMajorVersionRaw.readUInt8(0), 43 | minor: firmwareMinorVersionRaw.readUInt8(0), 44 | }, 45 | type: ControllerType[typeRaw[0]], 46 | macAddress: macAddressRaw.toString('hex').match(/(.{2})/g)!.join(':'), 47 | spiColorInUsed: spiColorInUsedRaw[0] === 0x1, 48 | }; 49 | 50 | resolve(result); 51 | } 52 | }; 53 | 54 | manageHandler('add', handler); 55 | }); 56 | } 57 | 58 | /** 59 | * **Subcommand 0x40**: Enable IMU (6-Axis sensor) 60 | * 61 | * **Argument 0x00**: Disable 62 | * 63 | * **Argument 0x01**: Enable 64 | * 65 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x40-enable-imu-6-axis-sensor 66 | */ 67 | export function enableIMU(hid: HID, manageHandler: Function, enable: boolean) { 68 | const outputReportID = 0x01; 69 | const subcommand = enable 70 | ? [0x40, 0x01] 71 | : [0x40, 0x00]; 72 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 73 | 74 | return new Promise((resolve) => { 75 | const handler = (packet: IInputReport0x21) => { 76 | const { inputReportID, subcommandID } = packet; 77 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x40) { 78 | manageHandler('remove', handler); 79 | resolve(); 80 | } 81 | }; 82 | 83 | manageHandler('add', handler); 84 | }); 85 | } 86 | 87 | /** 88 | * **Subcommand 0x03**: Set input report mode 89 | * 90 | * **Argument 0x30**: Standard full mode. Pushes current state @60Hz 91 | * 92 | * **Argument 0x3f**: Simple HID mode. Pushes updates with every button press 93 | * 94 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x03-set-input-report-mode 95 | */ 96 | export function setInputReportMode(hid: HID, manageHandler: Function, mode: InputReportMode) { 97 | const outputReportID = 0x01; 98 | const subcommand = mode === 'standard-full-mode' 99 | ? [0x03, 0x30] 100 | : [0x03, 0x3f]; 101 | hid.write([outputReportID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...subcommand]); 102 | 103 | return new Promise((resolve) => { 104 | const handler = (packet: IInputReport0x21) => { 105 | const { inputReportID, subcommandID } = packet; 106 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x03) { 107 | manageHandler('remove', handler); 108 | resolve(); 109 | } 110 | }; 111 | 112 | manageHandler('add', handler); 113 | }); 114 | } 115 | 116 | /** 117 | * **Subcommand 0x48**: Enable vibration 118 | * 119 | * **Argument 0x00**: Disable 120 | * 121 | * **Argument 0x01**: Enable 122 | * 123 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x48-enable-vibration 124 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_notes.md#rumble-data 125 | */ 126 | export function enableVibration(hid: HID, manageHandler: Function, enable: boolean) { 127 | const outputReportID = 0x01; 128 | const subcommand = enable 129 | ? [0x48, 0x01] 130 | : [0x48, 0x00]; 131 | // TODO: Make more rumble styles in future. 132 | hid.write([outputReportID, 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40, 0x00, ...subcommand]); 133 | 134 | return new Promise((resolve) => { 135 | const handler = (packet: IInputReport0x21) => { 136 | const { inputReportID, subcommandID } = packet; 137 | if (inputReportID._raw[0] === 0x21 && subcommandID._raw[0] === 0x48) { 138 | manageHandler('remove', handler); 139 | resolve(); 140 | } 141 | }; 142 | 143 | manageHandler('add', handler); 144 | }); 145 | } 146 | -------------------------------------------------------------------------------- /tests/data/parseAccelerometers.1.out.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x": { 4 | "_raw": { 5 | "type": "Buffer", 6 | "data": [ 7 | 26, 8 | 0 9 | ] 10 | }, 11 | "_hex": [ 12 | "1a", 13 | "00" 14 | ], 15 | "acc": 0.006344 16 | }, 17 | "y": { 18 | "_raw": { 19 | "type": "Buffer", 20 | "data": [ 21 | 241, 22 | 9 23 | ] 24 | }, 25 | "_hex": [ 26 | "f1", 27 | "09" 28 | ], 29 | "acc": 0.62098 30 | }, 31 | "z": { 32 | "_raw": { 33 | "type": "Buffer", 34 | "data": [ 35 | 213, 36 | 243 37 | ] 38 | }, 39 | "_hex": [ 40 | "d5", 41 | "f3" 42 | ], 43 | "acc": -0.76006 44 | } 45 | }, 46 | { 47 | "x": { 48 | "_raw": { 49 | "type": "Buffer", 50 | "data": [ 51 | 23, 52 | 0 53 | ] 54 | }, 55 | "_hex": [ 56 | "17", 57 | "00" 58 | ], 59 | "acc": 0.005612 60 | }, 61 | "y": { 62 | "_raw": { 63 | "type": "Buffer", 64 | "data": [ 65 | 222, 66 | 9 67 | ] 68 | }, 69 | "_hex": [ 70 | "de", 71 | "09" 72 | ], 73 | "acc": 0.616344 74 | }, 75 | "z": { 76 | "_raw": { 77 | "type": "Buffer", 78 | "data": [ 79 | 199, 80 | 243 81 | ] 82 | }, 83 | "_hex": [ 84 | "c7", 85 | "f3" 86 | ], 87 | "acc": -0.763476 88 | } 89 | }, 90 | { 91 | "x": { 92 | "_raw": { 93 | "type": "Buffer", 94 | "data": [ 95 | 10, 96 | 0 97 | ] 98 | }, 99 | "_hex": [ 100 | "0a", 101 | "00" 102 | ], 103 | "acc": 0.00244 104 | }, 105 | "y": { 106 | "_raw": { 107 | "type": "Buffer", 108 | "data": [ 109 | 205, 110 | 9 111 | ] 112 | }, 113 | "_hex": [ 114 | "cd", 115 | "09" 116 | ], 117 | "acc": 0.612196 118 | }, 119 | "z": { 120 | "_raw": { 121 | "type": "Buffer", 122 | "data": [ 123 | 199, 124 | 243 125 | ] 126 | }, 127 | "_hex": [ 128 | "c7", 129 | "f3" 130 | ], 131 | "acc": -0.763476 132 | } 133 | } 134 | ] -------------------------------------------------------------------------------- /tests/data/parseCompleteButtonStatus.1.out.json: -------------------------------------------------------------------------------- 1 | { 2 | "_raw": { 3 | "type": "Buffer", 4 | "data": [ 5 | 10, 6 | 16, 7 | 0 8 | ] 9 | }, 10 | "_hex": [ 11 | "0a", 12 | "10", 13 | "00" 14 | ], 15 | "y": false, 16 | "x": true, 17 | "b": false, 18 | "a": true, 19 | "r": false, 20 | "zr": false, 21 | "down": false, 22 | "up": false, 23 | "right": false, 24 | "left": false, 25 | "l": false, 26 | "zl": false, 27 | "sr": false, 28 | "sl": false, 29 | "minus": false, 30 | "plus": false, 31 | "rightStick": false, 32 | "leftStick": false, 33 | "home": true, 34 | "caputure": false, 35 | "chargingGrip": false 36 | } -------------------------------------------------------------------------------- /tests/data/parseGyroscopes.1.out.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | { 4 | "_raw": { 5 | "type": "Buffer", 6 | "data": [ 7 | 180, 8 | 255 9 | ] 10 | }, 11 | "_hex": [ 12 | "b4", 13 | "ff" 14 | ], 15 | "dps": -4.63828, 16 | "rps": -0.012874 17 | }, 18 | { 19 | "_raw": { 20 | "type": "Buffer", 21 | "data": [ 22 | 91, 23 | 255 24 | ] 25 | }, 26 | "_hex": [ 27 | "5b", 28 | "ff" 29 | ], 30 | "dps": -10.06995, 31 | "rps": -0.027951 32 | }, 33 | { 34 | "_raw": { 35 | "type": "Buffer", 36 | "data": [ 37 | 144, 38 | 255 39 | ] 40 | }, 41 | "_hex": [ 42 | "90", 43 | "ff" 44 | ], 45 | "dps": -6.83536, 46 | "rps": -0.018973 47 | } 48 | ], 49 | [ 50 | { 51 | "_raw": { 52 | "type": "Buffer", 53 | "data": [ 54 | 175, 55 | 255 56 | ] 57 | }, 58 | "_hex": [ 59 | "af", 60 | "ff" 61 | ], 62 | "dps": -4.94343, 63 | "rps": -0.013721 64 | }, 65 | { 66 | "_raw": { 67 | "type": "Buffer", 68 | "data": [ 69 | 94, 70 | 255 71 | ] 72 | }, 73 | "_hex": [ 74 | "5e", 75 | "ff" 76 | ], 77 | "dps": -9.88686, 78 | "rps": -0.027443 79 | }, 80 | { 81 | "_raw": { 82 | "type": "Buffer", 83 | "data": [ 84 | 143, 85 | 255 86 | ] 87 | }, 88 | "_hex": [ 89 | "8f", 90 | "ff" 91 | ], 92 | "dps": -6.89639, 93 | "rps": -0.019142 94 | } 95 | ], 96 | [ 97 | { 98 | "_raw": { 99 | "type": "Buffer", 100 | "data": [ 101 | 184, 102 | 255 103 | ] 104 | }, 105 | "_hex": [ 106 | "b8", 107 | "ff" 108 | ], 109 | "dps": -4.39416, 110 | "rps": -0.012197 111 | }, 112 | { 113 | "_raw": { 114 | "type": "Buffer", 115 | "data": [ 116 | 101, 117 | 255 118 | ] 119 | }, 120 | "_hex": [ 121 | "65", 122 | "ff" 123 | ], 124 | "dps": -9.45965, 125 | "rps": -0.026257 126 | }, 127 | { 128 | "_raw": { 129 | "type": "Buffer", 130 | "data": [ 131 | 137, 132 | 255 133 | ] 134 | }, 135 | "_hex": [ 136 | "89", 137 | "ff" 138 | ], 139 | "dps": -7.26257, 140 | "rps": -0.020159 141 | } 142 | ] 143 | ] -------------------------------------------------------------------------------- /tests/rawdata-convertion.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const PacketParser = require('../src/utils/packet-parser'); 3 | 4 | const stringify = (str) => JSON.stringify(str, null, 2); 5 | 6 | describe('Packet Parser', () => { 7 | // Inputs. 8 | const buffer = new Buffer([ 9 | 0x30, 0x3e, 0x4e, 0x0a, 0x10, 0x00, 0x00, 0x00, 10 | 0x00, 0xc7, 0x18, 0x76, 0x09, 0x1a, 0x00, 0xf1, 11 | 0x09, 0xd5, 0xf3, 0xb4, 0xff, 0x5b, 0xff, 0x90, 12 | 0xff, 0x17, 0x00, 0xde, 0x09, 0xc7, 0xf3, 0xaf, 13 | 0xff, 0x5e, 0xff, 0x8f, 0xff, 0x0a, 0x00, 0xcd, 14 | 0x09, 0xc7, 0xf3, 0xb8, 0xff, 0x65, 0xff, 0x89, 15 | 0xff, 16 | ]); 17 | 18 | it('parseCompleteButtonStatus', async () => { 19 | const data = buffer.toString('hex').match(/.{2}/g); 20 | 21 | // Execute. 22 | const results = PacketParser.parseCompleteButtonStatus(buffer, data); 23 | 24 | // Results. 25 | const outputFilePath = './tests/data/parseCompleteButtonStatus.1.out.json'; 26 | // fs.writeFileSync(outputFilePath, stringify(results), { encoding: 'utf8' }); 27 | const expected = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); 28 | expect(stringify(results)).toBe(expected); 29 | }); 30 | 31 | it('parseAccelerometers', async () => { 32 | const data = buffer.toString('hex').match(/.{2}/g); 33 | 34 | // Execute. 35 | const results = PacketParser.parseAccelerometers(buffer, data); 36 | 37 | // Results. 38 | const outputFilePath = './tests/data/parseAccelerometers.1.out.json'; 39 | // fs.writeFileSync(outputFilePath, stringify(results), { encoding: 'utf8' }); 40 | const expected = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); 41 | expect(stringify(results)).toBe(expected); 42 | }); 43 | 44 | it('parseGyroscopes', async () => { 45 | const data = buffer.toString('hex').match(/.{2}/g); 46 | 47 | // Execute. 48 | const results = PacketParser.parseGyroscopes(buffer, data); 49 | 50 | // Results. 51 | const outputFilePath = './tests/data/parseGyroscopes.1.out.json'; 52 | // fs.writeFileSync(outputFilePath, stringify(results), { encoding: 'utf8' }); 53 | const expected = fs.readFileSync(outputFilePath, { encoding: 'utf8' }); 54 | expect(stringify(results)).toBe(expected); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "strict": true, 6 | "noUnusedLocals": false, 7 | "noUnusedParameters": false, 8 | "noImplicitAny": true, 9 | "noImplicitThis": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "paths": {}, 13 | "outDir": "dist", 14 | "rootDir": "src", 15 | "moduleResolution": "node", 16 | "emitDecoratorMetadata": true, 17 | "experimentalDecorators": true, 18 | "allowSyntheticDefaultImports": true, 19 | "esModuleInterop": true, 20 | "declaration": true, 21 | "declarationDir": "./types", 22 | "typeRoots": [ 23 | "./node_modules/@types", 24 | "./src/typings/**" 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "linebreak-style": [ 5 | true, 6 | "LF" 7 | ], 8 | "max-classes-per-file": false, 9 | "max-line-length": false, 10 | "member-ordering": [ 11 | true, 12 | { 13 | "order": "fields-first" 14 | } 15 | ], 16 | "no-implicit-dependencies": false, 17 | "no-submodule-imports": false, 18 | "no-switch-case-fall-through": true, 19 | "object-literal-key-quotes": false, 20 | "object-literal-sort-keys": false, 21 | "ordered-imports": false, 22 | "one-variable-per-declaration": false, 23 | "quotemark": [ 24 | true, 25 | "single" 26 | ], 27 | "radix": false, 28 | "space-before-function-paren": [ 29 | true, 30 | { 31 | "anonymous": false, 32 | "asyncArrow": "always", 33 | "constructor": "never", 34 | "method": "never", 35 | "named": "never" 36 | } 37 | ], 38 | "trailing-comma": false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Device } from 'node-hid'; 2 | import { InputReport } from './models/'; 3 | import { IDeviceInfo } from './models/subcommand'; 4 | declare class NsSwitchHID { 5 | private vendorId; 6 | private productId; 7 | private serialNumber?; 8 | private product?; 9 | private type; 10 | private path?; 11 | private usage?; 12 | private hid; 13 | private listeners; 14 | constructor(device: Device); 15 | readonly meta: { 16 | vendorId: number; 17 | productId: number; 18 | serialNumber: string | undefined; 19 | product: string | undefined; 20 | type: "unknown" | "pro-controller" | "joy-con"; 21 | path: string | undefined; 22 | usage: number | undefined; 23 | }; 24 | /** 25 | * Add / remove a handler to recevice packets when device send streaming data. 26 | */ 27 | manageHandler(action: 'add' | 'remove', callback: (packet: InputReport) => void): void; 28 | /** 29 | * Request device info to Jon-Con. 30 | */ 31 | requestDeviceInfo(): Promise; 32 | /** 33 | * Enable IMU data will make Jon-Con sends **Input Report 0x30**. 34 | */ 35 | enableIMU(): Promise; 36 | /** 37 | * Disable IMU data will cancel Jon-Con to send **Input Report 0x30**. 38 | */ 39 | disableIMU(): Promise; 40 | /** 41 | * Enable Jon-Con's vibration. 42 | */ 43 | enableVibration(): Promise; 44 | /** 45 | * Disable Jon-Con's vibration. 46 | */ 47 | disableVibration(): Promise; 48 | private activateJoyConStream; 49 | } 50 | export declare function findControllers(callback: (controllers: NsSwitchHID[]) => void): void; 51 | export {}; 52 | -------------------------------------------------------------------------------- /types/models/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | interface IPacketBuffer { 3 | _raw: Buffer; 4 | _hex: String | String[]; 5 | } 6 | export declare type BatteryLevel = 'full' | 'medium' | 'low' | 'critical' | 'empty' | 'charging'; 7 | interface IBatteryLevel extends IPacketBuffer { 8 | level: BatteryLevel; 9 | } 10 | interface IButtonStatus extends IPacketBuffer { 11 | y: Boolean; 12 | x: Boolean; 13 | b: Boolean; 14 | a: Boolean; 15 | r: Boolean; 16 | zr: Boolean; 17 | down: Boolean; 18 | up: Boolean; 19 | right: Boolean; 20 | left: Boolean; 21 | l: Boolean; 22 | zl: Boolean; 23 | sr: Boolean; 24 | sl: Boolean; 25 | minus: Boolean; 26 | plus: Boolean; 27 | rightStick: Boolean; 28 | leftStick: Boolean; 29 | home: Boolean; 30 | caputure: Boolean; 31 | chargingGrip: Boolean; 32 | } 33 | interface IAnalogStick extends IPacketBuffer { 34 | horizontal: number; 35 | vertical: number; 36 | } 37 | interface IStandardInputReport { 38 | inputReportID: IPacketBuffer; 39 | timer: IPacketBuffer; 40 | batteryLevel: IBatteryLevel; 41 | connectionInfo: IPacketBuffer; 42 | buttonStatus: IButtonStatus; 43 | analogStickLeft: IAnalogStick; 44 | analogStickRight: IAnalogStick; 45 | vibrator: IPacketBuffer; 46 | } 47 | export declare type Accelerometer = { 48 | x: IPacketBuffer & { 49 | acc: number; 50 | }; 51 | y: IPacketBuffer & { 52 | acc: number; 53 | }; 54 | z: IPacketBuffer & { 55 | acc: number; 56 | }; 57 | }; 58 | export declare type Gyroscope = Array; 62 | export interface IInputReport0x21 extends IStandardInputReport { 63 | ack: IPacketBuffer; 64 | subcommandID: IPacketBuffer; 65 | subcommandReplyData: IPacketBuffer; 66 | } 67 | export interface IInputReport0x30 extends IStandardInputReport { 68 | accelerometers: Accelerometer[]; 69 | gyroscopes: Gyroscope[]; 70 | actualAccelerometer: { 71 | acc: { 72 | x: number; 73 | y: number; 74 | z: number; 75 | }; 76 | }; 77 | actualGyroscope: { 78 | dps: number[]; 79 | rps: number[]; 80 | }; 81 | } 82 | export interface IInputReport0x3f { 83 | inputReportID: IPacketBuffer; 84 | buttonStatus: IPacketBuffer; 85 | analogStick: IPacketBuffer; 86 | filter: IPacketBuffer; 87 | } 88 | export declare type InputReport = IInputReport0x3f | IInputReport0x21 | IInputReport0x30; 89 | export {}; 90 | -------------------------------------------------------------------------------- /types/models/subcommand.d.ts: -------------------------------------------------------------------------------- 1 | export interface IDeviceInfo { 2 | firmwareVersion: { 3 | major: number; 4 | minor: number; 5 | }; 6 | type: string; 7 | macAddress: string; 8 | spiColorInUsed: boolean; 9 | } 10 | export declare type InputReportMode = 'standard-full-mode' | 'simple-hid-mode'; 11 | -------------------------------------------------------------------------------- /types/sample.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /types/utils/packet-parser.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { BatteryLevel } from '../models/'; 3 | export declare function parseInputReportID(rawData: Buffer, data: RegExpMatchArray): { 4 | _raw: Buffer; 5 | _hex: string[]; 6 | }; 7 | export declare function parseTimer(rawData: Buffer, data: RegExpMatchArray): { 8 | _raw: Buffer; 9 | _hex: string[]; 10 | }; 11 | export declare function parseBatteryLevel(rawData: Buffer, data: RegExpMatchArray): { 12 | _raw: Buffer; 13 | _hex: string; 14 | level: BatteryLevel; 15 | }; 16 | export declare function parseConnectionInfo(rawData: Buffer, data: RegExpMatchArray): { 17 | _raw: Buffer; 18 | _hex: string; 19 | }; 20 | export declare function parseButtonStatus(rawData: Buffer, data: RegExpMatchArray): { 21 | _raw: Buffer; 22 | _hex: string[]; 23 | }; 24 | export declare function parseCompleteButtonStatus(rawData: Buffer, data: RegExpMatchArray): { 25 | _raw: Buffer; 26 | _hex: string[]; 27 | y: boolean; 28 | x: boolean; 29 | b: boolean; 30 | a: boolean; 31 | r: boolean; 32 | zr: boolean; 33 | down: boolean; 34 | up: boolean; 35 | right: boolean; 36 | left: boolean; 37 | l: boolean; 38 | zl: boolean; 39 | sr: boolean; 40 | sl: boolean; 41 | minus: boolean; 42 | plus: boolean; 43 | rightStick: boolean; 44 | leftStick: boolean; 45 | home: boolean; 46 | caputure: boolean; 47 | chargingGrip: boolean; 48 | }; 49 | export declare function parseAnalogStick(rawData: Buffer, data: RegExpMatchArray): { 50 | _raw: Buffer; 51 | _hex: string[]; 52 | }; 53 | export declare function parseAnalogStickLeft(rawData: Buffer, data: RegExpMatchArray): { 54 | _raw: Buffer; 55 | _hex: string[]; 56 | horizontal: number; 57 | vertical: number; 58 | }; 59 | export declare function parseAnalogStickRight(rawData: Buffer, data: RegExpMatchArray): { 60 | _raw: Buffer; 61 | _hex: string[]; 62 | horizontal: number; 63 | vertical: number; 64 | }; 65 | export declare function parseFilter(rawData: Buffer, data: RegExpMatchArray): { 66 | _raw: Buffer; 67 | _hex: string[]; 68 | }; 69 | export declare function parseVibrator(rawData: Buffer, data: RegExpMatchArray): { 70 | _raw: Buffer; 71 | _hex: string[]; 72 | }; 73 | export declare function parseAck(rawData: Buffer, data: RegExpMatchArray): { 74 | _raw: Buffer; 75 | _hex: string[]; 76 | }; 77 | export declare function parseSubcommandID(rawData: Buffer, data: RegExpMatchArray): { 78 | _raw: Buffer; 79 | _hex: string[]; 80 | }; 81 | export declare function parseSubcommandReplyData(rawData: Buffer, data: RegExpMatchArray): { 82 | _raw: Buffer; 83 | _hex: string[]; 84 | }; 85 | export declare function parseAccelerometers(rawData: Buffer, data: RegExpMatchArray): { 86 | x: { 87 | _raw: Buffer; 88 | _hex: string[]; 89 | acc: number; 90 | }; 91 | y: { 92 | _raw: Buffer; 93 | _hex: string[]; 94 | acc: number; 95 | }; 96 | z: { 97 | _raw: Buffer; 98 | _hex: string[]; 99 | acc: number; 100 | }; 101 | }[]; 102 | export declare function parseGyroscopes(rawData: Buffer, data: RegExpMatchArray): { 103 | _raw: Buffer; 104 | _hex: string[]; 105 | dps: number; 106 | rps: number; 107 | }[][]; 108 | export declare function calculateActualAccelerometer(accelerometers: number[][]): { 109 | x: number; 110 | y: number; 111 | z: number; 112 | }; 113 | export declare function calculateActualGyroscope(gyroscopes: number[][]): number[]; 114 | -------------------------------------------------------------------------------- /types/utils/subcommand-sender.d.ts: -------------------------------------------------------------------------------- 1 | import { HID } from 'node-hid'; 2 | import { IDeviceInfo, InputReportMode } from '../models/subcommand'; 3 | /** 4 | * **Subcommand 0x02**: Request device info 5 | * 6 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x02-request-device-info 7 | */ 8 | export declare function requestDeviceInfo(hid: HID, manageHandler: Function): Promise; 9 | /** 10 | * **Subcommand 0x40**: Enable IMU (6-Axis sensor) 11 | * 12 | * **Argument 0x00**: Disable 13 | * 14 | * **Argument 0x01**: Enable 15 | * 16 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x40-enable-imu-6-axis-sensor 17 | */ 18 | export declare function enableIMU(hid: HID, manageHandler: Function, enable: boolean): Promise<{}>; 19 | /** 20 | * **Subcommand 0x03**: Set input report mode 21 | * 22 | * **Argument 0x30**: Standard full mode. Pushes current state @60Hz 23 | * 24 | * **Argument 0x3f**: Simple HID mode. Pushes updates with every button press 25 | * 26 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x03-set-input-report-mode 27 | */ 28 | export declare function setInputReportMode(hid: HID, manageHandler: Function, mode: InputReportMode): Promise<{}>; 29 | /** 30 | * **Subcommand 0x48**: Enable vibration 31 | * 32 | * **Argument 0x00**: Disable 33 | * 34 | * **Argument 0x01**: Enable 35 | * 36 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_subcommands_notes.md#subcommand-0x48-enable-vibration 37 | * doc: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/66935b7f456f6724464a53781035d25a215d7caa/bluetooth_hid_notes.md#rumble-data 38 | */ 39 | export declare function enableVibration(hid: HID, manageHandler: Function, enable: boolean): Promise<{}>; 40 | --------------------------------------------------------------------------------