summaryrefslogtreecommitdiff
path: root/examples/device/webusb_serial/website/serial.js
blob: 2b60f2cc7230e0e4baa476862169aee9a443e65f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
'use strict';

const serial = {
  isWebUsbSupported: () => 'usb' in navigator,

  // Returns array of connected devices wrapped as serial.Port instances
  async getPorts() {
    const devices = await navigator.usb.getDevices();
    return devices.map(device => new serial.Port(device));
  },

  // Prompts user to select a device matching filters and wraps it in serial.Port
  async requestPort() {
    const filters = [
      { vendorId: 0xcafe }, // TinyUSB
      { vendorId: 0x239a }, // Adafruit
      { vendorId: 0x2e8a }, // Raspberry Pi
      { vendorId: 0x303a }, // Espressif
      { vendorId: 0x2341 }, // Arduino
    ];
    const device = await navigator.usb.requestDevice({ filters });
    return new serial.Port(device);
  },

  Port: class {
    constructor(device) {
      this.device = device;
      this.interfaceNumber = 0;
      this.endpointIn = 0;
      this.endpointOut = 0;
      this.readLoopActive = false;
    }

    portPointToSameDevice(port) {
      if (this.device.vendorId !== port.device.vendorId) return false;
      if (this.device.productId !== port.device.productId) return false;
      if (this.device.serialNumber !== port.device.serialNumber) return false;
      return true;
    }

    // Connect and start reading loop
    async connect() {
      await this.device.open();

      if (!this.device.configuration) {
        await this.device.selectConfiguration(1);
      }

      // Find interface with vendor-specific class (0xFF) and endpoints
      for (const iface of this.device.configuration.interfaces) {
        for (const alternate of iface.alternates) {
          if (alternate.interfaceClass === 0xff) {
            this.interfaceNumber = iface.interfaceNumber;
            for (const endpoint of alternate.endpoints) {
              if (endpoint.direction === 'out') this.endpointOut = endpoint.endpointNumber;
              else if (endpoint.direction === 'in') this.endpointIn = endpoint.endpointNumber;
            }
          }
        }
      }

      if (this.interfaceNumber === undefined) {
        throw new Error('No suitable interface found.');
      }

      await this.device.claimInterface(this.interfaceNumber);
      await this.device.selectAlternateInterface(this.interfaceNumber, 0);

      // Set device to ENABLE (0x22 = SET_CONTROL_LINE_STATE, value 0x01 = activate)
      await this.device.controlTransferOut({
        requestType: 'class',
        recipient: 'interface',
        request: 0x22,
        value: 0x01,
        index: this.interfaceNumber,
      });

      this.readLoopActive = true;
      this._readLoop();
    }

    // Internal continuous read loop
    async _readLoop() {
      while (this.readLoopActive) {
        try {
          const result = await this.device.transferIn(this.endpointIn, 64);
          if (result.data && this.onReceive) {
            this.onReceive(result.data);
          }
        } catch (error) {
          this.readLoopActive = false;
          if (this.onReceiveError) {
            this.onReceiveError(error);
          }
        }
      }
    }

    // Stop reading and release device
    async disconnect() {
      this.readLoopActive = false;
      await this.device.controlTransferOut({
        requestType: 'class',
        recipient: 'interface',
        request: 0x22,
        value: 0x00,
        index: this.interfaceNumber,
      });
      await this.device.close();
    }

    // Send data to device
    send(data) {
      return this.device.transferOut(this.endpointOut, data);
    }

    async forgetDevice() {
      if (this.device.opened) {
        await this.device.close();
      }
      await this.device.forget();
    }
  }
};