// -----------------------
// TypeScript [2.6.2]
// -----------------------
import MSeriesReceiverParser from './mSeriesReceiverParser'
const RECEIVER_VENDOR_ID = 0x0483
const RECEIVER_PRODUCT_ID = 0x5740
const DEVICE_NAME = 'USB Multi-Bike Reciever'
export class Receiver {
callback: ((broadcast: Broadcast) => void)
targetDevice: Device | null
constructor (callback: ((broadcast: Broadcast) => void)) {
this.callback = callback
this.targetDevice = null
navigator.usb.addEventListener('connect', event => { this.attached(event) })
navigator.usb.addEventListener('disconnect', event => { this.detached(event) })
this.checkDevices()
}
async checkDevices () {
let devices = await navigator.usb.getDevices()
devices.forEach(device => {
if (this.matchesTarget(device)) {
console.log(DEVICE_NAME + ': Already Connected')
this.connect(device)
}
})
}
async requestPermission () {
if (!this.targetDevice) {
try {
let device = await navigator.usb.requestDevice({
filters: [{
vendorId: RECEIVER_VENDOR_ID,
productId: RECEIVER_PRODUCT_ID
}]
})
this.connect(device)
} catch (error) {
console.log(DEVICE_NAME + ': Permission Denied')
}
}
}
close () {
if (this.targetDevice) {
this.targetDevice.disconnect()
this.targetDevice = null
}
}
private attached (event: WebUSB.ConnectionEvent) {
if (this.matchesTarget(event.device)) {
console.log(DEVICE_NAME + ': Connected')
this.connect(event.device)
}
}
private detached (event: WebUSB.ConnectionEvent) {
if (this.matchesTarget(event.device) && this.targetDevice && this.targetDevice.isSameDevice(event.device)) {
console.log(DEVICE_NAME + ': Disconnected')
this.close()
}
}
private connect (device: WebUSB.Device) {
this.targetDevice = new Device(device, this.callback)
}
private matchesTarget (device: WebUSB.Device) {
return device.vendorId === RECEIVER_VENDOR_ID &&
device.productId === RECEIVER_PRODUCT_ID
}
}
export default class Device {
bulkInEndpoint: WebUSB.Endpoint | null
continueTransfer: boolean
callback: ((broadcast: Broadcast) => void)
coreDevice: WebUSB.Device
constructor (device: WebUSB.Device, callback: ((broadcast: Broadcast) => void)) {
this.bulkInEndpoint = null
this.continueTransfer = false
this.callback = callback
this.coreDevice = device
this.initialize()
}
/*****************************************
* Exposed Controls
*****************************************/
isSameDevice (device: WebUSB.Device) {
return device.serialNumber === this.coreDevice.serialNumber
}
async disconnect () {
try {
this.stopTransfer()
this.coreDevice.close()
if (process.env.NODE_ENV !== 'production') {
console.log('Device closed')
}
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
console.error('Error closing device\n', error)
}
}
}
/*****************************************
* Control Flow
*****************************************/
async initialize () {
await this.coreDevice.open()
await this.mapEndpoints()
this.startTransfer()
}
/*****************************************
* Interface and Endpoints
*****************************************/
async mapEndpoints () {
let targetConfiguration: WebUSB.Configuration | null = null
let targetInterface: WebUSB.USBInterface | null = null
this.coreDevice.configurations.forEach(configuration => {
configuration.interfaces.forEach(usbInterface => {
usbInterface.alternates.forEach(alternate => {
alternate.endpoints.forEach(endpoint => {
if (endpoint.direction === 'in' && endpoint.type === 'bulk') {
targetConfiguration = configuration
targetInterface = usbInterface
this.bulkInEndpoint = endpoint
}
})
})
})
})
if (!targetInterface || !targetConfiguration) {
throw new Error('Device interfaces unavailable')
}
try {
if (this.coreDevice.configuration === null || this.coreDevice.configuration.configurationValue !== targetConfiguration!.configurationValue) {
await this.coreDevice.selectConfiguration(targetConfiguration!.configurationValue)
}
await this.coreDevice.claimInterface(targetInterface!.interfaceNumber)
} catch (error) {
console.error(error)
}
}
/*****************************************
* Transfer
*****************************************/
async startTransfer () {
if (!this.bulkInEndpoint) {
throw new Error('Device endpoint not assigned')
}
this.continueTransfer = true
try {
while (this.continueTransfer) {
let result = await this.coreDevice.transferIn(this.bulkInEndpoint.endpointNumber, 4096)
if (result.data && result.data.byteLength > 0) {
this.parse(result.data)
}
if (result.status === 'stall') {
await this.coreDevice.clearHalt('in', this.bulkInEndpoint.endpointNumber)
}
}
} catch (error) {
this.stopTransfer()
}
}
stopTransfer () {
this.continueTransfer = false
}
/*****************************************
* Parse
*****************************************/
parse (data: DataView) {
let rawStream = new Uint8Array(data.buffer)
let segments = String.fromCharCode.apply(null, rawStream).split(' ')
let firstSegmentValue = parseInt(segments[0], 10)
if (firstSegmentValue <= 200) {
let swValue = parseFloat(segments[6])
if (swValue >= 6.0 && swValue < 7.0) {
try {
let broadcast = MSeriesReceiverParser(segments)
this.callback(broadcast)
} catch (error) {
console.error('M Series parse error\n', error)
}
} else if (swValue >= 8.0 && swValue < 9.0) {
console.error('Incorrect device connected')
}
}
}
}